ASP.NET Core 集成 React SPA应用的步骤 |
|
AgileConfig的UI使用react重写快完成了 。上次搞定了基于jwt的登录模式(AntDesign Pro + .NET Core 实现基于JWT的登录认证),但是还有点问题 。现在使用react重写后,agileconfig成了个确确实实的前后端分离项目 。那么其实部署的话要分2个站点部署,把前端build完的静态内容部署在一个网站,把server端也部署在一个站点 。然后修改前端的baseURL让spa的api请求都指向server的网站 。
但是这样我们的入口是index.html,这样看起来比较别扭,不够友好 。而且这些文件直接丢在wwwroot的根目录下,会跟网站其他js、css等内容混合在一起,也很混乱 。
要实现以上内容只需要一个自定义中间件就可以了 。 wwwrootui
我们把build完的静态文件全部复制到wwwrootui文件夹内,以跟其他静态资源进行区分 。当然你也可以放在任意目录下,只要是能读取到就可以 。 ReactUIMiddleware
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 ,以此为依据判断是否是reactui需要的静态文件
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;
}
}
}
}
大概解释下这个中间件的思路 。这个中间件的逻辑大概是分量部分 。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseMiddleware<ExceptionHandlerMiddleware>();
}
app.UseMiddleware<ReactUIMiddleware>();
...
...
}
在Startup类的Configure方法内使用这个中间件 。这样我们的改造就差不多了 。 运行一下
访问下http://localhost:5000/ui 可以看到spa成功加载进来了 。 总结
|