在本文中,我為創建的自定義的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
- 寫入所有的端點
EndpointDataSource
到DfaMatcherBuilder
。 - 調用
DfaMatcherBuilder
的BuildDfaTree
。這將創建一個DfaNode
的 圖。 - 訪問
DfaNode
樹中的每一個,並將其寫入TextWriter
輸出。我們將在下一篇文章中探討這種方法。
創建我們自己的自定義編寫器的目的是通過控制如何將不同的節點寫入輸出來定製最後一步,因此我們可以創建更多的描述性的圖形,如我先前所示:
我們的問題是兩個重點類,DfaMatcherBuilder
和DfaNode
,是internal
所以我們不能輕易實例化它們,或者使用它們的寫入方法。這給出了兩個選擇:
- 重新實現這些
internal
類,包括它們依賴的其他任何internal
類。 - 使用反射在現有類上創建和調用方法。
這些都不是很好的選擇,但是鑒於端點圖不是性能關鍵的東西,我決定使用反射將是最簡單的。為了使事情變得更加簡單,我使用了開源庫ImpromptuInterface。
ImpromptuInterface使反射更容易
ImpromptuInterface是一個庫它使調用動態對象或調用存儲在對象引用中的底層對象上的方法變得更加容易。它本質上增加了簡單的duck/structural類型,允許您為對象使用stronlgy類型化介面。它使用Dynamic Language Runtime和Reflection.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
,我們只需要調用兩個方法AddEndpoint
和BuildDfaTree
。原始類如下所示:
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
中的等效的方法:
在代碼中,如下所示:
// 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
類中,Parameters
和CatchAll
屬性返回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
類型的屬性還有另一個問題:Literals
和PolicyEdges
。實際返回的類型分別為Dictionary<string, DfaNode>
和Dictionary<object, DfaNode>
,但是我們需要使用一個不包含該DfaNode
類型的類型。不幸的是,這意味著我們不得不退回到.NET 1.1 IDictionary
介面!
您不能將一個
Dictionary<string, DfaNode>
強制轉換為IDictionary<string, object>
,因為這樣做將是不安全的協方差形式。
IDictionary
是一個非泛型介面,因此key
和value
僅作為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
類:DfaMatcherBuilder
和DfaNode
。這些類是內部類的事實使得創建我們自己的DfaWriter
實現非常棘手。為了乾凈地實現它,我們將不得不重新實現這兩種類型以及它們所依賴的所有類。
作為替代,我使用ImpromptuInterface庫創建了一個包裝器代理,該代理實現與被包裝的對象擁有類似的方法。這使用反射來調用包裝屬性上的方法,但允許我們使用強類型介面。在下一篇文章中,我將展示如何使用這些包裝器創建一個定製的DfaWriter
來進行端點圖的自定義。