1. 程式人生 > >ASP.NET Core學習總結(1)

ASP.NET Core學習總結(1)

ask 怎樣 desktop 就是 app routing 動作 reac form

  經過那麽長時間的學習,終於想給自己這段時間的學習工作做個總結了。記得剛開始學習的時候,什麽資料都沒有,光就啃文檔。不過,值得慶幸的是,自己總算還有一些Web開發的基礎。至少ASP.NET的WebForm和MVC那一套還是有所了解的,雖然也不是很精通。說起來,那時候對整個網絡應用的整體流程以及什麽HTTP協議都不是很了解。終歸是在微軟爸爸的庇護下艱難的成長。

1、概念

  概念這種東西,感覺還是太過於學術化。也就是時間長了,慢慢就能理解的一些經常用到的詞而已。對於大多數人來說,我們幾乎每天都會瀏覽網頁。也許,我們對於網絡應用的基本認識,就是從這裏開始的。可惜,很多人的認識仍然停留在打開瀏覽器看網頁上。以至於,對於網頁是怎麽來的,怎麽呈現的毫無概念。

  網絡應用是一種分布式系統,通常由客戶端和服務端組成。通過HTTP協議進行通信,是一種請求/應答模式。瀏覽器通常作為客戶端,而我們開發的Web應用,通常作為服務端。

2、原理

技術分享圖片

  上面這張圖來源於微軟的官方文檔,它簡單直觀的描述了我們將要開發的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都實現了IRouter接口。第一部分的代碼的意圖在於將這兩個Handler添加到服務容器。而第二部分代碼的意圖在於將它們從服務容器中取出,傳遞給路由中間件。

? 那麽,問題在於,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;
        }

  我們來解釋一下,關鍵在於_actionSelector。它會根據routeContext挑選出candidates(候選者),這是第一步。這一步只是篩選出了所有URL匹配的Action,而第二步則需要繼續考慮路由約束的問題。如果第二步還是有多個符合條件的Action,則會引發異常。第三步我們看到,利用一個lambda表達式生成RequestDelegate的委托,即一個Handler(前面我們曾看到在路由中間件中調用)。在這個委托中我們需要關註的是invoker,它是一個IActionInvoker類型的變量。它的默認實現通常是ControllerActionInvoker,我們稍後會深入討論這個類的內部實現。我們看到,invoker是由工廠函數根據actionContext生成的,最終調用了InvokeAsync方法。也就是說,至此為止,我們還是無法得知我們所編寫的Action代碼是怎樣執行的。而為了知道這一點,我們只能繼續深入。

  我們看到,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的神秘面紗了。

ASP.NET Core學習總結(1)