【问题标题】:How can I test for the presence of an Action Filter with constructor arguments?如何使用构造函数参数测试是否存在动作过滤器?
【发布时间】:2015-01-30 10:50:30
【问题描述】:

我正在尝试测试我的基本控制器是否装饰有某个动作过滤器。因为这个过滤器的构造函数看起来是web.config,所以我第一次尝试测试失败了,因为测试项目没有有效的配置文件。继续前进,我使用了一个TestConfigProvider,将其注入到过滤器构造函数中,但以下测试失败,因为配置提供程序没有传递给构造函数。如果应用了此过滤器,我还能如何测试?

[TestMethod]
public void Base_controller_must_have_MaxLengthFilter_attribute()
{
    var att = typeof(BaseController).GetCustomAttribute<MaxLengthFilter>();
    Assert.IsNotNull(att);
}

【问题讨论】:

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


    【解决方案1】:

    也许您可以通过“将文件添加为链接”将有效的配置文件添加到您的测试项目中

    【讨论】:

    • 依赖外部配置源只是我绝对的最后一个选择。
    【解决方案2】:

    最近我在这里有越来越多关于配置“问题”的问题。它们都有一个共同的基础——你有几个项目、服务器、服务需要使用相同的配置。我给你的建议 - 停止使用 Web.config。

    将所有配置放入数据库! 添加一个包含所有配置键和值的表(或多个表),并在应用程序启动时读取它们(global.asax)。

    这样您就不必担心将配置应用于每个项目或将其注入不同的构造函数。

    【讨论】:

    • 如何让 IIS 从我的数据库中读取?这里的问题根本不在于配置,而在于没有使用构造函数参数的 GetCustomAttribute。关于配置的整个辩论是另一回事。
    • Web.Config 是一个完全可以接受的存储配置值的地方,而且并不复杂。您可以将值注入到类中,并在每个环境中轻松配置它们。
    【解决方案3】:

    好吧,您已经迈出了良好的第一步,认识到 Web.config 只是另一个依赖项并将其包装到 ConfigProvider 中进行注入是一个很好的解决方案。

    但是,您遇到了 MVC 的设计问题之一 - 即,要对 DI 友好,属性应该只提供元数据,但 never actually define behavior。这不是您的测试方法的问题,而是过滤器设计方法的问题。

    正如帖子中所指出的,您可以通过将操作过滤器属性分成两部分来解决此问题。

    1. 一个不包含任何行为来标记您的控制器和操作方法的属性。
    2. 实现IActionFilter 并包含所需行为的DI 友好类。

    方法是使用 IActionFilter 来测试属性是否存在,然后执行所需的行为。动作过滤器可以提供所有依赖项,然后在组合应用程序时注入。

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

    注意:如果您需要过滤器的任何依赖项的生命周期短于单例,您将需要使用GlobalFilterProvider,如this answer

    MaxLengthActionFilter 的实现如下所示:

    public class MaxLengthActionFilter : IActionFilter
    {
        public readonly IConfigProvider configProvider;
    
        public MaxLengthActionFilter(IConfigProvider configProvider)
        {
            if (configProvider == null)
                throw new ArgumentNullException("configProvider");
            this.configProvider = configProvider;
        }
    
        public void OnActionExecuted(ActionExecutedContext filterContext)
        {
            var attribute = this.GetMaxLengthAttribute(filterContext.ActionDescriptor);
            if (attribute != null)
            {
                var maxLength = attribute.MaxLength;
    
                // Execute your behavior here, and use the configProvider as needed
            }
        }
    
        public void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var attribute = this.GetMaxLengthAttribute(filterContext.ActionDescriptor);
            if (attribute != null)
            {
                var maxLength = attribute.MaxLength;
    
                // Execute your behavior here, and use the configProvider as needed
            }
        }
    
        public MaxLengthAttribute GetMaxLengthAttribute(ActionDescriptor actionDescriptor)
        {
            MaxLengthAttribute result = null;
    
            // Check if the attribute exists on the controller
            result = (MaxLengthAttribute)actionDescriptor
                .ControllerDescriptor
                .GetCustomAttributes(typeof(MaxLengthAttribute), false)
                .SingleOrDefault();
    
            if (result != null)
            {
                return result;
            }
    
            // NOTE: You might need some additional logic to determine 
            // which attribute applies (or both apply)
    
            // Check if the attribute exists on the action method
            result = (MaxLengthAttribute)actionDescriptor
                .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; }
    }
    

    采用更松散耦合的设计,测试属性是否存在更加直接。

    [TestMethod]
    public void Base_controller_must_have_MaxLengthFilter_attribute()
    {
        var att = typeof(BaseController).GetCustomAttribute<MaxLengthAttribute>();
        Assert.IsNotNull(att);
    }
    

    【讨论】:

    • 谢谢@NightOwl888!这确实是一个我可以深信不疑的答案——而且很容易理解。我一直同意属性不应该定义行为,但是我有限的内省时间迫使我对动作过滤器属性进行了例外处理。
    • 在我看来,将 IActionFilter 添加到管道中有一些应该提到的缺点。它会稍微影响性能,因为应该为每个请求执行过滤器。此外,您应该小心,因为操作过滤器中的任何错误都会使您的服务不可用。
    • @PopovSergey - 实际上,如果您正确设计了过滤器,则不会(它们被称为过滤器是有原因的)。过滤器的目的只是为了确定 when 执行共享行为,它不应该实际执行它。当且仅当它需要运行时,应该通过设置filterContext.Result 将其委托给派生自ActionResult as Microsoft does in AuthorizeAttribute 的处理程序。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-08-19
    • 1970-01-01
    • 2011-12-07
    • 2022-10-15
    • 2017-01-29
    • 1970-01-01
    • 2018-06-16
    相关资源
    最近更新 更多