Русский
Русский
English
Статистика
Реклама

Entity framework

Cоздание переиспользуемых Linq фильтров (построителей предикатов для Where), которые можно применять для разных типов

15.04.2021 22:17:49 | Автор: admin

Способ создания переиспользуемых Linq фильтров (построителей предикатов для условия Where), которые можно применять для разных типов объектов. Поля объектов для фильтрации указываются с помощью MemberExpression.

Способ подходит для Entity Framework, включая Async операции.

Основная идея. Что такое переиспользуемый фильтр?

Например есть приказы:

class Order { public DateTime Start { get; set; }public DateTime? End { get; set; }}

Пусть нужно найти все приказы которые будут действовать в ближайшие 7 дней.

С помощью переиспользуемого построителя фильтра (если бы он был реализован) найти приказы можно так:

var ordersFiltred = orders.WhereOverlap(// с помощью MemberExpressions// указываем по каким полям производить поискfromField: oo => oo.Start,toField: oo => oo.End,// указываем период поискаfrom: DateTime.Now,to: DateTime.Now.AddDays(7)).ToList();

Этот же WhereOverlap можно переиспользовать и применить к другому типу. Например, для поиска командировок:

class Trip { public DateTime? From { get; set; }public DateTime? To { get; set; }}
var tripsFiltred = trips.WhereOverlap(// с помощью MemberExpressions// указываем по каким полям производить поискfromField: oo => oo.From,toField: oo => oo.To,from: DateTime.Now,to: DateTime.Now.AddDays(7)).ToList();

Приказы и командировки - это разные типы объектов, у них нет общего интерфейса, поля для поиска называются по-разному. И все таки для обоих типов (и приказов и командировок) применяется один переиспользуемый фильтр WhereOverlap.

Ниже описано как можно делать такие переиспользуемые построители предикатов.

Как сделать переиспользуемый фильтр

Выше было описано применение WhereOverlap, было бы логично показать его реализацию. Но для того, чтобы сделать WhereOverlap, нужна реализация операторов И, ИЛИ. Поэтому начнем с более простого примера.

Пусть есть выплаты и премии:

class Payout { public decimal Total { get; set; }public bool UnderControl { get; set; }}class Premium {public decimal Sum { get; set; }public bool RequiresConfirmation { get; set; }}

Сделаем переиспользуемый фильтр для поиска платежей больше определенной суммы:

class UnderControlPayFilter {readonly decimal Limit;public UnderControlPayFilter(decimal limit) {Limit = limit;}public Expression<Func<TEnt, bool>> Create<TEnt>(Expression<Func<TEnt, decimal>> sumField) {// GreaterOrEqual - нужно реализовать// GreaterOrEqual - это extension, который принимает//  - указание на поле (Expression sumField)//  - и значение с которым нужно сравнивать (Limit)return sumField.GreaterOrEqual(Limit);}}

Пример использования UnderControlPayFilter фильтра:

// фильтр поиска платежей требующих дополнительного контроля//// конкретный предел (здесь 1000) можно вынести в настройки,// а UnderControlPayFilter зарегистрировать в IoC-контейнере.// Тогда можно централизовано (через найстройки приложения)// управлять максимальным пределомvar underControlPayFilter = new UnderControlPayFilter(1000);//// Применение переиспользуемого фильтра для выплатvar payoutPredicate =underControlPayFilter.Create<Payout>(pp => pp.Total);// здесь, для упрощения, payouts - это массив,// в реальном приложении это может быть Entity Framework DbSet var payouts = new[] {new Payout{ Total = 100 },new Payout{ Total = 50, UnderControl = true },new Payout{ Total = 25.5m },new Payout{ Total = 1050.67m }}.AsQueryable().Where(payoutPredicate).ToList();//// Применение переиспользуемого фильтра для премийvar premiumPredicate =underControlPayFilter.Create<Premium>(pp => pp.Sum);// здесь, для упрощения, premiums - это массив,// в реальном приложении это может быть Entity Framework DbSet var premiums = new[] {new Premium{ Sum = 2000 },new Premium{ Sum = 50.08m },new Premium{ Sum = 25.5m, RequiresConfirmation = true },new Premium{ Sum = 1070.07m }}.AsQueryable().Where(premiumPredicate).ToList();

Все готово, осталось только реализовать GreaterOrEqual extension:

public static class MemberExpressionExtensions {    public static Expression<Func<TEnt, bool>> GreaterOrEqual<TEnt, TProp>(        this Expression<Func<TEnt, TProp>> field, TProp val)            => Expression.Lambda<Func<TEnt, bool>>(                Expression.GreaterThanOrEqual(field.Body, Expression.Constant(val, typeof(TProp))),                 field.Parameters);}

По аналогии можно реализовать extension-ы LessOrEqual, Equal, HasNoVal и другие.

Более сложные переиспользуемые фильтры с операторами И и ИЛИ

Пускай в выборку должны попадать не только платежи больше определенного предела, но и те, которые специально отмечены, как требующие дополнительного контроля.

Дополним UnderControlPayFilter:

class UnderControlPayFilter {readonly decimal Limit;public UnderControlPayFilter(decimal limit) {Limit = limit;}public Expression<Func<TEnt, bool>> Create<TEnt>(Expression<Func<TEnt, decimal>> sumField,Expression<Func<TEnt, bool>> controlMarkField) {// PredicateBuilder нужно реализовать (см. ниже)return PredicateBuilder.Or(sumField.GreaterOrEqual(Limit),controlMarkField.Equal(true));}}

Пример использования:

// для выплатvar payoutPredicate =underControlPayFilter.Create<Payout>(sumField: pp => pp.Total,controlMarkField: pp => pp.UnderControl);// для премийvar premiumPredicate = underControlPayFilter.Create<Premium>(sumField: pp => pp.Sum,controlMarkField: pp => pp.RequiresConfirmation);

PredicateBuilder это A universal PredicateBuilder сделанный Pete Montgomery.

Заключение

Чтобы делать свои переиспользуемые фильтры, нужен только PredicateBuilder и MemberExpressionExtensions. Просто скопируйте их в свой проект. Переиспользуемые фильтры можно оформить как extension (как WhereOverlap), как статический хелпер или класс (как UnderControlPayFilter).

Я сделал парочку переиспользуемых фильтров - GitHub, NuGet (включает PredicateBuilder и MemberExpressionExtensions).

Подробнее..

ASP.NET Core MVC WebAPI Entity Framework Microsoft SQL Server Angular

09.08.2020 20:18:26 | Автор: admin


Введение


Небольшой курс по созданию простого веб-приложения с помощью технологий ASP.NET Core MVC, фреймворка Entity Framework, СУБД Microsoft SQL Server и фреймворка Angular. Тестировать web API будем через приложение Postman.

Курс состоит из нескольких частей:
  1. Создание web API с помощью ASP.NET Core MVC и Entity Framework Core.
  2. Реализация пользовательского интерфейса на Angular.
  3. Добавление аутентификации в приложение.
  4. Расширение модели приложения и рассмотрение дополнительных возможностей Entity Framework.


Часть 1. Создание web API с помощью ASP.NET Core MVC и Entity Framework Core


В качестве примера будем расматривать уже ставшее классическим приложение списка дел. Для разработки приложения я буду использовать Visual Studio 2019(в Visual Studio 2017 процесс аналогичен).

Создание проекта


Создадим новый проект ASP.NET Core Web Application в Visual Studio:



Назовем приложение и укажем путь к каталогу с проектом:



И выберем шаблон приложения API:



Модель


Создадим каталог Models и в новый каталог добавим первый класс TodoItem.cs, объекты которого будут описывать некоторые задачи списка дел в приложении:

public class TodoItem{    public int Id { get; set; }    public string TaskDescription { get; set; }    public bool IsComplete { get; set; }}


В качестве СУБД мы будем использовать Sql Server, а доступ к базе данных будет осуществляться через Entity Framework Core и для начала установим фреймворк через встроенный пакетный менеджер NuGet:



Одним из подходов в работе с Entity Framework является подход Code-First. Суть подхода заключается в том, что на основе модели приложения(в нашем случае модель представляет единственный класс TodoItem.cs) формируется струткура базы данных(таблицы, первичные ключи, ссылки), вся эта работа происходит как бы за кулисами и напрямую с SQL мы не работаем. Обязательным условием класса модели является наличие поля первичного ключа, по умолчанию Entity Framework ищет целочисленное поле в имени которого присутствует подстрока id и формирует на его основе первичный ключ. Переопределить такое поведение можно с помощью специальных атрибутов или используя возможности Fluent API.
Главным компонентом в работе с Entity Framework является класс контекста базы данных, через который собственно и осуществляется доступ к данным в таблицах:

public class EFTodoDBContext : DbContext{    public EFTodoDBContext(DbContextOptions<EFTodoDBContext> options) : base(options)     { }    public DbSet<TodoItem> TodoItems{ get; set; }}


Базовый класс DbContext создает контекст БД и обеспечивает доступ к функциональности Entity Framework.
Для хранения данных приложения мы будем использовать SQL Server 2017 Express. Строки подключения хранятся в файле JSON под названием appsettings.json:

{  "ConnectionStrings": {    "DefaultConnection": "Server=.\\SQLEXPRESS;Database=Todo;Trusted_Connection=true"  }}


Далее нужно внести изменения в класс Startup.cs, добавив в метод ConfigureServices() следующий код:

services.AddDbContext<EFTodoDBContext>(options => options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));


Метод AddDbContext() настраивает службы, предоставляемые инфраструктурой Entity Framework Core для класса контекста базы EFTodoDBContext. Аргументом метода AddDbContext () является лямбда-выражение, которое получает объект options, конфигурирующий базу данных для класса контекста. В этом случае база данных конфигурируется с помощью метода UseSqlServer() и указания строки подключения.
Определим основные операции для работы с задачами в интерфейсе ITodoRepository:

 public interface ITodoRepository {    IEnumerable<TodoItem> Get();    TodoItem Get(int id);    void Create(TodoItem item);    void Update(TodoItem item);    TodoItem Delete(int id); }


Данный интерфейс позволяет нам не задумываться о конкретной реализации хранилища данных, возможно мы точно не определились с выбором СУБД или ORM фреймворком, сейчас это не важно, класс описывающий доступ к данным будет наследовать от этого интерфейса.
Реализуем репозиторий, который как уже сказано ранее, будет наследовать от ITodoRepository и использовать в качестве источника данных EFTodoDBContext:

public class EFTodoRepository : ITodoRepository{    private EFTodoDBContext Context;    public IEnumerable<TodoItem> Get()    {        return Context.TodoItems;    }    public TodoItem Get(int Id)    {        return Context.TodoItems.Find(Id);    }    public EFTodoRepository(EFTodoDBContext context)    {        Context = context;    }    public void Create(TodoItem item)    {        Context.TodoItems.Add(item);        Context.SaveChanges();    }    public void Update(TodoItem updatedTodoItem)    {        TodoItem currentItem = Get(updatedTodoItem.Id);        currentItem.IsComplete = updatedTodoItem.IsComplete;        currentItem.TaskDescription = updatedTodoItem.TaskDescription;        Context.TodoItems.Update(currentItem);        Context.SaveChanges();        }    public TodoItem Delete(int Id)    {        TodoItem todoItem = Get(Id);        if (todoItem != null)        {            Context.TodoItems.Remove(todoItem);            Context.SaveChanges();        }        return todoItem;    }    }


Контроллер


Контроллер, реализация которого будет описана ниже, ничего не будет знать о контексте данных EFTodoDBContext, а будет использовать в своей работе только интерфейс ITodoRepository, что позволяет изменить источник данных не меняя при этом контроллера. Такой подход Адам Фримен в своей книге Entity Framework Core 2 для ASP.NET Core MVC для профессионалов назвал паттерн Хранилище.
Контроллер реализует обработчики стандартных методов HTTP-запросов: GET, POST, PUT, DELETE, которые будут изменять состояние наших задач, описанных в классе TodoItem.cs. Добавим в каталог Controllers класс TodoController.cs со следующим содержимым:

[Route("api/[controller]")]public class TodoController : Controller{    ITodoRepository TodoRepository;    public TodoController(ITodoRepository todoRepository)    {        TodoRepository = todoRepository;    }    [HttpGet(Name = "GetAllItems")]    public IEnumerable<TodoItem> Get()    {        return TodoRepository.Get();    }    [HttpGet("{id}", Name = "GetTodoItem")]    public IActionResult Get(int Id)    {        TodoItem todoItem = TodoRepository.Get(Id);        if (todoItem == null)        {            return NotFound();        }        return new ObjectResult(todoItem);    }    [HttpPost]    public IActionResult Create([FromBody] TodoItem todoItem)     {        if (todoItem == null)        {            return BadRequest();        }        TodoRepository.Create(todoItem);        return CreatedAtRoute("GetTodoItem", new { id = todoItem.Id }, todoItem);    }    [HttpPut("{id}")]    public IActionResult Update(int Id, [FromBody] TodoItem updatedTodoItem)    {        if (updatedTodoItem == null || updatedTodoItem.Id != Id)        {            return BadRequest();        }        var todoItem = TodoRepository.Get(Id);        if (todoItem == null)        {            return NotFound();        }        TodoRepository.Update(updatedTodoItem);        return RedirectToRoute("GetAllItems");    }    [HttpDelete("{id}")]    public IActionResult Delete(int Id)    {        var deletedTodoItem = TodoRepository.Delete(Id);        if (deletedTodoItem == null)        {            return BadRequest();        }        return new ObjectResult(deletedTodoItem);    } }


Перед определением класса указан атрибут с описанием шаблона маршрута для доступа к контроллеру: [Route(api/[controller])]. Контроллер TodoController будет доступен по следующему маршруту: https://<ip хоста>:<порт>/api/todo. В [controller] указывается название класса контроллера в нижнем регистре, опуская часть Controller.
Перед определением каждого метода в контроллере TodoController указан специальный атрибут вида: [<метод HTTP>(параметр,Name = псевдоним метода)]. Атрибут определяет какой HTTP-запрос будет обработан данным методом, параметр, который передается в URL запроса и псевдоним метода с помощью которого можно переотправлять запрос. Если не указать атрибут, то по умолчанию инфраструктура MVC попытается найти самый подходящий метод в контроллере для обработки запроса исходя из названия метода и указанных параметров в запросе, так, если не указать в контроллере TodoController атрибут для метода Get(), то при HTTP-запросе методом GET: https://<ip хоста>:<порт>/api/todo, инфраструткура определит для обработки запроса метод Get() контроллера.
В своем конструкторе контроллер получает ссылку на объект типа ITodoRepository, но пока что инфраструктура MVC не знает, какой объект подставить при создании контроллера. Нужно создать сервис, который однозначно разрешит эту зависисмость, для этого внесем некотрые изменения в класс Startup.cs, добавив в метод ConfigureServices() следующий код:

services.AddTransient<ITodoRepository, EFTodoRepository>();


Метод AddTransient<ITodoRepository, EFTodoRepository>() определяет сервис, который каждый раз, когда требуется экземпляр типа ITodoRepository, например в контроллере, создает новый экземпляр класс EFTodoRepository.
Полный код класса Startup.cs:

public class Startup{    public Startup(IConfiguration configuration)    {        Configuration = configuration;    }    public IConfiguration Configuration { get; }    public void ConfigureServices(IServiceCollection services)    {        services.AddControllers();        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);        services.AddDbContext<EFTodoDBContext>(options => options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));        services.AddTransient<ITodoRepository, EFTodoRepository>();    }    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)    {        if (env.IsDevelopment())        {            app.UseDeveloperExceptionPage();        }        app.UseHttpsRedirection();        app.UseRouting();        app.UseAuthorization();        app.UseEndpoints(endpoints =>        {            endpoints.MapControllers();        });    } }


Миграции


Для того чтобы Entity Framework сгенерировал базу данных и таблицы на основе модели, нужно использовать процесс миграции базы данных. Миграции это группа команд, которая выполняет подготовку базы данных для работы с Entity Framework. Они используются для создания и синхронизации базы данных. Команды можно выполнять как в консоли диспетчера пакетов (Package Manager Console), так и в Power Shell(Developer Power Shell). Мы будем использовать консоль диспетчера пакетов, для работы с Entity Framework потребуется установить пакет Microsoft.EntityFrameworkCore.Tools:



Запустим консоль диспетчера пакетов и выполним команду Add-Migration Initial:





В проекте появится новый каталог Migrations, в котором будут хранится классы миграции, на основе которых и будут создаваться объекты в базе данных после выполнения команды Update-Database:



Web API готово, запустив приложение на локальном IIS Express мы можем протестировать работу контроллера.

Тестирование WebAPI


Создадим новую коллекцию запросов в Postman под названием TodoWebAPI:



Так как наша база пуста, протестируем для начала создание новой задачи. В контроллере за создание задач отвечает метод Create(), который будет обрабатывать HTTP запрос отправленный методом POST и будет содержать в теле запроса сериализированный объект TodoItem в JSON формате. Аттрибут [FromBody] перед параметром todoItem в методе Create() подсказывает инфраструктуре MVC, что нужно десериализировать объект TodoItem из тела запроса и передать его в качестве параметра методу. Создадим запрос в Postman, который отправит на webAPI запрос на создание новой задачи:



Метод Create() после успешного создания задачи перенаправляет запрос на метод Get() с псевдонимом GetTodoItem и передает в качестве параметра Id только что созданной задачи, в результате чего в ответ на запрос мы получим созданный объект задачи в формате JSON.

Отправив HTTP запрос методом PUT и указав при этом в URL Id(http://personeltest.ru/aways/localhost:44370/api/todo/1) уже созданного объекта, а в теле запроса передав объект с некоторыми изменениями в формате JSON, мы изменим этот объект в базе:



HTTP запросом с методом GET без указания параметров получим все объекты в базе:



Запрос HTTP с методом DELETE и указанием Id объекта в URL(http://personeltest.ru/aways/localhost:44370/api/todo/2), удалит объект из базы и вернет JSON с удаленной задачей:



На этом все, в следующей части реализуем пользовательский интерфейс с помощью JavaScript-фреймворка Angular.
Подробнее..

ASP.NET Core MVC WebAPI Entity Framework Microsoft SQL Server Angular. Часть 1

09.08.2020 22:07:12 | Автор: admin


Введение


Небольшой курс по созданию простого веб-приложения с помощью технологий ASP.NET Core MVC, фреймворка Entity Framework, СУБД Microsoft SQL Server и фреймворка Angular. Тестировать web API будем через приложение Postman.

Курс состоит из нескольких частей:
  1. Создание web API с помощью ASP.NET Core MVC и Entity Framework Core.
  2. Реализация пользовательского интерфейса на Angular.
  3. Добавление аутентификации в приложение.
  4. Расширение модели приложения и рассмотрение дополнительных возможностей Entity Framework.


Часть 1. Создание web API с помощью ASP.NET Core MVC и Entity Framework Core


В качестве примера будем расматривать уже ставшее классическим приложение списка дел. Для разработки приложения я буду использовать Visual Studio 2019(в Visual Studio 2017 процесс аналогичен).

Создание проекта


Создадим новый проект ASP.NET Core Web Application в Visual Studio:



Назовем приложение и укажем путь к каталогу с проектом:



И выберем шаблон приложения API:



Модель


Создадим каталог Models и в новый каталог добавим первый класс TodoItem.cs, объекты которого будут описывать некоторые задачи списка дел в приложении:

public class TodoItem{    public int Id { get; set; }    public string TaskDescription { get; set; }    public bool IsComplete { get; set; }}


В качестве СУБД мы будем использовать Sql Server, а доступ к базе данных будет осуществляться через Entity Framework Core и для начала установим фреймворк через встроенный пакетный менеджер NuGet:



Одним из подходов в работе с Entity Framework является подход Code-First. Суть подхода заключается в том, что на основе модели приложения(в нашем случае модель представляет единственный класс TodoItem.cs) формируется струткура базы данных(таблицы, первичные ключи, ссылки), вся эта работа происходит как бы за кулисами и напрямую с SQL мы не работаем. Обязательным условием класса модели является наличие поля первичного ключа, по умолчанию Entity Framework ищет целочисленное поле в имени которого присутствует подстрока id и формирует на его основе первичный ключ. Переопределить такое поведение можно с помощью специальных атрибутов или используя возможности Fluent API.
Главным компонентом в работе с Entity Framework является класс контекста базы данных, через который собственно и осуществляется доступ к данным в таблицах:

public class EFTodoDBContext : DbContext{    public EFTodoDBContext(DbContextOptions<EFTodoDBContext> options) : base(options)     { }    public DbSet<TodoItem> TodoItems{ get; set; }}


Базовый класс DbContext создает контекст БД и обеспечивает доступ к функциональности Entity Framework.
Для хранения данных приложения мы будем использовать SQL Server 2017 Express. Строки подключения хранятся в файле JSON под названием appsettings.json:

{  "ConnectionStrings": {    "DefaultConnection": "Server=.\\SQLEXPRESS;Database=Todo;Trusted_Connection=true"  }}


Далее нужно внести изменения в класс Startup.cs, добавив в метод ConfigureServices() следующий код:

services.AddDbContext<EFTodoDBContext>(options => options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));


Метод AddDbContext() настраивает службы, предоставляемые инфраструктурой Entity Framework Core для класса контекста базы EFTodoDBContext. Аргументом метода AddDbContext () является лямбда-выражение, которое получает объект options, конфигурирующий базу данных для класса контекста. В этом случае база данных конфигурируется с помощью метода UseSqlServer() и указания строки подключения.
Определим основные операции для работы с задачами в интерфейсе ITodoRepository:

 public interface ITodoRepository {    IEnumerable<TodoItem> Get();    TodoItem Get(int id);    void Create(TodoItem item);    void Update(TodoItem item);    TodoItem Delete(int id); }


Данный интерфейс позволяет нам не задумываться о конкретной реализации хранилища данных, возможно мы точно не определились с выбором СУБД или ORM фреймворком, сейчас это не важно, класс описывающий доступ к данным будет наследовать от этого интерфейса.
Реализуем репозиторий, который как уже сказано ранее, будет наследовать от ITodoRepository и использовать в качестве источника данных EFTodoDBContext:

public class EFTodoRepository : ITodoRepository{    private EFTodoDBContext Context;    public IEnumerable<TodoItem> Get()    {        return Context.TodoItems;    }    public TodoItem Get(int Id)    {        return Context.TodoItems.Find(Id);    }    public EFTodoRepository(EFTodoDBContext context)    {        Context = context;    }    public void Create(TodoItem item)    {        Context.TodoItems.Add(item);        Context.SaveChanges();    }    public void Update(TodoItem updatedTodoItem)    {        TodoItem currentItem = Get(updatedTodoItem.Id);        currentItem.IsComplete = updatedTodoItem.IsComplete;        currentItem.TaskDescription = updatedTodoItem.TaskDescription;        Context.TodoItems.Update(currentItem);        Context.SaveChanges();        }    public TodoItem Delete(int Id)    {        TodoItem todoItem = Get(Id);        if (todoItem != null)        {            Context.TodoItems.Remove(todoItem);            Context.SaveChanges();        }        return todoItem;    }    }


Контроллер


Контроллер, реализация которого будет описана ниже, ничего не будет знать о контексте данных EFTodoDBContext, а будет использовать в своей работе только интерфейс ITodoRepository, что позволяет изменить источник данных не меняя при этом контроллера. Такой подход Адам Фримен в своей книге Entity Framework Core 2 для ASP.NET Core MVC для профессионалов назвал паттерн Хранилище.
Контроллер реализует обработчики стандартных методов HTTP-запросов: GET, POST, PUT, DELETE, которые будут изменять состояние наших задач, описанных в классе TodoItem.cs. Добавим в каталог Controllers класс TodoController.cs со следующим содержимым:

[Route("api/[controller]")]public class TodoController : Controller{    ITodoRepository TodoRepository;    public TodoController(ITodoRepository todoRepository)    {        TodoRepository = todoRepository;    }    [HttpGet(Name = "GetAllItems")]    public IEnumerable<TodoItem> Get()    {        return TodoRepository.Get();    }    [HttpGet("{id}", Name = "GetTodoItem")]    public IActionResult Get(int Id)    {        TodoItem todoItem = TodoRepository.Get(Id);        if (todoItem == null)        {            return NotFound();        }        return new ObjectResult(todoItem);    }    [HttpPost]    public IActionResult Create([FromBody] TodoItem todoItem)     {        if (todoItem == null)        {            return BadRequest();        }        TodoRepository.Create(todoItem);        return CreatedAtRoute("GetTodoItem", new { id = todoItem.Id }, todoItem);    }    [HttpPut("{id}")]    public IActionResult Update(int Id, [FromBody] TodoItem updatedTodoItem)    {        if (updatedTodoItem == null || updatedTodoItem.Id != Id)        {            return BadRequest();        }        var todoItem = TodoRepository.Get(Id);        if (todoItem == null)        {            return NotFound();        }        TodoRepository.Update(updatedTodoItem);        return RedirectToRoute("GetAllItems");    }    [HttpDelete("{id}")]    public IActionResult Delete(int Id)    {        var deletedTodoItem = TodoRepository.Delete(Id);        if (deletedTodoItem == null)        {            return BadRequest();        }        return new ObjectResult(deletedTodoItem);    } }


Перед определением класса указан атрибут с описанием шаблона маршрута для доступа к контроллеру: [Route(api/[controller])]. Контроллер TodoController будет доступен по следующему маршруту: https://<ip хоста>:<порт>/api/todo. В [controller] указывается название класса контроллера в нижнем регистре, опуская часть Controller.
Перед определением каждого метода в контроллере TodoController указан специальный атрибут вида: [<метод HTTP>(параметр,Name = псевдоним метода)]. Атрибут определяет какой HTTP-запрос будет обработан данным методом, параметр, который передается в URL запроса и псевдоним метода с помощью которого можно переотправлять запрос. Если не указать атрибут, то по умолчанию инфраструктура MVC попытается найти самый подходящий метод в контроллере для обработки запроса исходя из названия метода и указанных параметров в запросе, так, если не указать в контроллере TodoController атрибут для метода Get(), то при HTTP-запросе методом GET: https://<ip хоста>:<порт>/api/todo, инфраструткура определит для обработки запроса метод Get() контроллера.
В своем конструкторе контроллер получает ссылку на объект типа ITodoRepository, но пока что инфраструктура MVC не знает, какой объект подставить при создании контроллера. Нужно создать сервис, который однозначно разрешит эту зависисмость, для этого внесем некотрые изменения в класс Startup.cs, добавив в метод ConfigureServices() следующий код:

services.AddTransient<ITodoRepository, EFTodoRepository>();


Метод AddTransient<ITodoRepository, EFTodoRepository>() определяет сервис, который каждый раз, когда требуется экземпляр типа ITodoRepository, например в контроллере, создает новый экземпляр класс EFTodoRepository.
Полный код класса Startup.cs:

public class Startup{    public Startup(IConfiguration configuration)    {        Configuration = configuration;    }    public IConfiguration Configuration { get; }    public void ConfigureServices(IServiceCollection services)    {        services.AddControllers();        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);        services.AddDbContext<EFTodoDBContext>(options => options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));        services.AddTransient<ITodoRepository, EFTodoRepository>();    }    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)    {        if (env.IsDevelopment())        {            app.UseDeveloperExceptionPage();        }        app.UseHttpsRedirection();        app.UseRouting();        app.UseAuthorization();        app.UseEndpoints(endpoints =>        {            endpoints.MapControllers();        });    } }


Миграции


Для того чтобы Entity Framework сгенерировал базу данных и таблицы на основе модели, нужно использовать процесс миграции базы данных. Миграции это группа команд, которая выполняет подготовку базы данных для работы с Entity Framework. Они используются для создания и синхронизации базы данных. Команды можно выполнять как в консоли диспетчера пакетов (Package Manager Console), так и в Power Shell(Developer Power Shell). Мы будем использовать консоль диспетчера пакетов, для работы с Entity Framework потребуется установить пакет Microsoft.EntityFrameworkCore.Tools:



Запустим консоль диспетчера пакетов и выполним команду Add-Migration Initial:





В проекте появится новый каталог Migrations, в котором будут хранится классы миграции, на основе которых и будут создаваться объекты в базе данных после выполнения команды Update-Database:



Web API готово, запустив приложение на локальном IIS Express мы можем протестировать работу контроллера.

Тестирование WebAPI


Создадим новую коллекцию запросов в Postman под названием TodoWebAPI:



Так как наша база пуста, протестируем для начала создание новой задачи. В контроллере за создание задач отвечает метод Create(), который будет обрабатывать HTTP запрос отправленный методом POST и будет содержать в теле запроса сериализированный объект TodoItem в JSON формате. Аттрибут [FromBody] перед параметром todoItem в методе Create() подсказывает инфраструктуре MVC, что нужно десериализировать объект TodoItem из тела запроса и передать его в качестве параметра методу. Создадим запрос в Postman, который отправит на webAPI запрос на создание новой задачи:



Метод Create() после успешного создания задачи перенаправляет запрос на метод Get() с псевдонимом GetTodoItem и передает в качестве параметра Id только что созданной задачи, в результате чего в ответ на запрос мы получим созданный объект задачи в формате JSON.

Отправив HTTP запрос методом PUT и указав при этом в URL Id(http://personeltest.ru/aways/localhost:44370/api/todo/1) уже созданного объекта, а в теле запроса передав объект с некоторыми изменениями в формате JSON, мы изменим этот объект в базе:



HTTP запросом с методом GET без указания параметров получим все объекты в базе:



Запрос HTTP с методом DELETE и указанием Id объекта в URL(http://personeltest.ru/aways/localhost:44370/api/todo/2), удалит объект из базы и вернет JSON с удаленной задачей:



На этом все, в следующей части реализуем пользовательский интерфейс с помощью JavaScript-фреймворка Angular.
Подробнее..

Добавляем CRUD в ASP.NET Core проект за 10 минут с помощью EasyData

19.04.2021 06:22:01 | Автор: admin

image


Одной из первых задач для большинства бизнес-приложений на ASP.NET Core является реализация операций CRUD (Create, Read, Update, Delete) для основных объектов, с которыми работает ваше решение.


Каждый разработчик, которому нужно решить эту задачу, знает, что создание CRUD-страниц и форм очень скучный и трудоемкий процесс.
Если делать это вручную, то получится очень медленно и наверняка с кучей недоработок (пропущенные поля, забытые валидаторы и т.д.).
Можно воспользоваться инструментом scaffolding'а, доступным в Visual Studio. Но даже в этом случае это будет совсем не быстрый процесс, поскольку его нужно запускать для каждого класса модели. В итоге вы получаете множество .cs/.cshtml файлов, которые нужно поддерживать и атуализировать по мере изменений в классах модели или просто когда нужно что-то исправить в поведении или внешнем виде CRUD страниц. Если количество сущностей в вашей БД превышает десяток, то весьма велики шансы того, что файлы для реализации CRUD операций занимают больше 50% всей кодовой базы вашего проекта. Более того это решение все равно не обеспечивает некоторых важных, а порой и необходимых функций, таких как разбитие на страницы в режиме просмотра (pagination) или банальные поиск/фильтрация.


Решение: использовать библиотеку с открытым кодом EasyData, о которой и пойдет речь в данной статье.


Что такое EasyData?


EasyData и была создана для решения большинства (если не всех) проблем описанных выше. Это библиотека с открытым кодом, распространяется по MIT лицензии, исходники доступны на GitHub. Главной особенностью является использование декларативного подхода.
Весь процесс можно разделить на два основных этапа:


  • Вы описываете, с какими данными (сущностями и атрибутами) вы хотите работать и как ваша программа должна работать с этими данными (типы, ограничения, отношения между сущностями и т.д.).

  • На основе этой информации библиотека EasyData разворачивает Web API для CRUD-операций и пользовательский интерфейс на основе чистого (ванильного) JavaScript, что позволяет вашим пользователям выполнять все те операции.

Самое замачательное здесь то, что в случае использования Entity Framework Core для первого шага (описания данных) вам нужен только ваш DbContext! Вы просто скармливаете его библиотеке, и EasyData автоматически извлекает оттуда всю необходимую информацию для разворачивания CRUD API и пользовательского интерфейса.


Весь процесс занимает всего несколько минут и около 10 строк кода:


EasyData quick demo


Подключаем EasyData в свой проект


Прежде всего, чтобы попробовать EasyData в работе, вы можете открыть и запустить один из примеров проектов, доступных на GitHub.


Для установки EasyData в собственный проект надо выполнить следующие 3 простых шага:


1. Устанавливаем NuGet пакеты EasyData


  • EasyData.AspNetCore
  • EasyData.EntityFrameworkCore.Relational

2. Добавляем EasyData middleware в Startup.Configure:


using EasyData.Services;.    .    .    .    .    app.UseEndpoints(endpoints => {        endpoints.MapEasyData(options => {            options.UseDbContext<AppDbContext>();        });        endpoints.MapRazorPages();    });

В опциях нашего middleware мы указываем класс объекта DbContext, который и будет использоваться как источник метаданных.


3. Настраиваем страницу CRUD операций


Если вы используете Razor Pages, добавьте новую страницу (например, EasyData.chstml). Если это MVC, вам понадобятся новый контроллер и соответствующий ему view.


Наша новая страница должна будет ловить все адреса, которые начинаются с определенного префикса (/easydata/ по умолчанию, но это можно настроить). Для этого мы используем специальный catch-all параметр в определении маршрута: "/easydata/{**entity}".


Кроме того, мы также добавляем .css и .js файлы EasyData (easydata.min.css и easydata.min.js), которые обеспечивают отрисовку интерфейса управления данными и обработку всех CRUD-операций на стороне клиента.


@page "/easydata/{**entity}"@{    ViewData["Title"] = "EasyData";}<link rel="stylesheet" href="http://personeltest.ru/aways/cdn.korzh.com/ed/1.2.4/easydata.min.css" /><div id="EasyDataContainer"></div>@section Scripts {    <script src="http://personeltest.ru/aways/cdn.korzh.com/ed/1.2.4/easydata.min.js" type="text/javascript"></script>    <script>        window.addEventListener('load', function () {            new easydata.crud.EasyDataViewDispatcher().run()        });    </script>}

Вот и все. Теперь вы можете запустить свой проект, открыть URL-адрес /easydata и наслаждаться функциями CRUD.


Вот как это выглядит в итоге:



Страница просмотра значений для некоторой сущности (в данном случае, Orders)



Диалог редактирования одной записи



Lookup диалог, который был открыт из диалога редактирования записи


Как это работает


Коротко о том, как работает вся эта магия.


Как мы уже упоминали ранее, EasyData решает 3 основных задачи:


  • Собирает метаданные из нашей базы данных.
  • Устанавливает API для основных CRUD операций.
  • Визуализирует интерфейс (опять же, на основе метаданных) и обрабатывает все взаимодействие пользователя с этим интерфейсом.

Давайте изучим все эти части более подробно.


Метаданные


Метаданные это данные о ваших данных: какие сущности (таблицы) хранятся в вашей базе данных, как они связаны, какие имеют атрибуты (поля) и другая полезная информация.


EasyData собирает метаданные (каким-либо способом об этом см. ниже) и сохраняет их в объекте класса MetaData. Этот объект содержит список сущностей (таблиц), атрибуты (поля) для каждой сущности, связи между сущностями и некоторую дополнительную информацию, используемую в API и при визуализации и обработке пользовательского интерфейса.


Для заполнения объекта MetaData нам нужно указать некоторый загрузчик метаданных. В нашем примере мы сделали это с помощью вызова UseDbContext для загрузки метаданных из объекта DbConext. На данный момент (в версии 1.2), это пока единственный доступный загрузчик метаданных. В будущих версиях можно будет загружать метаданные непосредственно с БД или, возможно, каким-нибудь другим способом.


EasyData middleware


EasyData middleware отвечает за обработку REST API для всех CRUD (и не только) операций, инициированных веб страницей.


Чтобы добавить middleware в очередь обработки вашего ASP.NET Core приложения используйте функцию MapEasyData в процессе настроки точек вызова (endpoints) UseEndpoints:


  app.UseEndpoints(endpoints =>    {       endpoints.MapEasyData(options => {            options.UseDbContext<AppDbContext>();        });    }

Этот вызов желательно поставить перед любым вызовом MapControllerRoute или MapRazorPages.
По умолчанию EasyData API будет откликаться по адресу /api/easydata, но вы легко можете изменить его на другой:


   endpoints.MapEasyData(options => {        options.Endpoint = "/api/my-crud";        .    .    .    .    });

Единственное, что нужно настроить для EasyData middleware, это сказать ему, откуда брать метаданные. Как уже упоминалось выше, сейчас доступен только один вариант, а именно получение метаданных с DbContext. Вот почему мы добавляем вызов UseDbContext <AppDbContext>() в приведенном выше примере. Кроме получения метаданных, UseDbContext также обеспечивает наш middlware всеми средствами для выполнения CRUD-операций (через сам объект DbContext).


Корневая страница интерфейса EasyData


В качестве такой страницы выступает Razor page или же MVC view. Эта страница должна обрабатывать все URL адреса, которые начинается с определенного префикса. По умолчанию это /easydata/ и поэтому все пути, типа /easydata/student или /easydata/invoice, должны быть обработаны этой страницей.


NB: /easydata/ префикс по умолчанию. Вы можете установить и другой путь к этой странице, но в этом случае его нужно будет указать в параметрах нашего объекта RootDispatcherView


Наша корневая страница может содержать любые элементы HTML на ваш выбор. Но для нормальной работы CRUD интерфейса, она должна включать следующие 4 элемента:


  • <link> элемент со ссылкой на CSS файл EasyData (easydata.min.cs)


  • Контейнер (пустой элемент div), где будет рисоваться наш CRUD интерфейс. По умолчанию он должен иметь идентификатор EasyDataContainer, но это также можно настроить с помощью опций.


  • <script> элемент со ссылкой на файл easydata.min.js.


  • небольшой скрипт, который создает и запускает объект EasyDataViewDispatcher при загрузке страницы.



Пример простейшей корневой страницы вы можете увидеть в разделе Подключаем EasyData в свой проект выше.


В завершение


На данный EasyData может работать с .NET Core 3.1 и .NET 5. Очевидно, поддерживаются все версии ASP.NET Core и Entity Framework Core, которые могут работать с указанными версиями .NET (Core).
Однако не будет большой проблемы при необходимости добавить также поддержку предыдущих версий .NET Core или даже .NET Framework 4.x. Если кому-то это нужно, создавайте новый issue про это в GitHub репозитории библиотеки.


EasyData был сделан, в первую очередь, для быстрого создания прототипов новых проектов или так называемых POC (proof of concept), когда уже есть некоторое понимание с какими данными придется работать, но не хочется тратить много времени на реализацию простейших операций с этими данными.


Конечно, мы надеемся, что со временем можно будет полноценно использовать это решение и для вполне себе рабочих решений (не только прототипов). Будем рады любым вашим замечаниям или советам по этому поводу.


Ну и конечно, не забудьте поставить звездочку в EasyData репозитории на GitHub. Особенно если эта библиотека помогла сэкономить вам немного времени.

Подробнее..
Категории: C , Net , Net core , Asp.net core , Entity framework , Net 5 , Crud

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru