web-dev-qa-db-fra.com

Comment faire DI dans le middleware core asp.net?

J'essaie d'injecter la dépendance dans mon constructeur de middleware comme suit

public class CreateCompanyMiddleware
{
    private readonly RequestDelegate _next;
    private readonly UserManager<ApplicationUser> _userManager;

    public CreateCompanyMiddleware(RequestDelegate next
        , UserManager<ApplicationUser> userManager
        )
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        await _next.Invoke(context);
    }
}

Mon fichier Startup.cs ressemble à

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseMySql(Configuration.GetConnectionString("IdentityConnection")));

    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();
    ...

    app.UseMiddleware<CreateCompanyMiddleware>();

    ...

Mais je reçois cette erreur

Une erreur s'est produite lors du démarrage de l'application. InvalidOperationException: impossible de résoudre le service de portée 'Microsoft.AspNetCore.Identity.UserManager`1 [Common.Models.ApplicationUser]' à partir du fournisseur racine. Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.ValidateResolution (Type serviceType, IServiceScope scope, IServiceScope rootScope)

26
Yasser Shaikh

UserManager<ApplicationUser> est (par défaut) enregistré en tant que dépendance de portée , tandis que votre middleware CreateCompanyMiddleware est construit au démarrage de l'application (ce qui en fait un singleton ). Il s'agit d'une erreur assez standard disant que vous ne pouvez pas prendre une dépendance de portée dans un singleton classe.

La solution est simple dans ce cas - vous pouvez injecter le UserManager<ApplicationUser> dans votre méthode Invoke:

public async Task Invoke(HttpContext context, UserManager<ApplicationUser> userManager)
{
    await _next.Invoke(context);
}

Ceci est documenté dans ASP.NET Core Middleware: dépendances du middleware par demande :

Étant donné que le middleware est construit au démarrage de l'application, et non par demande, les services de durée de vie étendus utilisés par les constructeurs de middleware ne sont pas partagés avec d'autres types injectés de dépendance au cours de chaque demande. Si vous devez partager un service de portée entre votre middleware et d'autres types, ajoutez ces services à la signature de la méthode Invoke. La méthode Invoke peut accepter des paramètres supplémentaires renseignés par DI:

58
Kirk Larkin

Une autre façon de le faire est de créer un middleware par l'interface IMiddleware et de l'enregistrer en tant que service

Par exemple, le middleware

public class CreateCompanyMiddlewareByInterface : IMiddleware
{
    private readonly UserManager<ApplicationUser> _userManager;

    public CreateCompanyMiddlewareByInterface(UserManager<ApplicationUser> userManager )
    {
        this._userManager = userManager;
    }


    public Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        return next(context);
    }
} 

et enregistrement des services:

services.AddScoped<CreateCompanyMiddlewareByInterface>();
  1. Alors pourquoi ça arrive?

Les middlewares utilisant IMiddleware sont construits par UseMiddlewareInterface(appBuilder, middlewareType type):

private static IApplicationBuilder UseMiddlewareInterface(IApplicationBuilder app, Type middlewareType)
{
    return app.Use(next =>
    {
        return async context =>
        {
            var middlewareFactory = (IMiddlewareFactory)context.RequestServices.GetService(typeof(IMiddlewareFactory));
            if (middlewareFactory == null) { /* throw ... */ }

            var middleware = middlewareFactory.Create(middlewareType);
            if (middleware == null) { /* throw ... */ }

            try{
                await middleware.InvokeAsync(context, next);
            }
            finally{
                middlewareFactory.Release(middleware);
            }
        };
    });
}

ici, les codes à l'intérieur du context=>{} sont exécutés par demande. Ainsi, chaque fois qu'il y a une demande entrante, la var middleware = middlewareFactory.Create(middlewareType); sera exécutée puis demandera un middleware de middlewareType (qui est déjà enregistré en tant que service) à partir de ServiceProvider.

En ce qui concerne les middlewares par convention, aucune usine ne les crée.

Ces instances sont toutes créées par ActivatorUtilities.CreateInstance() au démarrage. Et toute Invoke méthode de middlewares par convention, comme

Task Invoke(HttpContext context,UserManager<ApplicationUser> userManage, ILoggerFactory loggeryFactory , ... )

sera compilé dans une fonction comme ci-dessous:

Task Invoke(Middleware instance, HttpContext httpContext, IServiceprovider provider)
{
    var useManager  /* = get service from service provider */ ;
    var log = /* = get service from service provider */ ;
    // ... 
    return instance.Invoke(httpContext,userManager,log, ...);
}

Comme vous le voyez, ici l'instance est créée au démarrage et les services de la méthode Invoke sont demandés par requête.

11
itminus