AgileConfig的UI使用react重寫快完成了。上次搞定了基于jwt的登錄模式(AntDesign Pro + .NET Core 實(shí)現(xiàn)基于JWT的登錄認(rèn)證),但是還有點(diǎn)問題。現(xiàn)在使用react重寫后,agileconfig成了個確確實(shí)實(shí)的前后端分離項目。那么其實(shí)部署的話要分2個站點(diǎn)部署,把前端build完的靜態(tài)內(nèi)容部署在一個網(wǎng)站,把server端也部署在一個站點(diǎn)。然后修改前端的baseURL讓spa的api請求都指向server的網(wǎng)站。
這樣做也不是不行,但是這不符合AgileConfig的精神,那就是簡單。asp.net core程序本身其實(shí)就是一個http服務(wù)器,所以完全可以把spa網(wǎng)站使用它來承載。這樣只需要部署一個站點(diǎn)就可以同時跑spa跟后端server了。
其實(shí)最簡單的辦法就是把build完的文件全部丟wwwroot文件夾下面。然后訪問:
http://localhost:5000/index.html
但是這樣我們的入口是index.html,這樣看起來比較別扭,不夠友好。而且這些文件直接丟在wwwroot的根目錄下,會跟網(wǎng)站其他js、css等內(nèi)容混合在一起,也很混亂。
那么下面我們就要解決這兩個文件,我們要達(dá)到的目的有2個:
- spa的入口path友好,比如http://localhost:5000/ui
- spa靜態(tài)文件存放的目錄獨(dú)立,比如存放在wwwroot/ui文件夾下,或者別的什么目錄下。
要實(shí)現(xiàn)以上內(nèi)容只需要一個自定義中間件就可以了。
wwwroot\ui
wwwroot\ui
我們把build完的靜態(tài)文件全部復(fù)制到wwwroot\ui文件夾內(nèi),以跟其他靜態(tài)資源進(jìn)行區(qū)分。當(dāng)然你也可以放在任意目錄下,只要是能讀取到就可以。
ReactUIMiddleware
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
|
namespace AgileConfig.Server.Apisite.UIExtension { public class ReactUIMiddleware { private static Dictionary< string , string > _contentTypes = new Dictionary< string , string > { { ".html" , "text/html; charset=utf-8" }, { ".css" , "text/css; charset=utf-8" }, { ".js" , "application/javascript" }, { ".png" , "image/png" }, { ".svg" , "image/svg+xml" }, { ".json" , "application/json;charset=utf-8" }, { ".ico" , "image/x-icon" } }; private static ConcurrentDictionary< string , byte []> _staticFilesCache = new ConcurrentDictionary< string , byte []>(); private readonly RequestDelegate _next; private readonly ILogger _logger; public ReactUIMiddleware( RequestDelegate next, ILoggerFactory loggerFactory ) { _next = next; _logger = loggerFactory. CreateLogger<ReactUIMiddleware>(); } private bool ShouldHandleUIRequest(HttpContext context) { return context.Request.Path.HasValue && context.Request.Path.Value.Equals( "/ui" , StringComparison.OrdinalIgnoreCase); } private bool ShouldHandleUIStaticFilesRequest(HttpContext context) { //請求的的Referer為 0.0.0.0/ui ,以此為依據(jù)判斷是否是reactui需要的靜態(tài)文件 if (context.Request.Path.HasValue && context.Request.Path.Value.Contains( "." )) { context.Request.Headers.TryGetValue( "Referer" , out StringValues refererValues); if (refererValues.Any()) { var refererValue = refererValues.First(); if (refererValue.EndsWith( "/ui" , StringComparison.OrdinalIgnoreCase)) { return true ; } } } return false ; } public async Task Invoke(HttpContext context) { const string uiDirectory = "wwwroot/ui" ; //handle /ui request var filePath = "" ; if (ShouldHandleUIRequest(context)) { filePath = uiDirectory + "/index.html" ; } //handle static files that Referer = xxx/ui if (ShouldHandleUIStaticFilesRequest(context)) { filePath = uiDirectory + context.Request.Path; } if ( string .IsNullOrEmpty(filePath)) { await _next(context); } else { //output the file bytes if (!File.Exists(filePath)) { context.Response.StatusCode = 404; return ; } context.Response.OnStarting(() => { var extType = Path.GetExtension(filePath); if (_contentTypes.TryGetValue(extType, out string contentType)) { context.Response.ContentType = contentType; } return Task.CompletedTask; }); await context.Response.StartAsync(); byte [] fileData = null ; if (_staticFilesCache.TryGetValue(filePath, out byte [] outfileData)) { fileData = outfileData; } else { fileData = await File.ReadAllBytesAsync(filePath); _staticFilesCache.TryAdd(filePath, fileData); } await context.Response.BodyWriter.WriteAsync(fileData); return ; } } } } |
大概解釋下這個中間件的思路。這個中間件的邏輯大概是分量部分。
1.攔截請求的路徑為/ui的請求,直接從ui文件夾讀取index.html靜態(tài)文件的內(nèi)容然后輸出出去,這就相當(dāng)于直接訪問/index.html。但是這樣的路徑形式看起來更加友好。
2.攔截react spa需要的靜態(tài)資源文件,比如css文件,js文件等。這里比較麻煩,因為spa拉靜態(tài)文件的時候path是直接從網(wǎng)站root開始的,比如http://localhost:5000/xxx.js,那么怎么區(qū)分出來這個文件是react spa需要的呢?我們判斷一下請求的Referer頭部,如果Referer的path是/ui,那么就說明是react spa需要的靜態(tài)資源,同樣從ui文件夾去讀取。
這里還需要給每個response設(shè)置指定的contentType不然瀏覽器無法準(zhǔn)確識別資源。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseMiddleware<ExceptionHandlerMiddleware>(); } app.UseMiddleware<ReactUIMiddleware>(); ... ... } |
在Startup類的Configure方法內(nèi)使用這個中間件。這樣我們的改造就差不多了。
運(yùn)行一下
訪問下http://localhost:5000/ui 可以看到spa成功加載進(jìn)來了。
總結(jié)
為了能讓asp.net core承載react spa應(yīng)用,我們使用一個中間件進(jìn)行攔截。當(dāng)訪問對應(yīng)path的時候從本地文件夾內(nèi)讀取靜態(tài)資源返回給瀏覽器,從而完成spa所需要資源的加載。這次使用react spa來演示,其實(shí)換成任何spa應(yīng)用都是一樣的操作。
代碼在這:ReactUIMiddleware
以上就是ASP.NET Core 集成 React SPA應(yīng)用的步驟的詳細(xì)內(nèi)容,更多關(guān)于ASP.NET Core 集成 React SPA的資料請關(guān)注服務(wù)器之家其它相關(guān)文章!
原文鏈接:https://www.cnblogs.com/kklldog/p/netcore-embed-react.html