| File: Internal\DfaGraphWriter.cs | Web Access |
| Project: src\aspnetcore\src\Http\Routing\src\Microsoft.AspNetCore.Routing.csproj (Microsoft.AspNetCore.Routing) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.AspNetCore.Routing.Matching; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Routing.Internal; /// <summary> /// <para> /// A singleton service that can be used to write the route table as a state machine /// in GraphViz DOT language <see href="https://www.graphviz.org/doc/info/lang.html"/>. /// </para> /// <para> /// You can use <see href="http://www.webgraphviz.com/"/> to visualize the results. /// </para> /// <para> /// This type has no support contract, and may be removed or changed at any time in /// a future release. /// </para> /// </summary> public class DfaGraphWriter { private readonly IServiceProvider _services; /// <summary> /// Constructor for a <see cref="DfaGraphWriter"/> given <paramref name="services"/>. /// </summary> /// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param> public DfaGraphWriter(IServiceProvider services) { _services = services; } /// <summary> /// Displays a graph representation of <paramref name="dataSource"/> in DOT. /// </summary> /// <param name="dataSource">The <see cref="EndpointDataSource"/> to extract routes from.</param> /// <param name="writer">The <see cref="TextWriter"/> to which the content is written.</param> public void Write(EndpointDataSource dataSource, TextWriter writer) { var builder = _services.GetRequiredService<DfaMatcherBuilder>(); 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); } } // Assign each node a sequential index. var visited = new Dictionary<DfaNode, int>(); var tree = builder.BuildDfaTree(includeLabel: true); writer.WriteLine("digraph DFA {"); tree.Visit(WriteNode); writer.WriteLine("}"); void WriteNode(DfaNode node) { if (!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 (node.Literals != null) { foreach (var literal in node.Literals) { writer.WriteLine($"{label} -> {visited[literal.Value]} [label=\"/{literal.Key}\"]"); } } if (node.Parameters != null) { writer.WriteLine($"{label} -> {visited[node.Parameters]} [label=\"/*\"]"); } if (node.CatchAll != null && node.Parameters != node.CatchAll) { writer.WriteLine($"{label} -> {visited[node.CatchAll]} [label=\"/**\"]"); } if (node.PolicyEdges != null) { foreach (var policy in node.PolicyEdges) { writer.WriteLine($"{label} -> {visited[policy.Value]} [label=\"{policy.Key}\"]"); } } writer.WriteLine($"{label} [label=\"{node.Label}\"]"); } } }