使用ImpromptuInterface反射庫方便的創建自定義DfaGraphWriter

来源:https://www.cnblogs.com/yilezhu/archive/2020/07/21/13336066.html
-Advertisement-
Play Games

在本文中,我為創建的自定義的DfaGraphWriter實現奠定了基礎。DfaGraphWriter是公開的,因此您可以如上一篇文章中所示在應用程式中使用它,但它使用的所有類均已標記為internal。這使得創建自己的版本成為問題。要解決此問題,我使用了一個開源的反射庫ImpromptuInterf ...


在本文中,我為創建的自定義的DfaGraphWriter實現奠定了基礎。DfaGraphWriter是公開的,因此您可以如上一篇文章中所示在應用程式中使用它,但它使用的所有類均已標記為internal。這使得創建自己的版本成為問題。要解決此問題,我使用了一個開源的反射庫ImpromptuInterface,使創建自定義的DfaGraphWriter實現更加容易。

作者:依樂祝
原文地址:https://andrewlock.net/creating-a-custom-dfagraphwriter-using-impromptuinterface-and reflection/
譯文地址:https://www.cnblogs.com/yilezhu/p/13336066.html

我們將從查看現有的DfaGraphWriter開始,以瞭解其使用的internal類以及導致我們的問題。然後,我們來看一下使用一些自定義介面和ImpromptuInterface庫來允許我們調用這些類。在下一篇文章中,我們將研究如何使用自定義界面創建的自定義版本DfaGraphWriter

探索現有的 DfaGraphWriter

DfaGraphWriter類是存在於ASP.NET Core中的一個“pubternal”文件夾中的。它已註冊為單例,並使用註入的IServiceProvider來解析DfaMatcherBuilder

 public class DfaGraphWriter
{
    private readonly IServiceProvider _services;
    public DfaGraphWriter(IServiceProvider services)
    {
        _services = services;
    }

    public void Write(EndpointDataSource dataSource, TextWriter writer)
    {
        // retrieve the required DfaMatcherBuilder
        var builder = _services.GetRequiredService<DfaMatcherBuilder>();

        // loop through the endpoints in the dataSource, and add them to the builder
        var endpoints = dataSource.Endpoints;
        for (var i = 0; i < endpoints.Count; i++)
        {
            if (endpoints[i] is RouteEndpoint endpoint && (endpoint.Metadata.GetMetadata<ISuppressMatchingMetadata>()?.SuppressMatching ?? false) == false)
            {
                builder.AddEndpoint(endpoint);
            }
        }

        // Build the DfaTree. 
        // This is what we use to create the endpoint graph
        var tree = builder.BuildDfaTree(includeLabel: true);

        // Add the header
        writer.WriteLine("digraph DFA {");

        // Visit each node in the graph to create the output
        tree.Visit(WriteNode);

        //Close the graph
        writer.WriteLine("}");

        // Recursively walks the tree, writing it to the TextWriter
        void WriteNode(DfaNode node)
        {
            // Removed for brevity - we'll explore it in the next post
        }
    }
}

上面的代碼顯示了圖形編寫者Write方法的所有操作,終結如下:

  • 獲取一個 DfaMatcherBuilder
  • 寫入所有的端點EndpointDataSourceDfaMatcherBuilder
  • 調用DfaMatcherBuilderBuildDfaTree。這將創建一個DfaNode的 圖。
  • 訪問DfaNode樹中的每一個,並將其寫入TextWriter輸出。我們將在下一篇文章中探討這種方法。

創建我們自己的自定義編寫器的目的是通過控制如何將不同的節點寫入輸出來定製最後一步,因此我們可以創建更多的描述性的圖形,如我先前所示:

對端點圖使用不同的樣式

我們的問題是兩個重點類,DfaMatcherBuilderDfaNode,是internal所以我們不能輕易實例化它們,或者使用它們的寫入方法。這給出了兩個選擇:

  • 重新實現這些internal類,包括它們依賴的其他任何internal類。
  • 使用反射在現有類上創建和調用方法。

這些都不是很好的選擇,但是鑒於端點圖不是性能關鍵的東西,我決定使用反射將是最簡單的。為了使事情變得更加簡單,我使用了開源庫ImpromptuInterface

ImpromptuInterface使反射更容易

ImpromptuInterface是一個庫它使調用動態對象或調用存儲在對象引用中的底層對象上的方法變得更加容易。它本質上增加了簡單的duck/structural類型,允許您為對象使用stronlgy類型化介面。它使用Dynamic Language RuntimeReflection.Emit來實現。

例如,讓我們獲取我們要使用的現有DfaMatcherBuilder類。即使我們不能直接引用它,我們仍然可以從DI容器中獲取此類的實例,如下所示:

// get the DfaMatcherBuilder type - internal, so needs reflection :(
Type matcherBuilder = typeof(IEndpointSelectorPolicy).Assembly
    .GetType("Microsoft.AspNetCore.Routing.Matching.DfaMatcherBuilder");

object rawBuilder = _services.GetRequiredService(matcherBuilder);

rawBuilder是一個object引用,但它包含了一個DfaMatcherBuilder的實例。我們不能直接在調用它的方法,但是我們可以通過直接構建MethodInfo和直接調用invoke來使用反射來調用它們。。

ImpromptuInterface通過提供一個可以直接調用方法的靜態介面,使該過程更加容易。例如,對於DfaMatcherBuilder,我們只需要調用兩個方法AddEndpointBuildDfaTree。原始類如下所示:

internal class DfaMatcherBuilder : MatcherBuilder
{
    public override void AddEndpoint(RouteEndpoint endpoint) { /* body */ }
    public DfaNode BuildDfaTree(bool includeLabel = false)
}

我們可以創建一個暴露這些方法的介面:

public interface IDfaMatcherBuilder
{
    void AddEndpoint(RouteEndpoint endpoint);
    object BuildDfaTree(bool includeLabel = false);
}

然後,我們可以使用ImpromptuInterface ActLike<>方法創建實現了IDfaMatcherBuilder的代理對象。此代理包裝rawbuilder對象,因此當您在介面上調用方法時,它將在底層調用DfaMatcherBuilder中的等效的方法:

使用ImpromptuInterface添加包裝代理

在代碼中,如下所示:

// An instance of DfaMatcherBuilder in an object reference
object rawBuilder = _services.GetRequiredService(matcherBuilder);

// wrap the instance in the ImpromptuInterface interface
IDfaMatcherBuilder builder = rawBuilder.ActLike<IDfaMatcherBuilder>();

// we can now call methods on the builder directly, e.g. 
object rawTree =  builder.BuildDfaTree();

原始DfaMatcherBuilder.BuildDfaTree()方法和介面版本之間有一個重要區別:原始方法返回一個DfaNode,但這是另一個internal類,因此我們無法在介面中引用它。

相反,我們為DfaNode創建另一個ImpromptuInterface,暴露我們將需要的屬性(在接下來的文章中你就會明白為什麼我們需要他們):

public interface IDfaNode
{
    public string Label { get; set; }
    public List<Endpoint> Matches { get; }
    public IDictionary Literals { get; } // actually a Dictionary<string, DfaNode>
    public object Parameters { get; } // actually a DfaNode
    public object CatchAll { get; } // actually a DfaNode
    public IDictionary PolicyEdges { get; } // actually a Dictionary<object, DfaNode>
}

在下一篇文章中,我們將在WriteNode的方法中使用這些屬性,但是有一些複雜性。在原始DfaNode類中,ParametersCatchAll屬性返回DfaNode對象。在我們IDfaNode版本的屬性中,我們必須返回object。我們無法引用DfaNode(因為是internal)並且我們不能返回IDfaNode,因為DfaNode 它沒有實現IDfaNode,因此您不能將object引用隱式轉換為IDfaNode。你必須使用ImpromptuInterface顯式地添加一個實現了介面的代理,。

例如:

// Wrap the instance in the ImpromptuInterface interface
IDfaMatcherBuilder builder = rawBuilder.ActLike<IDfaMatcherBuilder>();

// We can now call methods on the builder directly, e.g. 
object rawTree =  builder.BuildDfaTree();
// Use ImpromptuInterface to add an IDfaNode wrapper
IDfaNode tree = rawTree.ActLike<IDfaNode>();

// We can now call methods and properties on the node...
object rawParameters = tree.Parameters;
// ...but they need to be wrapped using ImpromptuInterface too
IDfaNode parameters = rawParameters.ActLike<IDfaNode>();

返回Dictionary類型的屬性還有另一個問題:LiteralsPolicyEdges。實際返回的類型分別為Dictionary<string, DfaNode>Dictionary<object, DfaNode>,但是我們需要使用一個包含該DfaNode類型的類型。不幸的是,這意味著我們不得不退回到.NET 1.1 IDictionary介面!

您不能將一個Dictionary<string, DfaNode>強制轉換為IDictionary<string, object>,因為這樣做將是不安全的協方差形式

IDictionary是一個非泛型介面,因此keyvalue僅作為object公開。對於string鍵,您可以直接進行轉換,對於,DfaNode我們可以使用ImpromptuInterface為我們創建代理包裝器:

// Enumerate the key-value pairs as DictinoaryEntrys
foreach (DictionaryEntry dictEntry in node.Literals)
{
    // Cast the key value to a string directly
    var key = (string)dictEntry.Key;
    // Use ImpromptuInterface to add a wrapper
    IDfaNode value = dictEntry.Value.ActLike<IDfaNode>();
}

現在,我們已經擁有了通過實現WriteNode來創建自定義DfaWriter實現所需的一切對象,但是這篇文章已經有點長了,所以我們將在下一篇文章中探討如何實現這一點!

摘要

在本文中,我探討了DfaWriter在ASP.NET Core 中的實現以及它使用的兩個internal類:DfaMatcherBuilderDfaNode。這些類是內部類的事實使得創建我們自己的DfaWriter實現非常棘手。為了乾凈地實現它,我們將不得不重新實現這兩種類型以及它們所依賴的所有類。

作為替代,我使用ImpromptuInterface庫創建了一個包裝器代理,該代理實現與被包裝的對象擁有類似的方法。這使用反射來調用包裝屬性上的方法,但允許我們使用強類型介面。在下一篇文章中,我將展示如何使用這些包裝器創建一個定製的DfaWriter來進行端點圖的自定義。


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

-Advertisement-
Play Games
更多相關文章
  • 公司項目已經開發好幾年了,用的WPF開發的,期間遇到好多問題,都是些小細節。很久沒有寫博客了,以後有時間還是需要寫寫博客啊!作為分享也好、記錄也好,利人利己嘛。 今天主要說一下顯示線條的問題,因為我們做的是設計軟體,會用到對齊線啥的,關鍵是頁面有放大縮小。(可參考ps或AI這些專業設計軟體的參考線) ...
  • #1 linq介紹 ##1.1 linq產生背景 一個應用服務後臺程式,肯定會需要格式各樣的數據檢索跟操作,而這些數據在過去的這些年裡一般都會包含在關係型資料庫或者xml文件中。 .Net3.5版本發行之前,傳統的數據源訪問方式就是直接對資料庫或者xml文件進行檢索操作。在.Net3.5 Visua ...
  • 最近一個同事遇到進度條載入不出來問題,即使偶爾載入出來了卻不顯示進度, 看到這個問題想到的肯定是把UI線程給占住了, 由於使用了幾個框架,簡單查看框架後,在框架中改為線程調用 問題解決了, 但是在思考一個問題,框架中的代碼我是能夠看到也可以修改,如果是不能更改的框架怎麼辦? 研究了一下,在需要用的地 ...
  • 效果圖預覽 新建UserControl <Border Background="#F3F6F9" Height="50" Width="400" CornerRadius="10" HorizontalAlignment="Stretch"> <Grid Height="Auto"> <Grid.C ...
  • 分散式系統中,有一些需要使用全局唯一ID的場景,這種時候為了防止ID衝突可以使用36位的UUID,但是UUID有一些缺點,首先他相對比較長,另外UUID一般是無序的。有些時候我們希望能使用一種簡單一些的ID,並且希望ID能夠按照時間有序生成。而twitter的snowflake解決了這種需求,最初T... ...
  • <Button Name="button" Content="Hello" Height="100" Width="200" Click="button_Click_2"> <Button.ContentTemplate> <DataTemplate> <Viewbox> <TextBlock>My ...
  • 最近用FFmpeg處理視頻, 提示“”當代碼嘗試讀取或寫入無法訪問的記憶體“”,然後程式退出。已經設置全局異常也沒有捕獲到。 C#調用非托管方法程式容易奔潰,原因是非退托管代碼報的異常未能被捕獲到。記錄一下兩種解決方法: 一、使用配置文件相容以前代碼 為了與舊代碼相容,在app.config添加leg ...
  • 上次課程我們完成了菜單的配置和開發里程碑的劃定。 按照計劃,我們先來開發數據倉庫管理中的數據源管理(對應菜單為:數據倉庫管理 / 數據源),首批支持的數據源是SQL SERVER資料庫。 一、數據源管理功能任務分解 我們將這部分需求分解成以下幾個任務: 1、新建數據源 主要功能是配置一個連接字元串, ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...