Asp.net MVC源码分析 -- Filter 以及调用优先级(转http://www.cnblogs.com/RobbinHan/archive/2011/11/29/2268076.html)

Coordinator
Nov 29, 2011 at 12:57 PM

在Asp.net MVC 框架中一共有四种型的Filter,它们分别是

1.IActionFilter
2.IAuthorizationFilter
3.IExceptionFilter
4.IResultFilter

别外再加上一个GlobalFilters.Filters全局的,看起来挺多但是基本上这些Filter都与Action的调用有关,

让我沿着Mvc3.0源码一一找出它们的线索。

首先让我们看FilterProviders.cs,这是一个全局的系统默认FilterFilter provider,当然们也可以向里面加自定义的provider.

FilterProviders.cs

View Code
 1 namespace System.Web.Mvc {
2 public static class FilterProviders {
3 static FilterProviders() {
4 Providers = new FilterProviderCollection();
5 Providers.Add(GlobalFilters.Filters);
6 Providers.Add(new FilterAttributeFilterProvider());
7 Providers.Add(new ControllerInstanceFilterProvider());
8 }
9
10 public static FilterProviderCollection Providers {
11 get;
12 private set;
13 }
14 }
15 }

这里面最为重要的是FilterAttributeFilterProvider,它提供了找出Action所有在元数据中Filter的功能方法。

FilterAttributeFilterProvider.cs

View Code
 1   public class FilterAttributeFilterProvider : IFilterProvider {
2 private readonly bool _cacheAttributeInstances;
3
4 public FilterAttributeFilterProvider()
5 : this(true) {
6 }
7
8 public FilterAttributeFilterProvider(bool cacheAttributeInstances) {
9 _cacheAttributeInstances = cacheAttributeInstances;
10 }
11
12 protected virtual IEnumerable<FilterAttribute> GetActionAttributes(ControllerContext controllerContext, ActionDescriptor actionDescriptor) {
13 return actionDescriptor.GetFilterAttributes(_cacheAttributeInstances);
14 }
15
16 protected virtual IEnumerable<FilterAttribute> GetControllerAttributes(ControllerContext controllerContext, ActionDescriptor actionDescriptor) {
17 return actionDescriptor.ControllerDescriptor.GetFilterAttributes(_cacheAttributeInstances);
18 }
19
20 public virtual IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor) {
21 ControllerBase controller = controllerContext.Controller;
22 if (controller == null) {
23 return Enumerable.Empty<Filter>();
24 }
25
26 var typeFilters = GetControllerAttributes(controllerContext, actionDescriptor)
27 .Select(attr => new Filter(attr, FilterScope.Controller, null));
28 var methodFilters = GetActionAttributes(controllerContext, actionDescriptor)
29 .Select(attr => new Filter(attr, FilterScope.Action, null));
30
31 return typeFilters.Concat(methodFilters).ToList();
32 }
33 }

-------------------------------------------------------------------------------------------------
接下来我们看Mvm框架是如何获取和使用这些Filter的:

ControllerActionInvoker.cs

View Code
 1  public virtual bool InvokeAction(ControllerContext controllerContext, string actionName) {
2 if (controllerContext == null) {
3 throw new ArgumentNullException("controllerContext");
4 }
5 if (String.IsNullOrEmpty(actionName)) {
6 throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
7 }
8
9 ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);
10 ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);
11 if (actionDescriptor != null) {
12 FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);
13
14 try {
15 AuthorizationContext authContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor);
16 if (authContext.Result != null) {
17 // the auth filter signaled that we should let it short-circuit the request
18 InvokeActionResult(controllerContext, authContext.Result);
19 }
20 else {
21 if (controllerContext.Controller.ValidateRequest) {
22 ValidateRequest(controllerContext);
23 }
24
25 IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor);
26 ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters);
27 InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters, postActionContext.Result);
28 }
29 }
30 catch (ThreadAbortException) {
31 // This type of exception occurs as a result of Response.Redirect(), but we special-case so that
32 // the filters don't see this as an error.
33 throw;
34 }
35 catch (Exception ex) {
36 // something blew up, so execute the exception filters
37 ExceptionContext exceptionContext = InvokeExceptionFilters(controllerContext, filterInfo.ExceptionFilters, ex);
38 if (!exceptionContext.ExceptionHandled) {
39 throw;
40 }
41 InvokeActionResult(controllerContext, exceptionContext.Result);
42 }
43
44 return true;
45 }
46
47 // notify controller that no method matched
48 return false;
49 }

在ControllerActionInvoker.InvokeAction 方法中我们看到一句

FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);//这是得到Action和 Controller所有Filter的方法实现.

ControllerActionInvoker.cs

View Code
1 protected virtual FilterInfo GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor) {
2 return new FilterInfo(_getFiltersThunk(controllerContext, actionDescriptor));
3 }
4
5
6 private Func<ControllerContext, ActionDescriptor, IEnumerable<Filter>> _getFiltersThunk = (cc, ad) => FilterProviders.Providers.GetFilters(cc, ad);

这时我们看到了GetFilters最终调用了, FilterProviders.Providers.GetFilters 来得到Filter,那其中

又用到了FilterAttributeFilterProvider 来获取所有Action 和 Controller 的Filter.

------------------------------------------------------------------------------------------------

而我们如何来控制Filter的调用顺序呢?

FilterProviderCollection.cs

View Code
  1 namespace System.Web.Mvc {
2 using System.Collections.Generic;
3 using System.Collections.ObjectModel;
4 using System.Linq;
5
6 public class FilterProviderCollection : Collection<IFilterProvider> {
7
8 private static FilterComparer _filterComparer = new FilterComparer();
9 private IResolver<IEnumerable<IFilterProvider>> _serviceResolver;
10
11 public FilterProviderCollection() {
12 _serviceResolver = new MultiServiceResolver<IFilterProvider>(() => Items);
13 }
14
15 public FilterProviderCollection(IList<IFilterProvider> providers)
16 : base(providers) {
17 _serviceResolver = new MultiServiceResolver<IFilterProvider>(() => Items);
18 }
19
20 internal FilterProviderCollection(IResolver<IEnumerable<IFilterProvider>> serviceResolver, params IFilterProvider[] providers)
21 : base(providers) {
22 _serviceResolver = serviceResolver ?? new MultiServiceResolver<IFilterProvider>(
23 () => Items
24 );
25 }
26
27 private IEnumerable<IFilterProvider> CombinedItems {
28 get {
29 return _serviceResolver.Current;
30 }
31 }
32
33 private static bool AllowMultiple(object filterInstance) {
34 IMvcFilter mvcFilter = filterInstance as IMvcFilter;
35 if (mvcFilter == null) {
36 return true;
37 }
38
39 return mvcFilter.AllowMultiple;
40 }
41
42 public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor) {
43 if (controllerContext == null) {
44 throw new ArgumentNullException("controllerContext");
45 }
46 if (actionDescriptor == null) {
47 throw new ArgumentNullException("actionDescriptor");
48 }
49
50 IEnumerable<Filter> combinedFilters =
51 CombinedItems.SelectMany(fp => fp.GetFilters(controllerContext, actionDescriptor))
52 .OrderBy(filter => filter, _filterComparer);
53
54 // Remove duplicates from the back forward
55 return RemoveDuplicates(combinedFilters.Reverse()).Reverse();
56 }
57
58 private IEnumerable<Filter> RemoveDuplicates(IEnumerable<Filter> filters) {
59 HashSet<Type> visitedTypes = new HashSet<Type>();
60
61 foreach (Filter filter in filters) {
62 object filterInstance = filter.Instance;
63 Type filterInstanceType = filterInstance.GetType();
64
65 if (!visitedTypes.Contains(filterInstanceType) || AllowMultiple(filterInstance)) {
66 yield return filter;
67 visitedTypes.Add(filterInstanceType);
68 }
69 }
70 }
71
72 private class FilterComparer : IComparer<Filter> {
73 public int Compare(Filter x, Filter y) {
74 // Nulls always have to be less than non-nulls
75 if (x == null && y == null) {
76 return 0;
77 }
78 if (x == null) {
79 return -1;
80 }
81 if (y == null) {
82 return 1;
83 }
84
85 // Sort first by order...
86
87 if (x.Order < y.Order) {
88 return -1;
89 }
90 if (x.Order > y.Order) {
91 return 1;
92 }
93
94 // ...then by scope
95
96 if (x.Scope < y.Scope) {
97 return -1;
98 }
99 if (x.Scope > y.Scope) {
100 return 1;
101 }
102
103 return 0;
104 }
105 }
106 }
107 }

我们看到这里_filterComparer 起到了关键的作用,它通过Filter.Order 和 Filter.Scope 来决定了调用的优先顺序

IEnumerable<Filter> combinedFilters = 
CombinedItems.SelectMany(fp => fp.GetFilters(controllerContext, actionDescriptor))
.OrderBy(filter => filter, _filterComparer);

到这里我们已经知道如果获取Filter和filter的优先级别。

-------------------------------------------------------------------------------------------------

同时我们还需要注意一点的是在FilterProviderCollection的构造函数中:

 _serviceResolver = new MultiServiceResolver<IFilterProvider>(() => Items);

会把DependenyResolver中的注册的IFilterProvider 和 Items(自已的合并)。

MultiServiceResolver.cs

View Code
 1  internal class MultiServiceResolver<TService> : IResolver<IEnumerable<TService>> where TService : class {
2 private IEnumerable<TService> _itemsFromService;
3 private Func<IEnumerable<TService>> _itemsThunk;
4 private Func<IDependencyResolver> _resolverThunk;
5
6 public MultiServiceResolver(Func<IEnumerable<TService>> itemsThunk) {
7 if (itemsThunk == null) {
8 throw new ArgumentNullException("itemsThunk");
9 }
10
11 _itemsThunk = itemsThunk;
12 _resolverThunk = () => DependencyResolver.Current;
13 }
14
15 internal MultiServiceResolver(Func<IEnumerable<TService>> itemsThunk, IDependencyResolver resolver)
16 : this(itemsThunk) {
17 if (resolver != null) {
18 _resolverThunk = () => resolver;
19 }
20 }
21
22 public IEnumerable<TService> Current {
23 get {
24 if (_itemsFromService == null) {
25 lock (_itemsThunk) {
26 if (_itemsFromService == null) {
27 _itemsFromService = _resolverThunk().GetServices<TService>();
28 }
29 }
30 }
31 return _itemsFromService.Concat(_itemsThunk());
32 }
33 }
34 }
35 }

这就为我们提供了三个注册IFilterProvider 的时点:DependencyResolver/GlobalFilters.Filters/FilterProviders.Providers

-----------------------------------------------------------------------------------------------------

总结:

到这里我们已经知道如果获取Filter和filter的优先级别,那么我们如何来使用这些Filter呢?

在InvokeAction 方法中我们看到

FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);

View Code
 1  public class FilterInfo {
2 private List<IActionFilter> _actionFilters = new List<IActionFilter>();
3 private List<IAuthorizationFilter> _authorizationFilters = new List<IAuthorizationFilter>();
4 private List<IExceptionFilter> _exceptionFilters = new List<IExceptionFilter>();
5 private List<IResultFilter> _resultFilters = new List<IResultFilter>();
6
7 public FilterInfo() {
8 }
9
10 public FilterInfo(IEnumerable<Filter> filters) {
11 // evaluate the 'filters' enumerable only once since the operation can be quite expensive
12 var filterInstances = filters.Select(f => f.Instance).ToList();
13
14 _actionFilters.AddRange(filterInstances.OfType<IActionFilter>());
15 _authorizationFilters.AddRange(filterInstances.OfType<IAuthorizationFilter>());
16 _exceptionFilters.AddRange(filterInstances.OfType<IExceptionFilter>());
17 _resultFilters.AddRange(filterInstances.OfType<IResultFilter>());
18 }

这里会把得到的Filters 分类放好供MVC调用,这时我们看到了以下几句代码就是对Filter的调用。

 1 //IAuthorizationFilter调用
2 AuthorizationContext authContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor)
3
4 //IActionFilter调用
5 ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters);
6
7 //IResultFilter调用
8 InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters, postActionContext.Result);
9
10 //IExceptionFilter调用
11 ExceptionContext exceptionContext = InvokeExceptionFilters(controllerContext, filterInfo.ExceptionFilters, ex);

当然这些调用时会判断调用状态,如果IAuthorizationFilter没有成功,这时authContext.Result 就会有值,

就会调用InvokeActionResult(controllerContext, authContext.Result); 而不会往下调用其它Filter了。

 

以上分析如各位看客有不同意见,欢迎给砖。:)

谢谢。