Комментарии 12
Основанный на фреймворке ASP.NET MVC, Web API был предназначен для того, чтобы рассматривать глаголы и существительные архитектуры REST как граждан первого класса.
Звучит сильно!
Но значит ли это, что он плох?
По крайней мере вот это:
catch (Exception)
{
return Results.BadRequest("Failed");
}
Это не только плохо, но и совсем не нужно. Во первых, по коду ясно, что тут должен быть 500, во вторых эту 500 ASP.NET pipeline сам вернет, в третьих он её еще и сам запишет в лог. А в четвертых тут совсем нет нужды возвращать IActionResult
, можно просто коллекцию объектов и в итоге весь этот метод превращается просто в:
[HttpGet]
public IEnumerable<OrderViewModel> Get(bool includeItems = true) =>
_mapper.Map<IEnumerable<OrderViewModel>>(
_repository.GetOrdersByUser(User.Identity.Name, includeItems));
А для обработки типа:
if (client == null)
{
return Results.NotFound();
}
лично я уже давным-давно использую однажды написанный готовый кастомный атрибут (MVC action filter в котором от силы пару дюжин строчек кода), и выглядит это, например, так:
[HttpGet(clients/{id}]
[NullMeans404] // <== преобразует возвращенный ObjectResult с null в NotFoundResult
public async Client GetClient([FromRoute] int id) => await _repo.GetClientAsync(id);
Про содержание статьи в целом еще можно поспорить (мне, например, еще в принципе не нравится что логика обработки запросов, какая бы она простая не была, у вас смешивается с кодом конфигурации приложения), но примеры кода, на мой взгляд, однозначно плохи.
Новомодные веяния - это, наверное, неплохо, но в данном случае, мы экономим пару строк кода для каждого эндпойнта. А взамен? Понадобится на эндпойнт повесить какие-то атрибуты, или ещё что-то- вклинивать это в подобный сжатый синтаксис- это делать код ещё менее читаемым.
Второе. Если настройка эндпойнтов уезжает в startup/program, это плохо. Файлы получают больше ответственности. Сложнее делить логически (как это можно делать контроллерами). Вот если бы можно было прямо в файле контроллера описывать эндпойнты в таком стиле, как много лет назад в язык пришли get; set; без необходимости расписывать аксессоры и поле данных - другой вопрос.
Третье. В некоторых типах автотестов мне удобно вызывать методы контроллеров, чтобы покрыть их, почти как реальные запросы, без моков и фейков. А тут как? Вызывать из тестов сервисные методы? А если я хочу прям всю цепочку от начала покрыть?
Дальше лень перечислять. Одни минусы какие-то.
Don’t JS my C#
В статье явно написано, что это хорошо пойдет для прототипирования или простенького приложения. И практики еще не наработали.
Или делать методы однострочными — тогда это будет по сути описание всех маршрутов, что вполне удобно (не нужно по всему проекту судорожно атрибуты читать и пытаться понять, какой будет итоговый маршрут)
2. Удобство: можно легко на этапе стартапа включать/выключать некоторые ендпоинты простым ифом. Или добавлять новые через циклы (хз где это может пригодиться, а вот выключение — вполне)
3.
Вот если бы можно было прямо в файле контроллера описывать эндпойнты в таком стиле, как много лет назад в язык пришли get; set; без необходимости расписывать аксессоры и поле данных — другой вопрос.
Велком: Carter
4.
В некоторых типах автотестов мне удобно вызывать методы контроллеров, чтобы покрыть их, почти как реальные запросы, без моков и фейков.
Ценность таких автотестов почти нулевая, так как игнорируется весь конвеер обработки запросов. Если хочется так протестировать — выноси логику из контроллеров в нормальные сервисы или Mediatr, и тестируй уже их. Контроллеры же лучше воспринимать как такое объектное описание маршрутов.
А если я хочу прям всю цепочку от начала покрыть?
Как и с контроллерами — поднимать сервер и слать http запросы.
Если прям как вы описываете — делаешь хендлеры не анонимными и вызываешь их. Точно также, как и контроллеры. (но есть нюанс)
А если и правда мелкий api на пару методов и ты не хочешь пихать в входящие параметры DI сервис, как правильно стоит это в коде изложить? Обернуть все методы в scope ?
var bldr = WebApplication.CreateBuilder(args);
bldr.Services.AddDbContext<JurisContext>();
var app = bldr.Build();
using (var scope = bldr.Services.CreateScope()){
var myDb = scope.Get<JurisContext>();
app.MapGet("/", () => mydb.GetById(1));
app.MapGet("/page2", () => mydb.GetById(1));
app.MapPost("/page2", (inputModel) => mydb.GetById(1));
}
Выглядит не очень
Оно и работать не будет.
Скоуп будет задиспозен сразу после выхода из using, вместе со всеми сервисами
ну перед скобкой поставить app.Run();
Но зачем? Ведь скоуп предполагается соответствующим какому-то действию, а в контектсе ASP.NET веб приложения - обработке входящего запроса. Тем более, в статье показано, что можно инжектить зависимости в лямбду-обработчик.
Если использовать DbContext как синглтон (долгоживущий класс), то уже через пару сотен запросов на достать из БД/сохранить в БД Вы увидите деградацию производительности, а через пару тысяч запросов - то что Ваш сервис перестал работать.
Щачем тогда вообще скоуп тормошить, если, по сути, у тебя один инстанс на все приложение получается. Возми та сделай статик класс. Только это будет все равно говнецо
Минимальные API в .NET 6