在 .NET Core 5 中集成 Create React app
翻譯自 Camilo Reyes 2021年2月22日的文章?《Integrate Create React app with .NET Core 5》?[1]
?
本文演示了如何將 Create React app 與 .NET Core 集成,以生成一個移除了幾個依賴項的腳手架。
Create React app?是社區中創建一個全新 React 項目的首選方式。該工具生成了基礎的腳手架用于開始編寫代碼,并抽象出了許多具有挑戰性的依賴項。webpack 和 Babel 之類的 React 工具被集中到一個單獨的依賴項中,使得 React 開發者可以專注于手頭的工作,這降低了構建單頁應用的必要門檻。
不過問題依然存在,雖然 React 解決了客戶端的問題,但服務端呢?.NET 開發者在使用 Razor、服務器端配置,并通過 session cookie 處理 ASP.NET 用戶會話(session)方面有著悠久的歷史。在本文中,我將向您展示如何通過兩者之間的良好集成來實現兩全其美的效果。
本文提供了一種動手實踐的方式,因此您可以依照自上而下的順序,獲得更佳的閱讀效果。如果您更喜歡隨著代碼學習,可以從 GitHub 上獲取源碼[2],使閱讀更愉快。
一般的解決方案涉及兩個主要部分——前端和后端。后端是一個典型的 ASP.NET MVC 應用,任何人都可以在 .NET Core 5 中啟動。請確保您已安裝 .NET Core 5,并將項目的目標設置為 .NET Core 5,只要執行了此操作也便開啟了 C# 9 特性。隨著集成的進行,我還將添加更多的部分。前端會有 React 項目和輸出像?index.html?之類靜態資產的 NPM 工具。我將假定您具有 .NET 和 React 的工作知識,因此我不會深究諸如在開發機上設置 .NET Core 或 Node 的基礎。也就是說,請注意下面一些有用的 using 語句,以便后面使用:
using Microsoft.AspNetCore.Http; using System.Net; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authorization; using System.Text.RegularExpressions;初始化項目設置
好消息是,微軟提供了一個基礎的腳手架模板,用于啟動新的帶有 React 前端的 ASP.NET 項目。該 ASP.NET React 項目具有一個客戶端應用,它輸出可以托管在任何地方的靜態資產;以及一個 ASP.NET 后端應用,它可以通過調用 API Endpoints 獲取 JSON 數據。這里的一個優點是,整個解決方案可以作為一個整體同時部署,而無需將前后兩端拆分成單獨的部署管道。
要安裝基礎的腳手架,請執行以下操作:
mkdir integrate-dotnet-core-create-react-app cd integrate-dotnet-core-create-react-app dotnet new react --no-https dotnet new sln dotnet sln add integrate-dotnet-core-create-react-app.csproj有了這些,就可以在 Visual Studio 或 VS Code 中打開解決方案文件。您可以運行?dotnet run?來啟動項目,看看該腳手架都為您做了些什么。請注意命令?dotnet new react,這是我用于該 React 項目的模板。
下面是初始模板的樣子:
如果您在使用 React 時遇到任何問題,只需將目錄更改為?ClientApp?并運行?npm install,即可啟動并運行 Create React App。整個 React 應用程序在客戶端渲染,而不需要服務器渲染。它有一個具有三個不同頁面的?react-router:一個計數器、一個獲取天氣數據的頁面和一個主頁。如果您看一下控制器,會發現?WeatherForecastController?有一個 API Endpoint 來獲取天氣數據。
該腳手架已經包含了一個 Create React App 項目。為了驗證這一點,請打開?ClientApp?文件夾中的?package.json?文件進行檢查。
這就是它的證據:
{"scripts": {"start": "rimraf ./build && react-scripts start","build": "react-scripts build",} }找到?react-scripts,這是像 webpack 一樣封裝所有其他 React 依賴項的單一依賴項。若要在將來升級 React 和它的依賴項,您只需升級這一依賴項。它抽象化了可能有潛在危險的升級以保持最新狀態,因此這才是 React App 的真正魔力。
ClientApp?中的整個文件夾結構遵循常規的 Create React App 項目,在其周圍包裹著 ASP.NET 項目。
文件夾結構如下所示:
該 React 應用程序有很多優點,但是它缺少一些重要的 ASP.NET 功能:
沒有通過 Razor 進行的服務端渲染,使任何其他 MVC 頁面像一個單獨的應用程序一樣工作
很難從 React 客戶端訪問 ASP.NET 服務端配置數據
它不會集成由 session cookie 實現的 ASP.NET 用戶會話
隨著集成的推進,我將逐一解決這些問題。好在這些理想的功能是可以使用 Create React App 和 ASP.NET 實現的。
為了跟蹤集成更改,我將使用 Git 提交初始腳手架。假設 Git 已安裝,請執行?git init、git add?和?git commit?來提交這個初始項目。查看提交歷史是跟蹤集成所需更改的一種很好的方法。
現在,創建以下對此集成很有用的文件夾和文件。我建議使用 Visual Studio 右鍵單擊創建控制器 、類或 View:
/Controllers/HomeController.cs:服務端主頁,它將覆蓋 Create React App 的?index.html?入口頁
/Views/Home/Index.cshtml:Razor 視圖,它渲染服務端組件和來自 React 項目的解析過的?index.html
/CreateReactAppViewModel.cs:主要的集成視圖模型,它將抓取?index.html?靜態資產并將其解析出來以供 MVC 使用
有了這些文件夾和文件后,請終止當前正在運行的應用程序,并通過?dotnet watch run?以監視模式啟動該應用程序。此命令跟蹤前端和后端的更改,甚至在需要時刷新頁面。
其余的必要更改將放入腳手架現有的文件中。這好極了,因為可以最大限度地減少必要的代碼調整來充實這個集成。
是時候擼起袖子,做個深呼吸,處理這個集成的主要部分了。
CreateReactAppViewModel 集成
我將從創建一個執行大部分集成工作的視圖模型開始。打開?CreateReactAppViewModel?并放入以下代碼:
public class CreateReactAppViewModel {private static readonly Regex _parser = new(@"<head>(?<HeadContent>.*)</head>\s*<body>(?<BodyContent>.*)</body>",RegexOptions.IgnoreCase | RegexOptions.Singleline);public string HeadContent { get;set;}public string BodyContent { get;set;}public CreateReactAppViewModel(HttpContext context){var request = WebRequest.Create(context.Request.Scheme + "://" + context.Request.Host +context.Request.PathBase + "/index.html");var response = request.GetResponse();var stream = response.GetResponseStream();var reader = new StreamReader(stream ?? throw new InvalidOperationException("The create-react-app build output could not be found in " +"/ClientApp/build. You probably need to run npm run build. " +"For local development, consider npm start."));var htmlFileContent = reader.ReadToEnd();var matches = _parser.Matches(htmlFileContent);if (matches.Count != 1){throw new InvalidOperationException("The create-react-app build output does not appear " +"to be a valid html file.");}var match = matches[0];HeadContent = match.Groups["HeadContent"].Value;BodyContent = match.Groups["BodyContent"].Value;} }這段代碼乍一看可能有點嚇人,但它只做了兩件事:從開發服務器獲取輸出的?index.html?文件,并解析出?head?和?body?標簽。這使得 ASP.NET 中的消費方應用程序可以訪問 HTML,該 HTML 鏈接到來自 Create React App 的靜態資產。這些資產是靜態文件,其中包含帶有 JavaScript 和 CSS 的代碼包。例如,JavaScript 包?js\main.3549aedc.chunk.js?或 CSS 包?css\2.ed890e5e.chunk.css。這就是在 React 中 webpack 接收所編寫的代碼并將其呈現到瀏覽器的方式。
我選擇直接向開發服務器發起一個?WebRequest,是因為在開發模式下,Create React App 不會生成 ASP.NET 可訪問的任何實際文件。這對于本地開發來說足夠了,因為它可以與 webpack 開發服務器很好地配合。客戶端上的任何更改都將自動更新到瀏覽器。在監視模式下進行的任何后端更改也會刷新到瀏覽器。因此,您可以在兩全其美的環境中獲得最佳的生產力。
在生產環境中,將會通過?npm run build?創建靜態資產。我建議執行文件 IO,并從?ClientApp/build?中的實際位置讀取 index 文件。另外,在生產模式下,最好在靜態資產部署到托管服務器之后緩存該文件的內容。
為了讓您有一個更好的概念,下面是一個 build 后的?index.html?文件的樣子:
我高亮顯示了消費方 ASP.NET 應用需要解析的?head?和?body?標簽。有了這些原始的 HTML,剩下的就簡單多了。
視圖模型就緒后,就該花點時間處理 home 控制器了,它將覆蓋來自 React 的?index.html。
打開?HomeController?并添加以下代碼:
public class HomeController : Controller {public IActionResult Index(){var vm = new CreateReactAppViewModel(HttpContext);return View(vm);} }在 ASP.NET 中,該控制器是默認路由,它會在服務端渲染的支持下覆蓋 Create React App。這就是解鎖集成的訣竅,從而可以兩全其美。
接著,把下面的 Razor 代碼放入?Home/Index.cshtml?中:
@model integrate_dotnet_core_create_react_app.CreateReactAppViewModel<!DOCTYPE html> <html lang="en"> <head>@Html.Raw(Model.HeadContent) </head> <body>@Html.Raw(Model.BodyContent)<div class="container "><h2>Server-side rendering</h2></div> </body> </html>React 應用程序使用?react-router?來定義客戶端的路由。如果在瀏覽器處于非 home 路由時刷新頁面,它將恢復為靜態的?index.html。
要解決這種不一致性,請在?Startup?中定義下面的服務端路由,路由是在?UseEndpoints?中定義的:
endpoints.MapControllerRoute("default","{controller=Home}/{action=Index}"); endpoints.MapControllerRoute("counter","/counter",new { controller = "Home", action = "Index"}); endpoints.MapControllerRoute("fetch-data","/fetch-data",new { controller = "Home", action = "Index"});此時,看一下瀏覽器,現在它將通過?h2?顯示這個服務端“組件”。這看起來似乎有點愚蠢,因為它只是在頁面上渲染的一些簡單 HTML,但其潛力是無窮的。ASP.NET Razor 頁面可以具有完整的應用程序外殼,其中包含菜單、品牌和導航,它可以在多個 React 應用之間共享。如果有任何舊版 MVC Razor 頁面,這個閃亮的新 React 應用能夠無縫集成。
服務器端應用程序配置
接下來,假如我想顯示此應用上來自 ASP.NET 的服務端配置,比如 HTTP 協議、主機名和 base URL。我選擇這些主要是為了保持簡單,不過這些配置值可以來自任何地方,它們可以是?appsettings.json?設置,或者甚至可以是來自配置數據庫中的值。
要使服務端設置可以被 React 客戶端訪問,請將其放在?Index.cshtml?中:
<script>window.SERVER_PROTOCOL = '@Context.Request.Protocol';window.SERVER_SCHEME = '@Context.Request.Scheme';window.SERVER_HOST = '@Context.Request.Host';window.SERVER_PATH_BASE = '@Context.Request.PathBase'; </script><p>@Context.Request.Protocol@Context.Request.Scheme://@Context.Request.Host@Context.Request.PathBase </p>這里在全局?window?瀏覽器對象中設置來自服務器的任意配置值。React 應用可以輕而易舉地檢索這些值。我選擇在 Razor 中渲染這些相同的值,主要是為了演示它們與客戶端應用將看到的是相同的值。
在 React 中,打開?components\NavMenu.js?并添加下面的代碼段;其中大部分將放在?Navbar?中:
import { NavbarText } from 'reactstrap';<NavbarText>{window.SERVER_PROTOCOL}{window.SERVER_SCHEME}://{window.SERVER_HOST}{window.SERVER_PATH_BASE} </NavbarText>這個客戶端應用現在將顯示通過全局?window?對象設置的服務器端配置。它不需要觸發 Ajax 請求來加載這些數據,也不需要以某種方式讓?index.html?靜態資產可以訪問它。
假如您使用了 Redux,這會變得更加容易,因為可以在應用程序初始化 store 時進行設置。初始化狀態值可以在客戶端渲染任何內容之前傳遞到 store 中。
例如:
const preloadedState = {config: {protocol: window.SERVER_PROTOCOL,scheme: window.SERVER_SCHEME,host: window.SERVER_HOST,pathBase: window.SERVER_PATH_BASE} };const store = createStore(reducers, preloadedState, applyMiddleware(...middleware));為了簡潔起見,我選擇不使用 Redux store,而是通過?window?對象的方式實現,這只是一個粗略的想法。這種方法的好處是,整個應用都可以保持單元可測試的狀態,而不會受到類似?window?對象的瀏覽器依賴項的污染。
.NET Core 用戶會話集成
最后,作為主菜,現在我將這個 React 應用與 ASP.NET 用戶會話(Session)集成在一起。我將鎖定獲取天氣數據的后端 API,并僅在使用有效會話時顯示此信息。這意味著當瀏覽器觸發 Ajax 請求時,它必須包含一個 ASP.NET session cookie。否則,該請求將被拒絕,并重定向以指示瀏覽器必須先登錄。
要在 ASP.NET 中啟用用戶會話支持,請打開?Startup?文件并添加:
public void ConfigureServices(IServiceCollection services) {services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>{options.Cookie.HttpOnly = true;}); }public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {// 將下面代碼放在 UseRouting 和 UseEndpoints 之間app.UseAuthentication();app.UseAuthorization(); }請務必保留其余的腳手架代碼,只是在恰當的方法中添加上面的代碼段。啟用了身份驗證和授權后,轉到?WeatherForecastController?并給該控制器添加一個?Authorize?屬性。這將有效地將其鎖定,從而需要由 cookie 實現的 ASP.NET 用戶會話來獲取數據。
Authorize?屬性假定用戶可以登錄到該應用。回到?HomeController?并添加 Login 和 Logout 方法,記得添加 using?Microsoft.AspNetCore.Authentication、Microsoft.AspNetCore.Authentication.Cookies?和?Microsft.AspNetCore.Mvc。
這是建立然后終止用戶會話的一種方法:
public async Task<ActionResult> Login() {var userId = Guid.NewGuid().ToString();var claims = new List<Claim>{new(ClaimTypes.Name, userId)};var claimsIdentity = new ClaimsIdentity(claims,CookieAuthenticationDefaults.AuthenticationScheme);var authProperties = new AuthenticationProperties();await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,new ClaimsPrincipal(claimsIdentity),authProperties);return RedirectToAction("Index"); }public async Task<ActionResult> Logout() {await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);return RedirectToAction("Index"); }請注意,通常使用重定向和 ASP.NET session cookie 來建立用戶會話。我添加了一個?ClaimsPrincipal,它帶有一個設置為隨機?Guid?的用戶 ID,使其看起來更加真實。[3]?在實際應用中,這些 Claims 可能來自數據庫或者 JWT。
要將此功能公開給客戶端,請打開?components\NavMenu.js?并將下面的鏈接添加到?Navbar:
<NavItem><a class="text-dark nav-link" href="/home/login">Log In</a> </NavItem> <NavItem><a class="text-dark nav-link" href="/home/logout">Log Out</a> </NavItem>最后,我希望客戶端應用處理請求失敗的情況,并給最終用戶提供一些提示,指出出了點問題。打開?components\FetchData.js?并用下面的代碼段替換?populateWeatherData:
async populateWeatherData() {try {const response = await fetch('weatherforecast',{ redirect: 'error' });const data = await response.json();this.setState({ forecasts: data, loading: false });} catch {this.setState({forecasts: [{ date: 'Unable to get weather forecast' }],loading: false});} }我調整了一下?fetch,以使它不會用重定向跟蹤失敗的請求,而是返回一個錯誤響應。當 Ajax 請求獲取數據失敗時,ASP.NET 中間件將以重定向到登錄頁的方式響應。在實際的應用中,我建議將其自定義為 401 (Unauthorized) 狀態碼,以便客戶端可以更優雅地處理此問題;或者,設置某種方式來輪詢后端并檢查活動會話,然后通過?window.location?進行相應地重定向。
完成后,dotnet 監視程序應該會在刷新瀏覽器時跟蹤前后兩端的更改。為了進行測試,我將首先訪問 Fetch Data 頁,請注意會請求失敗,然后登錄,并使用有效的會話再次嘗試獲取天氣數據。我將打開“Network”選項卡,以在瀏覽器中顯示 Ajax 請求。
請注意當我第一次獲取天氣數據時的 302 重定向,它失敗了。接著,來自登錄頁的后續重定向建立了一個會話。查看一下瀏覽器的 cookies,會顯示這個名為?AspNetCore.Cookies?的 cookie,它是一個 session cookie,正是它讓后面的 Ajax 請求工作正常了。
結論
.NET Core 5 和 React 不必獨立存在。通過出色的集成,便可以在 React 中解鎖服務端渲染、服務端配置數據和 ASP.NET 用戶會話。
相關鏈接:
https://www.red-gate.com/simple-talk/dotnet/net-tools/integrate-create-react-app-with-net-core-5/?Integrate Create React app with .NET Core 5???
https://github.com/beautifulcoder/integrate-dotnet-core-create-react-app.git?示例代碼???
用 Cookie 代表一個通過驗證的主體,它包含 Claims, ClaimsIdentity, ClaimsPrincipal 三部分信息,其中 ClaimsPrincipal 相當于持有證件的人,ClaimsIdentity 就是持有的證件,Claims 是證件上的信息。https://andrewlock.net/introduction-to-authentication-with-asp-net-core/???
作者 :Camilo Reyes??
譯者 :技術譯民??
出品 :技術譯站(https://ITTranslator.cn/)
END
總結
以上是生活随笔為你收集整理的在 .NET Core 5 中集成 Create React app的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 优化 ASP.NET Core Dock
- 下一篇: WPF 如何将IconFont图标转成G