在本文中,我为创建的自定义的DfaGraphWriter实现奠定了基础。DfaGraphWriter是公开的,因此您可以如上一篇文章《将终结点图添加到你的ASP.NET Core应用程序中》中所示在应用程序中使用它,但它使用的所有类均已标记为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 DfaMatcherBuildervar builder = _services.GetRequiredService<DfaMatcherBuilder>();// loop through the endpoints in the dataSource, and add them to the buildervar 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 graphvar tree = builder.BuildDfaTree(includeLabel: true);// Add the headerwriter.WriteLine("digraph DFA {");// Visit each node in the graph to create the outputtree.Visit(WriteNode);//Close the graphwriter.WriteLine("}");// Recursively walks the tree, writing it to the TextWritervoid 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 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,我们只需要调用两个方法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 DfaNodepublic object CatchAll { get; } // actually a DfaNodepublic 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 directlyvar key = (string)dictEntry.Key;// Use ImpromptuInterface to add a wrapperIDfaNode value = dictEntry.Value.ActLike<IDfaNode>();
}

现在,我们已经拥有了通过实现WriteNode来创建自定义DfaWriter实现所需的一切对象,但是这篇文章已经有点长了,所以我们将在下一篇文章中探讨如何实现这一点!

摘要

在本文中,我探讨了DfaWriter在ASP.NET Core 中的实现以及它使用的两个internal类:DfaMatcherBuilderDfaNode。这些类是内部类的事实使得创建我们自己的DfaWriter实现非常棘手。为了干净地实现它,我们将不得不重新实现这两种类型以及它们所依赖的所有类。

作为替代,我使用ImpromptuInterface库创建了一个包装器代理,该代理实现与被包装的对象拥有类似的方法。这使用反射来调用包装属性上的方法,但允许我们使用强类型接口。在下一篇文章中,我将展示如何使用这些包装器创建一个定制的DfaWriter来进行端点图的自定义。

相关阅读:

[译]使用DOT语言和GraphvizOnline来可视化你的ASP.NETCore3.0终结点01

将终结点图添加到你的ASP.NET Core应用程序中

往期精彩回顾

【推荐】.NET Core开发实战视频课程 ★★★

.NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划

【.NET Core微服务实战-统一身份认证】开篇及目录索引

Redis基本使用及百亿数据量中的使用技巧分享(附视频地址及观看指南)

.NET Core中的一个接口多种实现的依赖注入与动态选择看这篇就够了

10个小技巧助您写出高性能的ASP.NET Core代码

用abp vNext快速开发Quartz.NET定时任务管理界面

在ASP.NET Core中创建基于Quartz.NET托管服务轻松实现作业调度

现身说法:实际业务出发分析百亿数据量下的多表查询优化

关于C#异步编程你应该了解的几点建议

C#异步编程看这篇就够了

给我好看
您看此文用  · 秒,转发只需1秒呦~
好看你就点点我

使用ImpromptuInterface反射方便的创建自定义DfaGraphWriter相关推荐

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

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

  2. Java中如何创建自定义的注解学习笔记(MD版)

    概要 Java中如何创建自定义的注解学习笔记(MD版). 博客 博客地址:IT老兵驿站. 前言 记得这篇笔记还是在泉州的龙玲酒店记录的,是一个周六的晚上,坐飞机从上海到泉州,从笔记中能勾起一些旅游的回 ...

  3. Delta3D———通过游戏管理器组件和消息的扩展创建自定义行为 《转》

    游戏管理器组件给我们提供了在不修改游戏管理器的情况下灵活扩展我们的自定义行为的能力.游戏管理器组件是基于消息来工作的,定义自定义行为的基本 流程就是创建自定义类型的消息,在合适的时候发送消息,创建自定 ...

  4. 小D学blend-----如何创建自定义的Tooltip控件

    运行环境:blend 4.0或者blend 3.0 +silverlight 3.0(其实我相信步骤应该是差不多的) 语言:C# Tooltip类:它是表示一个长方形的小弹出窗口,该窗口在用户将指针悬 ...

  5. 为 ASP.NET Datagrid 创建自定义列

    Marcie Robillard DatagridGirl.com 2003 年 9 月 简介 不得不承认,为 Microsoft® ASP.NET 编写 Datagrid 代码包括大量的重复工作.尽 ...

  6. reddit_如何使用Python创建自定义Reddit通知系统

    reddit by Kelsey Wang 王凯西 如何使用Python创建自定义Reddit通知系统 (How to make a custom Reddit notification system ...

  7. 如何使用Bootstrap4和ES6创建自定义确认框

    by Prashant Yadav 通过Prashant Yadav 如何使用Bootstrap4和ES6创建自定义确认框 (How to create a custom confirm box wi ...

  8. php创建菜单_php实现微信公众号创建自定义菜单功能的实例代码

    目的 创建自定义菜单,实现菜单事件. 首先获取Access_Token 接口: 我用的是测试号,修改APPID和APPSECRET,然后浏览器访问上面这个Url即可生成Access_Token 然后配 ...

  9. 独家 | 使用TensorFlow 2创建自定义损失函数

    作者:Arjun Sarkar 翻译:陈之炎 校对:欧阳锦 本文约1900字,建议阅读8分钟 本文带你学习使用Python中的wrapper函数和OOP来编写自定义损失函数. 标签:TensorFlo ...

最新文章

  1. 为什么分库分表后不建议跨分片查询
  2. 【深度学习】Swin-Transformer和EfficientNet对比分析
  3. 页面浏览事件之 $AppViewScreen 全埋点
  4. java \t怎么从头开始_通过这些简单的步骤从头开始学习Java
  5. 运算符优先级与结合性
  6. linux安装gcc运行时库,Linux安装gcc-6.1.0
  7. 安卓深度探索(卷一)第六章
  8. Beats:如何在 Elastic Stack 中得到并使用 Root CA Certificate fingerprint
  9. oracle监听启动失败12560,Oracle监听器无法启动(TNS-12555,TNS-12560,TNS-00525)
  10. 批量自动下单(拼多多)
  11. OpenType字库文件
  12. 第一章 编程基础_ASCII 编码和GBK编码
  13. 计算机网络测试仪,网络测试仪如何使用
  14. 第一章 计算机网络和因特网
  15. Python自学笔记10:实操案例七(根据星座测试性格特点、模拟12306火车订票下单)
  16. sipXecs技术交流QQ群
  17. 安卓APP源码和设计报告——魔幻相机
  18. C#调用打印机,打印图片
  19. Microsoft Azure云计算第一步—试用帐户申请
  20. 【NLP基础理论】02 N-grams语言模型和Smoothing

热门文章

  1. 面向对象——一起来复习托付与事件!
  2. 【27前端】base标签带有href属性会让chrome里的svg元素url失效
  3. SQL Server索引进阶第十篇:索引的内部结构
  4. C#委托,事件理解入门 (译稿)
  5. php 怎么实现收藏功能,php收藏功能如何实现
  6. wifi名称可以有空格吗_收购公司后可以变更公司名称吗,变更公司名称和股权如何处理?...
  7. Kinect开发笔记之三Kinect开发环境配置详解
  8. XP调整禁用页面文件
  9. 什么是Google On.Here,以及如何设置?
  10. SpringBoot获取ApplicationContext