使用ImpromptuInterface反射庫方便的建立自定義DfaGraphWriter
阿新 • • 發佈:2020-07-21
在本文中,我為建立的自定義的`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