AgileConfig的UI使用react重写快完成了。上次搞定了基于jwt的登录模式(AntDesign Pro + .NET Core 实现基于JWT的登录认证),但是还有点问题。现在使用react重写后,agileconfig成了个确确实实的前后端分离项目。那么其实部署的话要分2个站点部署,把前端build完的静态内容部署在一个网站,把server端也部署在一个站点。然后修改前端的baseURL让spa的api请求都指向server的网站。

这样做也不是不行,但是这不符合AgileConfig的精神,那就是简单。asp.net core程序本身其实就是一个http服务器,所以完全可以把spa网站使用它来承载。这样只需要部署一个站点就可以同时跑spa跟后端server了。

其实最简单的办法就是把build完的文件全部丢wwwroot文件夹下面。然后访问:

  1. http://localhost:5000/index.html

但是这样我们的入口是index.html,这样看起来比较别扭,不够友好。而且这些文件直接丢在wwwroot的根目录下,会跟网站其他js、css等内容混合在一起,也很混乱。

那么下面我们就要解决这两个文件,我们要达到的目的有2个:

  1. spa的入口path友好,比如http://localhost:5000/ui
  2. spa静态文件存放的目录独立,比如存放在wwwroot/ui文件夹下,或者别的什么目录下。

要实现以上内容只需要一个自定义中间件就可以了。

wwwroot\ui

  1. wwwroot\ui



我们把build完的静态文件全部复制到wwwroot\ui文件夹内,以跟其他静态资源进行区分。当然你也可以放在任意目录下,只要是能读取到就可以。

ReactUIMiddleware

  1. namespace AgileConfig.Server.Apisite.UIExtension
  2. {
  3. public class ReactUIMiddleware
  4. {
  5. private static Dictionary<string, string> _contentTypes = new Dictionary<string, string>
  6. {
  7. {".html", "text/html; charset=utf-8"},
  8. {".css", "text/css; charset=utf-8"},
  9. {".js", "application/javascript"},
  10. {".png", "image/png"},
  11. {".svg", "image/svg+xml"},
  12. { ".json","application/json;charset=utf-8"},
  13. { ".ico","image/x-icon"}
  14. };
  15. private static ConcurrentDictionary<string, byte[]> _staticFilesCache = new ConcurrentDictionary<string, byte[]>();
  16. private readonly RequestDelegate _next;
  17. private readonly ILogger _logger;
  18. public ReactUIMiddleware(
  19. RequestDelegate next,
  20. ILoggerFactory loggerFactory
  21. )
  22. {
  23. _next = next;
  24. _logger = loggerFactory.
  25. CreateLogger<ReactUIMiddleware>();
  26. }
  27. private bool ShouldHandleUIRequest(HttpContext context)
  28. {
  29. return context.Request.Path.HasValue && context.Request.Path.Value.Equals("/ui", StringComparison.OrdinalIgnoreCase);
  30. }
  31. private bool ShouldHandleUIStaticFilesRequest(HttpContext context)
  32. {
  33. //请求的的Referer为 0.0.0.0/ui ,以此为依据判断是否是reactui需要的静态文件
  34. if (context.Request.Path.HasValue && context.Request.Path.Value.Contains("."))
  35. {
  36. context.Request.Headers.TryGetValue("Referer", out StringValues refererValues);
  37. if (refererValues.Any())
  38. {
  39. var refererValue = refererValues.First();
  40. if (refererValue.EndsWith("/ui", StringComparison.OrdinalIgnoreCase))
  41. {
  42. return true;
  43. }
  44. }
  45. }
  46. return false;
  47. }
  48. public async Task Invoke(HttpContext context)
  49. {
  50. const string uiDirectory = "wwwroot/ui";
  51. //handle /ui request
  52. var filePath = "";
  53. if (ShouldHandleUIRequest(context))
  54. {
  55. filePath = uiDirectory + "/index.html";
  56. }
  57. //handle static files that Referer = xxx/ui
  58. if (ShouldHandleUIStaticFilesRequest(context))
  59. {
  60. filePath = uiDirectory + context.Request.Path;
  61. }
  62. if (string.IsNullOrEmpty(filePath))
  63. {
  64. await _next(context);
  65. }
  66. else
  67. {
  68. //output the file bytes
  69. if (!File.Exists(filePath))
  70. {
  71. context.Response.StatusCode = 404;
  72. return;
  73. }
  74. context.Response.OnStarting(() =>
  75. {
  76. var extType = Path.GetExtension(filePath);
  77. if (_contentTypes.TryGetValue(extType, out string contentType))
  78. {
  79. context.Response.ContentType = contentType;
  80. }
  81. return Task.CompletedTask;
  82. });
  83. await context.Response.StartAsync();
  84. byte[] fileData = null;
  85. if (_staticFilesCache.TryGetValue(filePath, out byte[] outfileData))
  86. {
  87. fileData = outfileData;
  88. }
  89. else
  90. {
  91. fileData = await File.ReadAllBytesAsync(filePath);
  92. _staticFilesCache.TryAdd(filePath, fileData);
  93. }
  94. await context.Response.BodyWriter.WriteAsync(fileData);
  95. return;
  96. }
  97. }
  98. }
  99. }

大概解释下这个中间件的思路。这个中间件的逻辑大概是分量部分。

1.拦截请求的路径为/ui的请求,直接从ui文件夹读取index.html静态文件的内容然后输出出去,这就相当于直接访问/index.html。但是这样的路径形式看起来更加友好。

2.拦截react spa需要的静态资源文件,比如css文件,js文件等。这里比较麻烦,因为spa拉静态文件的时候path是直接从网站root开始的,比如http://localhost:5000/xxx.js,那么怎么区分出来这个文件是react spa需要的呢?我们判断一下请求的Referer头部,如果Referer的path是/ui,那么就说明是react spa需要的静态资源,同样从ui文件夹去读取。

这里还需要给每个response设置指定的contentType不然浏览器无法准确识别资源。

  1. public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider)
  2. {
  3. if (env.IsDevelopment())
  4. {
  5. app.UseDeveloperExceptionPage();
  6. }
  7. else
  8. {
  9. app.UseMiddleware<ExceptionHandlerMiddleware>();
  10. }
  11. app.UseMiddleware<ReactUIMiddleware>();
  12. ...
  13. ...
  14. }

在Startup类的Configure方法内使用这个中间件。这样我们的改造就差不多了。

运行一下



访问下http://localhost:5000/ui 可以看到spa成功加载进来了。

总结

为了能让asp.net core承载react spa应用,我们使用一个中间件进行拦截。当访问对应path的时候从本地文件夹内读取静态资源返回给浏览器,从而完成spa所需要资源的加载。这次使用react spa来演示,其实换成任何spa应用都是一样的操作。

代码在这:ReactUIMiddleware

关注我的公众号一起玩转技术

ASP.NET .Core 集成 React SPA 应用的更多相关文章

  1. ABP官方文档翻译 6.2.1 ASP.NET Core集成

    ASP.NET Core 介绍 迁移到ASP.NET Core? 启动模板 配置 启动类 模块配置 控制器 应用服务作为控制器 过滤器 授权过滤器 审计Action过滤器 校验过滤器 工作单元Acti ...

  2. asp.net core 集成 log4net 日志框架

    asp.net core 集成 log4net 日志框架 Intro 在 asp.net core 中有些日志我们可能想输出到数据库或文件或elasticsearch等,如果不自己去实现一个 Logg ...

  3. [Abp 源码分析]十七、ASP.NET Core 集成

    0. 简介 整个 Abp 框架最为核心的除了 Abp 库之外,其次就是 Abp.AspNetCore 库了.虽然 Abp 本身是可以用于控制台程序的,不过那样的话 Abp 就基本没什么用,还是需要集合 ...

  4. Asp.Net Core 集成 Hangfire 配置使用 Redis 存储

    Hangfire 官方支持 MSSQL 与 Redis(Hangfire.Pro.Redis) 两种 ,由于我的数据库是 MYSQL ,粗略查询了一下文档,现在对 .NET Core 支持的并不够好, ...

  5. asp.net core集成MongoDB

    0.目录 整体架构目录:ASP.NET Core分布式项目实战-目录 一.前言及MongoDB的介绍 最近在整合自己的框架,顺便把MongoDBD的最简单CRUD重构一下作为组件化集成到asp.net ...

  6. asp.net core集成CAP(分布式事务总线)

    一.前言 感谢杨晓东大佬为社区贡献的CAP开源项目,传送门在此:.NET Core 事件总线,分布式事务解决方案:CAP 以及 如何在你的项目中集成 CAP[手把手视频教程],之前也在工作中遇到分布式 ...

  7. asp.net core 集成JWT(一)

    [什么是JWT] JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案. JWT的官网地址:https://jwt.io/ 通俗地来讲,JWT是能代表用户身份的令牌,可以使用JWT ...

  8. asp.net core 集成JWT(二)token的强制失效,基于策略模式细化api权限

    [前言] 上一篇我们介绍了什么是JWT,以及如何在asp.net core api项目中集成JWT权限认证.传送门:https://www.cnblogs.com/7tiny/p/11012035.h ...

  9. asp.net core 集成 Prometheus

    asp.net core 集成 prometheus Intro Prometheus 是一个开源的现代化,云原生的系统监控框架,并且可以轻松的集成 PushGateway, AlertManager ...

随机推荐

  1. Leetcode(104)-二叉树的最大深度

    给定一个二叉树,找出其最大深度. 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数. 说明: 叶子节点是指没有子节点的节点. 示例:给定二叉树 [3,9,20,null,null,15,7], ...

  2. cin的用法

    int val=0; cin>>val; 上述程序先定义了一个整型数据val,通过cin读取数据放在val中,如果输入的整型数据,则读取成功,返回的是>>左侧的对象,也就是is ...

  3. HDU 6706 huntian oy(杜教筛 + 一些定理)题解

    题意: 已知\(f(n,a,b)=\sum_{i=1}^n\sum_{j=1}^igcd(i^a-j^a,i^b-j^b)[gcd(i,j)=1]\mod 1e9+7\),\(n\leq1e9\),且 ...

  4. 牛客多校第八场E Explorer(左开右闭线段树+可撤回并查集)题解

    题意: 传送门 有\(n\)个点构成一个无向图,每条边有\(L_i,R_i\)表示这条边只能允许编号为\(L_i\dots R_i\)的人通过,现在问你最多有几个人能从\(1\)走到\(n\). 思路 ...

  5. openssl的用法

    Openssl详细用法: OpenSSL 是一个开源项目,其组成主要包括一下三个组件: openssl:多用途的命令行工具 libcrypto:加密算法库 libssl:加密模块应用库,实现了ssl及 ...

  6. Code Spell Checker & VSCode 单词拼写验证

    Code Spell Checker & VSCode 单词拼写验证 https://marketplace.visualstudio.com/items?itemName=streetsid ...

  7. JavaScript for, for...in, for...of, for-await...of difference All In One

    JavaScript for, for...in, for...of, for-await...of difference All In One for for...in for...of for-a ...

  8. GitHub user language statistics

    GitHub user language statistics 2020 https://madnight.github.io/githut/#/pull_requests/2020/2 2011 ~ ...

  9. HTTPS clone !== SSH clone

    HTTPS clone !== SSH clone https clone bug SSH clone OK testing SSH key https://www.cnblogs.com/xgqfr ...

  10. PWA & TWA

    PWA & TWA https://www.bilibili.com/video/av68082979/ Service Worker workbox.js https://developer ...