.NET Core開發日誌——Action

来源:https://www.cnblogs.com/kenwoo/archive/2018/08/19/9499725.html
-Advertisement-
Play Games

在敘述 "Controller" 一文中,有一處未做解釋,即CreateControllerFactory方法中ControllerActionDescriptor參數是如何產生的。這是因為其與Action的關聯性更大,所以放在本文中繼續描述。 回到MvcRouteHandler或者MvcAttri ...


在敘述Controller一文中,有一處未做解釋,即CreateControllerFactory方法中ControllerActionDescriptor參數是如何產生的。這是因為其與Action的關聯性更大,所以放在本文中繼續描述。

回到MvcRouteHandler或者MvcAttributeRouteHandler的方法中:

public Task RouteAsync(RouteContext 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;
    }

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

    ...
}

不難發現作為源頭的ActionContext中傳入了actionDescriptor,而這個參數的值是在ActionSelector中被篩選出來的。

public IReadOnlyList<ActionDescriptor> SelectCandidates(RouteContext context)
{
    ...

    var cache = Current;

    // The Cache works based on a string[] of the route values in a pre-calculated order. This code extracts
    // those values in the correct order.
    var keys = cache.RouteKeys;
    var values = new string[keys.Length];
    for (var i = 0; i < keys.Length; i++)
    {
        context.RouteData.Values.TryGetValue(keys[i], out object value);

        if (value != null)
        {
            values[i] = value as string ?? Convert.ToString(value);
        }
    }
    
    if (cache.OrdinalEntries.TryGetValue(values, out var matchingRouteValues) ||
        cache.OrdinalIgnoreCaseEntries.TryGetValue(values, out matchingRouteValues))
    {
        Debug.Assert(matchingRouteValues != null);
        return matchingRouteValues;
    }

    _logger.NoActionsMatched(context.RouteData.Values);
    return EmptyActions;
}

然後可供篩選的ActionDescriptors集合又是來自ActionDescriptorCollectionProvider類。

private Cache Current
{
    get
    {
        var actions = _actionDescriptorCollectionProvider.ActionDescriptors;
        var cache = Volatile.Read(ref _cache);

        if (cache != null && cache.Version == actions.Version)
        {
            return cache;
        }

        cache = new Cache(actions);
        Volatile.Write(ref _cache, cache);
        return cache;
    }
}

它的內部又再調用了ControllerActionDescriptorProvider類的OnProvidersExecuting方法。

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

        return _collection;
    }
}

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));
}

調用鏈繼續深入到DefaultApplicationModelProvider之中。

public void OnProvidersExecuting(ActionDescriptorProviderContext context)
{
    if (context == null)
    {
        throw new ArgumentNullException(nameof(context));
    }

    foreach (var descriptor in GetDescriptors())
    {
        context.Results.Add(descriptor);
    }
}

protected internal IEnumerable<ControllerActionDescriptor> GetDescriptors()
{
    var applicationModel = BuildModel();
    ApplicationModelConventions.ApplyConventions(applicationModel, _conventions);
    return ControllerActionDescriptorBuilder.Build(applicationModel);
}

protected internal 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;
}

private IEnumerable<TypeInfo> GetControllerTypes()
{
    var feature = new ControllerFeature();
    _partManager.PopulateFeature(feature);

    return feature.Controllers;
}

到了這裡終於可以看到Action的影子,雖然現在還只是ActionModel。

public virtual void OnProvidersExecuting(ApplicationModelProviderContext context)
{
    ...

    foreach (var controllerType in context.ControllerTypes)
    {
        var controllerModel = CreateControllerModel(controllerType);
        if (controllerModel == null)
        {
            continue;
        }

        context.Result.Controllers.Add(controllerModel);
        controllerModel.Application = context.Result;

        ...

        foreach (var methodInfo in controllerType.AsType().GetMethods())
        {
            var actionModel = CreateActionModel(controllerType, methodInfo);
            if (actionModel == null)
            {
                continue;
            }

            actionModel.Controller = controllerModel;
            controllerModel.Actions.Add(actionModel);

            foreach (var parameterInfo in actionModel.ActionMethod.GetParameters())
            {
                var parameterModel = CreateParameterModel(parameterInfo);
                if (parameterModel != null)
                {
                    parameterModel.Action = actionModel;
                    actionModel.Parameters.Add(parameterModel);
                }
            }
        }
    }
}

利用ControllerActionDescriptorBuilder類的Build方法,可以得到預期的ControllerActionDescriptor。

public static IList<ControllerActionDescriptor> Build(ApplicationModel application)
{
    var actions = new List<ControllerActionDescriptor>();

    var methodInfoMap = new MethodToActionMap();

    var routeTemplateErrors = new List<string>();
    var attributeRoutingConfigurationErrors = new Dictionary<MethodInfo, string>();

    foreach (var controller in application.Controllers)
    {
        // Only add properties which are explicitly marked to bind.
        // The attribute check is required for ModelBinder attribute.
        var controllerPropertyDescriptors = controller.ControllerProperties
            .Where(p => p.BindingInfo != null)
            .Select(CreateParameterDescriptor)
            .ToList();
        foreach (var action in controller.Actions)
        {
            // Controllers with multiple [Route] attributes (or user defined implementation of
            // IRouteTemplateProvider) will generate one action descriptor per IRouteTemplateProvider
            // instance.
            // Actions with multiple [Http*] attributes or other (IRouteTemplateProvider implementations
            // have already been identified as different actions during action discovery.
            var actionDescriptors = CreateActionDescriptors(application, controller, action);

            foreach (var actionDescriptor in actionDescriptors)
            {
                actionDescriptor.ControllerName = controller.ControllerName;
                actionDescriptor.ControllerTypeInfo = controller.ControllerType;

                AddApiExplorerInfo(actionDescriptor, application, controller, action);
                AddRouteValues(actionDescriptor, controller, action);
                AddProperties(actionDescriptor, action, controller, application);

                actionDescriptor.BoundProperties = controllerPropertyDescriptors;

                if (IsAttributeRoutedAction(actionDescriptor))
                {
                    // Replaces tokens like [controller]/[action] in the route template with the actual values
                    // for this action.
                    ReplaceAttributeRouteTokens(actionDescriptor, routeTemplateErrors);
                }
            }

            methodInfoMap.AddToMethodInfo(action, actionDescriptors);
            actions.AddRange(actionDescriptors);
        }
    }

    ...

    return actions;
}

ControllerActionDescriptor包含了足以構建Controller與Action的屬性。

public string ControllerName { get; set; }

public virtual string ActionName { get; set; }

public MethodInfo MethodInfo { get; set; }

public TypeInfo ControllerTypeInfo { get; set; }

public IList<ParameterDescriptor> Parameters { get; set; }

Controller的構建已經介紹過了,現在該談談關於Action的。

先找到創建ControllerActionInvokerCacheEntry對象的ControllerActionInvokerCache類的GetCachedResult方法。可以看到兩個關鍵參數objectMethodExecutor與actionMethodExecutor的創建方式。

public (ControllerActionInvokerCacheEntry cacheEntry, IFilterMetadata[] filters) GetCachedResult(ControllerContext controllerContext)
{
    var cache = CurrentCache;
    var actionDescriptor = controllerContext.ActionDescriptor;

    IFilterMetadata[] filters;
    if (!cache.Entries.TryGetValue(actionDescriptor, out var cacheEntry))
    {
        ...

        var objectMethodExecutor = ObjectMethodExecutor.Create(
            actionDescriptor.MethodInfo,
            actionDescriptor.ControllerTypeInfo,
            parameterDefaultValues);

        ...

        var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);

        cacheEntry = new ControllerActionInvokerCacheEntry(
            filterFactoryResult.CacheableFilters, 
            controllerFactory, 
            controllerReleaser,
            propertyBinderFactory,
            objectMethodExecutor,
            actionMethodExecutor);
        cacheEntry = cache.Entries.GetOrAdd(actionDescriptor, cacheEntry);
    }
    ...

    return (cacheEntry, filters);
}

再到ControllerActionInvoker類的Next方法中跟蹤到State.ActionInside環節:

case State.ActionInside:
    {
        var task = InvokeActionMethodAsync();
        if (task.Status != TaskStatus.RanToCompletion)
        {
            next = State.ActionEnd;
            return task;
        }

        goto case State.ActionEnd;
    }

終於可以找到創建Action的方法。

private async Task InvokeActionMethodAsync()
{
    var controllerContext = _controllerContext;
    var objectMethodExecutor = _cacheEntry.ObjectMethodExecutor;
    var controller = _instance;
    var arguments = _arguments;
    var actionMethodExecutor = _cacheEntry.ActionMethodExecutor;
    var orderedArguments = PrepareArguments(arguments, objectMethodExecutor);

    var diagnosticSource = _diagnosticSource;
    var logger = _logger;

    IActionResult result = null;
    try
    {
        diagnosticSource.BeforeActionMethod(
            controllerContext,
            arguments,
            controller);
        logger.ActionMethodExecuting(controllerContext, orderedArguments);
        var stopwatch = ValueStopwatch.StartNew();
        var actionResultValueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, orderedArguments);
        if (actionResultValueTask.IsCompletedSuccessfully)
        {
            result = actionResultValueTask.Result;
        }
        else
        {
            result = await actionResultValueTask;
        }

        _result = result;
        logger.ActionMethodExecuted(controllerContext, result, stopwatch.GetElapsedTime());
    }
    ...
}

核心的代碼是這一句actionMethodExecutor.Execute(objectMethodExecutor, controller, orderedArguments)

actionMethodExecutor與objectMethodExecutor即是之前生成ControllerActionInvokerCacheEntry對象時傳入的兩個參數,controller是在State.ActionBegin環節通過_instance = _cacheEntry.ControllerFactory(controllerContext);生成的。orderedArguments是Action方法所需的參數。

至於更詳細的創建過程,可以到ActionMethodExecutor類與ObjectMethodExecutor類中探尋,主要是涉及反射相關的知識,這裡就不做進一步解釋了。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • luogu原題 最近剛學了博弈論,拿來練練手qwq 其實和數值的大小並沒有關係 我們用$N/P$態來表示必勝/必敗狀態 先在草稿紙上探究硬幣在最左側(其實左右側是等價的)的一條長鏈的$N/P$態,設鏈長為$n$ 我們用$1$代替其他所有非$0$數 $n=2: 11$ $N$態 $n=3: 111$ ...
  • 近一個月一直在寫業務,空閑時間刷刷leetcode,刷題過程中遇到了一道比較有意思的題目,和大家分享。 題目描述: 給定兩個整數,被除數 dividend 和除數 divisor。將兩數相除,要求不使用乘法、除法和 mod 運算符。返回被除數 dividend 除以除數 divisor 得到的商。 ...
  • 廢話不說,直接開門見山! 需要在WebContent下的lib下導入兩個包 mybatis-3.2.5.jar ojdbc6.jar 1 package com.xdl.entity; 2 3 import java.io.Serializable; 4 5 public class Dept im ...
  • 使用mapper代理方式開發: 需要編寫mapper介面,UserMapper.java需要編寫映射文件,UserMapper.xml需要遵循一些開發規範,mybatis便可以自動生成mapper介面實現類代理對象 遵循的開發規範:1:UserMapper.xml中namespace命名空間 與 U ...
  • CLR線程池並不會在CLR初始化時立即建立線程,而是在應用程式要創建線程來運行任務時,線程池才初始化一個線程。線程池初始化時是沒有線程的,線程池裡的線程的初始化與其他線程一樣,但是在完成任務以後,該線程不會自行銷毀,而是以掛起的狀態返回到線程池。直到應用程式再次向線程池發出請求時,線程池裡掛起的線程 ...
  • 概述 Windows Community Toolkit 4.0 於 2018 月 8 月初發佈:Windows Community Toolkit 4.0 Release Note. 4.0 版本相較於 3.0,增加了 DataGrid 等控制項,Sample App 支持了 Fluent Desi ...
  • 之前研究過c#的async和await關鍵字,幕後幹了什麼,但是不知道為什麼找不到相關資料了。現在重新研究一遍,順便記錄下來,方便以後查閱。基礎知識async 關鍵字標註一個方法,該方法返回值是一個Task、或者Task、void、包含GetAwaiter方法的類型。該方法通常包含一個await表達... ...
  • protected override void OnStartup(StartupEventArgs e){base.OnStartup(e);RegisterEvents();} private void RegisterEvents(){//TaskScheduler.UnobservedTas ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...