上一章介紹了經過路由的處理,一個請求找到了具體處理這個請求的EndPoint,並最終執行它的RequestDelegate方法來處理這個Httpcontext。本章繼續這個處理進程,按照慣例,依然通過幾幅圖來聊一聊這個RequestDelegate之後的故事。在此就避免不了的聊到各種Filter,它 ...
上一章介紹了經過路由的處理,一個請求找到了具體處理這個請求的EndPoint,並最終執行它的RequestDelegate方法來處理這個Httpcontext。本章繼續這個處理進程,按照慣例,依然通過幾幅圖來聊一聊這個RequestDelegate之後的故事。在此就避免不了的聊到各種Filter,它方便我們在action執行的前後做一些 “小動作”。(ASP.NET Core 系列目錄)
一、概述
首先看一下RequestDelegate這個方法:
RequestDelegate requestDelegate = (context) => { var routeData = context.GetRouteData(); var actionContext = new ActionContext(context, routeData, action); var invoker = _invokerFactory.CreateInvoker(actionContext); return invoker.InvokeAsync(); };
將這個方法的內容分為兩部分:
A. invoker的生成,前三句,通過CreateInvoker方法生成了一個invoker,它是一個比較複雜的綜合體,包含了controller的創建工廠、參數的綁定方法以及本action相關的各種Filter的集合等, 也就是說它是前期準備工作階段,這個階段擴展開來涉及面比較廣,在下文詳細描述。
B.invoker的執行,最後一句,前面已經萬事俱備,準備了好多方法,到此來執行。此時涉及到前面準備的各種方法的執行,各種Filter也在此時被執行。
二、invoker的生成
依照習慣,還是通過流程圖來看一看:
圖一(點擊查看大圖)
首先說一下此圖的結構,每個泳道相當於是上一個泳道中的圖標的細化說明,例如第二條泳道是圖標①標識的方塊的明細化。
A. 泳道一:
就是第一節【概述】中描述的的內容,不再贅述。另外提一下本文的核心invoker本質上就是一個ControllerActionInvoker,也是圖中的ActionInvokerProviderContext.Result。
B.泳道二:即①的詳細描述
① ActionInvokerFactory.CreateInvoker(actionContext)
1 public IActionInvoker CreateInvoker(ActionContext actionContext) 2 { 3 var context = new ActionInvokerProviderContext(actionContext); 4 5 foreach (var provider in _actionInvokerProviders) 6 { 7 provider.OnProvidersExecuting(context); 8 } 9 10 for (var i = _actionInvokerProviders.Length - 1; i >= 0; i--) 11 { 12 _actionInvokerProviders[i].OnProvidersExecuted(context); 13 } 14 15 return context.Result; 16 }
本章設計的這部分內容比較常見的一個操作就是context的封裝,這從第一個泳道的第一個操作就開始了, 他將HttpContext、RouteData,ActionDescriptor封裝到一起成了一個ActionContext ,而到了這個方法,又將這個ActionContext 封裝成了ActionInvokerProviderContext,接下來就是遍歷_actionInvokerProviders調用它們的OnProvidersExecuting和OnProvidersExecuted方法來設置ActionInvokerProviderContext.Result,也就是最終的ControllerActionInvoker。
這裡說一下_actionInvokerProviders,它的類型是IActionInvokerProvider[],預設情況下包含了兩個,分別是ControllerActionInvokerProvider和PageActionInvokerProvider,第一個是用於MVC的action的處理,而第二個用於Razor Pages Web的處理。二者的OnProvidersExecuting方法都會首先判斷當前action是不是自己對應的類型,若不是則直接跳過。二者的OnProvidersExecuted方法目前均為空。所以圖中和下麵關於OnProvidersExecuting的描述也僅限於ControllerActionInvokerProvider的OnProvidersExecuting方法。
C.泳道三:ControllerActionInvokerProvider.OnProvidersExecuting(context)
即泳道二中的③的詳細描述
1 public void OnProvidersExecuting(ActionInvokerProviderContext context) 2 { 3 if (context.ActionContext.ActionDescriptor is ControllerActionDescriptor) 4 { 5 var controllerContext = new ControllerContext(context.ActionContext); 6 // PERF: These are rarely going to be changed, so let's go copy-on-write. 7 controllerContext.ValueProviderFactories = new CopyOnWriteList<IValueProviderFactory>(_valueProviderFactories); 8 controllerContext.ModelState.MaxAllowedErrors = _maxModelValidationErrors; 9 10 var cacheResult = _controllerActionInvokerCache.GetCachedResult(controllerContext); 11 12 var invoker = new ControllerActionInvoker( 13 _logger, 14 _diagnosticSource, 15 _mapper, 16 controllerContext, 17 cacheResult.cacheEntry, 18 cacheResult.filters); 19 20 context.Result = invoker; 21 } 22 }
如上文所述,在處理之前,首先就是判斷當前action是否是自己對應處理的類型。然後就是繼續封裝大法,將ActionContext封裝成了ControllerContext。進而是調用GetCachedResult方法讀取兩個關鍵內容cacheResult.cacheEntry和cacheResult.filters後,將其封裝成ControllerActionInvoker(⑤)。
D.第四條泳道:
對應的是第三條中的④ControllerActionInvokerCache.GetCachedResult(controllerContext);
1 public (ControllerActionInvokerCacheEntry cacheEntry, IFilterMetadata[] filters) GetCachedResult(ControllerContext controllerContext) 2 { 3 var cache = CurrentCache; 4 var actionDescriptor = controllerContext.ActionDescriptor; 5 6 IFilterMetadata[] filters; 7 if (!cache.Entries.TryGetValue(actionDescriptor, out var cacheEntry)) 8 { 9 var filterFactoryResult = FilterFactory.GetAllFilters(_filterProviders, controllerContext); 10 filters = filterFactoryResult.Filters; 11 12 var parameterDefaultValues = ParameterDefaultValues 13 .GetParameterDefaultValues(actionDescriptor.MethodInfo); 14 15 var objectMethodExecutor = ObjectMethodExecutor.Create( 16 actionDescriptor.MethodInfo, 17 actionDescriptor.ControllerTypeInfo, 18 parameterDefaultValues); 19 20 var controllerFactory = _controllerFactoryProvider.CreateControllerFactory(actionDescriptor); 21 var controllerReleaser = _controllerFactoryProvider.CreateControllerReleaser(actionDescriptor); 22 var propertyBinderFactory = ControllerBinderDelegateProvider.CreateBinderDelegate( 23 _parameterBinder, 24 _modelBinderFactory, 25 _modelMetadataProvider, 26 actionDescriptor, 27 _mvcOptions); 28 29 var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor); 30 31 cacheEntry = new ControllerActionInvokerCacheEntry( 32 filterFactoryResult.CacheableFilters, 33 controllerFactory, 34 controllerReleaser, 35 propertyBinderFactory, 36 objectMethodExecutor, 37 actionMethodExecutor); 38 cacheEntry = cache.Entries.GetOrAdd(actionDescriptor, cacheEntry); 39 } 40 else 41 { 42 // Filter instances from statically defined filter descriptors + from filter providers 43 filters = FilterFactory.CreateUncachedFilters(_filterProviders, controllerContext, cacheEntry.CachedFilters); 44 } 45 46 return (cacheEntry, filters);
總的來看,本段內容主要是為了組裝cacheEntry和 filters兩個內容,而一個大的 if 體現出這裡加入了緩存機制,使系統不必每次都去拼湊這些,提高執行效率。
⑥IFilterMetadata[] filters,它是一個filter的集和,首先調用FilterFactory的GetAllFilters(_filterProviders, controllerContext)方法獲取當前action對應的所有Filter並對這些Filter進行排序(Filter部分將在之後章節分享)。
接下來就是組裝⑦cacheEntry,它的內容比較多,比較重要的幾個有:⑧ controllerFactory和controllerReleaser他們的本質都是Func<ControllerContext, object>,也就是Controller的Create和Release方法。 ⑨propertyBinderFactory 是一個用於參數綁定的Task,可以說也是一個組裝好準備被執行的方法。最後一個⑩actionMethodExecutor也就是執行者,通過ActionMethodExecutor.GetExecutor(objectMethodExecutor)方法從眾多的action執行者(如圖二)中找出一個當前action對應的執行者出來。
圖二
總結: 本節invoker的生成,總的來說就是一個執行前“萬事俱備”的過程,invoker是一個組裝起來的集合,它包含一個人(執行者actionMethodExecutor)、N把槍(組裝好用於“被執行”的方法例如controllerFactory、controllerReleaser和propertyBinderFactory,當然還有個filter的集和)。由此也可以進一步想到,接下來的過程就是這些準備好的內容按照一定的順序逐步執行的過程。
二、invoker的執行
invoker的執行也就是invoker.InvokeAsync(),雖然invoker本質上是ControllerActionInvoker,但這個方法寫在ResourceInvoker類中, ControllerActionInvoker : ResourceInvoker, IActionInvoker 。
public virtual async Task InvokeAsync() { try { await InvokeFilterPipelineAsync(); } finally { ReleaseResources(); _logger.ExecutedAction(_actionContext.ActionDescriptor, stopwatch.GetElapsedTime()); } } private async Task InvokeFilterPipelineAsync() { var next = State.InvokeBegin; var scope = Scope.Invoker; var state = (object)null; // `isCompleted` will be set to true when we've reached a terminal state. var isCompleted = false; while (!isCompleted) { await Next(ref next, ref scope, ref state, ref isCompleted); } }
看似比較簡單的兩個方法,從InvokeAsync方法中可以看出來,請求會進入篩選器管道進行處理,也就是 Task InvokeFilterPipelineAsync() 方法,借用官方文檔中的一個圖看一下
圖三
此圖描述了請求經過其他中間件處理後,進入路由處理最終找到了對應的action,最終進入篩選器管道進行處理。而這個處理的核心部分就是方法中的 while (!isCompleted) 迴圈,它對應的Next方法比較長,如下(較長已摺疊)
1 private Task Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted) 2 { 3 switch (next) 4 { 5 case State.InvokeBegin: 6 { 7 goto case State.AuthorizationBegin; 8 } 9 10 case State.AuthorizationBegin: 11 { 12 _cursor.Reset(); 13 goto case State.AuthorizationNext; 14 } 15 16 case State.AuthorizationNext: 17 { 18 var current = _cursor.GetNextFilter<IAuthorizationFilter, IAsyncAuthorizationFilter>(); 19 if (current.FilterAsync != null) 20 { 21 if (_authorizationContext == null) 22 { 23 _authorizationContext = new AuthorizationFilterContext(_actionContext, _filters); 24 } 25 26 state = current.FilterAsync; 27 goto case State.AuthorizationAsyncBegin; 28 } 29 else if (current.Filter != null) 30 { 31 if (_authorizationContext == null) 32 { 33 _authorizationContext = new AuthorizationFilterContext(_actionContext, _filters); 34 } 35 36 state = current.Filter; 37 goto case State.AuthorizationSync; 38 } 39 else 40 { 41 goto case State.AuthorizationEnd; 42 } 43 } 44 45 case State.AuthorizationAsyncBegin: 46 { 47 Debug.Assert(state != null); 48 Debug.Assert(_authorizationContext != null); 49 50 var filter = (IAsyncAuthorizationFilter)state; 51 var authorizationContext = _authorizationContext; 52 53 _diagnosticSource.BeforeOnAuthorizationAsync(authorizationContext, filter); 54 _logger.BeforeExecutingMethodOnFilter( 55 FilterTypeConstants.AuthorizationFilter, 56 nameof(IAsyncAuthorizationFilter.OnAuthorizationAsync), 57 filter); 58 59 var task = filter.OnAuthorizationAsync(authorizationContext); 60 if (task.Status != TaskStatus.RanToCompletion) 61 { 62 next = State.AuthorizationAsyncEnd; 63 return task; 64 } 65 66 goto case State.AuthorizationAsyncEnd; 67 } 68 69 case State.AuthorizationAsyncEnd: 70 { 71 Debug.Assert(state != null); 72 Debug.Assert(_authorizationContext != null); 73 74 var filter = (IAsyncAuthorizationFilter)state; 75 var authorizationContext = _authorizationContext; 76 77 _diagnosticSource.AfterOnAuthorizationAsync(authorizationContext, filter); 78 _logger.AfterExecutingMethodOnFilter( 79 FilterTypeConstants.AuthorizationFilter, 80 nameof(IAsyncAuthorizationFilter.OnAuthorizationAsync), 81 filter); 82 83 if (authorizationContext.Result != null) 84 { 85 goto case State.AuthorizationShortCircuit; 86 } 87 88 goto case State.AuthorizationNext; 89 } 90 91 case State.AuthorizationSync: 92 { 93 Debug.Assert(state != null); 94 Debug.Assert(_authorizationContext != null); 95 96 var filter = (IAuthorizationFilter)state; 97 var authorizationContext = _authorizationContext; 98 99 _diagnosticSource.BeforeOnAuthorization(authorizationContext, filter); 100 _logger.BeforeExecutingMethodOnFilter( 101 FilterTypeConstants.AuthorizationFilter, 102 nameof(IAuthorizationFilter.OnAuthorization), 103 filter); 104 105 filter.OnAuthorization(authorizationContext); 106 107 _diagnosticSource.AfterOnAuthorization(authorizationContext, filter); 108 _logger.AfterExecutingMethodOnFilter( 109 FilterTypeConstants.AuthorizationFilter, 110 nameof(IAuthorizationFilter.OnAuthorization), 111 filter); 112 113 if (authorizationContext.Result != null) 114 { 115 goto case State.AuthorizationShortCircuit; 116 } 117 118 goto case State.AuthorizationNext; 119 } 120 121 case State.AuthorizationShortCircuit: 122 { 123 Debug.Assert(state != null); 124 Debug.Assert(_authorizationContext != null); 125 Debug.Assert(_authorizationContext.Result != null); 126 127 _logger.AuthorizationFailure((IFilterMetadata)state); 128 129 // This is a short-circuit - execute relevant result filters + result and complete this invocation. 130 isCompleted = true; 131 _result = _authorizationContext.Result; 132 return InvokeAlwaysRunResultFilters(); 133 } 134 135 case State.AuthorizationEnd: 136 { 137 goto case State.ResourceBegin; 138 } 139 140 case State.ResourceBegin: 141 { 142 _cursor.Reset(); 143 goto case State.ResourceNext; 144 } 145 146 case State.ResourceNext: 147 { 148 var current = _cursor.GetNextFilter<IResourceFilter, IAsyncResourceFilter>(); 149 if (current.FilterAsync != null) 150 { 151 if (_resourceExecutingContext == null) 152 { 153 _resourceExecutingContext = new ResourceExecutingContext( 154 _actionContext, 155 _filters, 156 _valueProviderFactories); 157 } 158 159 state = current.FilterAsync; 160 goto case State.ResourceAsyncBegin; 161 } 162 else if (current.Filter != null) 163 { 164 if (_resourceExecutingContext == null) 165 { 166 _resourceExecutingContext = new ResourceExecutingContext( 167 _actionContext, 168 _filters, 169 _valueProviderFactories); 170 } 171 172 state = current.Filter; 173 goto case State.ResourceSyncBegin; 174 } 175 else 176 { 177 // All resource filters are currently on the stack - now execute the 'inside'. 178 goto case State.ResourceInside; 179 } 180 } 181 182 case State.ResourceAsyncBegin: 183 { 184 Debug.Assert(state != null); 185 Debug.Assert(_resourceExecutingContext != null); 186 187 var filter = (IAsyncResourceFilter)state; 188 var resourceExecutingContext = _resourceExecutingContext; 189 190 _diagnosticSource.BeforeOnResourceExecution(resourceExecutingContext, filter); 191 _logger.BeforeExecutingMethodOnFilter( 192 FilterTypeConstants.ResourceFilter, 193 nameof(IAsyncResourceFilter.OnResourceExecutionAsync), 194 filter); 195 196 var task = filter.OnResourceExecutionAsync(resourceExecutingContext, InvokeNextResourceFilterAwaitedAsync); 197 if (task.Status != TaskStatus.RanToCompletion) 198 { 199 next = State.ResourceAsyncEnd; 200 return task; 201 } 202 203 goto case State.ResourceAsyncEnd; 204 } 205 206 case State.ResourceAsyncEnd: 207 { 208 Debug.Assert(state != null); 209 Debug.Assert(_resourceExecutingContext != null); 210 211 var filter = (IAsyncResourceFilter)state; 212 if (_resourceExecutedContext == null) 213 { 214 // If we get here then the filter didn't call 'next' indicating a short circuit. 215 _resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters) 216 { 217 Canceled = true, 218 Result = _resourceExecutingContext.Result, 219 }; 220 221 _diagnosticSource.AfterOnResourceExecution(_resourceExecutedContext, filter); 222 _logger.AfterExecutingMethodOnFilter( 223 FilterTypeConstants.ResourceFilter, 224 nameof(IAsyncResourceFilter.OnResourceExecutionAsync), 225 filter); 226 227 // A filter could complete a Task without setting a result 228 if (_resourceExecutingContext.Result != null) 229 { 230 goto case State.ResourceShortCircuit; 231 } 232 } 233 234 goto case State.ResourceEnd; 235 } 236 237 case State.ResourceSyncBegin: 238 { 239 Debug.Assert(state != null); 240 Debug.Assert(_resourceExecutingContext != null); 241 242 var filter = (IResourceFilter)state; 243 var resourceExecutingContext = _resourceExecutingContext; 244 245 _diagnosticSource.BeforeOnResourceExecuting(resourceExecutingContext, filter); 246 _logger.BeforeExecutingMethodOnFilter( 247 FilterTypeConstants.ResourceFilter, 248 nameof(IResourceFilter.OnResourceExecuting), 249 filter); 250 251 filter.OnResourceExecuting(resourceExecutingContext); 252 253 _diagnosticSource.AfterOnResourceExecuting(resourceExecutingContext, filter); 254 _logger.AfterExecutingMethodOnFilter( 255 FilterTypeConstants.ResourceFilter, 256 nameof(IResourceFilter.OnResourceExecuting), 257 filter); 258 259 if (resourceExecutingContext.Result != null) 260 { 261 _resourceExecutedContext = new ResourceExecutedContext(resourceExecutingContext, _filters) 262 { 263 Canceled = true, 264 Result = _resourceExecutingContext.Result, 265 }; 266 267 goto case State.ResourceShortCircuit; 268 } 269 270 var task = InvokeNextResourceFilter(); 271 if (task.Status != TaskStatus.RanToCompletion) 272 { 273 next = State.ResourceSyncEnd; 274 return task; 275 } 276 277 goto case State.ResourceSyncEnd; 278 } 279 280 case State.ResourceSyncEnd: 281 { 282 Debug.Assert(state != null); 283 Debug.Assert(_resourceExecutingContext != null); 284 Debug.Assert(_resourceExecutedContext != null); 285 286 var filter = (IResourceFilter)state; 287 var resourceExecutedContext = _resourceExecutedContext; 288 289 _diagnosticSource.BeforeOnResourceExecuted(resourceExecutedContext, filter); 290 _logger.BeforeExecutingMethodOnFilter( 291 FilterTypeConstants.ResourceFilter, 292 nameof(IResourceFilter.OnResourceExecuted), 293 filter); 294 295 filter.OnResourceExecuted(resourceExecutedContext); 296 297 _diagnosticSource.AfterOnResourceExecuted(resourceExecutedContext, filter); 298 _logger.AfterExecutingMethodOnFilter( 299 FilterTypeConstants.ResourceFilter, 300 nameof(IResourceFilter.OnResourceExecuted), 301 filter); 302 303 goto case State.ResourceEnd; 304 } 305 306 case State.ResourceShortCircuit: 307 { 308 Debug.Assert(state != null); 309 Debug.Assert(_resourceExecutingContext != null); 310 Debug.Assert(_resourceExecutedContext != null); 311 312 _logger.ResourceFilterShortCircuited((IFilterMetadata)state); 313 314 _result = _resourceExecutingContext.Result; 315 var task = InvokeAlwaysRunResultFilters(); 316