【问题标题】:How to use dependency injection with an attribute?如何使用带有属性的依赖注入?
【发布时间】:2011-05-05 08:48:32
【问题描述】:

在我正在创建的 MVC 项目中,我有以下 RequirePermissionAttribute 用于需要特定权限的任何操作(在此示例中已简化):

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

所以问题显然是我的授权属性依赖于我创建的ASPNETAuthorizationService。因为属性是编译时检查的,所以我不能使用构造方法。

有一点要提一下,我使用的是我自己制作的小 IoC,但它不支持属性注入(目前)。当然,如果我确实采用了属性注入路线,我必须添加对它的支持(我必须对此进行一些研究)。

将某些东西注入属性类的最佳方法是什么?

【问题讨论】:

    标签: c# .net asp.net-mvc dependency-injection


    【解决方案1】:

    将某些东西注入属性类的最佳方法是什么?

    严格来说,我们不能使用依赖注入将依赖注入到属性中。 Attributes are for metadata 不是行为。 [AttributeSpecification()] 通过禁止引用类型作为参数来鼓励这一点。

    您可能正在寻找use an attribute and a filter together, and then to inject dependencies into the filter。属性添加元数据,决定是否应用过滤器,过滤器接收注入的依赖。

    如何对属性使用依赖注入?

    这样做的理由很少。

    也就是说,如果您打算注入属性,则可以使用 ASP.NET Core MVC IApplicationModelProvider。框架将依赖传递给提供者的构造函数,提供者可以将依赖传递给属性的属性或方法。

    在您的 Startup 中,注册您的提供商。

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

    在提供程序中使用构造函数注入,并将这些依赖项传递给属性。

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

    使用可以接收依赖项的公共设置器创建一个属性。

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

    将属性应用于控制器或动作。

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

    上面演示了一种方法,对于罕见的用例,您可以将依赖项注入属性。如果您找到这样做的正当理由,请将其发布在 cmets 中。

    【讨论】:

    • 非常感谢 - 我的用例(我不确定它是否正确)是将依赖项传递给验证器属性。我们使用属性来验证我们的模型,并且一些验证逻辑需要我们隔离到注入服务中的共享逻辑。
    【解决方案2】:

    我最初认为这是不可能的,但我已得到纠正。这是 Ninject 的一个例子:

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

    2016 年 10 月 13 日更新

    到目前为止,这是一个相当老的问题,并且框架已经发生了很大变化。 Ninject now allows you 根据特定属性的存在添加特定过滤器的绑定,代码如下:

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

    这符合 by Mark Seeman 和 by the author of Simple Injector 争论的原则,即您应该将操作过滤器的逻辑与自定义属性类分开。

    MVC 5 and 6 also make it far easier 将值注入到属性中,而不是以前。尽管如此,将您的操作过滤器与您的属性分开确实是最好的方法。

    【讨论】:

    • @ShaunLuttin:随意添加您自己的答案以及代码示例和解释。我会投票给它。
    • @ShaunLuttin:这是一种有趣的方法。正如承诺的那样,我给了你一个赞成票。另外,在考虑了一会儿之后,我意识到我的答案需要更新。感谢您提请我注意。
    • 研究让我发现属性不应该包含行为而只能包含数据。当您写“将您的操作过滤器的逻辑与自定义属性类分开”时,您的意思是这样吗?
    • @ShaunLuttin:是的,我就是这个意思。该属性只是一个 POCO,旨在保存元数据。实现IActionFilter 接口的类是一个包含行为的单独类。
    • 这是有道理的。基于此,我更新了我的答案,以强调注入属性和注入与属性相关的过滤器之间的区别。
    猜你喜欢
    • 2013-09-17
    • 2012-05-03
    • 1970-01-01
    • 2015-07-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多