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

在本文中,我将展现如何使用DfaGraphWriter服务在ASP.NET Core 3.0应用程序中可视化你的终结点路由。上面文章我向您演示了如何生成一个有向图(如我上篇文章中所示),可使用GraphVizOnline将其可视化。最后,我描述了应用程序生命周期中能够检索图形数据的点。javascript

做者:依乐祝html

原文地址:http://www.javashuo.com/article/p-omdvvxoe-nb.htmljava

译文地址:https://andrewlock.net/adding-an-endpoint-graph-to-your-aspnetcore-application/git

在本文中,我仅展现如何建立图形的“默认”样式。在个人下一批那文章中,我再建立一个自定义的writer来生成自定义的图如上篇文章所示github

使用DfaGraphWriter可视化您的终结点

ASP.NET Core附带了一个方便的类DfaGraphWriter可用于可视化ASP.NET Core 3.x应用程序中的终结点路由:web

public class DfaGraphWriter
{
    public void Write(EndpointDataSource dataSource, TextWriter writer);
}

此类只有一个方法WriteEndpointDataSource包含描述您的应用程序的Endpoint集合,TextWriter用于编写DOT语言图(如您在前一篇文章中所见)。api

如今,咱们将建立一个中间件,该中间件使用DfaGraphWriter将该图编写为HTTP响应。您可使用DI 将DfaGraphWriterEndpointDataSource注入到构造函数中:bash

public class GraphEndpointMiddleware
{
    // inject required services using DI
    private readonly DfaGraphWriter _graphWriter;
    private readonly EndpointDataSource _endpointData;

    public GraphEndpointMiddleware(
        RequestDelegate next, 
        DfaGraphWriter graphWriter, 
        EndpointDataSource endpointData)
    {
        _graphWriter = graphWriter;
        _endpointData = endpointData;
    }

    public async Task Invoke(HttpContext context)
    {
        // set the response
        context.Response.StatusCode = 200;
        context.Response.ContentType = "text/plain";

        // Write the response into memory
        await using (var sw = new StringWriter())
        {
            // Write the graph
            _graphWriter.Write(_endpointData, sw);
            var graph = sw.ToString();

            // Write the graph to the response
            await context.Response.WriteAsync(graph);
        }
    }
}

这个中间件很是简单-咱们使用依赖注入将必要的服务注入到中间件中。将图形写入响应有点复杂:您必须在内存中将响应写到一个 StringWriter,再将其转换为 string而后将其写到图形。服务器

这一切都是必要的,由于DfaGraphWriter写入TextWriter使用同步 Stream API调用,如Write,而不是WriteAsync。若是有异步方法,理想状况下,咱们将可以执行如下操做:网络

// Create a stream writer that wraps the body
await using (var sw = new StreamWriter(context.Response.Body))
{
    // write asynchronously to the stream
    await _graphWriter.WriteAsync(_endpointData, sw);
}

若是DfaGraphWriter使用了异步API,则能够如上所述直接写入Response.Body,而避免使用in-memory string。不幸的是,它是同步的,出于性能缘由您不该该使用同步调用直接写入Response.Body。若是您尝试使用上面的模式,则可能会获得以下所示内容的InvalidOperationException异常,具体取决于所写图形的大小:

System.InvalidOperationException: Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.

若是图形很小,则可能不会出现此异常,可是若是您尝试映射中等规模的应用程序(例如带有Identity的默认Razor Pages应用程序),则能够看到此异常。

让咱们回到正轨上-咱们如今有了一个图形生成中间件,因此让咱们把它添加到管道中。这里有两个选择:

  • 使用终结点路由将其添加为终结点。
  • 从中间件管道中将其添加为简单的“分支”。

一般建议使用前一种方法,将终结点添加到ASP.NET Core 3.0应用程序,所以从这里开始。

将图形可视化器添加为终结点

为了简化终结点注册代码,我将建立一个简单的扩展方法以将GraphEndpointMiddleware做为终结点添加:

public static class GraphEndpointMiddlewareExtensions
{
    public static IEndpointConventionBuilder MapGraphVisualisation(
        this IEndpointRouteBuilder endpoints, string pattern)
    {
        var pipeline = endpoints
            .CreateApplicationBuilder()
            .UseMiddleware<GraphEndpointMiddleware>()
            .Build();

        return endpoints.Map(pattern, pipeline).WithDisplayName("Endpoint Graph");
    }
}

而后,咱们能够在Startup.Configure()中的UseEndpoints()方法中调用MapGraphVisualisation("/graph")将图形终结点添加到咱们的ASP.NET Core应用程序中:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseRouting();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapHealthChecks("/healthz");
        endpoints.MapControllers();
        // Add the graph endpoint
        endpoints.MapGraphVisualisation("/graph");
    });
}

这就是咱们要作的。该DfaGraphWriter已经在DI中可用,所以不须要额外的配置。导航至http://localhost:5000/graph将以纯文本形式生成咱们的终结点图:

digraph DFA {
    0 [label="/graph/"]
    1 [label="/healthz/"]
    2 [label="/api/Values/{...}/ HTTP: GET"]
    3 [label="/api/Values/{...}/ HTTP: PUT"]
    4 [label="/api/Values/{...}/ HTTP: DELETE"]
    5 [label="/api/Values/{...}/ HTTP: *"]
    6 -> 2 [label="HTTP: GET"]
    6 -> 3 [label="HTTP: PUT"]
    6 -> 4 [label="HTTP: DELETE"]
    6 -> 5 [label="HTTP: *"]
    6 [label="/api/Values/{...}/"]
    7 [label="/api/Values/ HTTP: GET"]
    8 [label="/api/Values/ HTTP: POST"]
    9 [label="/api/Values/ HTTP: *"]
    10 -> 6 [label="/*"]
    10 -> 7 [label="HTTP: GET"]
    10 -> 8 [label="HTTP: POST"]
    10 -> 9 [label="HTTP: *"]
    10 [label="/api/Values/"]
    11 -> 10 [label="/Values"]
    11 [label="/api/"]
    12 -> 0 [label="/graph"]
    12 -> 1 [label="/healthz"]
    12 -> 11 [label="/api"]
    12 [label="/"]
}

咱们可使用GraphVizOnline进行可视化显示以下:

一个ValuesController终结点路由应用程序

在终结点路由系统中将图形公开为终结点具备以下优势和缺点:

  • 您能够轻松地向终结点添加受权。您可能不但愿任何人都能查看此数据!
  • 图形终结点显示为系统中的终结点。这显然是正确的,但可能会很烦人。

若是最后一点对您来讲很重要,那么您能够使用传统的方法来建立终结点,即便用分支中间件。

将图形可视化工具添加为中间件分支

在您进行终结点路由以前,将分支添加到中间件管道是建立“终结点”的最简单方法之一。它在ASP.NET Core 3.0中仍然可用,它比终结点路由系统要更为,但不能轻松添加受权或高级路由。

要建立中间件分支,请使用Map()命令。例如,您可使用如下命令添加分支:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // add the graph endpoint as a branch of the pipeline
    app.Map("/graph", branch => 
        branch.UseMiddleware<GraphEndpointMiddleware>());

    app.UseRouting();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapHealthChecks("/healthz");
        endpoints.MapControllers();
    });
}

使用此方法的优缺点在本质上与终结点路由版本相反:图形中没有/graph终结点,您没法轻松地将受权应用于此终结点!

没有图形终结点的ValuesController终结点路由应用程序

对我来讲,像这样公开应用程序的图形是没有意义的。在下一节中,我将展现如何经过小型集成测试来生成图形。

从集成测试生成终结点图

ASP.NET Core对于运行内存集成测试有很好的设计,它能够在不须要进行网络调用的状况下运行完整的中间件管道和API控制器/Razor页面。

除了能够用来确认应用程序总体正确运行的传统“端到端”集成测试以外,我有时还喜欢编写“健全性检查”测试,以确认应用程序配置正确。您可使用,在Microsoft.AspNetCore.Mvc.Testing中暴露的底层DI容器中的WebApplicationFactory<>设施实现。这样,您就能够在应用程序的DI上下文中运行代码,而无需经过单元测试。

如今,让咱们来试下吧

  • 使用VS或dotnet new xunit来运行一个新的xUnit项目(我选择的测试框架)
  • 经过运行dotnet add package Microsoft.AspNetCore.Mvc.Testing安装Microsoft.AspNetCore.Mvc.Testing
  • 将测试项目的<Project>元素更新为<Project Sdk="Microsoft.NET.Sdk.Web">
  • 从测试项目中引用您的ASP.NET Core项目

如今,咱们能够建立一个简单的测试来生成终结点图,并将其写入测试输出。在下面的示例中,我将默认值WebApplicationFactory<>做为类基础设施;若是您须要自定义工厂,请参阅文档以获取详细信息。

除了WebApplicationFactory<>,我还注入了ITestOutputHelper。您须要使用此类来记录xUnit的测试输出。直接写Console不会起做用。

public class GenerateGraphTest
    : IClassFixture<WebApplicationFactory<ApiRoutes.Startup>>
{

    // Inject the factory and the output helper
    private readonly WebApplicationFactory<ApiRoutes.Startup> _factory;
    private readonly ITestOutputHelper _output;

    public GenerateGraphTest(
        WebApplicationFactory<Startup> factory, ITestOutputHelper output)
    {
        _factory = factory;
        _output = output;
    }

    [Fact]
    public void GenerateGraph()
    {
        // fetch the required services from the root container of the app
        var graphWriter = _factory.Services.GetRequiredService<DfaGraphWriter>();
        var endpointData = _factory.Services.GetRequiredService<EndpointDataSource>();

        // build the graph as before
        using (var sw = new StringWriter())
        {
            graphWriter.Write(endpointData, sw);
            var graph = sw.ToString();

            // write the graph to the test output
            _output.WriteLine(graph);
        }
    }
}

测试的大部份内容与中间件相同,可是咱们没有编写响应,而是编写了xUnit的ITestOutputHelper以将记录测试的结果输出。在Visual Studio中,您能够经过如下方式查看此输出:打开“测试资源管理器”,导航到GenerateGraph测试,而后单击“为此结果打开其余输出”,这将以选项卡的形式打开结果:

在Visual Studio中查看测试中的终结点数据

我发现像这样的简单测试一般足以知足个人目的。在我看来有以下这些优势:

  • 它不会将此数据公开为终结点
  • 对您的应用没有影响
  • 容易产生

不过,也许您想从应用程序中生成此图,可是您不想使用到目前为止显示的任何一种中间件方法将其包括在内。若是是这样,请务必当心在哪里进行。

您没法在IHostedService中生成图形

通常而言,您能够在应用程序中任何使用依赖项注入或有权访问实例的任何位置经过IServiceProvider访问DfaGraphWriterEndpointDataSource服务。这意味着在请求的上下文中(例如从MVC控制器或Razor Page生成)图很容易,而且与您到目前为止所看到的方法相同。

若是您要尝试在应用程序生命周期的早期生成图形,则必须当心。尤为是IHostedService

在ASP.NET Core 3.0中,Web基础结构是在通用主机的基础上重建的,这意味着您的服务器(Kestrel)做为一个IHostedService在你的应用程序中运行的。在大多数状况下,这不会产生太大影响,可是与ASP.NET Core 2.x相比,它改变了应用程序的生成顺序

在ASP.NET Core 2.x中,将发生如下状况:

  • 中间件管道已创建。
  • 服务器(Kestrel)开始侦听请求。
  • IHostedService实现启动。

而是在ASP.NET Core 3.x上,以下所示:

  • IHostedService实现启动。

GenericWebHostService

启动:

  • 中间件管道已创建
  • 服务器(Kestrel)开始侦听请求。

须要注意的重要一点是,直到您的IHostedServices的执行后中间件管道才会创建。因为UseEndpoints()还没有被调用,EndpointDataSource将不包含任何数据!

若是您尝试从一个IHostedService中的DfaGraphWriter生成图表,该EndpointDataSource是空的。

若是尝试使用其余标准机制来注入早期行为,状况也是如此,如IStartupFilter- Startup.Configure()执行以前 调用 ,所以EndpointDataSource将为空。

一样,您不能只是在Program.Main调用IHostBuilder.Build()来构建一个Host,而后使用IHost.Services:来访问服务,直到您调用IHost.Run,而且服务器已启动,不然您的终结点列表将为空!

这些限制可能不是问题,具体取决于您要实现的目标。对我来讲,单元测试方法能够解决个人大多数问题。

不管使用哪一种方法,都只能生成本文中显示的“默认”终结点图。这隐藏了不少真正有用的信息,例如哪些节点生成了终结点。在下一篇文章中,我将展现如何建立自定义图形编写器,以便您能够生成本身的图形。

总结

在这篇文章中,我展现了如何使用DfaGraphWriterEndpointDataSource建立应用程序中全部终结点的图形。我展现了如何建立中间件终结点来公开此数据,以及如何将这种中间件与分支中间件策略一块儿用做终结点路由。

我还展现了如何使用简单的集成测试来生成图形数据而无需运行您的应用程序。这避免了公开(可能敏感)的终结点图,同时仍然容许轻松访问数据。

最后,我讨论了什么时候能够在应用程序的生命周期中生成图形。该EndpointDataSource未填充,直到后Server(Kestrel)已经开始,因此你主要限于在请求上下文访问数据。IHostedServiceIStartupFilter执行得太早以致于没法访问数据,IHostBuilder.Build()只是构建DI容器,而没有构建中间件管道。

相关文章
相关标签/搜索