web-dev-qa-db-fra.com

Comment puis-je utiliser l'injection de dépendances dans un .Net Core ActionFilterAttribute?

Classe AuthenticationRequiredAttribute

public class AuthenticationRequiredAttribute : ActionFilterAttribute
{
    ILoginTokenKeyApi _loginTokenKeyApi;
    IMemoryCache _memoryCache;

    public AuthenticationRequiredAttribute(IMemoryCache memoryCache)
    {
        _memoryCache = memoryCache;

        _loginTokenKeyApi = new LoginTokenKeyController(new UnitOfWork());
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var memory = _memoryCache.Get(Constants.KEYNAME_FOR_AUTHENTICATED_PAGES);

        string requestedPath = filterContext.HttpContext.Request.Path;

        string tokenKey = filterContext.HttpContext.Session.GetString("TokenKey")?.ToString();

        bool? isLoggedIn = _loginTokenKeyApi.IsLoggedInByTokenKey(tokenKey).Data;

        if (isLoggedIn == null ||
            !((bool)isLoggedIn) ||
            !Constants.AUTHENTICATED_PAGES_FOR_NORMAL_USERS.Contains(requestedPath))
        {
            filterContext.Result = new JsonResult(new { HttpStatusCode.Unauthorized });
        }
    }
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
    }
}

HomeController

public class HomeController : Controller
{
    IUserApi _userApi;
    ILoginTokenKeyApi _loginTokenKey;
    IMemoryCache _memoryCache;

    public HomeController(IUserApi userApi, ILoginTokenKeyApi loginTokenKey, IMemoryCache memoryCache)
    {
        _loginTokenKey = loginTokenKey;
        _userApi = userApi;

        _memoryCache = memoryCache;
    }

    [AuthenticationRequired] // There is AN ERROR !!
    public IActionResult Example()
    {
        return View();
    }
}

ERREUR :

Erreur CS7036 Aucun argument donné ne correspond au paramètre formel requis 'memoryCache' de 'AuthenticationRequiredAttribute.AuthenticationRequiredAttribute (IMemoryCache)' Project.Ground.WebUI

Mon problème est en fait: je ne peux pas utiliser l'injection de dépendance dans les classes d'attributs.

Je veux utiliser cet attribut sans aucun paramètre. Y a-t-il une solution pour le résoudre? J'utilise l'injection de dépendance, mais elle ne peut pas être utilisée pour les attributs. Comment puis-je l'utiliser?

6
canmustu

Selon la documentation , vous avez quelques options ici:

Si vos filtres ont des dépendances auxquelles vous devez accéder à partir de DI, il existe plusieurs approches prises en charge. Vous pouvez appliquer votre filtre à une classe ou une méthode d'action à l'aide de l'une des méthodes suivantes:

Attributs ServiceFilter ou TypeFilter

Si vous souhaitez simplement que cela fonctionne rapidement, vous pouvez simplement utiliser l'une des deux premières options pour appliquer votre filtre à un contrôleur ou à une action de contrôleur. En faisant cela, votre filtre n'a pas besoin d'être lui-même un attribut:

[TypeFilter(typeof(ExampleActionFilter))]
public IActionResult Example()
    => View();

ExampleActionFilter peut alors simplement implémenter par exemple IAsyncActionFilter et vous pouvez dépendre directement des choses en utilisant l'injection de constructeur:

public class ExampleActionFilter : IAsyncActionFilter
{
    private readonly IMemoryCache _memoryCache;
    public ExampleActionFilter(IMemoryCache memoryCache)
    {
        _memoryCache = memoryCache;
    }

    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    { … }
}

Vous pouvez également utiliser le [ServiceFilter] à la place pour obtenir le même effet, mais vous devrez également enregistrer votre ExampleActionFilter avec le conteneur d'injection de dépendances dans votre Startup.

Usine de filtres

Si vous avez besoin de plus de flexibilité, vous pouvez implémenter votre propre usine de filtres. Cela vous permet d'écrire le code d'usine pour créer vous-même l'instance de filtre réelle. Une implémentation possible pour le ExampleActionFilter ci-dessus pourrait ressembler à ceci:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class ExampleActionFilterAttribute : Attribute, IFilterFactory
{
    public bool IsReusable => false;

    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
    {
        return serviceProvider.GetService<ExampleActionFilter>();
    }
}

Vous pouvez ensuite utiliser ce [ExampleActionFilter] attribut pour que le framework MVC crée pour vous une instance de ExampleActionFilter, à l'aide du conteneur DI.

Notez que cette implémentation est essentiellement la même chose que ServiceFilterAttribute. C'est juste que l'implémenter vous-même évite d'avoir à utiliser directement le ServiceFilterAttribute et vous permet d'avoir votre propre attribut.

Utilisation du localisateur de services

Enfin, il existe une autre option rapide qui vous permet d'éviter complètement l'injection de constructeur. Cela utilise le modèle de localisateur de services pour résoudre les services de manière dynamique lorsque votre filtre s'exécute réellement. Ainsi, au lieu d'injecter la dépendance et de l'utiliser directement, vous la récupérez explicitement dans le contexte:

public class ExampleActionFilter : ActionFilterAttribute
{
    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        var memoryCache = context.HttpContext.RequestServices.GetService<IMemoryCache>();

        // …
    }
}
15
poke

Au lieu de résoudre lors de la construction, ActionExecutingContext.HttpContext.RequestServices devrait vous donner une référence au conteneur de service de la demande au moment de la demande.

Donc:

public override void OnActionExecuting(ActionExecutingContext filterContext)
{
    var svc = filterContext.HttpContext.RequestServices;
    var memCache = svc.GetService<IMemoryCache>();
    //..etc
7
spender