如何使用带有属性的依赖项注入?

时间:2022-09-02 09:44:35

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):

在我正在创建的一个MVC项目中,我有如下的requimissionattribute,它可以执行任何需要特定权限的操作(本例中已经简化了):

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.

显然,问题是我的authorize属性对我创建的ASPNETAuthorizationService有依赖关系。我不能使用构造函数,因为属性是编译时检查的。

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

值得一提的是,我正在使用我自己创建的小IoC,它还不支持属性注入。当然,如果我选择了属性注入路径,我就必须为它添加支持(我必须对此进行一些研究)。

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

向属性类中注入什么最好的方法是什么?

2 个解决方案

#1


10  

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

我原本以为这是不可能的,但我还是接受了纠正。这里有一个Ninject的例子:

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

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:

这是一个非常古老的问题,框架已经发生了很大的变化。Ninject现在允许您基于特定属性的存在向特定过滤器添加绑定,代码如下:

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

这与Mark Seeman和《简单注入器》的作者所主张的原则是一致的,即你应该将你的行为过滤器的逻辑与自定义属性类分开。

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.

MVC 5和6也使得向属性中注入值比以前容易得多。不过,将操作过滤器与属性分离确实是最好的方法。

#2


3  

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.

严格地说,我们不能使用依赖注入将依赖注入到属性中。属性是用于元数据而不是行为的。[attributspecific()]通过禁止引用类型作为参数来鼓励这种做法。

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.

也就是说,如果你想要注入一个属性,你可以使用ASP。净MVC IApplicationModelProvider核心。框架将依赖项传递给提供者的构造函数,提供者可以将依赖项传递给属性的属性或方法。

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.

使用可以接收依赖项的公共setter创建属性。

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.

以上演示了一种方法,对于罕见的用例,您可以将依赖项注入到属性中。如果你找到了这样做的理由,请在评论中留言。

#1


10  

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

我原本以为这是不可能的,但我还是接受了纠正。这里有一个Ninject的例子:

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

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:

这是一个非常古老的问题,框架已经发生了很大的变化。Ninject现在允许您基于特定属性的存在向特定过滤器添加绑定,代码如下:

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

这与Mark Seeman和《简单注入器》的作者所主张的原则是一致的,即你应该将你的行为过滤器的逻辑与自定义属性类分开。

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.

MVC 5和6也使得向属性中注入值比以前容易得多。不过,将操作过滤器与属性分离确实是最好的方法。

#2


3  

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.

严格地说,我们不能使用依赖注入将依赖注入到属性中。属性是用于元数据而不是行为的。[attributspecific()]通过禁止引用类型作为参数来鼓励这种做法。

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.

也就是说,如果你想要注入一个属性,你可以使用ASP。净MVC IApplicationModelProvider核心。框架将依赖项传递给提供者的构造函数,提供者可以将依赖项传递给属性的属性或方法。

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.

使用可以接收依赖项的公共setter创建属性。

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.

以上演示了一种方法,对于罕见的用例,您可以将依赖项注入到属性中。如果你找到了这样做的理由,请在评论中留言。