前言

在 asp.net core 中,存在着中间件这一概念,在中间件中,我们可以比过滤器更早的介入到 http 请求管道,从而实现对每一次的 http 请求、响应做切面处理,从而实现一些特殊的功能

在使用中间件时,我们经常实现的是鉴权、请求日志记录、全局异常处理等等这种非业务性的需求,而如果你有在 asp.net core 中使用过 swashbuckle(swagger)、health check、mini profiler 等等这样的组件的话,你会发现,这些第三方的组件往往都提供了页面,允许我们通过可视化的方式完成某些操作或浏览某些数据

因为自己也需要实现类似的功能,虽然使用到的知识点很少、也很简单,但是在网上搜了搜也没有专门介绍这块的文档或文章,所以本篇文章就来说明如何在中间件中返回页面,如果你有类似的需求,希望可以对你有所帮助

Step by Step

最终实现的功能其实很简单,当用户跳转到某个指定的地址后,自定义的中间件通过匹配到该路径,从而返回指定的页面,所以这里主要会涉及到中间件是如何创建,以及如何处理页面中的静态文件引用

因为这块并不会包含很多的代码,所以这里主要是通过分析 Swashbuckle.AspNetCore 的代码,了解它是如何实现的这一功能,从而给我们的功能实现提供一个思路

在 asp.net core 中使用 Swashbuckle.AspNetCore 时,我们通常需要在 Startup 类中针对组件做如下的配置,根据当前程序的信息生成 json 文件 =》 公开生成的 json 文件地址 =》 根据 json 文件生成可视化的交互页面

public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// 生成 swagger 配置的 json 文件
services.AddSwaggerGen(s =>
{
s.SwaggerDoc("v1", new OpenApiInfo
{
Contact = new OpenApiContact
{
Name = "Danvic Wang",
Url = new Uri("https://yuiter.com"),
},
Description = "Template.API - ASP.NET Core 后端接口模板",
Title = "Template.API",
Version = "v1"
}); // 参数使用驼峰的命名方式
s.DescribeAllParametersInCamelCase();
});
} // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// 公开 swagger 生成的 json 文件节点
app.UseSwagger(); // 启用 swagger 可视化交互页面
app.UseSwaggerUI(s =>
{
s.SwaggerEndpoint($"/swagger/v1/swagger.json",
$"Swagger doc v1");
});
}
}

可以看到最终呈现给用户的页面,其实是在 Configure 方法中通过调用 UseSwaggerUI 方法来完成的,这个方法是在 Swashbuckle.AspNetCore.SwaggerUI 这个程序集中,所以这里直接从 github 上找到对应的文件夹,clone 下源代码,来看下是如何实现在中间件中返回特定的页面

在 clone 下的代码中,排除掉一些 c#、node.js 使用到的项目性文件,可以看到整个项目中的文件按照功能可以分为三大块,其中最核心的则是在 SwaggerUIMiddleware 类中,因此,这里主要聚焦在这个中间件类的实现

在一个 asp.net core 中间件中,核心的处理逻辑是在 Invoke/InvokeAsync 方法中,结合我们使用 swagger 时的场景,可以看到,在将组件中所包含的页面呈现给用户时,主要存在如下两个处理逻辑

1、当匹配到用户访问的是 /swagger 时,返回 301 的 http 状态码,浏览器重定向到 /swagger/index.html,从而再次触发该中间件的执行

2、当匹配到请求的地址为 /swagger/index.html 时,将嵌入到程序集中的文件通过 stream 流的形式获取到,转换成字符串,再指定请求的响应的类型为 text/html,从而实现将页面返回给用户

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(_options.RoutePrefix)}/?$"))
{
// Use relative redirect to support proxy environments
var relativeRedirectPath = path.EndsWith("/")
? "index.html"
: $"{path.Split('/').Last()}/index.html"; RespondWithRedirect(httpContext.Response, relativeRedirectPath);
return;
} if (httpMethod == "GET" && Regex.IsMatch(path, $"^/{Regex.Escape(_options.RoutePrefix)}/?index.html$"))
{
await RespondWithIndexHtml(httpContext.Response);
return;
} await _staticFileMiddleware.Invoke(httpContext);
}

这里需要注意,因为类似于这种功能,我们可能会打包成独立的 nuget 包,然后通过 nuget 进行引用,所以为了能够正确获取到页面及其使用到的静态资源文件,我们需要将这些静态文件的属性修改成嵌入的资源,从而在打包时可以包含在程序集中

对于网页来说,在引用这些静态资源文件时存在一种相对的路径关系,因此,这里在中间件的构造函数中,我们需要将页面需要使用到的静态文件,通过构建 StaticFileMiddleware 中间件,将文件映射与网页相同的 /swagger 路径下面,从而确保页面所需的资源可以正确加载

public class SwaggerUIMiddleware
{
private const string EmbeddedFileNamespace = "Swashbuckle.AspNetCore.SwaggerUI.node_modules.swagger_ui_dist"; private readonly SwaggerUIOptions _options;
private readonly StaticFileMiddleware _staticFileMiddleware; public SwaggerUIMiddleware(
RequestDelegate next,
IHostingEnvironment hostingEnv,
ILoggerFactory loggerFactory,
SwaggerUIOptions options)
{
_options = options ?? new SwaggerUIOptions(); _staticFileMiddleware = CreateStaticFileMiddleware(next, hostingEnv, loggerFactory, options);
} private StaticFileMiddleware CreateStaticFileMiddleware(
RequestDelegate next,
IHostingEnvironment hostingEnv,
ILoggerFactory loggerFactory,
SwaggerUIOptions options)
{
var staticFileOptions = new StaticFileOptions
{
RequestPath = string.IsNullOrEmpty(options.RoutePrefix) ? string.Empty : $"/{options.RoutePrefix}",
FileProvider = new EmbeddedFileProvider(typeof(SwaggerUIMiddleware).GetTypeInfo().Assembly, EmbeddedFileNamespace),
}; return new StaticFileMiddleware(next, hostingEnv, Options.Create(staticFileOptions), loggerFactory);
}
}

当完成了页面的呈现后,因为一般我们会创建一个单独的类库来实现这些功能,在页面中,可能会包含前后端的数据交互,由于我们在宿主的 API 项目中已经完成了对于路由规则的设定,所以这里只需要在类库中通过 nuget 引用 Microsoft.AspNetCore.Mvc.Core ,然后与 Web API 一样的定义 controller,确保这个中间件在宿主程序的调用位于路由匹配规则之后即可

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
} app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); // Endpoint 路由规则设定
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
}); // 自定义中间件
app.UseMiddleware<SampleUIMiddleware>();
}

参考

  1. ASP.NET Core 应用针对静态文件请求的处理: 以 web 的形式发布静态文件

如何在 asp.net core 的中间件中返回具体的页面的更多相关文章

  1. 如何在ASP.NET Core自定义中间件中读取Request.Body和Response.Body的内容?

    原文:如何在ASP.NET Core自定义中间件中读取Request.Body和Response.Body的内容? 文章名称: 如何在ASP.NET Core自定义中间件读取Request.Body和 ...

  2. 如何在ASP.NET Core 2.0中使用Razor页面

    如何在ASP.NET Core 2.0中使用Razor页面  DotNetCore2017-11-22 14:49 问题 如何在ASP.NET Core 2.0中使用Razor页面 解 创建一个空的项 ...

  3. 如何在ASP.NET Core Web API中使用Mini Profiler

    原文如何在ASP.NET Core Web API中使用Mini Profiler 由Anuraj发表于2019年11月25日星期一阅读时间:1分钟 ASPNETCoreMiniProfiler 这篇 ...

  4. 避免在ASP.NET Core 3.0中为启动类注入服务

    本篇是如何升级到ASP.NET Core 3.0系列文章的第二篇. Part 1 - 将.NET Standard 2.0类库转换为.NET Core 3.0类库 Part 2 - IHostingE ...

  5. 如何在ASP.NET Core中自定义Azure Storage File Provider

    文章标题:如何在ASP.NET Core中自定义Azure Storage File Provider 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p ...

  6. 如何在ASP.NET Core中实现CORS跨域

    注:下载本文的完整代码示例请访问 > How to enable CORS(Cross-origin resource sharing) in ASP.NET Core 如何在ASP.NET C ...

  7. 如何在ASP.NET Core中实现一个基础的身份认证

    注:本文提到的代码示例下载地址> How to achieve a basic authorization in ASP.NET Core 如何在ASP.NET Core中实现一个基础的身份认证 ...

  8. 如何在ASP.NET Core中应用Entity Framework

    注:本文提到的代码示例下载地址> How to using Entity Framework DB first in ASP.NET Core 如何在ASP.NET Core中应用Entity ...

  9. [转]如何在ASP.NET Core中实现一个基础的身份认证

    本文转自:http://www.cnblogs.com/onecodeonescript/p/6015512.html 注:本文提到的代码示例下载地址> How to achieve a bas ...

随机推荐

  1. Shell基本语法---函数

    函数 函数定义 function 函数名 () { 指令... return n } 函数调用及参数传递 function func() { echo "第零个参数:" $ #脚本 ...

  2. 扩展BSGS

    \(BSGS\) 求解\(a^x\equiv b\pmod p\),且\(a\)与\(p\)互质 由\(a^{φ(p)}\equiv1 \pmod p\)和\(a^0\equiv 1\pmod p\) ...

  3. git 缓存密码 unable to access... 403错误

    如果输入了 git config credential.helper 命令之后还是出现了osxkeychain, store 或者 cache 等,说明 git 的配置还是没有被清空,我参考了stac ...

  4. Monster Audio 使用教程 (六) 发送音轨的设置

    因为最近有些用户,不太清楚怎么发送给混响音轨,所以这里我简单介绍一下. Monster Audio的音轨,主要分为两种类型: 1.白色推子:表示普通音轨 2.蓝色推子:表示“可接收发送音轨”,其他音轨 ...

  5. Pycharm远程解释器SFTP开发和调试

    转载:https://blog.csdn.net/ll641058431/article/details/53049453 使用PyCharm进行远程开发和调试 你是否经常要在Windows 7或MA ...

  6. 萌新学渗透系列之Hack The Box_Devel

    我将我的walkthrough过程用视频解说的形式记载 视频地址https://www.bilibili.com/video/BV1Ck4y1B7DB 一是因为看我视频的后来者应该都是刚入门的新手,视 ...

  7. pdb 进行调试

    import pdb a = 'aaa' pdb.set_trace( ) b = 'bbb' c = 'ccc' final = a+b+c print(final) import pdb a = ...

  8. 性能分析(2)- 应用程序 CPU 使用率过高案例

    性能分析小案例系列,可以通过下面链接查看哦 https://www.cnblogs.com/poloyy/category/1814570.html 系统架构背景 其中一台用作 Web 服务器,来模拟 ...

  9. PHP array_udiff_uassoc() 函数

    实例 比较两个数组的键名和键值(使用用户自定义函数进行比较),并返回差集: <?phpfunction myfunction_key($a,$b){if ($a===$b){return 0;} ...

  10. PHP krsort() 函数

    ------------恢复内容开始------------ 实例 对关联数组按照键名进行降序排序: <?php$age=array("Peter"=>"35 ...