如何在 asp.net core 的中间件中返回具体的页面
前言
在 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>();
}
参考
如何在 asp.net core 的中间件中返回具体的页面的更多相关文章
- 如何在ASP.NET Core自定义中间件中读取Request.Body和Response.Body的内容?
原文:如何在ASP.NET Core自定义中间件中读取Request.Body和Response.Body的内容? 文章名称: 如何在ASP.NET Core自定义中间件读取Request.Body和 ...
- 如何在ASP.NET Core 2.0中使用Razor页面
如何在ASP.NET Core 2.0中使用Razor页面 DotNetCore2017-11-22 14:49 问题 如何在ASP.NET Core 2.0中使用Razor页面 解 创建一个空的项 ...
- 如何在ASP.NET Core Web API中使用Mini Profiler
原文如何在ASP.NET Core Web API中使用Mini Profiler 由Anuraj发表于2019年11月25日星期一阅读时间:1分钟 ASPNETCoreMiniProfiler 这篇 ...
- 避免在ASP.NET Core 3.0中为启动类注入服务
本篇是如何升级到ASP.NET Core 3.0系列文章的第二篇. Part 1 - 将.NET Standard 2.0类库转换为.NET Core 3.0类库 Part 2 - IHostingE ...
- 如何在ASP.NET Core中自定义Azure Storage File Provider
文章标题:如何在ASP.NET Core中自定义Azure Storage File Provider 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p ...
- 如何在ASP.NET Core中实现CORS跨域
注:下载本文的完整代码示例请访问 > How to enable CORS(Cross-origin resource sharing) in ASP.NET Core 如何在ASP.NET C ...
- 如何在ASP.NET Core中实现一个基础的身份认证
注:本文提到的代码示例下载地址> How to achieve a basic authorization in ASP.NET Core 如何在ASP.NET Core中实现一个基础的身份认证 ...
- 如何在ASP.NET Core中应用Entity Framework
注:本文提到的代码示例下载地址> How to using Entity Framework DB first in ASP.NET Core 如何在ASP.NET Core中应用Entity ...
- [转]如何在ASP.NET Core中实现一个基础的身份认证
本文转自:http://www.cnblogs.com/onecodeonescript/p/6015512.html 注:本文提到的代码示例下载地址> How to achieve a bas ...
随机推荐
- 学会这个,助你升值加薪自动化框架之python+selenium+pytest
1.概述 selenium: 基于JavaScript代码库的自动化测试框架,通过脚本语言,模拟用户行为操作,最接近用户真实场景,实现对web自动测试. Selenium,是目前的最火爆企业最主流的w ...
- 题解 SP1841 【PPATH - Prime Path】
模拟赛考到了这个题,但我傻傻的用了\(DFS\),于是爆了零 后来才想明白,因为搜索树的分支很多,但答案的深度却又没有那么深,所以在这里\(BFS\),而\(DFS\)一路搜到底的做法则会稳稳地\(T ...
- Webpack前世今生
在正式介绍Webpack之前,先给大家说明一下前端为什么需要模块化 1.为什么需要模块化 1.1JS原始功能 在网页开发的早期,js制作作为一种脚本语言,做一些简单的表单验证或动画实现等,那个时候代码 ...
- Python网络编程基础|百度网盘免费下载|零基础入门学习资料
百度网盘免费下载:Python网络编程基础|零基础学习资料 提取码:k7a1 目录: 第1部分 底层网络 第1章 客户/服务器网络介绍 第2章 网络客户端 第3章 网络服务器 第4章 域名系统 第5章 ...
- Python编程初学者指南|百度网盘免费下载|Python新手入门资料
Python编程初学者指南|百度网盘免费下载 提取码:9ozx 目录 · · · · · · 第1章 启程:Game Over程序1.1 剖析Game Over程序1.2 Python简介1.2.1 ...
- Android中service的生命周期
Service作为Android四大组件 Service Activity ContentProvider BroadcastReceiver 之一,应用非常广泛,和Activity一样,Servic ...
- Day01_虚拟化架构与系统部署
学于千峰教育开源课程 感恩 千峰教育官网 b站在线视频 前言:本人所使用的操作系统是MacOS 使用的虚拟机软件为parallels desktop 本章结构 构建桌面端虚拟环境 虚拟机的概述 VMa ...
- PHP array_slice() 函数
实例 从数组的第二个元素开始取出,并返回直到数组末端的所有元素: <?php$a=array("red","green","blue" ...
- PHP scandir() 函数
------------恢复内容开始------------ 实例 列出 images 目录中的文件和目录: <?php$dir = "/images/"; // Sort ...
- Promise核心基础
基础 Promise 抽象表达:是js中进行异步编程的新的解决方案 具体解释:1.从语法上来说是一个构造函数 2.从功能上来说promise对象用来封装一个异步操作并可以获取其结果 状态改变:0.ne ...