Как стать автором
Обновить

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

Startup в .net8?

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

Давно пора уже этот 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 и пусть клиент с ним работает.

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

Публикации