Comments 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");
});
}
Статья не про это
ValidationExceptionHandler следовало бы тоже возвращать ValidationProblemDetails вместо голого ValidationResult
Спорно к сожалению, вызов BadRequest() в Controller не возвращает ProblemDetails, решил в этом случае лучше придерживаться такого же подхода.
Хотя спецификация описывает и ошибку 400
вызов BadRequest() в Controller не возвращает ProblemDetails
Так ведь и не должен. Фреймворк не делает предположений как ваш IActionResult должен быть трансформирован. Если хотите возвращать из контроллера ProblemDetails/ValidationProblemDetails, то у контроллера есть соответствующие методы Problem()
/ ValidatonProblem()
.
Я очень редко имею дело с ASP, но API поднимаю регулярно на C#. Все ошибки обрабатываю в кастомном middleware. Это нововведение для удобства или что-то ещё умеет?
В статье есть ссылка на код Middleware который вы подключаете вызовом UseExceptionHandler, также в статье описаны ключевые аспекты которые этот Middleware обрабатывает.
Да, есть такое, знакомо - по незнанию пихают в pipeline какую-нибудь свою кастомщину вместо default-ной, а потом сидишь гадаешь, почему у тебя что-то ведет себя совсем не так, например ошибочные запросы в лог не пишутся, или пишутся как-то совсем нестандартно, или коды не те. Видел, например, один сервис, который при недоступности БД возвращал клиенту 400. Коды HTTP-ошибок в API это вообще какая-то больная тема - я уже каких только фантазий на этот счет не насмотрелся вживую - после такого уже и удивляться чему-то перестаешь.
ASP.NET может самостоятельно превращать все ошибки в Problem Details, включая 500-ки, без дополнительных приседаний :) Основная проблема многих разработчиков бекенд веб-приложений, что они основательно перестали понимать, что возвращаемый ответ должен опираться на тип запроса. Если в запросе клиент говорит, что он хочет принимать html, text, не надо ему JSON впихивать.
Отсюда, пользователи иногда наблюдают вываливающиеся на них страшные JSON-ы. Это говорит о плохой экологии разработки, на лицо непонимание технологий веб, задачи решаются тупо в лоб. Ну примерно, как разработка жигулей -- ну едет же, что ещё надо?
Платформа ASP.NET, из коробки, даёт решение большинства задач и проблем. Но пилить велосипеды со своими мидлварями значительно интереснее, чем разбираться в платформе. Я это прекрасно понимаю :)
Обработка ошибок с помощью IExceptionHandler в ASP.NET Core 8.0