如何编写提供 UI 服务的 aspnetcore 中间件

kim*_*gro 2 middleware asp.net-core

在 aspnetcore 3.1 中制作 UI 中间件的推荐方法是什么?

我正在尝试重现诸如squashbuckle之类的中间件的功能,消费者可以导入NuGet包,然后将其注册为:

app.UseMyCustomMiddleUI(c =>
{
    c.RoutePrefix = "custom/ui";
});
Run Code Online (Sandbox Code Playgroud)

然后导航到http://localhost:5000/custom/ui将提供捆绑在我的 NuGet 包中的资源。

具体来说,如何打包 css、js 甚至 razor 页面等资源,以便在注册中间件时将它们暴露在某个路径上?

建议使用中间件的人static files。在 NuGet 包的上下文中,它是如何工作的,在该包中,我不提供来自文件系统的文件,而是提供嵌入在程序集中的文件(我猜测)

Jus*_*ard 5

基本上你需要添加一个StaticFileMiddleware带有路由前缀的。在您的情况下,该前缀将是/custom/ui. 然后,您可以使用中间件从包中提供静态文件。

您可以查看SwaggerUI 源代码以了解他们是如何做到的。

我根据您的情况稍微调整了他们的代码。它看起来像这样。

public class CustomUiMiddleware
{
    private const string RoutePrefix = "custom/ui";
    private readonly StaticFileMiddleware _staticFileMiddleware;

    public CustomUiMiddleware(
        RequestDelegate next,
        IWebHostEnvironment hostingEnv,
        ILoggerFactory loggerFactory)
    {
        _staticFileMiddleware = CreateStaticFileMiddleware(next, hostingEnv, loggerFactory);
    }

    public async Task Invoke(HttpContext httpContext)
    {
        var httpMethod = httpContext.Request.Method;
        var path = httpContext.Request.Path.Value;

        // If the RoutePrefix is requested (with or without trailing slash), redirect to index URL
        if (httpMethod == "GET" && Regex.IsMatch(path, $"^/?{Regex.Escape(RoutePrefix)}/?$", RegexOptions.IgnoreCase))
        {
            var indexUrl = httpContext.Request.GetEncodedUrl().TrimEnd('/') + "/index.html";
            RespondWithRedirect(httpContext.Response, indexUrl);
            return;
        }

        // Serve the index.html file
        if (httpMethod == "GET" && Regex.IsMatch(path, $"^/{Regex.Escape(RoutePrefix)}/?index.html$", RegexOptions.IgnoreCase))
        {
            await RespondWithIndexHtml(httpContext.Response);
            return;
        }

        // Serve a static file or continue to the next middleware
        await _staticFileMiddleware.Invoke(httpContext);
    }

    private StaticFileMiddleware CreateStaticFileMiddleware(
        RequestDelegate next, 
        IWebHostEnvironment hostingEnv, 
        ILoggerFactory loggerFactory)
    {
        var staticFileOptions = new StaticFileOptions
        {
            RequestPath = $"/{RoutePrefix}",
            FileProvider = new EmbeddedFileProvider(typeof(CustomUiMiddleware).GetTypeInfo().Assembly),
        };

        return new StaticFileMiddleware(next, hostingEnv, Options.Create(staticFileOptions), loggerFactory);
    }

    private void RespondWithRedirect(HttpResponse response, string location)
    {
        response.StatusCode = 301;
        response.Headers["Location"] = location;
    }

    private async Task RespondWithIndexHtml(HttpResponse response)
    {
        response.StatusCode = 200;
        response.ContentType = "text/html;charset=utf-8";

        await using var stream = typeof(CustomUiMiddleware).GetTypeInfo().Assembly.GetManifestResourceStream(typeof(CustomUiMiddleware), "index.html") ?? throw new NullReferenceException();
        var htmlBuilder = new StringBuilder(await new StreamReader(stream).ReadToEndAsync());
        await response.WriteAsync(htmlBuilder.ToString(), Encoding.UTF8);
    }
}
Run Code Online (Sandbox Code Playgroud)