ASP.NET Core and ASP.NET Core MVC Integration Guide

Simple Injector offers the Simple Injector ASP.NET Core MVC Integration NuGet package for integration with ASP.NET Core MVC.

IMPORTANT: This page is specific to the integration packages for Simple Injector v4.8 and up. In case you are using an older version of Simple Injector, please see the old integration page. However, in the context of integration with ASP.NET Core, you are advised to upgrade to v4.8. Those versions fixed numerous bugs concerning integration with ASP.NET Core.

The following code snippet shows how to use the integration package to apply Simple Injector to your web application’s Startup class.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using SimpleInjector;

public class Startup
{
    private Container container = new SimpleInjector.Container();

    public Startup(IConfiguration configuration)
    {
        // Set to false. This will be the default in v5.x and going forward.
        container.Options.ResolveUnregisteredConcreteTypes = false;

        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        // ASP.NET default stuff here
        services.AddControllersWithViews();

        services.AddLogging();
        services.AddLocalization(options => options.ResourcesPath = "Resources");

        // Sets up the basic configuration that for integrating Simple Injector with
        // ASP.NET Core by setting the DefaultScopedLifestyle, and setting up auto
        // cross wiring.
        services.AddSimpleInjector(container, options =>
        {
            // AddAspNetCore() wraps web requests in a Simple Injector scope and
            // allows request-scoped framework services to be resolved.
            options.AddAspNetCore()

                // Ensure activation of a specific framework type to be created by
                // Simple Injector instead of the built-in configuration system.
                // All calls are optional. You can enable what you need. For instance,
                // PageModels and TagHelpers are not needed when you build a Web API.
                .AddControllerActivation()
                .AddViewComponentActivation()
                .AddPageModelActivation()
                .AddTagHelperActivation();

            // Optionally, allow application components to depend on the non-generic
            // ILogger (Microsoft.Extensions.Logging) or IStringLocalizer
            // (Microsoft.Extensions.Localization) abstractions.
            options.AddLogging();
            options.AddLocalization();
        });

        InitializeContainer();
    }

    private void InitializeContainer()
    {
        // Add application services. For instance:
        container.Register<IUserService, UserService>(Lifestyle.Singleton);
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        // UseSimpleInjector() finalizes the integration process.
        app.UseSimpleInjector(container);

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }

        // Default ASP.NET middleware
        app.UseStaticFiles();
        app.UseRouting();
        app.UseAuthorization();

        // Add your custom Simple Injector-created middleware to the pipeline.
        app.UseMiddleware<CustomMiddleware1>(container);
        app.UseMiddleware<CustomMiddleware2>(container);

        // ASP.NET MVC default stuff here
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
        });

        // Always verify the container
        container.Verify();
    }
}
NOTE: Please note that when integrating Simple Injector in ASP.NET Core, you do not replace ASP.NET’s built-in container, as advised by the Microsoft documentation. The practice with Simple Injector is to use Simple Injector to build up object graphs of your application components and let the built-in container build framework and third-party components, as shown in the previous code snippet. To understand the rationale around this, please read this article.

Available integration packages

In case you need more fine-grained control over the number of Microsoft packages that get included in your application, you can decide to use one of the other available ASP.NET Core integration packages. The following table lists the relevant integration packages sorted from most complete to most basic integration:

Integration Package Description
SimpleInjector.Integration.AspNetCore.Mvc

Adds Tag Helper and Page Model integration for ASP.NET Core MVC. The features of this package are described on his page.

This package contains the following dependencies:

  • SimpleInjector.Integration.AspNetCore.Mvc.Core
  • Microsoft.AspNetCore.Mvc.Razor
  • Microsoft.AspNetCore.Mvc.RazorPages
SimpleInjector.Integration.AspNetCore.Mvc.Core

Adds Controller and View Component integration for ASP.NET Core MVC. The features of this package are described on his page.

This package contains the following dependencies:

  • SimpleInjector.Integration .AspNetCore
  • Microsoft.AspNetCore.Mvc.Core
  • Microsoft.AspNetCore.Mvc.ViewFeatures
SimpleInjector.Integration.AspNetCore

Adds request scoping and middleware integration ASP.NET Core. The features of this package are described on his page.

This package contains the following dependencies:

  • SimpleInjector.Integration.ServiceCollection
  • Microsoft.AspNetCore.Abstractions
  • Microsoft.AspNetCore.Http
  • Microsoft.AspNetCore.Http.Abstractions
  • Microsoft.Extensions.Hosting.Abstractions
SimpleInjector.Integration.GenericHost

Adds .NET Core 2.1 Hosted Service integration and integration on top of IHost. The features of this package are discussed in the .NET Generic Host Integration Guide.

This package contains the following dependencies:

  • SimpleInjector.Integration .ServiceCollection
  • Microsoft.Extensions .DependencyInjection.Abstractions
  • Microsoft.Extensions.Hosting .Abstractions
SimpleInjector.Integration.ServiceCollection

Adds integration with .NET Core’s configuration system (i.e. IServiceCollection) by allowing framework-configured services to be injected into Simple Injector-managed components. Furthermore, simplifies integration with .NET Core’s logging infrastructure. The features of this package are discussed in the ServiceCollection Integration Guide.

This package contains the following dependencies:

  • SimpleInjector (core library)
  • Microsoft.Extensions .DependencyInjection.Abstractions
  • Microsoft.Extensions.Hosting.Abstractions
  • Microsoft.Extensions.Localization.Abstractions
  • Microsoft.Extensions.Logging.Abstractions

Wiring custom middleware

The previous Startup snippet already showed how a custom middleware class can be used in the ASP.NET Core pipeline. The Simple Injector ASP.NET Core integration packages add an UseMiddleware extension method that allows adding custom middleware. The following listing shows how a CustomMiddleware class is added to the pipeline.

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseSimpleInjector(container);

    app.UseMiddleware<CustomMiddleware>(container);

    ...
}
IMPORTANT: The API changed in v4.8 of the Simple Injector ASP.NET Core integration packages. Previously, UseMiddleware was called inside the UseSimpleInjector method. Doing so, caused middleware to be applied at the wrong stage in the pipeline. This could, for instance, cause your middleware to be executed before the static files middleware (i.e. the .UseStaticFiles() call) or before authorization is applied (i.e. the .UseAuthorization() call). Instead, take care that you call .UseMiddleware<TMiddleware>(Container) at the right stage. This typically means after .UseStaticFiles() and .UseAuthorization(), but before .UseEndpoints(…), as shown in the next listing.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // UseSimpleInjector() enables framework services to be injected into
    // application components, resolved by Simple Injector.
    app.UseSimpleInjector(container);

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    // In ASP.NET Core, middleware is applied in the order of registration.
    // (opposite to how decorators are applied in Simple Injector). This means
    // that the following two custom middleware components are wrapped inside
    // the authorization middleware, which is typically what you'd want.
    app.UseMiddleware<CustomMiddleware1>(container);
    app.UseMiddleware<CustomMiddleware2>(container);

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });

    // Always verify the container
    container.Verify();
}

The type supplied to UseMiddleware<T> should implement the IMiddleware interface from the Microsoft.AspNetCore.Http namespace. A compile error will be given in case the middleware does not implement that interface.

This UseMiddleware overload ensures two particular things:

  • Adds a middleware type to the application’s request pipeline. The middleware will be resolved from the supplied the Simple Injector container.
  • The middleware type will be added to the container for verification. This means that you should call container.Verify() after the calls to UseMiddleware to ensure that your middleware components are verified.

The following code snippet shows how such CustomMiddleware class might look like:

// Example of some custom user-defined middleware component.
public sealed class CustomMiddleware : Microsoft.AspNetCore.Http.IMiddleware
{
    private readonly IUserService userService;

    public CustomMiddleware(IUserService userService)
    {
        this.userService = userService;
    }

    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        // Do something before
        await next(context);
        // Do something after
    }
}

Notice how the CustomMiddleware class contains dependencies. When the middleware is added to the pipeline using the previously shown UseMiddleware overload, it will be resolved from Simple Injector on each request, and its dependencies will be injected.

Cross wiring ASP.NET and third-party services

This topic has been moved. Please go here.

Working with IOptions<T>

This topic has been moved. Please go here.

Using Hosted Services

Simple Injector simplifies integration of Hosted Services into ASP.NET Core. For this, you need to include the SimpleInjector.Integration.GenericHost NuGet package. For more information on how to integrate Hosted Services into your ASP.NET Core web application, please read the Using Hosted Services section of the .NET Generic Host Integration Guide.

Using [FromServices] in ASP.NET Core MVC Controllers

Besides injecting dependencies into a controller’s constructor, ASP.NET Core MVC allows injecting dependencies directly into action methods using method injection. This is done by marking a corresponding action method argument with the [FromServices] attribute.

While the use of [FromServices] works for services registered in ASP.NET Core’s built-in configuration system (i.e. IServiceCollection), the Simple Injector integration package, however, does not integrate with [FromServices] out of the box. This is by design and adheres to our design guidelines, as explained below.

IMPORTANT: Simple Injector’s ASP.NET Core integration packages do not allow any Simple Injector registered dependencies to be injected into ASP.NET Core MVC controller action methods using the [FromServices] attribute.

The use of method injection, as the [FromServices] attribute allows, has a few considerate downsides that should be prevented.

Compared to constructor injection, the use of method injection in action methods hides the relationship between the controller and its dependencies from the container. This allows a controller to be created by Simple Injector (or ASP.NET Core’s built-in container for that matter), while the invocation of an individual action might fail, because of the absence of a dependency or a misconfiguration in the dependency’s object graph. This can cause configuration errors to stay undetected longer than strictly required. Especially when using Simple Injector, it blinds its diagnostic abilities which allow you to verify the correctness at application start-up or as part of a unit test.

You might be tempted to apply method injection to prevent the controller’s constructor from becoming too large. But big constructors are actually an indication that the controller itself is too big. It is a common code smell named Constructor over-injection. This is typically an indication that the class violates the Single Responsibility Principle meaning that the class is too complex and will be hard to maintain.

A typical solution to this problem is to split up the class into multiple smaller classes. At first this might seem problematic for controller classes, because they can act as gateway to the business layer and the API signature follows the naming of controllers and their actions. Do note, however, that this one-to-one mapping between controller names and the route of your application is not a requirement. ASP.NET Core has a very flexible routing system that allows you to completely change how routes map to controller names and even action names. This allows you to split controllers into very small chunks with a very limited number of constructor dependencies and without the need to fall back to method injection using [FromServices].

Simple Injector promotes best practices, and because of downsides described above, we consider the use of the [FromServices] attribute not to be a best practice. This is why we choose not to provide out-of-the-box support for injecting Simple Injector registered dependencies into controller actions.

In case you still feel method injection is the best option for you, you can plug in a custom IModelBinderProvider implementation returning a custom IModelBinder that resolves instances from Simple Injector.

Resolving services from MVC’s ValidationContext

ASP.NET Core MVC allows you to implement custom validation logic inside model classes using the IValidatableObject interface. Although there is nothing inherently wrong with placing validation logic inside the model object itself, problems start to appear when that validation logic requires services to work. By default this will not work with Simple Injector, as the ValidationContext.GetService method forwards the call to the built-in configuration system—not to Simple Injector.

In general, you should prevent calling GetService or similar methods from within application code, such as MVC model classes. This leads to the Service Locator anti-pattern.

Instead, follow the advice given in this Stack Overflow answer.

Using Razor Pages

ASP.NET Core 2.0 introduced an MVVM-like model, called Razor Pages. A Razor Page combines both data and behavior in a single class.

Integration for Razor Pages is part of the SimpleInjector.Integration.AspNetCore.Mvc integration package. This integration comes in the form of the AddPageModelActivation extension method. This extension method should be used in the ConfigureServices method of your Startup class:

// This method gets called by the runtime.
public void ConfigureServices(IServiceCollection services)
{
    ...

    services.AddSimpleInjector(container, options =>
    {
        options.AddAspNetCore()
            .AddPageModelActivation();
    });
}

This is all that is required to integrate Simple Injector with ASP.NET Core Razor Pages.

Working with ASP.NET Core Identity

The default Visual Studio template comes with built-in authentication through the use of ASP.NET Core Identity. The default template requires a fair amount of cross-wired dependencies. When auto cross wiring is enabled (when calling AddSimpleInjector) integration with ASP.NET Core Identity couldn’t be more straightforward. When you followed the cross wire guidelines, this is all you’ll have to do to get Identity running.

NOTE: It is highly advisable to refactor the AccountController to not to depend on IOptions<IdentityCookieOptions> and ILoggerFactory. See the topic about IOptions<T> for more information.