經過那麼長時間的學習,終於想給自己這段時間的學習工作做個總結了。記得剛開始學習的時候,什麼資料都沒有,光就啃文檔。不過,值得慶幸的是,自己總算還有一些Web開發的基礎。至少ASP.NET的WebForm和MVC那一套還是有所瞭解的,雖然也不是很精通。說起來,那時候對整個網路應用的整體流程以及什麼HT ...
1、概念
概念這種東西,感覺還是太過於學術化。也就是時間長了,慢慢就能理解的一些經常用到的詞而已。對於大多數人來說,我們幾乎每天都會瀏覽網頁。也許,我們對於網路應用的基本認識,就是從這裡開始的。可惜,很多人的認識仍然停留在打開瀏覽器看網頁上。以至於,對於網頁是怎麼來的,怎麼呈現的毫無概念。
網路應用是一種分散式系統,通常由客戶端和服務端組成。通過HTTP協議進行通信,是一種請求/應答模式。瀏覽器通常作為客戶端,而我們開發的Web應用,通常作為服務端。
上面這張圖來源於微軟的官方文檔,它簡單直觀的描述了我們將要開發的Web應用的基本原理。首先,ASP.NET Core application代表了我們的整個Web應用,它通過HTTP協議與外部進行通信。而在我們程式的內部,首先就是由ASP.NET Core的框架所支配的。Kestrel是一個可以監聽和響應請求的底層服務,它會把接收到的HTTP報文封裝成HttpContext傳遞給我們的應用程式代碼。同時,把應用程式處理好的響應轉換為響應報文返回給客戶端。
現在,讓我們深入應用程式代碼的內部。應用程式管道,本質上是由一個委托鏈構成的。這個名為RequestDelegate的委托有兩個參數,第一個是httpContext,第二個是next,類型也是RequestDeletage,指向下一個委托。
由上圖我們看到,請求和響應實質上是由一系列中間件處理共同處理的。而事實上,這些中間件最終會編譯為一個委托鏈(所有Middleware類按照約定都應該包含一個Invoke方法和構造函數,構造函數中包含了next,Invoke中包含了httpContext)。總結來說,當請求進來以後,首先會執行第一個委托。而第一個委托的內部可以選擇是否調用下一個委托。如同上圖所示,如果第一中間件,實質上會變成一個委托,不調用next()。那麼,請求便在該中間件短路了,即請求不再向下傳遞,而是直接返迴響應了。
接下來,我們來介紹ASP.NET Core框架的核心部分,即MVC。對於每一個請求來說,應該都會有一個對應的URL。而我們的程式通常也會有一個對應的處理方法,即我們的控制器動作。現在,框架所解決的第一個問題即是,如何根據URL映射到對應的處理方法,即路由機制。
路由機制是由Microsoft.AspNetCore.Routing實現的。它最核心的部分是RouterMiddleware中的那段代碼。
public async Task Invoke(HttpContext httpContext) { var context = new RouteContext(httpContext); context.RouteData.Routers.Add(_router);//_router是通過依賴註入,從服務容器中獲得的 //這一步是最重要的,它會根據RouteContext尋找一個合適的Handler //也就是說,整個路由匹配在於這一步是如何實現的 await _router.RouteAsync(context); if (context.Handler == null) { _logger.RequestDidNotMatchRoutes(); await _next.Invoke(httpContext); } else { httpContext.Features[typeof(IRoutingFeature)] = new RoutingFeature() { RouteData = context.RouteData, }; await context.Handler(context.HttpContext); } }
上面這個_router是一個IRouter介面類型的變數。實質上,當我們在註冊MVC服務的時候,已經添加了實現類。如下所示:
// // Route Handlers // services.TryAddSingleton<MvcRouteHandler>(); // Only one per app services.TryAddTransient<MvcAttributeRouteHandler>(); // Many per app
var routes = new RouteBuilder(app) { DefaultHandler = app.ApplicationServices.GetRequiredService<MvcRouteHandler>(), }; configureRoutes(routes); routes.Routes.Insert(0,AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices)); return app.UseRouter(routes.Build());
那麼,問題在於,MvcRouteHandler和MvcAttributeRouteHandler是如何實現的?首先,我們來看看MvcRouteHandler內部是如何實現的。
public Task RouteAsync(RouteContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } //海選 var candidates = _actionSelector.SelectCandidates(context); if (candidates == null || candidates.Count == 0) { _logger.NoActionsMatched(context.RouteData.Values); return Task.CompletedTask; } //精選 var actionDescriptor = _actionSelector.SelectBestCandidate(context, candidates); if (actionDescriptor == null) { _logger.NoActionsMatched(context.RouteData.Values); return Task.CompletedTask; } //使用lambda表達式編譯成RequestDelegate context.Handler = (c) => { var routeData = c.GetRouteData(); var actionContext = new ActionContext(context.HttpContext, routeData, actionDescriptor); if (_actionContextAccessor != null) { _actionContextAccessor.ActionContext = actionContext; } var invoker = _actionInvokerFactory.CreateInvoker(actionContext); if (invoker == null) { throw new InvalidOperationException( Resources.FormatActionInvokerFactory_CouldNotCreateInvoker( actionDescriptor.DisplayName)); } //這裡才是核心處理部分 return invoker.InvokeAsync(); }; return Task.CompletedTask; }
我們看到,invoker是由_actionInvokerFactory
創建的。而_actionInvokerFactory
是IActionInvokerFactory類型,從服務容器中獲取。我們來查找它是怎樣註入到容器中的。
// // Action Invoker // // The IActionInvokerFactory is cachable services.TryAddSingleton<IActionInvokerFactory, ActionInvokerFactory>(); services.TryAddEnumerable( ServiceDescriptor.Transient<IActionInvokerProvider, ControllerActionInvokerProvider>());
可以看到,它註入了一個預設實現,ActionInvokerFactory。它的內部是這樣的:
public IActionInvoker CreateInvoker(ActionContext actionContext) { var context = new ActionInvokerProviderContext(actionContext); foreach (var provider in _actionInvokerProviders) { provider.OnProvidersExecuting(context); } for (var i = _actionInvokerProviders.Length - 1; i >= 0; i--) { _actionInvokerProviders[i].OnProvidersExecuted(context); } return context.Result; }
事實上,ActionInvokerFactory並沒有直接處理,而是交給了IActionInokerProvider。而在上面我們看到它的預設實現是ControllerActionInvokerProvider。
public void OnProvidersExecuting(ActionInvokerProviderContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (context.ActionContext.ActionDescriptor is ControllerActionDescriptor) { var controllerContext = new ControllerContext(context.ActionContext); // PERF: These are rarely going to be changed, so let's go copy-on-write. controllerContext.ValueProviderFactories = new CopyOnWriteList<IValueProviderFactory>(_valueProviderFactories); controllerContext.ModelState.MaxAllowedErrors = _maxModelValidationErrors; //緩存策略 var cacheResult = _controllerActionInvokerCache.GetCachedResult(controllerContext); var invoker = new ControllerActionInvoker( _logger, _diagnosticSource, controllerContext, cacheResult.cacheEntry, cacheResult.filters); context.Result = invoker; } }
我們最終發現,invoker來源於這裡,其中還做了緩存策略。現在,是時候揭開這個ControllerActionInvoker的神秘面紗了。