文章出處

前言

上一篇 文章中,我們已經學習了 ASP.NET Core MVC 的啟動流程,那么 MVC 在啟動了之后,當請求到達過來的時候,它是怎么樣處理的呢? 又是怎么樣把我們的請求準確的傳達到我們的 Action 上呢? 那么,在這邊文章中,我們一起跟蹤源碼看一下,框架都做了些什么東西。

Getting Started

我們知道,Startup.cs 中的 Configure(IApplicationBuilder app) 中,我們使用 app.UseMvc()

在 UseMVC() 代碼執行的過程中,它可以接收一個 Action<IRouteBuilder> 形式的委托,我們使用這個委托可以進行自定義路由的配置,默認情況下,我們一般會如下進行配置:

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}/{id?}");
});

或者是你使用默認的 app.UseMvcWithDefaultRoute(),這個擴展方法在內部已經幫你做了上述代碼的內容。

那我們今天就從這個 Route 的配置開始看起吧。

RouteContext 如何初始化?

在 IRouteBuilder 通過配置 IRouteBuilder,IRouteBuilder 在 Build() 之后會得到 Router 會得到 IRouter


public static IApplicationBuilder UseMvc(
    this IApplicationBuilder app,
    Action<IRouteBuilder> configureRoutes)
{

    // ......
    
    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());
}

上面的代碼有兩個地方需要注意的。

第一個地方是 DefaultHandler,可以看到默認配置下,MVC 程序從 DI 中獲取 MvcRouteHandler 路由處理程序來作為路由的默認處理程序。

第二個地方是 AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices)
,那么這個地方是干嘛的呢?

CreateAttributeMegaRoute 它返回了一個 IRouter ,主要是用來處理帶 RouteAttribute 標記的 Action,我們來看一下這個方法:


public static IRouter CreateAttributeMegaRoute(IServiceProvider services)
{
    return new AttributeRoute(
        services.GetRequiredService<IActionDescriptorCollectionProvider>(),
        services,
        actions => 
        {
            var handler = services.GetRequiredService<MvcAttributeRouteHandler>();
            handler.Actions = actions;
            return handler;
        });
}

在方法內部,new 了一個 AttributeRoute 返回了回去,大家可以看到有一個參數 actions,它使用的是 MvcAttributeRouteHandler 這個處理程序,說明在實際調用過程中使用的是 MvcAttributeRouteHandler 進行的路由處理。

OK,我們總結一下關于 MVC 自己的幾個路由處理程序,還是用一個圖比較容易看的清楚,幸運的是,MVC 一共就這3個路由處理程序,我們已經全部接觸到了。

MVC 框架針對于 IRouter 接口的實現有以下三個:

提前告訴你,最左邊綠色的那個 AttributeRoute 其實只是一個包裝,在內部也是通過 MvcAttributeRouteHandler 或者 MvcRouteHandler 進行的處理。那么,現在關于路由的處理程序只剩下了兩個,他們分別是:

默認處理程序: MvcRouteHandler,用來處理約定的 Action。

注解處理程序: MvcAttributeRouteHandler ,用來處理注解(Attribute)路由。

細心的同學可能注意到了, MvcAttributeRouteHandlerMvcRouteHandler 多了一個 Actions : ActionDescriptor[]屬性。

我們再看一下這兩個處理程序的 RouteAsync 方法,這個方法是路由組件的入口方法,我們通過一個對比工具來看一下兩者之間的差距。

圖片看不清楚可以新標簽打開

可以看到,這兩個 RouteAsync 主要有兩處差距,第一處就是 SelectBestCandidate 這個函數第二個參數

ActionDescriptor SelectBestCandidate(RouteContext context, IReadOnlyList<ActionDescriptor> candidates)

MvcRouteHandler:

在這個流程中,顯示調用了 IActionSelect 接口中的 SelectCandidates() 用來找到所有符合條件的候選 Action,然后調用了 SelectBestCandidate 找出最佳的一個。

程序走到這里,這里會有兩個重點的地方,或者叫有疑問的地方?

1、 程序集中定義的 Action 是怎么找到的?

要想找到程序定義的所有 Action,那么首先需要找到 Controller,在上一篇文章中我們已經知道了有一個 MVC 程序用來管理 AssemblyPart 的東西叫 ApplicationPartManager ,它的里面存儲了所有 MVC 框架在啟動的時候加載的所有程序集,那么我們可以從這個程序集中找到需要的 Controller。下面這個流程圖顯示了查找Controller 的流程:

GetControllerTypes 返回的是一個 IEnumerable<TypeInfo> 的集合,有了 Controller 之后,MVC 框架使用了一個對象來包裝 Controller,因為在后續的流程中,除了需要 Controller 之外還需要其他的一些東西,比如 Filter, ApiExplorer 等。

ApplicationModel

ApplicationModel 就是MVC框架用來包裝 ControllerFilter , ApiExplorer 等的一個Model 對象,我們來看一下它的定義:


public class ApplicationModel : IPropertyModel, IFilterModel, IApiExplorerModel
{
    public ApplicationModel()
    {
        ApiExplorer = new ApiExplorerModel();
        Controllers = new List<ControllerModel>();
        Filters = new List<IFilterMetadata>();
        Properties = new Dictionary<object, object>();
    }

    public ApiExplorerModel ApiExplorer { get; set; }
    public IList<ControllerModel> Controllers { get; private set; }
    public IList<IFilterMetadata> Filters { get; private set; }
    public IDictionary<object, object> Properties { get; }
}

ApplicationModel 里面關于 Controller 的包裝是一個 IList<ControllerModel>,看一下 ControllerModel 的定義:


public class ControllerModel : ICommonModel, IFilterModel, IApiExplorerModel
{

    //......
    
    public IList<ActionModel> Actions { get; }
    public ApiExplorerModel ApiExplorer { get; set; }
    public ApplicationModel Application { get; set; }
    public IReadOnlyList<object> Attributes { get; }
    MemberInfo ICommonModel.MemberInfo => ControllerType;
    string ICommonModel.Name => ControllerName;
    public string ControllerName { get; set; }
    public TypeInfo ControllerType { get; }
    public IList<PropertyModel> ControllerProperties { get; }
    public IList<IFilterMetadata> Filters { get; }
    public IDictionary<string, string> RouteValues { get; }
    public IDictionary<object, object> Properties { get; }
    public IList<SelectorModel> Selectors { get; }
}

在 ASP.NET Core MVC 框架中,ApplicationModel 有下面幾個提供者,他們用于初始化整個 ApplicationModel 的各個部分,我們還是分別看一下吧。

AuthorizationApplicationModelProvider: 處理認證相關業務邏輯,在它的Executing方法中會將 AuthorizeFilter,AllowAnonymousFilter 等過濾器添加到 ApplicationModelProviderContext 里面的 ApplicationModel 里。

DefaultApplicationModelProvider:初始化 ControllerModel, 添加 Controller 相關的各種信息,添加用戶自定義 Filter,遍歷 ControllerTypes : 創建 ControllerModel --> 初始化Properties --> 初始化Parameters

CorsApplicationModelProvider:跨域資源相關邏輯,添加CorsAuthorizationFilterFactory,DisableCorsAuthorizationFilter,CorsAuthorizationFilterFactory,DisableCorsAuthorizationFilter等過濾器。

TempDataApplicationModelProvider: 添加 SaveTempDataPropertyFilterFactory 過濾器,存儲Controller中的TempData信息,注意 TempDataAttribute 修飾的屬性只能是基元類型或字符串。

構建ApplicationModel

MVC 框架通過 ControllerActionDescriptorProvider 中的 BuildModel() 這個方法進行 ApplicationModel 的構建:


internal protected ApplicationModel BuildModel()
{
    var controllerTypes = GetControllerTypes();
    var context = new ApplicationModelProviderContext(controllerTypes);

    for (var i = 0; i < _applicationModelProviders.Length; i++)
    {
        _applicationModelProviders[i].OnProvidersExecuting(context);
    }

    for (var i = _applicationModelProviders.Length - 1; i >= 0; i--)
    {
        _applicationModelProviders[i].OnProvidersExecuted(context);
    }

    return context.Result;
}

現在,我們已經有一個完整的 ApplicationModel 對象了。

有了 ApplicationModel 對象之后,會再進行一次約定的應用。比如以下Action重寫路由的情況或者配置多個路由的情況。

2、ActionDescriptorCollection是怎么創建的?

ControllerActionDescriptor構建

ControllerActionDescriptor的構建是基于ApplicationModel對象的,下面我就畫了一個流程圖用來展示構建 ControllerActionDescriptor 的整個過程,就不過多描述了。

截止到目前,我們會得到一個 IEnumerable<ControllerActionDescriptor> 集合對象。

在有了 ControllerActionDescriptor 之后,ActionDescriptorCollectionProvider 會提供一個屬性,

public ActionDescriptorCollection ActionDescriptors
{
    get
    {
        if (_collection == null)
        {
            UpdateCollection();
        }

        return _collection;
    }
}

在這個屬性中使用了 UpdateCollection 這個方法來更新 ActionDescriptorCollection


private void UpdateCollection()
{
    var context = new ActionDescriptorProviderContext();

    for (var i = 0; i < _actionDescriptorProviders.Length; i++)
    {
        _actionDescriptorProviders[i].OnProvidersExecuting(context);
    }

    for (var i = _actionDescriptorProviders.Length - 1; i >= 0; i--)
    {
        _actionDescriptorProviders[i].OnProvidersExecuted(context);
    }

    _collection = new ActionDescriptorCollection(
        new ReadOnlyCollection<ActionDescriptor>(context.Results),
        Interlocked.Increment(ref _version));
}

OK , 現在我們有了 ActionDescriptorCollection , 之后的流程就比較簡單了,但是會涉及到幾個算法。

接下來,輪到 ActionSelectorDecisionTreeProvider 上場了,它主要是把 ActionDescriptorCollection,組裝成為一個 IActionSelectionDecisionTree 對象以便于后續的查找匹配工作, IActionSelectionDecisionTree 的數據結構是一個多叉樹,組裝過程是使用了一個深度優先的遞歸算法。

我們回到起點,繼續看這張圖:

現在 SelectCandidates 你應該能夠看懂了:

public IReadOnlyList<ActionDescriptor> SelectCandidates(RouteContext context)
{
    //IActionSelectionDecisionTree 對象
    var tree = _decisionTreeProvider.DecisionTree;
    
    //使用的是一個多叉樹查找算法,關于算法可以看我這篇博文:
    //http://www.cnblogs.com/savorboard/p/6582399.html
    return tree.Select(context.RouteData.Values);
}

接下來就是 SelectBestCandidates 這個流程:

1、遍歷 Action 列表,評估 Action 的相關約束,返回匹配的 ActionDescriptor 列表。

2、從匹配的 ActionDescriptor 列表中返回最佳的 Action 列表,注意這里這個方法 SelectBestActions,它是一個虛方法,默認是沒有實現的會直接返回上一步的結果,也就是說用戶可以通過重寫這個方法來自定義一些Action匹配規則。

3、如果SelectBestActions 返回的是一個ActionDescriptor,則直接返回,當路由系統匹配到多個 Action 的時候,那么 MVC 需要從這些 Action 候選者中選中最佳的哪一個,當兩個動作通過路由匹配時,MVC必須消除歧義以選擇“最佳”候選者,否則拋出 AmbiguousActionException 異常

最終 SelectBestCandidates 會返回一個 ActionDescriptor ,即需要執行的 Action。

后續流程的執行,我又畫了一個圖來表示,希望能夠更加清晰一些:

終于講解結束了,心好累,如果你認為本篇文章對你有幫助的話,順手點個【推薦】吧。

MvcAttributeRouteHandler:

下面是MvcAttributeRouteHandlerRouteAsync

可以看到,在 MvcAttributeRouteHandler 中,少了 SelectCandidates() 這個流程,取而代之的是用 Actions 的屬性參數。 這個Actions 就比較簡單了,就是MVC框架啟動的時候配置的IRouter Action。

然后就是 SelectBestCandidates 這個流程了,參考上文的流程吧,都一樣。

總結

本文詳細描述了 MVC 在 Request 到達的時候是怎么樣通過自定義的路由處理程序來選擇一個Action 的,并且講解了其中的過程。

如果你對 .NET Core 感興趣可以關注我,我會定期在博客分享關于 .NET Core 的學習心得,如果你認為本篇文章對你有幫助的話,謝謝你的【推薦】。


本文地址:http://www.cnblogs.com/savorboard/p/aspnetcore-mvc-routing-action.html
作者博客:Savorboard
歡迎轉載,請在明顯位置給出出處及鏈接


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()