如何使用构造函数参数测试动作过滤器的存在? [英] How can I test for the presence of an Action Filter with constructor arguments?

查看:23
本文介绍了如何使用构造函数参数测试动作过滤器的存在?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

[测试方法]public void Base_controller_must_have_MaxLengthFilter_attribute(){var att = typeof(BaseController).GetCustomAttribute();Assert.IsNotNull(att);}

解决方案

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

但是,您被 MVC 的设计问题之一绊倒了 - 即,要对 DI 友好,属性应该只提供元数据,但是 从未真正定义行为.这不是您的测试方法的问题,而是过滤器设计方法的问题.

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

  1. 不包含用于标记控制器和操作方法的行为的属性.
  2. 实现 IActionFilter 并包含所需的行为.

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

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

<块引用>

注意:如果您需要过滤器的任何依赖项的生命周期比单例短,则需要使用 GlobalFilterProvider,如这个答案.

MaxLengthActionFilter 的实现看起来像这样:

公共类 MaxLengthActionFilter : IActionFilter{公共只读 IConfigProvider configProvider;公共 MaxLengthActionFilter(IConfigProvider configProvider){if (configProvider == null)throw new ArgumentNullException("configProvider");this.configProvider = configProvider;}public void OnActionExecuted(ActionExecutedContext filterContext){var 属性 = this.GetMaxLengthAttribute(filterContext.ActionDescriptor);如果(属性!= null){var maxLength = attribute.MaxLength;//在这里执行你的行为,并根据需要使用 configProvider}}public void OnActionExecuting(ActionExecutingContext filterContext){var 属性 = this.GetMaxLengthAttribute(filterContext.ActionDescriptor);如果(属性!= null){var maxLength = attribute.MaxLength;//在这里执行你的行为,并根据需要使用 configProvider}}公共 MaxLengthAttribute GetMaxLengthAttribute(ActionDescriptor actionDescriptor){MaxLengthAttribute 结果 = null;//检查控制器上是否存在该属性结果 = (MaxLengthAttribute)actionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof(MaxLengthAttribute), false).SingleOrDefault();如果(结果!= null){返回结果;}//注意:您可能需要一些额外的逻辑来确定//哪个属性适用(或两者都适用)//检查属性是否存在于动作方法上结果 = (MaxLengthAttribute)actionDescriptor.GetCustomAttributes(typeof(MaxLengthAttribute), false).SingleOrDefault();返回结果;}}

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

//该属性不应包含任何行为.没有行为,不需要注入任何东西.[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]公共类 MaxLengthAttribute : 属性{公共最大长度属性(int maxLength){this.MaxLength = maxLength;}公共 int MaxLength { 得到;私人订制;}}

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

[测试方法]public void Base_controller_must_have_MaxLengthFilter_attribute(){var att = typeof(BaseController).GetCustomAttribute();Assert.IsNotNull(att);}

I am trying to test that my base controller is decorated with a certain action filter. Because this filter's constructor looks to web.config, my first try at testing fails because the test project doesn't have a valid config file. Moving on, I used a TestConfigProvider that I inject into the filter constructor, but the following test fails because the config provider isn't passed to the constructor. How else can I test if this filter is applied?

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

解决方案

Well, you have taken a good first step by recognizing that Web.config is just another dependency and wrapping it into a ConfigProvider to inject is an excellent solution.

But, you are getting tripped up on one of the design problems of MVC - namely, that to be DI-friendly, attributes should only provide meta-data, but never actually define behavior. This isn't an issue with your approach to testing, it is an issue with the approach to the design of the filter.

As pointed out in the post, you can get around this issue by splitting your action filter attribute into 2 parts.

  1. An attribute that contains no behavior to flag your controllers and action methods with.
  2. A DI-friendly class that implements IActionFilter and contains the desired behavior.

The approach is to use the IActionFilter to test for the presence of the attribute, and then execute the desired behavior. The action filter can be supplied with all dependencies and then injected when the application is composed.

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

NOTE: If you need any of the filter's dependencies to have a lifetime shorter than singleton, you will need to use a GlobalFilterProvider as in this answer.

The implementation of MaxLengthActionFilter would look something like this:

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

And, your attribute which should not contain any behavior should look something like this:

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

With a more loosely coupled design, testing for the existence of the attribute is much more straightforward.

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

这篇关于如何使用构造函数参数测试动作过滤器的存在?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆