web-dev-qa-db-fra.com

Comment utiliser l'injection de dépendance avec un attribut?

Dans un projet MVC que je crée, j'ai le RequirePermissionAttribute suivant qui est mis sur toute action nécessitant des autorisations spécifiques (il a été simplifié pour cet exemple):

public class RequirePermissionAttribute : ActionFilterAttribute, IAuthorizationFilter
{
    public Operation Permissions { get; set; }

    public RequirePermissionAttribute() { }

    public RequirePermissionAttribute(Operation permissions)
    {
        this.Permissions = permissions;
    }

    public bool AuthorizeCore(HttpContextBase httpContext)
    {
        IAuthorizationService authServ = new ASPNETAuthorizationService();
        return authServ.Authorize(httpContext);
    }

    public void OnAuthorization(AuthorizationContext filterContext)
    {
        Enforce.ArgNotNull(filterContext);

        if (this.AuthorizeCore(filterContext.HttpContext))
        {
            // code snipped.
        }
        else
        {
            // code snipped.
        }
    }
}

Donc, le problème évidemment avec ceci est que mon attribut d'autorisation a une dépendance sur le ASPNETAuthorizationService que j'ai créé. Je ne peux pas suivre la voie constructeur car les attributs sont vérifiés à la compilation.

Une chose à mentionner, j'utilise mon propre petit IoC que j'ai fait et il n'a pas encore de support pour l'injection de propriété. Bien sûr, si je prenais la voie de l'injection de propriété, je devrais y ajouter un support (sur lequel je devrais faire des recherches).

Quelle est la meilleure façon d'injecter quelque chose dans une classe d'attributs?

51
TheCloudlessSky

Au départ, je pensais que ce n'était pas possible, mais je suis corrigé. Voici un exemple avec Ninject:

http://codeclimber.net.nz/archive/2009/02/10/how-to-use-ninject-to-inject-dependencies-into-asp.net-mvc.aspx

Mise à jour 2016-10-13

C'est une assez vieille question maintenant, et les cadres ont beaucoup changé. Ninject vous permet désormais d'ajouter des liaisons à des filtres spécifiques en fonction de la présence d'attributs spécifiques, avec un code comme celui-ci:

// LogFilter is applied to controllers that have the LogAttribute
this.BindFilter<LogFilter>(FilterScope.Controller, 0)
     .WhenControllerHas<LogAttribute>()
     .WithConstructorArgument("logLevel", Level.Info);

// LogFilter is applied to actions that have the LogAttribute
this.BindFilter<LogFilter>(FilterScope.Action, 0)
     .WhenActionHas<LogAttribute>()
     .WithConstructorArgument("logLevel", Level.Info);

// LogFilter is applied to all actions of the HomeController
this.BindFilter<LogFilter>(FilterScope.Action, 0)
     .WhenControllerTypeIs<HomeController>()
     .WithConstructorArgument("logLevel", Level.Info);

// LogFilter is applied to all Index actions
this.BindFilter(FilterScope.Action, 0)
     .When((controllerContext,  actionDescriptor) =>
                actionDescriptor.ActionName == "Index")
     .WithConstructorArgument("logLevel", Level.Info);

Ceci est conforme au principe, a fait valoir par Mark Seeman et par l'auteur de Simple Injector , qui est que vous devez garder la logique de votre filtre d'action distincte de la coutume classe d'attribut.

MVC 5 et 6 rendent aussi beaucoup plus facile pour injecter des valeurs dans des attributs qu'auparavant. Pourtant, séparer votre filtre d'action de votre attribut est vraiment la meilleure approche à adopter.

11
StriplingWarrior

Quelle est la meilleure façon d'injecter quelque chose dans une classe d'attributs?

À strictement parler, nous ne pouvons pas utiliser l'injection de dépendance pour injecter une dépendance dans un attribut. Les attributs sont pour les métadonnées pas le comportement. [AttributeSpecification()] encourage cela en interdisant les types de référence comme arguments.

Ce que vous cherchez probablement, c'est tilisez un attribut et un filtre ensemble, puis injectez des dépendances dans le filtre . L'attribut ajoute des métadonnées, qui déterminent s'il faut appliquer le filtre, et le filtre reçoit les dépendances injectées.

Comment utiliser l'injection de dépendance avec un attribut?

Il y a très peu de raisons de le faire.

Cela dit, si vous avez l'intention d'injecter dans un attribut, vous pouvez utiliser ASP.NET Core MVC IApplicationModelProvider. Le framework transmet les dépendances au constructeur du fournisseur, et le fournisseur peut transmettre les dépendances aux propriétés ou méthodes de l'attribut.

Dans votre démarrage, enregistrez votre fournisseur.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.TryAddEnumerable(ServiceDescriptor.Transient
            <IApplicationModelProvider, MyApplicationModelProvider>());

        services.AddMvc();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseMvc();
    }
}

Utilisez l'injection de constructeur dans le fournisseur et transmettez ces dépendances à l'attribut.

using System.Linq;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Routing;

public class MyApplicationModelProvider : IApplicationModelProvider
{
    private IUrlHelperFactory _urlHelperFactory;

    // constructor injection
    public MyApplicationModelProvider(IUrlHelperFactory urlHelperFactory)
    {
        _urlHelperFactory = urlHelperFactory;
    }

    public int Order { get { return -1000 + 10; } }

    public void OnProvidersExecuted(ApplicationModelProviderContext context)
    {
        foreach (var controllerModel in context.Result.Controllers)
        {
            // pass the depencency to controller attibutes
            controllerModel.Attributes
                .OfType<MyAttribute>().ToList()
                .ForEach(a => a.UrlHelperFactory = _urlHelperFactory);

            // pass the dependency to action attributes
            controllerModel.Actions.SelectMany(a => a.Attributes)
                .OfType<MyAttribute>().ToList()
                .ForEach(a => a.UrlHelperFactory = _urlHelperFactory);
        }
    }

    public void OnProvidersExecuting(ApplicationModelProviderContext context)
    {
        // intentionally empty
    }
}

Créez un attribut avec des setters publics pouvant recevoir des dépendances.

using System;
using Microsoft.AspNetCore.Mvc.Routing;

public sealed class MyAttribute : Attribute
{
    private string _someParameter;

    public IUrlHelperFactory UrlHelperFactory { get; set; }

    public MyAttribute(string someParameter)
    {
        _someParameter = someParameter;
    }
}

Appliquez l'attribut à un contrôleur ou à une action.

using Microsoft.AspNetCore.Mvc;

[Route("api/[controller]")]
[MyAttribute("SomeArgument")]
public class ValuesController : Controller
{
    [HttpGet]
    [MyAttribute("AnotherArgument")]
    public string Get()
    {
        return "Foobar";
    }
}

Ce qui précède montre une façon, pour le cas d'utilisation rare, que vous pouvez injecter des dépendances dans un attribut. Si vous trouvez une raison valable de le faire, veuillez la poster dans les commentaires.

7
Shaun Luttin