.NET MVC深度分析 扩展MVC内置过滤器

  最近在公司项目中用MVC内置的权限过滤器实现权限控制功能,查阅以前的代码发现权限过滤接口已经被实现在其他地方,作用是用户访问系统任何页面时都要判断是否登录,如果没有登录,就会自动读取本机的域账号信息,并自动完成登录动作。现在遇到的问题是,如果新加的权限验证功能基于权限过滤器,就必须考虑过滤器的执行先后顺序,由于权限验证基于用户信息,所以我要保证用户自动登录的过滤器在权限过滤器之前被执行,但是MVC的权限过滤器默认是优先级最高的,我又不想让非权限验证的功能丢在权限过滤器中执行,又想要这个过滤器执行优先级一定高于权限过滤器,怎么办呢?看来只有通过扩展MVC内置过滤器的方式实现了。

一、初步计划

  自定义一个过滤器接口IFirstFilter,它会在MVC框架中被最先调用,程序员只需要创建实现该接口的特性,就可以实现自定义过滤器。

二、实现步骤

   1) 重写ControllerActionInvoker的方法InvokeAction

  由于MVC框架的action方法调用统一通过InvokeAction执行,在这个方法中会调用MVC内置过滤器,我们通过重写它来给自定义过滤器“内置”进去。另外还自定义两个方法,GetFirstFilters方法用来通过反射特性获取自定义的特性,InvokeFirstFilters用来执行自定义的过滤器。

  新建类文件UserControllerActionInvoker.cs,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Reflection;
 
namespace MvcUserFilter.MVC
{
    public class UserControllerActionInvoker : ControllerActionInvoker
    {
        public override bool InvokeAction(ControllerContext controllerContext, string actionName)
        {
            if (controllerContext != null && !string.IsNullOrEmpty(actionName))
            {
                ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);
                ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);
 
                if (actionDescriptor != null)
                {
                    IList<IFirstFilter> firstFilters = GetFirstFilters(actionDescriptor);
                    FirstFilterContext firstContext = InvokeFirstFilters(controllerContext, firstFilters, actionDescriptor);
                    if (firstContext.Result != null)
                    {
                        InvokeActionResult(controllerContext, firstContext.Result);
                        return true;
                    }
                }
            }
 
            return base.InvokeAction(controllerContext, actionName);
 
        }
 
        private IList<IFirstFilter> GetFirstFilters(ActionDescriptor actionDescriptor)
        {
 
            MethodInfo methodInfo = (actionDescriptor as ReflectedActionDescriptor).MethodInfo;
 
            FilterAttribute[] typeFilters = (FilterAttribute[])methodInfo.ReflectedType.GetCustomAttributes(typeof(FilterAttribute), true /* inherit */);
            FilterAttribute[] methodFilters = (FilterAttribute[])methodInfo.GetCustomAttributes(typeof(FilterAttribute), true /* inherit */);
            List<FilterAttribute> orderedFilters = typeFilters.Concat(methodFilters).OrderBy(attr => attr.Order).ToList();
 
            IList<IFirstFilter> firstFilters = new List<IFirstFilter>();
 
            foreach (FilterAttribute filter in orderedFilters)
            {
                IFirstFilter castFilter = filter as IFirstFilter;
                if (castFilter != null)
                {
                    firstFilters.Add(castFilter);
                }
            }
            return firstFilters;
        }
 
        private FirstFilterContext InvokeFirstFilters(ControllerContext controllerContext, IList<IFirstFilter> firstFilters, ActionDescriptor actionDescriptor)
        {
            FirstFilterContext context = new FirstFilterContext(controllerContext, actionDescriptor);
            foreach (IFirstFilter filter in firstFilters)
            {
                filter.OnFirstFilterDoing(context);
                if (context.Result != null)
                {
                    break;
                }
            }
 
            return context;
        }
    }
}

   2) 定义自定义的过滤器接口

  新建类文件IFirstFilter.cs,代码如下:

1
2
3
4
5
6
7
namespace MvcUserFilter.MVC
{
    interface IFirstFilter
    {
        void OnFirstFilterDoing(FirstFilterContext filterContext);
    }
}

   3) 定义自定义的过滤器上下文

  新建类文件FirstFilterContext.cs,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
namespace MvcUserFilter.MVC
{
    public class FirstFilterContext : ControllerContext
    {
 
        public FirstFilterContext()
        {
        }
 
        [Obsolete("The recommended alternative is the constructor AuthorizationContext(ControllerContext controllerContext, ActionDescriptor actionDescriptor).")]
        public FirstFilterContext(ControllerContext controllerContext)
            : base(controllerContext)
        {
        }
 
        [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors",
            Justification = "The virtual property setters are only to support mocking frameworks, in which case this constructor shouldn't be called anyway.")]
        public FirstFilterContext(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
            : base(controllerContext)
        {
            if (actionDescriptor == null)
            {
                throw new ArgumentNullException("actionDescriptor");
            }
 
            ActionDescriptor = actionDescriptor;
        }
 
        public virtual ActionDescriptor ActionDescriptor
        {
            get;
            set;
        }
 
        public ActionResult Result
        {
            get;
            set;
        }
 
    }
}

   4) 重写控制器CreateActionInvoker方法,创建自定义ActionInvoker实例

  定义一个UserController.cs,继承自Controller,重写方法CreateActionInvoker方法,代码如下:

1
2
3
4
5
6
7
8
9
10
namespace MvcUserFilter.MVC
{
    public class UserController : Controller
    {
        protected override IActionInvoker CreateActionInvoker()
        {
            return new UserControllerActionInvoker();
        }
    }
}

三、使用方法

  到此我们已经成功的将自定义的过滤器“内置”到MVC框架里了,现在看看怎么使用,使用方式跟MVC自带的过滤器基本一致(目前只能通过特性体现,不支持控制器虚方法派生):

  首先,定义一个FirstFilterAttribute特性,从FilterAttribute, IFirstFilter 派生,实现接口IFirstFilter的方法OnFirstFilterDoing

1
2
3
4
5
6
7
8
9
10
11
public class FirstFilterAttribute : FilterAttribute, IFirstFilter
{
 
    public void OnFirstFilterDoing(FirstFilterContext filterContext)
    {
        filterContext.Result = new ContentResult()
        {
            Content="用户自定义过滤器被执行了。",
        };
    }
}

  然后,就可以在控制器的action上使用了,注意,控制器要从UserController继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class HomeController : UserController
{
    [FirstFilter]
    public ActionResult Index()
    {
        ViewData["Message"] = "欢迎使用 ASP.NET MVC!";
 
        return View();
    }
 
    public ActionResult About()
    {
        return View();
    }
}

四、执行结果

  编译运行,访问首页时页面输出“用户自定义过滤器被执行了。”

作者:李盼(Lipan)
出处:[Lipan]http://www.cnblogs.com/lipan/
版权声明:本文的版权归作者与博客园共有。转载时须注明本文指向型链接,否则作者将保留追究其法律责任。
posted @   lipan  阅读(7213)  评论(18编辑  收藏  举报
编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
点击右上角即可分享
微信分享提示