如何在 asp.net core 的中介軟體中返回具體的頁面
阿新 • • 發佈:2020-08-17
## 前言
在 asp.net core 中,存在著中介軟體這一概念,在中介軟體中,我們可以比過濾器更早的介入到 http 請求管道,從而實現對每一次的 http 請求、響應做切面處理,從而實現一些特殊的功能
在使用中介軟體時,我們經常實現的是鑑權、請求日誌記錄、全域性異常處理等等這種非業務性的需求,而如果你有在 asp.net core 中使用過 swashbuckle(swagger)、health check、mini profiler 等等這樣的元件的話,你會發現,這些第三方的元件往往都提供了頁面,允許我們通過視覺化的方式完成某些操作或瀏覽某些資料
因為自己也需要實現類似的功能,雖然使用到的知識點很少、也很簡單,但是在網上搜了搜也沒有專門介紹這塊的文件或文章,所以本篇文章就來說明如何在中介軟體中返回頁面,如果你有類似的需求,希望可以對你有所幫助
## Step by Step
最終實現的功能其實很簡單,當用戶跳轉到某個指定的地址後,自定義的中介軟體通過匹配到該路徑,從而返回指定的頁面,所以這裡主要會涉及到中介軟體是如何建立,以及如何處理頁面中的靜態檔案引用
因為這塊並不會包含很多的程式碼,所以這裡主要是通過分析 [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore "Swagger tools for documenting API's built on ASP.NET Core") 的程式碼,瞭解它是如何實現的這一功能,從而給我們的功能實現提供一個思路
在 asp.net core 中使用 Swashbuckle.AspNetCore 時,我們通常需要在 Startup 類中針對元件做如下的配置,根據當前程式的資訊生成 json 檔案 =》 公開生成的 json 檔案地址 =》 根據 json 檔案生成視覺化的互動頁面
```csharp
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 下[原始碼](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/tree/master/src/Swashbuckle.AspNetCore.SwaggerUI),來看下是如何實現在中介軟體中返回特定的頁面
在 clone 下的程式碼中,排除掉一些 c#、node.js 使用到的專案性檔案,可以看到整個專案中的檔案按照功能可以分為三大塊,其中最核心的則是在 `SwaggerUIMiddleware` 類中,因此,這裡主要聚焦在這個中介軟體類的實現
![原始碼分析](https://img2020.cnblogs.com/blog/1310859/202008/1310859-20200816171205802-1940241681.png)
在一個 asp.net core 中介軟體中,核心的處理邏輯是在 `Invoke/InvokeAsync` 方法中,結合我們使用 swagger 時的場景,可以看到,在將元件中所包含的頁面呈現給使用者時,主要存在如下兩個處理邏輯
1、當匹配到使用者訪問的是 /swagger 時,返回 301 的 http 狀態碼,瀏覽器重定向到 /swagger/index.html,從而再次觸發該中介軟體的執行
2、當匹配到請求的地址為 /swagger/index.html 時,將嵌入到程式集中的檔案通過 stream 流的形式獲取到,轉換成字串,再指定請求的響應的型別為 `text/html`,從而實現將頁面返回給使用者
```csharp
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 路徑下面,從而確保頁面所需的資源可以正確載入
```csharp
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);
}
}
```
![對映檔案路徑](https://img2020.cnblogs.com/blog/1310859/202008/1310859-20200816171225002-1366763350.png)
當完成了頁面的呈現後,因為一般我們會建立一個單獨的類庫來實現這些功能,在頁面中,可能會包含前後端的資料互動,由於我們在宿主的 API 專案中已經完成了對於路由規則的設定,所以這裡只需要在類庫中通過 nuget 引用 `Microsoft.AspNetCore.Mvc.Core` ,然後與 Web API 一樣的定義 controller,確保這個中介軟體在宿主程式的呼叫位於路由匹配規則之後即可
```csharp
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();
}
```
## 參考
1. [ASP.NET Core 應用針對靜態檔案請求的處理: 以 web 的形式釋出靜態檔案](https://wwww.cnblogs.com/artech/p/static-file-for-asp-net-core-