Как стать автором
Поиск
Написать публикацию
Обновить

Комментарии 28

Никто не отменял

Давно пора уже этот 20 летний атавизм отрезать

Глупости. Мне нравится.

Почему? Это же намеренное игнорирование интерфейсов.

Мне нравится разделение настройки хоста и аппликации. Там все очень четко разделено. В новой настройке через свойства вместо делегатов такая мешанина получается...

Ну вот ребятки отрезали себе static class Program { static void Main(){} } и вроде понизился порог входа для новичков, можно сразу как в питончике писать hello world, но вместе с тем прибавилось геммороя с обьяснением порядка обьявления классов, и рассказов что мы тут Program.Main не пишем, но на самом деле он есть.. так что некоторые вещи не надо было трогать, как более очевидные и простые для понимания

Если тот код, который в Program.cs завернуть в static class Program { static void Main() { ... } } то все будет работать точно так же. Не пойму что у вас конкретно с этим сложности вызывает?

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

Впрочем, могу рассказать. Генерируемый класс Program был internal и это препятствовало написанию интеграционных тестов с WebApplicationFactory, причем, поскольку, этот класс (Program) не просто использовался, например, внутри какого-нибудь метода, а нужен был как параметр для generic, то добавление атрибута [assembly:InternalsVisibleTo(...)] эту ситуацию никак не лечило. Лечилось добавлением в любой файл проекта (в тот же Program.cs) единственной строки public partial class Program {}, но это было как-то не совсем красиво, впрочем, как я уже написал, это поправили.

Принцип есть такой: явное лучше, чем неявное.

Я соглашусь с вашим коллегой, на комментарий которого вы отвечаете, что C# в какой-то момент свернул не туда. Посмотрите на Minimal API и скажите честно: оно точно надо было? У кого-то attention span короче 10 строчек? app.UseEndpoints(e => {}); - это что вообще? Почему нужен какой-то костыль, чтобы вся эта автоматическая магия не сломалась?

Радует только то, что этим не заставляют пользоваться, можно всё выкинуть и вернуть старый-добрый Startup. Да, многословнее, зато весь пайплайн перед глазами.

Посмотрите на Minimal API и скажите честно: оно точно надо было?

Я вполне допускаю ситуацию, когда может быть нужен очень простой сервис (вплоть, даже до сервиса из одного-единственного метода) - делать для этого полный код с Program, Main, Startup, ConfigureServices, и Configure это в таком случае откровенный overhead. И, вот, еще, чего реально не хватает - это простой возможности прикрутить к серверному приложению один только healthcheck без остального ASP.NET (например, для приложения в котором только background services и Web API не нужен) - я уверен, что это как-то можно сделать и даже слепить это в виде собственного отдельного пакета, и кое-какие зацепки даже видел на StackOverflow, но, как-то руки с этим как следует поразбираться не доходят. Оно, как-то, и не особо критично с практической т.з, но, ради обработки одного-единственного HTTP-запроса цеплять к приложению целый фреймворк это как-то не очень изящно выглядит.

Да, многословнее, зато весь пайплайн перед глазами.

Он и так весь перед глазами - те же вызовы services.AddBlblabla(); и app.UseBlablabla() - просто не в отдельном файле и не разбиты по двум методам. Мне в плане "вкусовщины", в принципе, без разницы, что по старому, что по новому, но, вот никаких преимуществ "старого" перед "новым" я вообще даже придумать не могу, только что лишний файл и несколько лишних строчек кода - я, вот, совершенно не вижу, как эта лишняя многословность, в данном случае, хотя бы читаемость улучшает. И, кстати, вообще, то как в ASP.NET изначально сделана работа с классом Startup это что-то такое спроектированное вопреки всему объектно-ориентированному подходу (возможно, этот, как раз, и показывает, что, на деле, он не особо-то и нужен :)).

Принцип есть такой: явное лучше, чем неявное.

Так в том-то и дело, что в этом классе Startup на самом деле явного как раз ничего и нет. Откуда мне, например, если я не знаю внутренностей ASP.NET догадаться, что его код запуска как-то магически находит этот класс, и как-то магически вызывает его методы в определенном порядке и с определенными параметрами? По внешнему виду это вообще какой-то сбоку-припеку левый класс, который вообще нигде не используется :)) Я бы еще понял, если бы был какой-нибудь абстрактный класс, типа, StartupBase, с абстрактными методами ConfigureServices(...) и Configure(...), класс Startup объявлялся бы, как производный от него и эти методы перегружал - это тогда был бы дизайн по принципу "open-closed" с паттерном "шаблонный метод". Но, опять таки, повторюсь, сама суть этого класса такая, что его существование в виде отдельной программной конструкции оно совершенно незначимо - с тем же успехом можно было бы методы ConfigureServiсes и Configure задизайнить хоть как статические методы класса Program, поэтому всякие паттерны и принципы ООП в случае этого класса вообще без разницы.

app.UseEndpoints(e => {}); - это что вообще?

Endpoints уже сто лет - они были еще начиная, как минимум, с 3.1. На самом деле, старые вызовы тоже до сих пор так и работают, но endpoints позволяют сделать некоторые вещи, которые, в общем-то, не так чтобы часто нужны, но если они нужны, то сделать их "по старинке" это будет муторно и выглядеть будет некрасиво. Например, с ними можно по разному настроить pipeline для запросов с различными путями.

app.UseEndpoints(e => {}); - это что вообще?

Я имею в виду, я не вижу в этом вызове эндпоинтов, мы же передаём пустой делегат, правильно? А нет, оказывается, неправильно: это одно и то же:

app.MapGet("/", () => "Hello world");

app.UseEndpoints(e => e.MapGet("/", () => "Hello world"));

Или нет? Почему нужен пустой делегат?

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

Давайте посмотрим на этот пайплайн:

// ...
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Use<CustomMiddleware>();

app.Run();

Очевидно ли для вас, что здесь есть developer exception page и аутентификация? Точнее, может быть есть, а может и нет. Что сначала выполнится CustomMiddleware, а уж потом "/"? Что если вам не нужно настраивать CORS, то .UseAuthentication() можно не указывать, но если написали .UseCors(), то будьте добры, укажите и аутентификацию.

Очевидно ли для вас, что здесь есть developer exception page и аутентификация?

А если все то же самое будет в отдельном методе Configure, то это сразу станет очевидно?

В классическом, не Minimal, API вы описываете пайплайн от начала до конца, поэтому да?

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseCors();
    app.UseAuthentication();
    app.UseAuthorization();

    app.UseRouting();
    
    app.UseMiddleware<CustomMiddleware>();
    app.UseEndpoints(e =>
    {
        e.MapGet("/", () => "Hello world");
    });
}

Используйте вместо WebApplication.CreateBuilder() CreateEmptyBuilder() - тогда вам вообще все руками надо будет настраивать, даже конфигурацию конфигурации.

Статья не про это

ValidationExceptionHandler следовало бы тоже возвращать ValidationProblemDetails вместо голого ValidationResult

Спорно к сожалению, вызов BadRequest() в Controller не возвращает ProblemDetails, решил в этом случае лучше придерживаться такого же подхода.

Хотя спецификация описывает и ошибку 400

вызов BadRequest() в Controller не возвращает ProblemDetails

Так ведь и не должен. Фреймворк не делает предположений как ваш IActionResult должен быть трансформирован. Если хотите возвращать из контроллера ProblemDetails/ValidationProblemDetails, то у контроллера есть соответствующие методы Problem() / ValidatonProblem().

По-моему, если в BadRequest передать ModelState, то он как раз вернет 400 c ProblemDetails теле, но, впрочем это всё-таки только частный случай, который все возможные кейсы не покрывает.

Я очень редко имею дело с ASP, но API поднимаю регулярно на C#. Все ошибки обрабатываю в кастомном middleware. Это нововведение для удобства или что-то ещё умеет?

В статье есть ссылка на код Middleware который вы подключаете вызовом UseExceptionHandler, также в статье описаны ключевые аспекты которые этот Middleware обрабатывает.

Да, есть такое, знакомо - по незнанию пихают в pipeline какую-нибудь свою кастомщину вместо default-ной, а потом сидишь гадаешь, почему у тебя что-то ведет себя совсем не так, например ошибочные запросы в лог не пишутся, или пишутся как-то совсем нестандартно, или коды не те. Видел, например, один сервис, который при недоступности БД возвращал клиенту 400. Коды HTTP-ошибок в API это вообще какая-то больная тема - я уже каких только фантазий на этот счет не насмотрелся вживую - после такого уже и удивляться чему-то перестаешь.

ASP.NET может самостоятельно превращать все ошибки в Problem Details, включая 500-ки, без дополнительных приседаний :) Основная проблема многих разработчиков бекенд веб-приложений, что они основательно перестали понимать, что возвращаемый ответ должен опираться на тип запроса. Если в запросе клиент говорит, что он хочет принимать html, text, не надо ему JSON впихивать.

Отсюда, пользователи иногда наблюдают вываливающиеся на них страшные JSON-ы. Это говорит о плохой экологии разработки, на лицо непонимание технологий веб, задачи решаются тупо в лоб. Ну примерно, как разработка жигулей -- ну едет же, что ещё надо?

Платформа ASP.NET, из коробки, даёт решение большинства задач и проблем. Но пилить велосипеды со своими мидлварями значительно интереснее, чем разбираться в платформе. Я это прекрасно понимаю :)

А как можно в asp.net возвращать ответ нужного вида в зависимости от responseType? Или в запросе надо каждый тип проверять и возвращать нужный? Если да, то зачем этим заниматься, если можно возвращать json и пусть клиент с ним работает.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации