Почему ASP.NET Класс запуска Core не является интерфейсом или абстрактным классом?

#c# #asp.net-core

#c# #шаблоны проектирования #asp.net-core #класс-дизайн

Вопрос:

Это касается принципов проектирования, лежащих в Startup основе класса, описанного здесь:

https://learn.microsoft.com/en-us/aspnet/core/fundamentals/startup?view=aspnetcore-2.1

Я понимаю, что класс должен включать такие методы, как ConfigureServices или Configure .

Почему CreateDefaultBuilder(args).UseStartup<Startup>() не требует какого-либо базового класса или интерфейса для лучшей читаемости?

При таком подходе к проектированию кто-то должен прочитать документацию и знать о названиях магических методов, таких как ConfigureServices или Configure .

Если это часть нового подхода к проектированию классов, то где я могу прочитать об этом больше?

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

1. «Волшебные имена» — это имена, которые вы должны создать самостоятельно и которые не документированы. Это позволяет вам настраивать / ConfigureServices для каждой среды, и это не может быть известно на уровне пакета. Извините, но это похоже на напыщенную речь.

2. Соглашение. Я бы сказал, что это обеспечивает большую гибкость и требует от разработчика меньше работы. Возьмите ConfigureServices , по умолчанию это void . Если вы используете другой контейнер DI, вам нужно вернуться IServiceProvider из ConfigureServices . Как бы вы сделали это с базовым классом? Всегда приходится возвращать его? Кроме того, по умолчанию, когда вы создаете ASP.NET Основной проект в Visual Studio, эти методы будут созданы для вас.

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

4. @CodingYoshi, извините, не понял. Не могли бы вы немного уточнить свой комментарий.

5. @CamiloTerevinto, что касается вашего «Это позволяет вам настраивать / ConfigureServices для каждой среды» — можете ли вы немного уточнить или отсылать меня к документу, который объясняет

Ответ №1:

Есть несколько причин, почему это сделано так, как это сделано. Одна из наиболее очевидных причин заключается в том, что вы можете внедрять службы в Configure метод, такие как

 public void Configure(IAppBuilder app, IMyService myService)
{
    myService.DoSomething();
}
  

Очевидно, что вы не можете сделать это с интерфейсами, абстрактными классами или наследованием.

Вторая причина, по которой это делается с помощью метода соглашения, заключается в том, что существует не только Configure/ConfigureServices метод, но и бесконечное количество методов настройки, зависящих от среды.

 public void Configure(IAppBuilder app) { }
public void ConfigureDevelopment(IAppBuilder app) { }
public void ConfigureProduction(IAppBuilder app) { }
public void ConfigureStaging(IAppBuilder app) { }
public void ConfigureSomethingElse(IAppBuilder app) { }
  

и в зависимости от вашей переменной окружения для ASPNET_ENVIRONMENT будет выбран и выполнен другой метод (или по умолчанию Configure/ConfigureServices , если не найден соответствующий метод, специфичный для среды).

Ничего из этого невозможно с традиционным ООП (наследование / интерфейсы / абстрактные классы).

То же самое относится к другим частям ASP.NET Ядро, как и промежуточные программы и Invoke Метод. В Invoke метод также могут быть введены зависимости, но для вызова следующего промежуточного программного обеспечения вы просто делаете

 await next?.Invoke();
  

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

И для полноты можно также иметь несколько Startup классов с именами методов по умолчанию ( Configure / ConfigureServices ) с именем StartupDevelopment , StartupProduction , Startup (как запасной вариант) и ASP.NET Core подберет правильный класс на основе набора переменных среды.

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

1. Все, что вы упомянули, может быть сделано как с интерфейсами, так и / или с наследованием. Я не уверен, почему вы думаете, что это не может быть.

2. @CodingYoshi: Хорошо, скажите мне, как вы создаете интерфейс, который принимает неограниченное количество параметров типа и комбинацию lol. Конечно, в строго типизированном виде means param object[] здесь не подходит, поскольку вы не можете определять зависимости таким образом. Помните, что здесь мы говорим о методах в интерфейсах и абстрактных классах, а не о внедрении конструктора, которое является деталью реализации

3. Я думаю, что в первую очередь нет необходимости создавать интерфейс с неограниченным количеством и комбинацией параметров типа. Зачем вводить службы в Configure, если вы можете предоставлять зависимости через конструктор. В этом случае класс запуска может быть абстрагирован от интерфейса.

4. @VitaliyMarkitanov: На самом деле не собираюсь взлетать 😉 Как бы вы внедрили класс в конструктор Startups, когда IoC настроен внутри, ConfigureServices а контейнер создается только после ConfigureServices вызова и до Configure вызова. Вы не получаете такой гибкости, как в текущей системе. Кроме того, использование соглашений для ConfigureServices{Environmentname} не сработало бы, как и для Start{Environmentname} , и вы не получили бы ровно ничего по сравнению с текущей системой

5. @Tseng. Конструктор запуска вызывается перед ConfigureService(). Установите контрольные точки для подтверждения. Также класс запуска может иметь конструктор с внедренными зависимостями — это означает, что DI инициализируется перед созданием класса запуска. В этом случае deps класса могут переходить в const (что является хорошо известным шаблоном). В C # (и других строго типизированных языках) стандартным подходом является наличие интерфейсов и абстрактных классов. В соглашении о запуске .Net Core используется специфичный метод signatures…it это влияние языков без типов. Таким образом, класс может быть абстрагирован через интерфейс или abstract и нет необходимости в перегрузках.

Ответ №2:

Класс запуска может быть унаследован от интерфейса IStartup.

 // packagesmicrosoft.aspnetcore.hosting.abstractions2.2.0libnetstandard2.0Microsoft.AspNetCore.Hosting.Abstractions.dll
namespace Microsoft.AspNetCore.Hosting
{
 public interface IStartup
  {
   IServiceProvider ConfigureServices(IServiceCollection services);
   void Configure(IApplicationBuilder app);
  }
}
  

По умолчанию мастер не создает файл шаблона с реализацией из IStartup. Почему нет — вероятно, ошибка или влияние нетипизированных языков..

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

1. Прочитайте мой ответ, и вы поймете, почему. С интерфейсом вы не можете этого сделать void Configure(IApplicationBuilder app, IMyService myService) , потому что интерфейс — это контракт, в котором указывается точное количество, тип и порядок параметров. Также вы не можете вызвать его, ConfigureDevelopment чтобы он вызывался только тогда, ASPNETCORE_ENVIRONMENT когда установлено значение «Разработка»

2. Вы потеряете настройки, зависящие от среды, если вы это сделаете, и вы также потеряете инъекции внутри Configure/ConfigureXxx методов, так что нет: с текущими наборами функций и гибкостью это невозможно сделать в виде интерфейсов. В итоге вы либо получаете полдюжины интерфейсов ( IConfigureServices/IConfigureServicesDevelopment/IConfigureProduct/IConfigureStaging и эквивалентных IConfigureXxx интерфейсов) и ограничены только 3 предопределенными средами, либо дублируете установочный код (несколько классов запуска). Ни один из которых не «проще» начать с пустого проекта, чем текущий подход

3. Первоначальный вопрос был: «Почему ASP.NET Класс запуска Core не является интерфейсом или абстрактным классом?» Ответ: Он может быть абстрагирован от интерфейса. Если ваш класс запуска абстрагирован (унаследован от IStartup), конечно, ваша перегрузка Configure (otherParams) не будет вызвана. Но вам это все равно не нужно, поскольку ваши службы, зарегистрированные в ConfigureServices (), могут быть использованы в Configure (IApplicationBuilder), например, через ссылку на частные переменные. Опять же, суть в том, что в типизированном языке идея заключалась в использовании типов, а не отражения, как это делают в DI в javascript или angular. Это был первоначальный вопрос.

4. Нет, ваш ответ на вопрос «возможно ли реализовать is как интерфейс» (который не был задан), вопрос в том, «почему это не сделано так». Ответ на это таков: «Это не сделано из-за нестабильности при настройке и того, что текущий подход имеет более гибкую настройку, зависящую от окружающей среды». Пожалуйста, прочтите вопрос целиком: Почему CreateDefaultBuilder(args).UseStartup<Startup>() не требуется какой-либо базовый класс или интерфейс для лучшей читаемости? Причина проста: потому что это ограничило бы способ его настройки. класс может быть любым из Startup/StartupXxx с любым из методов в нем