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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Internal;
|
using Microsoft.AspNetCore.Internal;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
using Microsoft.AspNetCore.Routing.Internal;
|
||||||
using Microsoft.AspNetCore.Routing.Matching;
|
using Microsoft.AspNetCore.Routing.Matching;
|
||||||
using Microsoft.AspNetCore.Routing.Patterns;
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
@ -77,6 +80,21 @@ namespace RoutingSample.Web
|
||||||
0,
|
0,
|
||||||
EndpointMetadataCollection.Empty,
|
EndpointMetadataCollection.Empty,
|
||||||
"withoptionalconstraints"),
|
"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));
|
services.TryAddEnumerable(ServiceDescriptor.Singleton<EndpointDataSource>(endpointDataSource));
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
services.TryAddSingleton<MatchProcessorFactory, DefaultMatchProcessorFactory>();
|
services.TryAddSingleton<MatchProcessorFactory, DefaultMatchProcessorFactory>();
|
||||||
services.TryAddSingleton<MatcherFactory, DfaMatcherFactory>();
|
services.TryAddSingleton<MatcherFactory, DfaMatcherFactory>();
|
||||||
services.TryAddTransient<DfaMatcherBuilder>();
|
services.TryAddTransient<DfaMatcherBuilder>();
|
||||||
|
services.TryAddSingleton<DfaGraphWriter>();
|
||||||
|
|
||||||
// Link generation related services
|
// Link generation related services
|
||||||
services.TryAddSingleton<IEndpointFinder<RouteValuesAddress>, RouteValuesBasedEndpointFinder>();
|
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 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
|
// TODO: https://github.com/aspnet/Routing/issues/648
|
||||||
next.Matches.AddRange(edge.Endpoints.Cast<MatcherEndpoint>().ToArray());
|
next.Matches.AddRange(edge.Endpoints.Cast<MatcherEndpoint>().ToArray());
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
@ -367,6 +368,12 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
hash.Add(HttpMethod, StringComparer.Ordinal);
|
hash.Add(HttpMethod, StringComparer.Ordinal);
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used in GraphViz output.
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return IsCorsPreflightRequest ? $"CORS: {HttpMethod}" : $"HTTP: {HttpMethod}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue