代码之家  ›  专栏  ›  技术社区  ›  Morn

构造函数依赖注入WebApi属性

  •  4
  • Morn  · 技术社区  · 10 年前

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

    我的问题很简单,这是否真的可以使用Structuremap?

    我一直在搜索,但不断地提出属性注入(我不喜欢使用)或构造函数注入的假设实现,而我至今无法复制这些实现。

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

    有人做到过吗?

    2 回复  |  直到 6 年前
        1
  •  25
  •   Community Marino Di Clemente    7 年前

    是的,这是可能的。你(和大多数人一样)正被微软的Action Filter Attributes营销所抛弃,这些属性很方便地放在一个类中,但对DI来说却一点都不友好。

    解决方案是将Action Filter属性分成两部分 as demonstrated in this post :

    1. 不包含用于标记控制器和操作方法的行为的属性。
    2. 实现的DI友好类 IActionFilter 并且包含期望的行为。

    方法是使用IActionFilter测试属性的存在,然后执行所需的行为。可以(通过构造函数)向动作筛选器提供所有依赖项,然后在编写应用程序时注入。

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

    注: 如果需要过滤器的任何依赖项的生存期比singleton短,则需要使用 GlobalFilterProvider 如中所示 this answer .

    要将其与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; }
    }
    
        2
  •  2
  •   Daniel Saidi Sachin Rasane    7 年前

    我与自定义操作筛选器提供程序进行了斗争,但没有使其适用于我的身份验证属性。我还尝试了构造函数和属性注入的各种方法,但没有找到一个感觉不错的解决方案。

    我最终将函数注入到我的属性中。这样,单元测试可以注入一个返回伪或模拟的函数,而应用程序可以注入一种解决IoC容器依赖关系的函数。

    我刚刚在这里写过这种方法: http://danielsaidi.com/blog/2015/09/11/asp-net-and-webapi-attributes-with-structuremap

    它在我的项目中非常有效,解决了我使用其他方法时遇到的所有问题。