构造函数依赖注入 WebApi 属性

4

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

我的问题很简单,是否可以使用Structuremap实现这个功能?

我已经搜索过了,但是要么是属性注入(我不想使用),要么是构造函数注入的所谓实现,但我迄今为止无法复制。

我的容器选择是Structuremap,但任何关于此的示例都可以,因为我能够进行转换。

有人曾经成功地做到这一点吗?

2个回答

25
是的,这是可能的。你(和大多数人一样)被微软营销的Action Filter Attributes所迷惑了,它们方便地放在一个类中,但并不适合DI。
解决方法是将Action Filter Attribute分成两个部分,如此帖子所示
1. 一个不包含任何行为以标记控制器和操作方法的属性。 2. 一个实现IActionFilter并包含所需行为的DI友好类。
方法是使用IActionFilter测试属性的存在,并执行所需行为。操作过滤器可以通过构造函数提供所有依赖项,然后在应用程序组合时进行注入。
IConfigProvider provider = new WebConfigProvider();
IActionFilter filter = new MaxLengthActionFilter(provider);
config.Filters.Add(filter);

注意: 如果您需要过滤器的任何依赖项具有比单例更短的生命周期,则需要使用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; }
}

感谢您提供详细的答案,我之前也见过这种方法,但是这个解释让我明白了。我会尝试一下并在尝试后回复并接受答案。 - Morn
我已经根据上述和您提供的链接实现了一个测试。但我仍然不知道如何使用它来让Structuremap自动注入构造函数参数。本质上,您正在创建过滤器的一个新实例并将其添加。您是在说这是我们能够接近的最好方法吗? - Morn
这是我在 DI 方面所达到的一个例子。它出现在 WebApiConfig 中,但需要通过 GetInstance 调用来调用。var filter = new StringToLowerFilter(WebApiApplication.Container.GetInstance<IStringService>()); config.Filters.Add(filter); - Morn
StructureMap会自动解析您过滤器中构造函数注入的依赖项。您只需要在StructureMap中唯一标识您的过滤器,以便您可以解析它。您可以使用命名实例来实现这一点,但最好是为过滤器提供一个独特的抽象或在GetInstance方法中明确指定具体类型,这样更加清晰简洁。 - NightOwl888
有道理。对于其他阅读此内容的人来说,以上方法完美地解决了问题。干杯。 - Morn

2
我曾经为自定义操作筛选器提供程序而苦苦挣扎,但无法使它与我的授权属性一起正常工作。我还尝试了各种构造函数和属性注入方法,但没有找到令人满意的解决方案。
最终,我选择将函数注入到我的属性中。这样,单元测试可以注入返回虚拟或模拟的函数,而应用程序可以注入解决依赖关系的IoC容器的函数。
我在这里写了这种方法:http://danielsaidi.com/blog/2015/09/11/asp-net-and-webapi-attributes-with-structuremap 这在我的项目中非常有效,并解决了我使用其他方法遇到的所有问题。

链接已损坏(“未找到”)。 - UserControl
1
顺便提一下,我正在阅读你的帖子,注意到你提到你无法使用依赖注入来实现授权过滤器。这个答案中有一个示例可以做到这一点:https://dev59.com/QY7ea4cB1Zd3GeqPB3_S#32254851。 - NightOwl888

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接