.NET 5.0, ORACLE, ASP.NET Identity с N-уровневой архитектурой — часть 2
В этой части нашего руководства мы погрузимся в создание базовой структуры проекта для N-уровневого проекта, включая создание базового репозитория и создание уровня данных, создание сервисного уровня и использование всех вновь созданных материалов в нашем веб-проекте.
Прежде всего, в предыдущей части мы создали наш контекст с сущностями, объяснили, как создать миграцию (сначала код) с базой данных Oracle с использованием EFCore и как создать шаблоны существующих сущностей базы данных в нашем проекте (сначала база данных). Теперь мы применим все это через пару шаблонов проектирования на нескольких уровнях.
Уровень логики данных и шаблон репозитория
Первый шаблон, который мы реализуем, — это шаблон репозитория на нашем уровне доступа к данным. Наш код создаст общий IBaseRepository с CRUD операции, содержащие ВСТАВИТЬ, ОБНОВИТЬ, УДАЛИТЬ и ВЫБРАТЬ операции, а позже мы собираемся добавить реализацию IBaseRepository.
Entity Framework уже реализует шаблон репозитория, поскольку мы можем получать доступ, добавлять, изменять и удалять сущности через DbSet. Мы создаем общий интерфейс IBaseRepository и реализуем этот репозиторий, чтобы избежать шаблонного кода для операций CRUD каждого репозитория для каждой сущности.
Теперь давайте создадим общий интерфейс IBaseRepository с базовой операцией CRUD.
Добавьте класс интерфейса IBaseRepository в папку Repositores/Abstract проекта DLL:
public interface IBaseRepository<T> where T : class
{
#region Select methods
Task<T> SelectById(int id);
Task<List<T>> SelectAll();
#endregion
#region Insert methods
Task<T> Insert(T entity);
#endregion
#region Update methods
void Update(T entity);
#endregion
#region Delete methods
void Delete(int Id);
#endregion
#region Other methods
void SaveChanges();
#endregion
}
IBaseRepository интерфейс принимает один общий тип T. T, как определено, должен быть классом, поэтому он должен быть сущностью, такой же, как и одна из нашей папки Entities в проекте DLL. Как объяснялось ранее, наш общий интерфейс IBaseRepository определяет базовые операции CRUD, которые мы реализуем позже в нашей реализации BaseRepository. Давайте создадим реализацию нашего универсального IBaseRepository.
Добавьте реализацию BaseRepository в папку Repositores/Implementation проекта DLL:
public class BaseRepository<T> : IBaseRepository<T> where T : class
{
#region Fields
protected readonly DbContext _context;
protected readonly DbSet<T> dbSet;
#endregion
#region Constructor
protected BaseRepository(DbContext context)
{
_context = context;
dbSet = context.Set<T>();
}
#endregion
#region Select methods
public virtual async Task<T> SelectById(int id)
{
return await dbSet.FindAsync(id);
}
public virtual async Task<List<T>> SelectAll()
{
return await dbSet.ToListAsync();
}
#endregion
#region Insert methods
public virtual async Task<T> Insert(T entity)
{
var addedEntity = (await dbSet.AddAsync(entity)).Entity;
await _context.SaveChangesAsync();
return addedEntity;
}
#endregion
#region Update methods
public virtual async void Update(T entity)
{
dbSet.Update(entity);
await _context.SaveChangesAsync();
}
#endregion
#region Delete methods
public async void Delete(int Id)
{
T entity = dbSet.Find(Id);
var removedEntity = dbSet.Remove(entity).Entity;
await _context.SaveChangesAsync();
}
#endregion
#region Other methods
public void SaveChanges()
{
_context.SaveChanges();
}
#endregion
}
Структура вашего проекта должна выглядеть так.
Теперь следующим шагом для нас будет реализация репозитория сущностей для наших сущностей базы данных. В предыдущей главе мы создали две сущности: Log и ExampleTable. Давайте создадим интерфейсы для наших репозиториев сущностей и реализации этих репозиториев. Сначала добавьте интерфейс ILogRepository в папку Repositores/Abstract проекта DLL:
public interface ILogRepository : IBaseRepository<Log>, IDisposable
{
//Additional methods or override ones from BaseRepository
#region Select methods
#endregion
#region Insert methods
#endregion
#region Update methods
#endregion
#region Delete methods
#endregion
#region Other methods
#endregion
}
Затем добавьте реализацию LogRepository в папку Repositores/Implementation проекта DLL:
public class LogRepository : BaseRepository<Log>, ILogRepository
{
//Implement additional methods or override ones from BaseRepository and implement them
#region Select methods
public LogRepository(EXAMPLE_SCHEMA_Context context) : base(context)
{ }
#endregion
#region Select methods
#endregion
#region Insert methods
#endregion
#region Update methods
#endregion
#region Delete methods
#endregion
#region Other methods
public void Dispose()
{
_context.Dispose();
}
#endregion
}
Теперь давайте сделаем то же самое для нашей второй сущности ExampleTable. Сначала добавьте интерфейс IExampleTableRepository в папку Repositores/Abstract проекта DLL:
public interface IExampleTableRepository : IBaseRepository<ExampleTable>, IDisposable
{
//Additional methods or override ones from BaseRepository
#region Select methods
#endregion
#region Insert methods
#endregion
#region Update methods
#endregion
#region Delete methods
#endregion
#region Other methods
#endregion
}
Затем добавьте реализацию ExampleTableRepository в папку Repositores/Implementation проекта DLL:
public class ExampleTableRepository : BaseRepository<ExampleTable>, IExampleTableRepository
{
//Implement additional methods or override ones from BaseRepository and implement them
#region Select methods
public ExampleTableRepository(EXAMPLE_SCHEMA_Context context) : base(context)
{ }
#endregion
#region Select methods
#endregion
#region Insert methods
#endregion
#region Update methods
#endregion
#region Delete methods
#endregion
#region Other methods
public void Dispose()
{
_context.Dispose();
}
#endregion
}
Как мы видим, мы расширяем BaseRepository в наших репозиториях сущностей. В ILogRepository или любом другом интерфейсе мы можем добавить дополнительные методы, применимые только к этому объекту. Вот почему мы расширяем LogRepository как BaseRepository и реализуем ILogRepository. BaseRepository содержит основные операции CRUD, а ILogRepository содержит методы, процедуры и другой код LINQ для конкретных объектов.
Структура вашего проекта должна выглядеть так.
Слой бизнес-логики и службы
Теперь, когда мы реализовали репозитории в нашем проекте DLL, пришло время перейти к проекту слоя бизнес-логики, чтобы реализовать наши службы, которые будут реализовывать шаблон Unit of Work. Шаблон единицы работы отслеживает бизнес-транзакцию и переводит эту транзакцию в транзакцию базы данных. Например, если у нас есть десять шагов для нашей бизнес-транзакции, и эти шаги каким-то образом связаны с бизнес-логикой, мы можем коллективно выполнить эту транзакцию как единое целое.
Реализация шаблона Unit of Work должна выглядеть так.
Итак, давайте создадим ILogService, IExampleTableServices интерфейсы и ЛогСервис, ПримерТаблСервис как реализация этих интерфейсов.
public interface ILogService
{
#region Select methods
Task<List<Log>> SelectLog();
Task<Log> SelectLogById(int Id);
#endregion
#region Insert methods
void InsertLog(Log entity);
#endregion
#region Update methods
void UpdateLog(Log entity);
#endregion
#region Delete methods
void DeleteLog(int Id);
#endregion
}
public class LogService : ILogService
{
//Service can have multiple repositories implementing UoW(Unit oof Work) design pattern.
#region Fields
private readonly ILogRepository _logRepository;
#endregion
#region Constructor
public LogService(ILogRepository logRepository)
{
_logRepository = logRepository;
}
#endregion
#region Select methods
public Task<List<Log>> SelectLog()
{
return _logRepository.SelectAll();
}
public Task<Log> SelectLogById(int Id)
{
return _logRepository.SelectById(Id);
}
#endregion
#region Insert methods
public void InsertLog(Log entity)
{
_logRepository.Insert(entity);
}
#endregion
#region Update methods
public void UpdateLog(Log entity)
{
_logRepository.Update(entity);
}
#endregion
#region Delete methods
public void DeleteLog(int Id)
{
_logRepository.Delete(Id);
}
#endregion
#region Other methods
#endregion
}
public interface IExampleTableService
{
#region Select methods
Task<List<ExampleTable>> SelectExampleTable();
Task<ExampleTable> SelectExampleTableById(int Id);
#endregion
#region Insert methods
void InsertExampleTable(ExampleTable entity);
#endregion
#region Update methods
void UpdateExampleTable(ExampleTable entity);
#endregion
#region Delete methods
void DeleteExampleTable(int Id);
#endregion
}
public class ExampleTableService : IExampleTableService
{
//Service can have multiple repositories implementing UoW(Unit oof Work) design pattern.
#region Fields
private readonly IExampleTableRepository _exampleTableRepository;
#endregion
#region Constructor
public ExampleTableService(IExampleTableRepository ExampleTableRepository)
{
_exampleTableRepository = ExampleTableRepository;
}
#endregion
#region Select methods
public Task<List<ExampleTable>> SelectExampleTable()
{
return _exampleTableRepository.SelectAll();
}
public Task<ExampleTable> SelectExampleTableById(int Id)
{
return _exampleTableRepository.SelectById(Id);
}
#endregion
#region Insert methods
public void InsertExampleTable(ExampleTable entity)
{
_exampleTableRepository.Insert(entity);
}
#endregion
#region Update methods
public void UpdateExampleTable(ExampleTable entity)
{
_exampleTableRepository.Update(entity);
}
#endregion
#region Delete methods
public void DeleteExampleTable(int Id)
{
_exampleTableRepository.Delete(Id);
}
#endregion
#region Other methods
#endregion
}
Структура нашего проекта BLL должна выглядеть так:
Вот и все. Единица работы состоит из нескольких репозиториев, которые реализуют некоторую бизнес-логику, объединяя их все в один сервис.
Внедрение зависимости
Итак, каков наш следующий шаг? Далее мы собираемся использовать наши репозитории и сервисы в наших контроллерах. Мы собираемся внедрить репозитории в наши сервисы, а затем эти сервисы в наши контроллеры, что является методом достижения инверсии управления (IoC) между классами и их зависимостями. Существует несколько библиотек, которые позволяют нам внедрять необходимые зависимости класса, но мы будем использовать стандартную платформу ASP.NET CORE DI для внедрения наших зависимостей.
Прежде чем мы углубимся в код, давайте объясним 3 режима жизни внедряемой службы:
Область применения — пожизненные службы создаются один раз для каждого запроса в рамках области.
Переходный — пожизненные сервисы создаются каждый раз, когда они запрашиваются.
Синглтон — который создает один экземпляр во всем приложении. Он создает экземпляр в первый раз и повторно использует один и тот же объект во всех вызовах.
Давайте откроем класс Startup.cs и в методе ConfigureServices внедрим наш DBContext и необходимые службы и репозитории.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
//Dependency injection services.AddEntityFrameworkOracle().AddDbContext<EXAMPLE_SCHEMA_Cont ext>(); services.AddScoped(typeof(IBaseRepository<>),typeof(BaseRepository<> )); services.AddScoped(typeof(ILogRepository),typeof(LogRepository)); services.AddScoped(typeof(IExampleTableRepository),typeof(ExampleTab leRepository));
}
Сопоставление AutoMapper между сущностями DLL и ViewModels
Далее, проблема, которую мы собираемся решить, — это проблема сопоставления наших сущностей DLL, которые генерируются либо с помощью кода, либо базы данных. Каждый раз, когда нам нужно изменить нашу сущность, мы теряем DataAnotations, и нам нужно снова применить это изменение, или нам нужно добавить новые свойства для каждой сущности, в которую мы копируем данные, и изменить нашу логику для копирования одного объекта в другой в нашем коде (Entity в EntityViewModel и наоборот).
AutoMapper в C# — это средство сопоставления двух объектов. Он сопоставляет свойства двух разных объектов, преобразуя входной объект одного типа в выходной объект другого типа.
Давайте сначала установим AutoMapper либо через графический интерфейс в нашем веб-проекте, щелкнув правой кнопкой мыши зависимости веб-проекта, а затем управляя пакетами NuGet.
или выполнив следующие команды консоли PM в веб-проекте:
- Пакет установки AutoMapper — версия 11.0.0
- Install-Package AutoMapper.Extensions.Microsoft.DependencyInjection — версия 11.0.0
Затем в нашем классе Startup.cs в методе ConfigureServices добавьте следующий код:
public void ConfigureServices(IServiceCollection services)
{
…
//AutoMapper setup
services.AddAutoMapper(typeof(Startup));
…
…
//Dependency injection
services.AddSingleton(provider => new MapperConfiguration(cfg =>
{
cfg.AddProfile(new EXAMPLE_SCHEMA_Profile());
}).CreateMapper());
…
}
Далее давайте создадим ViewModel для наших классов сущностей Log и ExampleTable:
public class LogViewModel
{
public int pk { get; set; }
public string CreatedBy { get; set; }
public DateTime CreationDate { get; set; }
public string ModifiedBy { get; set; }
public DateTime ModifiedDate { get; set; }
public DateTime? Date { get; set; }
public string Value { get; set; }
}
public class ExampleTableViewModel
{
public int Pk { get; set; }
public string CreatedBy { get; set; }
public DateTime CreationDate { get; set; }
public string ModifiedBy { get; set; }
public DateTime ModifiedDate { get; set; }
public string Name { get; set; }
}
Далее давайте создадим AutoMapperConfigurations папка в проекте NTierOracleIdentityExample.Web. Затем создайте новый профиль с именем EXAMPLE_SCHEMA_Профиль класс во вновь созданной папке:
public class EXAMPLE_SCHEMA_Profile : Profile
{
public EXAMPLE_SCHEMA_Profile()
{
CreateMap<Log, LogViewModel>().ReverseMap();
CreateMap<ExampleTable, ExampleTableViewModel>().ReverseMap();
}
}
Как видно из предыдущего кода, нам нужно сопоставить каждое свойство класса сущности с соответствующим классом ViewModel с помощью AutoMapper.
Примечание. Если имена свойств различаются в исходном и целевом типах, то по умолчанию C# Automapper не сопоставляет эти свойства.
Структура нашего веб-проекта должна выглядеть так:
Завершение контроллера
Наконец, давайте добавим код в наш HomeController. Мы внедрим интерфейсы ILogService и IMapper в конструктор HomeController. Далее мы добавим несколько фиктивных методов для операций CRUD нашего сервиса, таких как GetLog, GetLogById, EditLog и DeleteLog.
public class HomeController : Controller
{
#region Fields
private readonly IMapper _mapper;
private readonly ILogService _logService;
#endregion
#region Constructor
public HomeController(ILogService logService, IMapper mapper)
{
_logService = logService;
_mapper = mapper;
}
#endregion
#region GET methods
public IActionResult Index()
{
return View();
}
public JsonResult GetLog()
{
List<LogViewModel> logs = _mapper.Map<List<Log>, List<LogViewModel>>(_logService.SelectLog().Result);
var logList = from l in logs
select new
{
Id = l.pk,
LogDate = l.Date,
LogValue = l.Value
};
return Json(new { status = “success”, records = logList.OrderBy(l => l.Id).ToList(), total = logList.Count() });
}
public JsonResult GetLogById(int pk)
{
LogViewModel log = _mapper.Map<Log, LogViewModel>(_logService.SelectLogById(pk).Result);
return Json(new { status = “success”, records = log });
}
#endregion
#region POST methods
[HttpPost]
public JsonResult EditLog(LogViewModel viewModel)
{
if (ModelState.IsValid)
{
//Map viewModel to model
Log model = _mapper.Map<Log>(viewModel);
model.ModifiedBy = “DummyUser”;
model.ModifiedDate = DateTime.Now;
_logService.UpdateLog(model);
return Json(new
{
success = true,
message = “Log saved!”
});
}
else
{
return Json(new { success = false, message = “Error!” });
}
}
[HttpPost]
public JsonResult DeleteLog(int pk)
{
_logService.DeleteLog(pk);
return Json(new { success = false, message = “Log deleted!” });
}
#endregion
#region Helper methods
#endregion
}
Итак, это все для этой части. В следующей части мы рассмотрим удостоверение ASP.NET с базой данных ORACLE, а также способы создания необходимых сущностей в базе данных для удостоверения и способы его реализации в нашем приложении. И, наконец, запустите наше приложение.