#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
с любым из методов в нем