构造函数依赖注入WebApi属性

时间:2015-02-05 19:43:43

标签: asp.net-web-api dependency-injection structuremap custom-attributes structuremap3

我一直在寻找WebApi属性的非参数注入选项。

我的问题是使用Structuremap这是否真的可行?

我一直在谷歌搜索,但不断提出属性注入(我不喜欢使用)或假设的构造函数注入的实现,我到目前为止无法复制。

我选择的容器是Structuremap,但是任何这样的例子就足够了,因为我能够转换它。

有人管过这个吗?

1 个答案:

答案 0 :(得分:21)

是的,有可能。你(和大多数人一样)被微软的动作过滤器属性营销所抛弃,它们可以方便地放入一个类中,但根本不是DI友好的。

解决方案是将动作过滤器属性分为两部分as demonstrated in this post

  1. 不包含用于标记控制器和操作方法的行为的属性。
  2. 一个DI友好的类,它实现IActionFilter并包含所需的行为。
  3. 方法是使用IActionFilter测试属性的存在,然后执行所需的行为。可以为动作过滤器提供所有依赖项(通过构造函数),然后在组合应用程序时注入。

    IConfigProvider provider = new WebConfigProvider();
    IActionFilter filter = new MaxLengthActionFilter(provider);
    config.Filters.Add(filter);
    
      

    注意:如果您需要任何过滤器的依赖关系,其生命周期短于单身,则需要使用this answer中的GlobalFilterProvider

    要使用StructureMap进行连接,您需要从DI配置模块返回容器的实例。 Application_Start方法仍然是组合根的一部分,因此您可以在此方法中的任何位置使用容器,但它仍然不被视为服务定位器模式。请注意,我不会在此处显示完整的WebApi设置,因为我假设您已经使用WebApi进行了正常的DI配置。如果你需要一个,这是另一个问题。

    public class DIConfig()
    {
        public static IContainer Register()
        {
            // Create the DI container
            var container = new Container();
    
            // Setup configuration of DI
            container.Configure(r => r.AddRegistry<SomeRegistry>());
            // Add additional registries here...
    
            #if DEBUG
                container.AssertConfigurationIsValid();
            #endif
    
            // Return our DI container instance to the composition root
            return container;
        }
    }
    
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            // Hang on to the container instance so you can resolve
            // instances while still in the composition root
            IContainer container = DIConfig.Register();
    
            AreaRegistration.RegisterAllAreas();
    
            // Pass the container so we can resolve our IActionFilter
            WebApiConfig.Register(GlobalConfiguration.Configuration, container);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
            AuthConfig.RegisterAuth();
        }
    }
    
    public static class WebApiConfig
    {
        // Add a parameter for IContainer
        public static void Register(HttpConfiguration config, IContainer container)
        {
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
    
            // Uncomment the following line of code to enable query support for actions with an IQueryable or IQueryable<T> return type.
            // To avoid processing unexpected or malicious queries, use the validation settings on QueryableAttribute to validate incoming queries.
            // For more information, visit http://go.microsoft.com/fwlink/?LinkId=279712.
            //config.EnableQuerySupport();
    
            // Add our action filter
            config.Filters.Add(container.GetInstance<IMaxLengthActionFilter>());
            // Add additional filters here that look for other attributes...
        }
    }
    

    MaxLengthActionFilter的实现看起来像这样:

    // Used to uniquely identify the filter in StructureMap
    public interface IMaxLengthActionFilter : System.Web.Http.Filters.IActionFilter
    {
    }
    
    public class MaxLengthActionFitler : IMaxLengthActionFilter
    {
        public readonly IConfigProvider configProvider;
    
        public MaxLengthActionFilter(IConfigProvider configProvider)
        {
            if (configProvider == null)
                throw new ArgumentNullException("configProvider");
            this.configProvider = configProvider;
        }
    
        public Task<HttpResponseMessage> ExecuteActionFilterAsync(
            HttpActionContext actionContext,
            CancellationToken cancellationToken,
            Func<Task<HttpResponseMessage>> continuation)
        {
            var attribute = this.GetMaxLengthAttribute(filterContext.ActionDescriptor);
            if (attribute != null)
            {
                var maxLength = attribute.MaxLength;
                // Execute your behavior here (before the continuation), 
                // and use the configProvider as needed
    
                return continuation().ContinueWith(t =>
                {
                    // Execute your behavior here (after the continuation), 
                    // and use the configProvider as needed
    
                    return t.Result;
                });
            }
            return continuation();
        }
    
        public bool AllowMultiple
        {
            get { return true; }
        }
    
        public MaxLengthAttribute GetMaxLengthAttribute(ActionDescriptor actionDescriptor)
        {
            MaxLengthAttribute result = null;
    
            // Check if the attribute exists on the action method
            result = (MaxLengthAttribute)actionDescriptor
                .GetCustomAttributes(typeof(MaxLengthAttribute), false)
                .SingleOrDefault();
    
            if (result != null)
            {
                return result;
            }
    
            // Check if the attribute exists on the controller
            result = (MaxLengthAttribute)actionDescriptor
                .ControllerDescriptor
                .GetCustomAttributes(typeof(MaxLengthAttribute), false)
                .SingleOrDefault();
    
            return result;
        }
    }
    

    并且,您的属性不应包含任何行为应该如下所示:

    // This attribute should contain no behavior. No behavior, nothing needs to be injected.
    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
    public class MaxLengthAttribute : Attribute
    {
        public MaxLengthAttribute(int maxLength)
        {
            this.MaxLength = maxLength;
        }
    
        public int MaxLength { get; private set; }
    }