40

This is in regards to the design principals behind the Startup class explained here:

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

I understand that the class needs to include methods like ConfigureServices or Configure.

Why CreateDefaultBuilder(args).UseStartup<Startup>() does not mandate any base class or interface for better readability?

With this design approach, someone must read the documentation and know about the magic method names like ConfigureServices or Configure.

If this is part of a new class design mindset, then where can I read more about it?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Allan Xu
  • 7,998
  • 11
  • 51
  • 122
  • 2
    "Magic names" would be names you have to create on your own and aren't documented. This allows you to have a Configure/ConfigureServices per environment and that cannot be known at the package-level. Sorry but this seems like rant. – Camilo Terevinto Nov 12 '18 at 00:56
  • 1
    Convention. I'd argue that it allows more flexibility, and requires the developer to do less work. Take `ConfigureServices`, by default that's a `void`. If you use another DI container, you need to return `IServiceProvider` from `ConfigureServices`. How would you do that with a base class? Always have to return it? Also, by default, when you create an ASP.NET Core project in Visual Studio, those methods will be created for you. – ProgrammingLlama Nov 12 '18 at 01:13
  • 1
    Please explain how interfaces or abstract classes would mitigate the issue of reading documentation. – CodingYoshi Nov 12 '18 at 01:15
  • 1
    @CodingYoshi, sorry, not following. Can you elaborate your comment a bit. – Allan Xu Nov 12 '18 at 01:51
  • 1
    @CamiloTerevinto, in regards to your "This allows you to have a Configure/ConfigureServices per environment" - can you elaborate a bit or refer me to a doc that explain – Allan Xu Nov 12 '18 at 01:53
  • [This is what @CamiloTerevinto is referring to.](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/environments?view=aspnetcore-2.1#startup-method-conventions) – Kirk Larkin Nov 12 '18 at 08:05
  • @CodingYoshi - compiler errors, for starters. – OdeToCode Nov 13 '18 at 15:24

2 Answers2

30

There are several reasons why its done the way its done. One of the more obvious reasons is, because you can inject services into Configure method, such as

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

Obviously, you can't do that with interfaces, abstract classes or inheritence.

The second reason why its done by convention method is, that there is not only Configure/ConfigureServices method, there is an infinite number of environment-dependent configure methods.

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) { }

and depending on your environment variable for ASPNET_ENVIRONMENT a different method will be chosen and executed (or the default Configure/ConfigureServices if no matching environment specific method was found).

None of this is possible with traditional OOP (inheritance/interfaces/abstract classes).

The same applies to other parts of ASP.NET Core, like Middlewares and the Invoke Method. The Invoke method can also have dependencies injected into it, but in order to call the next middleware you simply do

await next?.Invoke();

and do not have to worry which dependencies the next middleware requires or may take.

And to be complete, one can also have multiple Startup classes with the default method names (Configure/ConfigureServices) named StartupDevelopment, StartupProduction, Startup (as fallback) and ASP.NET Core will pick up the correct one based on Environment variable set.

E_net4
  • 27,810
  • 13
  • 101
  • 139
Tseng
  • 61,549
  • 15
  • 193
  • 205
  • 7
    Everything you have mentioned can be done with both interfaces and/or inheritance. I am not sure why you think it cannot be. – CodingYoshi Nov 12 '18 at 13:05
  • 3
    @CodingYoshi: Well, tell me how do you create a interface which accepts an unlimited number and combination of type parameters lol. Of course in **strong typed** manner, means `param object[]` isn't suitable in here, since you can't define dependencies that way. Remember, we are talking about methods on interfaces and abstract classes here, not about constructor injection which is an implementation detail – Tseng Nov 12 '18 at 13:07
  • 1
    I think it is not needed to create interface with unlimited number and combination of type params in the first place. Why inject services into Configure if you can provide dependencies via constructor. In this case Startup class can be abstracted to interface. – Vitaliy Markitanov Jul 30 '19 at 00:40
  • @VitaliyMarkitanov: Not really gonna take off ;) How'd you inject a class into Startups constructor, when the IoC is configured inside `ConfigureServices` and the container is only created after `ConfigureServices` is called and before `Configure` is called. You get no where the flexibility as of the current system. Also, using conventions for `ConfigureServices{Environmentname}` wouldn't gonna work, neither would `Start{Environmentname}` and you would **gain exactly** nothing compared to the current system – Tseng Jul 30 '19 at 00:45
  • @Tseng. Startup constructor called before ConfigureService(). Set breakpoints to confirm. Also Startup class may have constructor with injected dependencies - that means that DI is initialized before Startup class created. In this case class deps can go into const (which is well known pattern). In C# (and other strongly typed languages) it is standard approach to have interfaces and abstract classes. In .Net Core Startup convention used- specific method signatures...it is influence of typeless languages. So class can be abstracted via Interface or abstract and no need for overloads. – Vitaliy Markitanov Jul 31 '19 at 15:05
  • @VitaliyMarkitanov: Dude,you can't inject something into `Start` class, if its the DI is built **after** `ConfigureServices` methods call. Having it built outside is just non-sense and you split the app configuration into two and you still end up with having to know how the DI is created and instantiated. The whole point of Startup class as its know in ASP.NET Core is to have **ONE** easy entry point of the application where everything is configured (dependencies and middlewares,that's the `Configure` method) w/o having to bother about IoC builder or even which container is used under the hood – Tseng Aug 01 '19 at 03:05
  • And as stated above, you can't inject stuff into `Configure` Method with an interface. No matter which approach you suggest, it will be **less** flexible then the current one, since you lose environmental specific overrides (such as `StartDevelopment` or `ConfigureServiceStraging` which will only be called if the evironment is set to Development or Staging respectively. The point is to have a template, which can be started directly, w/o having to choose an IoC (or later change, because every IoC has different API surface and methods) – Tseng Aug 01 '19 at 03:09
  • @Tseng. >you can't inject something into Start class, What do you mean? Try. ------------- public class Startup : IStartup { private readonly IHostingEnvironment _env; private readonly ILoggerFactory _loggerFactory; public IConfiguration Configuration { get; } public Startup(IConfiguration configuration, IHostingEnvironment env, ILoggerFactory loggerFactory) { _env = env; _loggerFactory = loggerFactory; Configuration = configuration; } //etc } ------- – Vitaliy Markitanov Aug 02 '19 at 02:12
  • @VitaliyMarkitanov: Thats just hard-wired types, which ASP.NET Core knows it exists.`IConfiguration/IWebHostEnvironment/IHostEnvironment` is now pretty much the only thing you can "inject" into `Startup`, because everything else is built **after** `ConfigureServices` (the IoC container doesn't exist when `Startup` is initialized, its created after `ConfigureServices`). See the announcement: https://github.com/aspnet/Announcements/issues/353 – Tseng Aug 02 '19 at 02:17
  • Point is, you can't resolve anything through IoC container until this container is built and `Startup` class **IS** where you configure (`ConfigureServices` method) and build (after `ConfigureServices` but before `Configure`) is called.To be more specific in [ConfigureContainer](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.hosting.startupbase-1.configurecontainer?view=aspnetcore-2.2) method.After that `Configure` is called (which supports injections from the configured container) and where you set up the middlewares. So not possible to inject anything else before that point – Tseng Aug 02 '19 at 02:19
  • 1. Startup constructor can have service injected which is provided by framework. https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2#framework-provided-services 2. Startup class can be inherited from IStartup interface. – Vitaliy Markitanov Aug 02 '19 at 15:10
  • @VitaliyMarkitanov: Just the fact that it can be inherted from `IStartup` doesn't make the current feature and flexibility of it possible. Feel free to provide evidence to proof me wrong, by implementing a single interface which allows all the Environment specific setup, such as `ConfigureDevelopment(...)` and where `ConfigureXxx(...)` methods can have any number and type of dependencies injected. The question was why it was done the way is done and it still stands still w/o any alternative to the current approach w/o losing features and/or flexibility in setup – Tseng Aug 02 '19 at 15:14
  • Very annoying. Startup is checked at reflection time, which throws a completely useless exception if it doesn't match. Make it into a type so this doesn't have to crop up at runtime. None of the issues here are blockers. – Yarek T Oct 28 '21 at 12:02
2

Startup class can be inherited from IStartup interface.

// \packages\microsoft.aspnetcore.hosting.abstractions\2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.Hosting.Abstractions.dll
namespace Microsoft.AspNetCore.Hosting
{
 public interface IStartup
  {
   IServiceProvider ConfigureServices(IServiceCollection services);
   void Configure(IApplicationBuilder app);
  }
}

By default wizard doesn't create template file with implementation from IStartup. Why not - probably mistake or influence of non-typed languages..

Vitaliy Markitanov
  • 2,205
  • 1
  • 24
  • 23
  • Read my answer and you'll see why. With an interface you can't do `void Configure(IApplicationBuilder app, IMyService myService)`, because the interface is a contract which tells the exact number, type and order of the parameters. Also you can't call it `ConfigureDevelopment` so that it will only get called when `ASPNETCORE_ENVIRONMENT` is set to "Development" – Tseng Aug 02 '19 at 02:30
  • You will lose Environment specific setups if you do so and you will also lose injections inside `Configure/ConfigureXxx` methods, so no: With current feature sets and flexibility, its not possible to do that as interfaces. You either end up with half dozen of interfaces (`IConfigureServices/IConfigureServicesDevelopment/IConfigureProduct/IConfigureStaging` and the equivalent `IConfigureXxx` interfaces) and be limited to only 3 predefined environments, or having duplicated setup code (multiple Startup classes). None of which is "easier" to start with an empty project then the current approach – Tseng Aug 02 '19 at 15:18
  • 1
    Original question was: "Why is ASP.NET Core's Startup class not an interface or abstract class?" Answer: It can be abstracted to interface. If your Startup class is abstracted (inherited from IStartup) of course your overload of Configure(otherParams) will not be invoked. But you don't need that anyway, as your services registered in ConfigureServices() can be used in Configure(IApplicationBuilder) via referencing private vars for example. Again point is that in typed language idea was to use types, not reflection as they do it in DI in javascript or angular. That was original question. – Vitaliy Markitanov Aug 02 '19 at 15:20
  • 1
    No, your answer is to a "is it possible to implement is as an interface" question (which wasn't asked), the question is "why its not done so". The answer to that is "Its not done because of the infexilbility on setup and that the current approach has more flexible envrionmental dependent setup". Please read the whole question: _Why `CreateDefaultBuilder(args).UseStartup()` does not mandate any base class or interface for better readability?_ Reason is simple: Because that would limit the way to configure it. the class can be any of `Startup/StartupXxx` with any of the methods on it – Tseng Aug 02 '19 at 15:24