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

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

在本文中,我将展示如何通过创建一个自定义的DfaGraphWriter来为自己的应用程序创建一个端点图。

这篇文章使用了本系列前几篇文章中的技巧和类,因此我强烈建议在继续之前先阅读这些技巧和类。

相关阅读:

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

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

使用ImpromptuInterface反射方便的创建自定义DfaGraphWriter

为端点图添加配置

我们首先要看的是如何配置最终端点图的外观。我们将为两种类型的节点和四种类型的边缘添加配置。边是:

  • 文字边缘:路线部分,例如apivalues中的文字匹配api/values/{id}

  • 参数边缘:路线的参数化部分,例如{id}route中api/values/{id}

  • 捕获所有边:与“全部捕获”路由参数相对应的边,例如{**slug}

  • 策略边缘:与URL以外的其他约束相对应的边缘。例如,图中的基于HTTP谓词的边HTTP: GET

节点是:

  • 匹配节点:与端点匹配关联的节点,因此将生成响应。

  • 默认节点:与端点匹配关联的节点。

每个节点和边都可以具有任意数量的Graphviz属性来控制其显示。下面的GraphDisplayOptions显示了我在本文开始时用于生成图形的默认值:

public class GraphDisplayOptions
{/// <summary>/// Additional display options for literal edges/// </summary>public string LiteralEdge { get; set; } = string.Empty;/// <summary>/// Additional display options for parameter edges/// </summary>public string ParametersEdge { get; set; } = "arrowhead=diamond color=\"blue\"";/// <summary>/// Additional display options for catchall parameter edges/// </summary>public string CatchAllEdge { get; set; } = "arrowhead=odot color=\"green\"";/// <summary>/// Additional display options for policy edges/// </summary>public string PolicyEdge { get; set; } = "color=\"red\" style=dashed arrowhead=open";/// <summary>/// Additional display options for node which contains a match/// </summary>public string MatchingNode { get; set; } = "shape=box style=filled color=\"brown\" fontcolor=\"white\"";/// <summary>/// Additional display options for node without matches/// </summary>public string DefaultNode { get; set; } = string.Empty;
}

我们现在可以使用这个对象来控制显示,并使用上一篇文章中所示的ImpromptuInterface“代理”技术来创建我们的自定义图形编写器。

创建自定义的DfaGraphWriter

我们的自定义图形编辑器(巧妙地称为CustomDfaGraphWriter)在很大程度上基于包含在ASP.NET Core中的DfaGraphWriter。该类的主体与原始类相同,但有以下更改:

  • GraphDisplayOptions注入类中以自定义显示。

  • 使用ImpromptuInterface库来处理内部DfaMatcherBuilderDfaNode类,如上一篇文章中所示。

  • 自定义WriteNode函数以使用我们的自定义样式。

  • 添加一个Visit函数来处理IDfaNode接口,而不是在内部DfaNode类上使用Visit()方法。

CustomDfaGraphWriter的全部代码如下所示,重点是主Write()功能。我保持了与原始版本几乎相同的实现,只是更新了我们必须更新的部分。

public class CustomDfaGraphWriter
{// Inject the GraphDisplayOptionsprivate readonly IServiceProvider _services;private readonly GraphDisplayOptions _options;public CustomDfaGraphWriter(IServiceProvider services, GraphDisplayOptions options){_services = services;_options = options;}public void Write(EndpointDataSource dataSource, TextWriter writer){// Use ImpromptuInterface to create the required dependencies as shown in previous postType matcherBuilder = typeof(IEndpointSelectorPolicy).Assembly.GetType("Microsoft.AspNetCore.Routing.Matching.DfaMatcherBuilder");// Build the list of endpoints used to build the graphvar rawBuilder = _services.GetRequiredService(matcherBuilder);IDfaMatcherBuilder builder = rawBuilder.ActLike<IDfaMatcherBuilder>();// This is the same logic as the original graph writervar 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 raw tree from the registered routesvar rawTree = builder.BuildDfaTree(includeLabel: true);IDfaNode tree = rawTree.ActLike<IDfaNode>();// Store a list of nodes that have already been visitedvar visited = new Dictionary<IDfaNode, int>();// Build the graph by visiting each node, and calling WriteNode on eachwriter.WriteLine("digraph DFA {");Visit(tree, WriteNode);writer.WriteLine("}");void WriteNode(IDfaNode node){/* Write the node to the TextWriter *//* Details shown later in this post*/}}static void Visit(IDfaNode node, Action<IDfaNode> visitor){/* Recursively visit each node in the tree. *//* Details shown later in this post*/}
}

为了简洁起见,我在这里省略了Visit和 WriteNode函数,但是我们会尽快对其进行研究。我们将从Visit函数开始,因为该函数最接近原始函数。

更新Visit函数以与IDfaNode一起使用

正如我在上一篇文章中所讨论的,创建自定义DfaGraphWriter的最大问题之一是它对内部类的使用。为了解决这个问题,我使用ImpromptuInterface创建了包装原始对象的代理对象:

原始的Visit()方法是DfaNode类中的方法。它递归地访问端点树中的每个节点,为每个节点调用一个提供的Action<>函数。

由于DfaNodeinternal,我在CustomDfaGraphWriter中实现了一个静态的Visit来代替。

我们的定制实现大体上与原始实现相同,但是我们必须在“原始”DfaNodes和我们的IDfaNode代理之间进行一些有点困难的转换。更新后的方法如下所示。该方法接受两个参数,即被检查的节点,以及在每个参数上运行的Action<>

static void Visit(IDfaNode node, Action<IDfaNode> visitor)
{// Does the node of interest have any nodes connected by literal edges?if (node.Literals?.Values != null){// node.Literals is actually a Dictionary<string, DfaNode>foreach (var dictValue in node.Literals.Values){// Create a proxy for the child DfaNode node and visit itIDfaNode value = dictValue.ActLike<IDfaNode>();Visit(value, visitor);}}// Does the node have a node connected by a parameter edge?// The reference check breaks any cycles in the graphif (node.Parameters != null && !ReferenceEquals(node, node.Parameters)){// Create a proxy for the DfaNode node and visit itIDfaNode parameters = node.Parameters.ActLike<IDfaNode>();Visit(parameters, visitor);}// Does the node have a node connected by a catch-all edge?// The refernece check breaks any cycles in the graphif (node.CatchAll != null && !ReferenceEquals(node, node.CatchAll)){// Create a proxy for the DfaNode node and visit itIDfaNode catchAll = node.CatchAll.ActLike<IDfaNode>();Visit(catchAll, visitor);}// Does the node have a node connected by a policy edges?if (node.PolicyEdges?.Values != null){// node.PolicyEdges is actually a Dictionary<object, DfaNode>foreach (var dictValue in node.PolicyEdges.Values){IDfaNode value = dictValue.ActLike<IDfaNode>();Visit(value, visitor);}}// Write the node using the provided Action<>visitor(node);
}

Visit函数使用post-order遍历,因此在使用visitor函数编写节点之前,它首先“深入”地遍历节点的子节点。这与原始DfaNode.Visit()功能相同。

我们现在快到了。我们有一个类,它构建端点节点树,遍历树中的所有节点,并为每个节点运行一个函数。剩下的就是定义访问者函数WriteNode()

定义自定义WriteNode函数

我们终于到了最重要的部分,控制了端点图的显示方式。到目前为止,所有自定义和努力都是使我们能够自定义WriteNode功能。

WriteNode()是一个局部函数,它使用点图描述语言将一个节点连同任何连接的边一起写入TextWriter输出。

我们的自定义WriteNode()函数与原始函数几乎相同。有两个主要区别:

  • 原始的图形编写器使用DfaNodes,我们必须转换为使用IDfaNode代理。

  • 原始图形编写器对所有节点和边使用相同的样式。我们根据配置的GraphDisplayOptions定制节点和边的显示。

由于WriteNode是一个局部函数,它可以从封闭函数访问变量。这包括writer参数(用于将图形写入输出)和以前写入节点的已访问字典。

下面显示了我们的方法(已被大量注释)的自定义版本WriteNode()

void WriteNode(IDfaNode node)
{// add the node to the visited node dictionary if it isn't already// generate a zero-based integer label for the nodeif (!visited.TryGetValue(node, out var label)){label = visited.Count;visited.Add(node, label);}// We can safely index into visited because this is a post-order traversal,// all of the children of this node are already in the dictionary.// If this node is linked to any nodes by a literal edgeif (node.Literals != null){foreach (DictionaryEntry dictEntry in node.Literals){// Foreach linked node, get the label for the edge and the linked nodevar edgeLabel = (string)dictEntry.Key;IDfaNode value = dictEntry.Value.ActLike<IDfaNode>();int nodeLabel = visited[value];// Write an edge, including our custom styling for literal edgeswriter.WriteLine($"{label} -> {nodeLabel} [label=\"/{edgeLabel}\" {_options.LiteralEdge}]");}}// If this node is linked to a nodes by a parameter edgeif (node.Parameters != null){IDfaNode parameters = node.Parameters.ActLike<IDfaNode>();int nodeLabel = visited[catchAll];// Write an edge labelled as /* using our custom styling for parameter edgeswriter.WriteLine($"{label} -> {nodeLabel} [label=\"/**\" {_options.CatchAllEdge}]");}// If this node is linked to a catch-all edgeif (node.CatchAll != null && node.Parameters != node.CatchAll){IDfaNode catchAll = node.CatchAll.ActLike<IDfaNode>();int nodeLabel = visited[catchAll];// Write an edge labelled as /** using our custom styling for catch-all edgeswriter.WriteLine($"{label} -> {nodelLabel} [label=\"/**\" {_options.CatchAllEdge}]");}// If this node is linked to any Policy Edgesif (node.PolicyEdges != null){foreach (DictionaryEntry dictEntry in node.PolicyEdges){// Foreach linked node, get the label for the edge and the linked nodevar edgeLabel = (object)dictEntry.Key;IDfaNode value = dictEntry.Value.ActLike<IDfaNode>();int nodeLabel = visited[value];// Write an edge, including our custom styling for policy edgeswriter.WriteLine($"{label} -> {nodeLabel} [label=\"{key}\" {_options.PolicyEdge}]");}}// Does this node have any associated matches, indicating it generates a response?var matchCount = node?.Matches?.Count ?? 0;var extras = matchCount > 0 ? _options.MatchingNode // If we have matches, use the styling for response-generating nodes...: _options.DefaultNode; // ...otherwise use the default style// Write the node to the graph outputwriter.WriteLine($"{label} [label=\"{node.Label}\" {extras}]");
}

由于我们将节点从“叶”节点写回到树的根的方式,因此跟踪这些交互的流程可能会有些混乱。例如,如果我们看一下本文开头显示的基本应用程序的输出,您会看到“叶子”端点都被首先写入:healthz运行状况检查端点和终端匹配生成路径最长的端点:

digraph DFA {1 [label="/healthz/" shape=box style=filled color="brown" fontcolor="white"]2 [label="/api/Values/{...}/ HTTP: GET" shape=box style=filled color="brown" fontcolor="white"]3 [label="/api/Values/{...}/ HTTP: PUT" shape=box style=filled color="brown" fontcolor="white"]4 [label="/api/Values/{...}/ HTTP: DELETE" shape=box style=filled color="brown"  fontcolor="white"]5 [label="/api/Values/{...}/ HTTP: *" shape=box style=filled color="brown" fontcolor="white"]6 -> 2 [label="HTTP: GET" color="red" style=dashed arrowhead=open]6 -> 3 [label="HTTP: PUT" color="red" style=dashed arrowhead=open]6 -> 4 [label="HTTP: DELETE" color="red" style=dashed arrowhead=open]6 -> 5 [label="HTTP: *" color="red" style=dashed arrowhead=open]6 [label="/api/Values/{...}/"]7 [label="/api/Values/ HTTP: GET" shape=box style=filled color="brown" fontcolor="white"]8 [label="/api/Values/ HTTP: POST" shape=box style=filled color="brown" fontcolor="white"]9 [label="/api/Values/ HTTP: *" shape=box style=filled color="brown" fontcolor="white"]10 -> 6 [label="/*" arrowhead=diamond color="blue"]10 -> 7 [label="HTTP: GET" color="red" style=dashed arrowhead=open]10 -> 8 [label="HTTP: POST" color="red" style=dashed arrowhead=open]10 -> 9 [label="HTTP: *" color="red" style=dashed arrowhead=open]10 [label="/api/Values/"]11 -> 10 [label="/Values"]11 [label="/api/"]12 -> 1 [label="/healthz"]12 -> 11 [label="/api"]12 [label="/"]
}

即使首先将叶节点写入图形输出,但Graphviz可视化工具通常会以叶节点在底部,边缘朝下的方式绘制图形。您可以在https://dreampuf.github.io/GraphvizOnline/在线显示图形:

如果要更改图形的呈现方式,可以自定义GraphDisplayOptions。如果使用我在上一篇文章中描述的“测试”方法,则可以在生成图形时直接传递这些选项。如果使用的是“中间件”方法,则可以改为使用IOptions<>系统进行GraphDisplayOptions注册,并使用配置系统控制显示。

摘要

在这篇文章中,我展示了如何创建自定义的DfaGraphWriter来控制如何生成应用程序的端点图。为了与internal内部类进行互操作,我们使用了ImpromptuInterface,如在上篇文章所示,创建代理,我们可以互动。然后,我们必须编写一个自定义Visit()函数来使用IDfaNode代理。最后,我们创建了一个自定义WriteNode函数,该函数使用在GraphDisplayOptions对象中定义的自定义设置来显示不同类型的节点和边。

往期精彩回顾

【推荐】.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秒呦~
好看你就点点我

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/308639.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[Redis6]常用数据类型_List列表

List列表 简介 单键多值 Redis 列表是简单的字符串列表&#xff0c;按照插入顺序排序。你可以添加一个元素到列表的头部&#xff08;左边&#xff09;或者尾部&#xff08;右边&#xff09;。 它的底层实际是个双向链表&#xff0c;对两端的操作性能很高&#xff0c;通过索…

[Redis6]常用数据类型_Set集合

Set集合 简介 Redis set对外提供的功能与list类似是一个列表的功能&#xff0c;特殊之处在于set是可以自动排重的&#xff0c;当你需要存储一个列表数据&#xff0c;又不希望出现重复数据时&#xff0c;set是一个很好的选择&#xff0c;并且set提供了判断某个成员是否在一个s…

来领.NET Core学习资料,7天整理了30多个G(适合各阶段.Net开发者)

干货分享2020/07/27大家好&#xff0c;我是CSDN的小黑&#xff0c;2020年的唯一跳槽季&#xff0c;金九银十马上到来&#xff0c;今天这波干货必须学习起来&#xff01;前后整理了半个月&#xff0c;从零基础到就业&#xff0c;进阶高级开发&#xff0c;走向架构&#xff0c;三…

[Redis6]常用数据结构_Hash哈希

Hash哈希 简介 Redis hash 是一个键值对集合。 Redis hash是一个string类型的field和value的映射表&#xff0c;hash特别适合用于存储对象。 类似Java里面的Map<String,Object> 用户ID为查找的key&#xff0c;存储的value用户对象包含姓名&#xff0c;年龄&#xff…

ASP.NET Core中的分布式缓存

ASP.NET Core中的分布式缓存在上一篇文章中[1]&#xff0c;我解释了如何使用内存缓存在ASP.NET Core应用程序中管理缓存。如果您的应用程序托管在单个服务器上&#xff0c;则可以使用这种类型的缓存。那.NET Core框架可以使用哪些工具在云中的分布式方案中进行缓存呢IDistribut…

marquee滚动起始位置_巧用喵影关键帧制作滚动水印,让视频小偷无可盗

视频创作者最奔溃瞬间&#xff0c;不是翻遍了所有网站还找不到合适的素材&#xff0c;也不是作品快完成了却发现电脑死机&#xff0c;而是自己呕心沥血做出来的视频被盗了&#xff0c;结果盗版视频的播放量还更高&#xff01;&#xff01;​理想的解决方法就是给视频打上水印。…

.NET Core + K8S + Loki 玩转日志聚合

Grafana loki1. Intro最近在了解日志聚合系统&#xff0c;正好前几天看到一篇文章《用了日志系统新贵Loki&#xff0c;ELK突然不香了&#xff01;》&#xff0c;所以就决定动手体验一下。本文就带大家快速了解下Loki&#xff0c;并简单介绍.NET Core如何集成Loki。2. Whats Lok…

[Redis6]常用数据类型_Zset有序集合

Zset有序集合 简介 Redis有序集合zset与普通集合set非常相似&#xff0c;是一个没有重复元素的字符串集合。 不同之处是有序集合的每个成员都关联了一个评分&#xff08;score&#xff09;,这个评分&#xff08;score&#xff09;被用来按照从最低分到最高分的方式排序集合中…

css 旋转45_CSS教程——第14期

警告&#xff01;本期内容建议CSS基础良好的人看&#xff0c;本期为实战篇&#xff0c;难度有点大哈喽大家好我是wawjf灰常抱歉我们咕咕了快两周&#xff0c;主要原因是我们的几位作者(加上我其实就两个)去夏令营学习了某重要东西&#xff0c;所以没时间更新&#xff0c;在这里…

使用Azure Blob Storage实现一个静态文件服务器

什么是Azure Blob StorageAzure Blob Storage是微软Azure的对象存储服务。国内的云一般叫OSS&#xff0c;是一种用来存储非结构化数据的服务&#xff0c;比如音频&#xff0c;视频&#xff0c;图片&#xff0c;文本等等。用户可以通过http在全球任意地方访问这些资源。这些资源…

台湾RD技术谈|嘉宾郭家齊董大偉黃保翕

欢迎来到Azure Show!Azure Show欢迎来到Azure Show第四期&#xff0c;本期特辑是首次推出RD技术谈栏目&#xff0c;我们邀请了宝岛台湾的三位重量级嘉宾&#xff0c;保哥、Edward和董大伟三位老师&#xff0c;他们既是微软的最有价值专家&#xff0c;也是微软技术社区区域总监&…

ajax option请求后无post请求_ThingJS:一种浏览器、服务器和技术的新组合方法——Ajax...

Web应用是一种极大方便用户的操作界面&#xff0c;数据维护技术Ajax也从中脱颖而出&#xff0c;ThingJS采用了Ajax的数据维护能够形成轻量化的开发流程。为什么Ajax通过XHR 实现Ajax 通信的一个主要限制&#xff0c;来源于跨域安全策略。默认情况下&#xff0c;XHR 对象只能访问…

[Nginx]负载均衡和动静分离

负载均衡 客户端发送多个请求到服务器&#xff0c;服务器处理请求&#xff0c;有一些可能要与数据库进行交互&#xff0c;服务器处理完毕后&#xff0c;再将结果返回给客户端。 这种架构模式对于早期的系统相对单一&#xff0c;并发请求相对较少的情况下是比较适合的&#xff…

[Nginx]nginx常用的命令

nginx常用的命令 使用nginx操作命令前提条件:必须进入nginx的目录 cd /www/local/webserver/nginx/sbin查看nginx的版本号 ./nginx -v启动nginx ./nginx关闭nginx ./nginx -s stop重新加载nginx ./nginx -s reload

pc 图片预览放大 端vue_安利一款简单好用的Vue图片预览插件

在项目中因为要经常用到图片预览效果&#xff0c;自己写的话麻烦死啦(懒)vue-photo-preview一个基于 photoswipe 的 vue 图片预览插件&#xff0c;支持移动端和PC端&#xff0c;支持各种手势操作&#xff0c;放大缩小&#xff0c;体验流畅。gitHub: https://github.com/8263277…

[Nginx]nginx的配置文件

nginx配置文件 nginx配置文件位置 nginx配置文件由三部分组成 第一部分 全局块 从配置文件开始到 events 块之间的内容&#xff0c;主要会设置一些影响 nginx 服务器整体运行的配置指令&#xff0c;主要包括配置运行 Nginx 服务器的用户&#xff08;组&#xff09;、允许生成…

Abp vNext 二进制大对象系统(BLOB)

一、简介ABP vNext 在 v 2.9.x 版本当中添加了 BLOB 系统&#xff0c;主要用于存储大型二进制文件。ABP 抽象了一套通用的 BLOB 体系&#xff0c;开发人员在存储或读取二进制文件时&#xff0c;可以忽略具体实现&#xff0c;直接使用IBlobContainer 或 IBlobContainer<T>…

[Nginx]location 指令说明

location 指令说明 该指令用于匹配 URL。 语法如下&#xff1a; 1、 &#xff1a;用于不含正则表达式的 uri 前&#xff0c;要求请求字符串与 uri 严格匹配&#xff0c;如果匹配 成功&#xff0c;就停止继续向下搜索并立即处理该请求。 2、~&#xff1a;用于表示 uri 包含正则…

基于GitBook框架搭建技术文档平台

源宝导读&#xff1a;为了向用户更好的传递ERP开放平台的价值与技术知识&#xff0c;我们基于GitBook框架搭建了一个文档中心站点&#xff0c;本文将介绍此站点的设计与实现过程。一、项目架构图因为文档会涉及到很多的产品线&#xff0c;所以目前主要是通过拉取各个产品线的文…

[Nginx]nginx 配置实例-负载均衡

nginx 配置实例-负载均衡 1、实现效果 &#xff08;1&#xff09;浏览器地址栏输入地址 http://192.168.111.134/edu/a.html&#xff0c;负载均衡效果&#xff0c;平均分担到 8080和 8081 端口中 2、准备工作 &#xff08;1&#xff09;准备两台 tomcat 服务器&#xff0c;…