77

In an MVC project I'm creating I have the following RequirePermissionAttribute that gets put on any action that needs specific permissions (it's been simplified for this example):

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.
        }
    }
}

So the problem obviously with this is that my authorize attribute has a dependency on the ASPNETAuthorizationService that I created. I can't go the constructor way since attributes are compile-time checked.

One thing to mention, I'm using my own little IoC that I made and it doesn't have support for property injection (yet). Of course, if I did go the property injection route, I'd have to add support for it (which I'd have to do some research on).

What's the best way to inject something into an attribute class?

Rafael Marques
  • 1,501
  • 15
  • 23
TheCloudlessSky
  • 18,608
  • 15
  • 75
  • 116

2 Answers2

29

What's the best way to inject something into an attribute class?

Strictly speaking, we cannot use dependency injection to inject a dependency into an attribute. Attributes are for metadata not behavior. [AttributeSpecification()] encourages this by forbidding reference types as arguments.

What you're probably looking for is to use an attribute and a filter together, and then to inject dependencies into the filter. The attribute adds metadata, which determines whether to apply the filter, and the filter receives the injected dependencies.

How to use dependency injection with an attribute?

There are very few reasons to do this.

That said, if you're intent on injecting into an attribute, you can use the ASP.NET Core MVC IApplicationModelProvider. The framework passes dependencies into the provider's constructor, and the provider can pass dependencies to the attribute's properties or methods.

In your Startup, register your provider.

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();
    }
}

Use constructor injection in the provider, and pass those dependencies to the attribute.

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
    }
}

Create an attribute with public setters that can receive dependencies.

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;
    }
}

Apply the attribute to a controller or an action.

using Microsoft.AspNetCore.Mvc;

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

The above demonstrates one way, for the rare use case, that you can inject dependencies into an attribute. If you figure out a valid reason to do this, please post it in the comments.

Community
  • 1
  • 1
Shaun Luttin
  • 133,272
  • 81
  • 405
  • 467
  • 2
    Thanks so much - my use case (which I'm unsure is a correct one) is to pass dependencies into a validator attribute. We use attributes to validate our models, and some validation logic requires shared logic which we isolate into injected services. – aaaaaa Jun 07 '19 at 13:33
13

I originally thought this was not possible, but I stand corrected. Here's an example with Ninject:

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

Update 2016-10-13

This is a pretty old question by now, and frameworks have changed quite a bit. Ninject now allows you to add bindings to specific filters based on the presence of specific attributes, with code like this:

// 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);

This is in keeping with the principle, argued by Mark Seeman and by the author of Simple Injector, which is that you should keep the logic of your action filter separate from the custom attribute class.

MVC 5 and 6 also make it far easier to inject values into attributes than it used to be. Still, separating your action filter from your attribute is really the best approach to take.

Community
  • 1
  • 1
StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
  • @ShaunLuttin: Feel free to add your own answer with code samples and explanations. I'll vote for it. – StriplingWarrior Oct 13 '16 at 15:17
  • @ShaunLuttin: That's an interesting approach. I gave you an up-vote, as promised. Also, after thinking about this a while, I realized that my answer was in need of an update. Thanks for bringing it to my attention. – StriplingWarrior Oct 13 '16 at 21:32
  • Research lead me to find that attributes are not supposed to contain behavior and only to have data. When you wrote, "keep the logic of your action filter separate from the custom attribute class," is that what you meant? – Shaun Luttin Oct 13 '16 at 21:35
  • @ShaunLuttin: Yes, that's what I meant. The attribute is just a POCO, designed to hold metadata. The class which implements the `IActionFilter` interface is a separate class that contains behavior. – StriplingWarrior Oct 14 '16 at 15:49
  • 1
    That makes sense. Based on that, I updated my answer to emphasize the difference between injecting into an attribute and injecting into a filter that is related to an attribute. – Shaun Luttin Oct 14 '16 at 16:32
  • Is this only possible by Ninject? can it not be done by unity? – BrainSlugs83 Oct 10 '18 at 21:41
  • @BrainSlugs83: This is a pretty old answer, and newer versions of ASP.NET have better ways to handle this problem, like ServiceFilterAttribute. If you're stuck maintaining some legacy code, I'm not going to be able to help you with the Unity aspect, though it's possible there's a way to do that. – StriplingWarrior Oct 11 '18 at 15:56