1. 程式人生 > >使用ImpromptuInterface反射庫方便的建立自定義DfaGraphWriter

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

在本文中,我為建立的自定義的`DfaGraphWriter`實現奠定了基礎。`DfaGraphWriter`是公開的,因此您可以如[上一篇文章中](https://www.cnblogs.com/yilezhu/p/13335749.html)所示在應用程式中使用它,但它*使用的*所有類均已標記為`internal`。這使得建立自己的版本成為問題。要解決此問題,我使用了[一個開源的反射庫*ImpromptuInterface*](https://github.com/ekonbenefits/impromptu-interface),使建立自定義的`DfaGraphWriter`實現更加容易。 > 作者:依樂祝 > 原文地址:https://andrewlock.net/creating-a-custom-dfagraphwriter-using-impromptuinterface-and%20reflection/ > 譯文地址:https://www.cnblogs.com/yilezhu/p/13336066.html 我們將從檢視現有的`DfaGraphWriter`開始,以瞭解其使用的`internal`類以及導致我們的問題。然後,我們來看一下使用一些自定義介面和`ImpromptuInterface`庫來允許我們呼叫這些類。在下一篇文章中,我們將研究如何使用自定義介面建立的自定義版本`DfaGraphWriter`。 ## 探索現有的 `DfaGraphWriter` [該`DfaGraphWriter`類是存在於ASP.NET Core中的一個“pubternal”資料夾](https://github.com/dotnet/aspnetcore/blob/083f81d7604352822820f6313c3d4eb219c044a8/src/Http/Routing/src/Internal/DfaGraphWriter.cs)中的。它已註冊為單例,並使用注入的`IServiceProvider`來解析`DfaMatcherBuilder`: ```csharp 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(); // 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()?.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` - 寫入所有的端點`EndpointDataSource`到`DfaMatcherBuilder`。 - 呼叫`DfaMatcherBuilder`的`BuildDfaTree`。這將建立一個`DfaNode`的 圖。 - 訪問`DfaNode`樹中的每一個,並將其寫入`TextWriter`輸出。我們將在下一篇文章中探討這種方法。 建立我們自己的自定義編寫器的目的是通過控制如何將不同的節點寫入輸出來定製最後一步,因此我們可以建立更多的描述性的圖形,如我先前所示: ![對端點圖使用不同的樣式](https://img2020.cnblogs.com/blog/1377250/202007/1377250-20200718155838530-166406482.png) 我們的問題是兩個重點類,`DfaMatcherBuilder`和`DfaNode`,是`internal`所以我們不能輕易例項化它們,或者使用它們的寫入方法。這給出了兩個選擇: - 重新實現這些`internal`類,包括*它們*依賴的其他任何`internal`類。 - 使用反射在現有類上建立和呼叫方法。 這些都不是很好的選擇,但是鑑於端點圖不是效能關鍵的東西,我決定使用反射將是最簡單的。為了使事情變得更加簡單,我使用了開源庫[*ImpromptuInterface*](https://github.com/ekonbenefits/impromptu-interface)。 ## ImpromptuInterface使反射更容易 *ImpromptuInterface*是一個庫它使呼叫動態物件或呼叫儲存在物件引用中的底層物件上的方法變得更加容易。它本質上增加了簡單的duck/structural型別,允許您為物件使用stronlgy型別化介面。它使用[Dynamic Language Runtime](https://docs.microsoft.com/en-us/dotnet/framework/reflection-and-codedom/dynamic-language-runtime-overview)和[`Reflection.Emit`](https://docs.microsoft.com/en-us/dotnet/framework/reflection-and-codedom/emitting-dynamic-methods-and-assemblies)來實現。 例如,讓我們獲取我們要使用的現有`DfaMatcherBuilder`類。即使我們不能直接引用它,我們仍然可以從DI容器中獲取此類的例項,如下所示: ```csharp // 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`,我們只需要呼叫兩個方法`AddEndpoint`和`BuildDfaTree`。原始類如下所示: ```csharp internal class DfaMatcherBuilder : MatcherBuilder { public override void AddEndpoint(RouteEndpoint endpoint) { /* body */ } public DfaNode BuildDfaTree(bool includeLabel = false) } ``` 我們可以建立一個暴露這些方法的介面: ```csharp public interface IDfaMatcherBuilder { void AddEndpoint(RouteEndpoint endpoint); object BuildDfaTree(bool includeLabel = false); } ``` 然後,我們可以使用*ImpromptuInterface* `ActLike<>`方法建立實現了`IDfaMatcherBuilder`的代理物件。此代理包裝`rawbuilder`物件,因此當您在介面上呼叫方法時,它將在底層呼叫`DfaMatcherBuilder`中的等效的方法: ![使用ImpromptuInterface新增包裝代理](https://img2020.cnblogs.com/blog/1377250/202007/1377250-20200718155838019-1453433961.png) 在程式碼中,如下所示: ```csharp // An instance of DfaMatcherBuilder in an object reference object rawBuilder = _services.GetRequiredService(matcherBuilder); // wrap the instance in the ImpromptuInterface interface IDfaMatcherBuilder builder = rawBuilder.ActLike(); // we can now call methods on the builder directly, e.g. object rawTree = builder.BuildDfaTree(); ``` 原始`DfaMatcherBuilder.BuildDfaTree()`方法和介面版本之間有一個重要區別:原始方法返回一個`DfaNode`,但這是另一個`internal`類,因此我們無法在介面中引用它。 相反,我們為[`DfaNode`類](https://github.com/dotnet/aspnetcore/blob/fae3dd12aeba7c9995f69bfaa1c9b74d82307ef1/src/Http/Routing/src/Matching/DfaNode.cs)建立另一個`ImpromptuInterface`,暴露我們將需要的屬性(在接下來的文章中你就會明白為什麼我們需要他們): ```csharp public interface IDfaNode { public string Label { get; set; } public List Matches { get; } public IDictionary Literals { get; } // actually a Dictionary public object Parameters { get; } // actually a DfaNode public object CatchAll { get; } // actually a DfaNode public IDictionary PolicyEdges { get; } // actually a Dictionary } ``` 在下一篇文章中,我們將在`WriteNode`的方法中使用這些屬性,但是有一些複雜性。在原始`DfaNode`類中,`Parameters`和`CatchAll`屬性返回`DfaNode`物件。在我們`IDfaNode`版本的屬性中,我們必須返回`object`。我們無法引用`DfaNode`(因為是`internal`)並且我們不能返回`IDfaNode`,因為`DfaNode` *它沒有*實現`IDfaNode`,因此您不能將`object`引用隱式轉換為`IDfaNode`。你必須使用*ImpromptuInterface*來*顯式地*新增一個實現了介面的代理,。 例如: ```csharp // Wrap the instance in the ImpromptuInterface interface IDfaMatcherBuilder builder = rawBuilder.ActLike(); // 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(); // 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(); ``` 返回`Dictionary`型別的屬性還有另一個問題:`Literals`和`PolicyEdges`。實際返回的型別分別為`Dictionary`和`Dictionary`,但是我們需要使用一個*不*包含該`DfaNode`型別的型別。不幸的是,這意味著我們不得不退回到.NET 1.1 `IDictionary`介面! > 您不能將一個`Dictionary`強制轉換為`IDictionary`,因為這樣做[將是不安全的協方差形式](https://stackoverflow.com/questions/2149589/idictionarytkey-tvalue-in-net-4-not-covariant)。 `IDictionary`是一個非泛型介面,因此`key`和`value`僅作為`object`公開。對於`string`鍵,您可以直接進行轉換,對於,`DfaNode`我們可以使用*ImpromptuInterface*為我們建立代理包裝器: ```csharp // 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(); } ``` 現在,我們已經擁有了通過實現`WriteNode`來建立自定義`DfaWriter`實現所需的一切物件,但是這篇文章已經有點長了,所以我們將在下一篇文章中探討如何實現這一點! ## 摘要 在本文中,我探討了`DfaWriter`在ASP.NET Core 中的實現以及它使用的兩個`internal`類:`DfaMatcherBuilder`和`DfaNode`。這些類是內部類的事實使得建立我們自己的`DfaWriter`實現非常棘手。為了乾淨地實現它,我們將不得不重新實現這兩種型別以及*它們所*依賴的所有類。 作為替代,我使用*ImpromptuInterface*庫建立了一個包裝器代理,該代理實現與被包裝的物件擁有類似的方法。這使用反射來呼叫包裝屬性上的方法,但允許我們使用強型別介面。在下一篇文章中,我將展示如何使用這些包裝器建立一個定製的`DfaWriter`來進行端點圖的自