在本文中,我为创建的自定义的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中的等效的方法:

在代码中,如下所示:

// 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来进行端点图的自定义。

使用ImpromptuInterface反射库方便的创建自定义DfaGraphWriter的更多相关文章

  1. 在ASP.NET Core中创建自定义端点可视化图

    在上篇文章中,我为构建自定义端点可视化图奠定了基础,正如我在第一篇文章中展示的那样.该图显示了端点路由的不同部分:文字值,参数,动词约束和产生结果的端点: 在本文中,我将展示如何通过创建一个自定义的D ...

  2. ASP.NET MVC随想录——创建自定义的Middleware中间件

    经过前2篇文章的介绍,相信大家已经对OWIN和Katana有了基本的了解,那么这篇文章我将继续OWIN和Katana之旅——创建自定义的Middleware中间件. 何为Middleware中间件 M ...

  3. 带你走近AngularJS - 创建自定义指令

    带你走近AngularJS系列: 带你走近AngularJS - 基本功能介绍 带你走近AngularJS - 体验指令实例 带你走近AngularJS - 创建自定义指令 ------------- ...

  4. [转]maven创建自定义的archetype

    创建自己的archetype一般有两种方式,比较简单的就是create from project 1.首先使用eclipse创建一个新的maven project,然后把配置好的一些公用的东西放到相应 ...

  5. ArcGIS Engine环境下创建自定义的ArcToolbox Geoprocessing工具

    在上一篇日志中介绍了自己通过几何的方法合并断开的线要素的ArcGIS插件式的应用程序.但是后来考虑到插件式的程序的配置和使用比较繁琐,也没有比较好的错误处理机制,于是我就把之前的程序封装成一个类似于A ...

  6. Dockerfile创建自定义Docker镜像以及CMD与ENTRYPOINT指令的比较

    1.概述 创建Docker镜像的方式有三种 docker commit命令:由容器生成镜像: Dockerfile文件+docker build命令: 从本地文件系统导入:OpenVZ的模板. 关于这 ...

  7. .NET微信公众号开发-2.0创建自定义菜单

    一.前言 开发之前,我们需要阅读官方的接口说明文档,不得不吐槽一下,微信的这个官方文档真的很烂,但是,为了开发我们需要的功能,我们也不得不去看这些文档. 接口文档地址:http://mp.weixin ...

  8. HTML5 UI框架Kendo UI Web教程:创建自定义组件(三)

    Kendo UI Web包 含数百个创建HTML5 web app的必备元素,包括UI组件.数据源.验证.一个MVVM框架.主题.模板等.在前面的2篇文章<HTML5 Web app开发工具Ke ...

  9. HTML5 UI框架Kendo UI Web中如何创建自定义组件(二)

    在前面的文章<HTML5 UI框架Kendo UI Web自定义组件(一)>中,对在Kendo UI Web中如何创建自定义组件作出了一些基础讲解,下面将继续前面的内容. 使用一个数据源 ...

随机推荐

  1. 尚学堂 208.Annotation注解和内置注解

    208.Annotation注解和内置注解 override:这个注释的作用是标识某一个方法是否覆盖了它的父类的方法deprecated:表示果某个类成员的提示中出现了个词,就表示这个并不建议使用这个 ...

  2. Mariadb之事务隔离级别

    上一篇我们聊到了mariadb的锁,以及怎么手动加锁和解锁等等,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/13196905.html:今天我们来聊一聊mar ...

  3. 黑鸟码表BB10S骑行记录导入行者

    前言 开始骑车用行者app记录, 后来觉得每次都要开app很麻烦, 于是在骑友的推荐下入手了黑鸟BB10S, 使用了一段时间感觉还不错, 不过也遇到之前大家说的问题, 黑鸟不支持直接导出fit文件, ...

  4. 【数位dp+状压】XHXJ 's LIS

    题目 define xhxj (Xin Hang senior sister(学姐)) If you do not know xhxj, then carefully reading the enti ...

  5. 告别传统机房:3D 机房数据可视化实现智能化与VR技术的新碰撞

    前言 随着各行业对计算机依赖性的日益提高,计算机信息系统的发展使得作为其网络设备.主机服务器.数据存储设备.网络安全设备等核心设备存放地的计算机机房日益显现出它的重要地位,而机房的环境和动力设备如供配 ...

  6. python基础知识-1

    1.python是静态的还是动态的?是强类型还弱类型? python是强类型的动态脚本语言: 强类型:不允许不同类型相加 动态:不使用显示类型声明,且确定一个变量的类型是在第一次给它赋值的时候 脚本语 ...

  7. 【Oracle】如何模拟resmgr:cpu quantum

    看完该篇文章你可以了解如下问题:resmgr:cpu quantum等待事件的知识,如何模拟该等待事件,如何避免该事件. 数据库版本: SYS@zkm> select banner from v ...

  8. 主存到Cache直接映射、全相联映射和组相联映射

    转自:https://blog.csdn.net/dongyanxia1000/article/details/53392315 ---- Cache的容量很小,它保存的内容只是主存内容的一个子集,且 ...

  9. Python 简明教程 --- 21,Python 继承与多态

    微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 程序不是年轻的专利,但是,它属于年轻. 目录 我们已经知道封装,继承和多态 是面向对象的三大特征,面 ...

  10. 蓝桥杯javaB组入坑

    蓝桥杯Java B组 准备工作 练习入口 | 准备资料 | 查阅说明 编辑环境 我们建议您使用大赛指定的编辑环境来编写你的代码,以保证评测时和我们的编译环境一致,同时和比赛时使用的环境也一致. 推荐的 ...