How to Resolve Services in ASP.NET Core Startup - Windows ASP.NET Core Hosting 2024 | Review and ComparisonWindows ASP.NET Core Hosting 2024 | Review and Comparison

There are times we need to resolve services during the start-up phase of our application. We’ll take a quick look at many of the possible approaches available to a developer building an ASP.NET Core application.

Why Resolve A Service In ASP.NET Core Startup?

It’s no secret that the latest ASP.NET infrastructure is heavily dependent on dependency injection, with the concept a core pillar of the latest pipeline. When working with dependency injection, we will configure our services once at the start of an application’s lifetime and then call upon the ASP.NET Core framework to instantiate our dependencies. In my opinion, the set-it once and use everywhere is a great advantage to using DI. It’s only natural to want to reuse configuration and setup as much as possible.

Resolving Services In ConfigureServices

The first location we may want to resolve a service is within our ConfigureServices method. Reasons to do this might be to initialize and register a singleton instance with the application’s services collection.

I do not recommend resolving any services within the ConfigureServices method. As you’ll see, there are several caveats and expenses in doing so..

Best ASP.NET Hosting

In ConfigureServices, we can build an instance of our service provider so far.

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<Dependency>();

    var provider = services.BuildServiceProvider();

    var dependency = provider.GetRequiredService<Dependency>();
    Console.WriteLine(dependency.Hello);
}

The operative word here is so far. The following code will fail.

public void ConfigureServices(IServiceCollection services)
{
    var provider = services.BuildServiceProvider();

    // we haven't registered Dependency yet 
    var dependency = provider.GetRequiredService<Dependency>();
    
    services.AddScoped<Dependency>();
    
    Console.WriteLine(dependency.Hello);
}

The above code fails because we haven’t yet registered our Dependency class, yet we are attempting to resolve it.

Resolving Services In Configure

If folks want to run some code at startup, it’s best to do that in the Configure method, as at this point, we’ve registered all of our services. We can perform tasks like calling a remote service, running database migrations, or logging a message. Be aware that any blocking operation may slow your application’s startup time, and any failure will prevent your application from starting at all.

The Best Method

Easily the best approach is to add our dependency to the list of parameters of the Configure method. ASP.NET Core will resolve our type from the service collection.

public void Configure(
    IApplicationBuilder app, 
    IWebHostEnvironment env,
    // our dependency
    Dependency dependency
)
{
    Console.WriteLine(dependency.Hello);
    
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGet("/", async context => { await context.Response.WriteAsync("Hello World!"); });
    });
}

The advantage of this approach is that we can even resolve scoped dependencies, as in this case, the scope is the Configure method.

The “Works But Limited” Way

Another approach, which I do not recommend, is to call the ApplicationServices property on the IApplicationBuilder interface. The IApplicationBuilder should be a parameter by default to all unmodified calls to the Configure method in Startup.

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(
    IApplicationBuilder app, 
    IWebHostEnvironment env
)
{
    // cannot call Scoped dependencies
    var dependency = app
        .ApplicationServices
        .GetRequiredService<Dependency>();
    
    Console.WriteLine(dependency.Hello);
    
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGet("/", async context => { await context.Response.WriteAsync("Hello World!"); });
    });
}

This approach only works for services registered as Singleton and Transient. Attempting to resolve Scoped services will result in the following exception. ASP.NET Core will throw this exception in an attempt to avoid potential captured dependencies.

 System.InvalidOperationException: Cannot resolve scoped service 'WebApplication7.Dependency' from root provider.
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.ValidateResolution(Type serviceType, IServiceScope scope, IServiceScope rootScope)
         at Microsoft.Extensions.DependencyInjection.ServiceProvider.Microsoft.Extensions.DependencyInjection.ServiceLookup.IServiceProviderEngineCallback.OnResolve(Type serviceType, IServiceScope scope)

While outside the scope of this post, let’s quickly discuss captured dependencies. Captured dependencies are when a Singleton service is holding onto a Scoped service. Captured dependencies are especially dangerous as the first instance of a Scoped service may have information regarding the first user to request our application. It’s imperative you do not use ANY service injected into Configure as part of your pipeline registration code.

Conclusion

There we have it, several ways to resolve services within our Startup class. As other authors have pointed out, there are many ways to solve this particular problem. Some are more straightforward than others. We must consider the cost of resolving our services in Startup and its impact on startup performance. Like many things, it all depends.

I hope you found this post enlightening, and please let me know if you have other ways of resolving types in Startup.