Add support for GraphViz
Adds **internal** support for dumping a route table to GraphViz DOT notation. This allows us to dump the DFA graph for a route table and visualize it. Example: https://gist.github.com/rynowak/2b24e4a6a602ca6f9c4de3ec227d621b
This commit is contained in:
parent
dce72c9553
commit
12cb35894e
|
|
@ -2,10 +2,13 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Internal;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Internal;
|
||||
using Microsoft.AspNetCore.Routing.Matching;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
|
@ -77,6 +80,21 @@ namespace RoutingSample.Web
|
|||
0,
|
||||
EndpointMetadataCollection.Empty,
|
||||
"withoptionalconstraints"),
|
||||
new MatcherEndpoint((next) => (httpContext) =>
|
||||
{
|
||||
using (var writer = new StreamWriter(httpContext.Response.Body, Encoding.UTF8, 1024, leaveOpen: true))
|
||||
{
|
||||
var graphWriter = httpContext.RequestServices.GetRequiredService<DfaGraphWriter>();
|
||||
var dataSource = httpContext.RequestServices.GetRequiredService<CompositeEndpointDataSource>();
|
||||
graphWriter.Write(dataSource, writer);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
RoutePatternFactory.Parse("/graph"),
|
||||
0,
|
||||
new EndpointMetadataCollection(new HttpMethodMetadata(new[]{ "GET", })),
|
||||
"DFA Graph"),
|
||||
});
|
||||
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<EndpointDataSource>(endpointDataSource));
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
services.TryAddSingleton<MatchProcessorFactory, DefaultMatchProcessorFactory>();
|
||||
services.TryAddSingleton<MatcherFactory, DfaMatcherFactory>();
|
||||
services.TryAddTransient<DfaMatcherBuilder>();
|
||||
services.TryAddSingleton<DfaGraphWriter>();
|
||||
|
||||
// Link generation related services
|
||||
services.TryAddSingleton<IEndpointFinder<RouteValuesAddress>, RouteValuesBasedEndpointFinder>();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
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 https://www.graphviz.org/doc/info/lang.html
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// You can use 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;
|
||||
|
||||
public DfaGraphWriter(IServiceProvider services)
|
||||
{
|
||||
_services = services;
|
||||
}
|
||||
|
||||
public void Write(EndpointDataSource dataSource, TextWriter writer)
|
||||
{
|
||||
var builder = _services.GetRequiredService<DfaMatcherBuilder>();
|
||||
|
||||
var endpoints = dataSource.Endpoints;
|
||||
for (var i = 0; i < endpoints.Count; i++)
|
||||
{
|
||||
var endpoint = endpoints[i] as MatcherEndpoint;
|
||||
if (endpoint != null)
|
||||
{
|
||||
builder.AddEndpoint(endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
// Assign each node a sequential index.
|
||||
var visited = new Dictionary<DfaNode, int>();
|
||||
|
||||
var tree = builder.BuildDfaTree();
|
||||
|
||||
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.
|
||||
|
||||
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=\"/**\"]");
|
||||
}
|
||||
|
||||
foreach (var policy in node.PolicyEdges)
|
||||
{
|
||||
writer.WriteLine($"{label} -> {visited[policy.Value]} [label=\"{policy.Key}\"]");
|
||||
}
|
||||
|
||||
writer.WriteLine($"{label} [label=\"{node.Label}\"]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -534,7 +534,10 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
{
|
||||
var edge = edges[k];
|
||||
|
||||
var next = new DfaNode();
|
||||
var next = new DfaNode()
|
||||
{
|
||||
Label = parent.Label + " " + edge.State.ToString(),
|
||||
};
|
||||
|
||||
// TODO: https://github.com/aspnet/Routing/issues/648
|
||||
next.Matches.AddRange(edge.Endpoints.Cast<MatcherEndpoint>().ToArray());
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
|
@ -367,6 +368,12 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
hash.Add(HttpMethod, StringComparer.Ordinal);
|
||||
return hash;
|
||||
}
|
||||
|
||||
// Used in GraphViz output.
|
||||
public override string ToString()
|
||||
{
|
||||
return IsCorsPreflightRequest ? $"CORS: {HttpMethod}" : $"HTTP: {HttpMethod}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue