在本文中,我为创建的自定义的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); }