[英]How to configure ASP.NET Core 3.1 application to run ReactJS and MVC website side by side
[英].NET Core 3.1 with ReactJs 16.9.0 - Run in IIS sub-folder
我有一個 .NET Core 3.1 和 ReactJs 前端(我正在使用 switch 仿生框架),我試圖在 IIS 上的子文件夾(子應用程序)中運行。 在 IIS 上將應用程序安裝為網站可以正常工作。 該應用程序運行沒有問題。 但是,當在 IIS 默認頁面下將應用程序安裝為子應用程序時,將始終在任何需要響應的資源上提供 404。 這是因為 React 使用的 baseUrl 默認為根 url。
到目前為止,除了將子文件夾名稱添加到 baseurl 本身之外,我還沒有找到改變這種行為的方法。 這對我來說不是一個解決方案,因為這個應用程序將安裝在一台服務器上 IIS 下的至少 43 個子文件夾中。 我不能僅僅因為必須更新子文件夾就構建和部署超過 43 個應用程序!
有誰知道如何讓反應前端識別當前正在使用的 url(包括子文件夾),而不僅僅是默認為根 url?
我試過設置“主頁”:“。” 和“主頁”:package.json 中的“./”,根據來自“gaearon”的帖子( https/github.com/527BADE )
我的配置文件(default.tsx)看起來像這樣(2021/04/09 編輯):
import Project from '../src/globals/interfaces/Project';
const isDevelopment = Object.is(process.env.NODE_ENV, 'development');
const baseUrl = isDevelopment ? 'http://localhost:5105' : '.';
const config: IConfigData<Project, {}> = {
core: {
i18n: {
defaultLocale: 'en',
},
},
project: {
baseUrl,
},
runtime: {},
};
export default config;
我的 package.json:
{
"name": "MyApp",
"version": "3.4.2",
"description": "My Application",
"license": "Whatever",
"author": {
"name": "Werner"
},
"config": {
"AppIcon": "./src/assets/appIcon/logo.png",
"title": "My App Title",
"devServer": {
"host": "0.0.0.0",
"port": "5170",
"https": false,
"publicPath": "/"
},
"publicPath": "",
"homepage": ".",
"functionalTestBrowsers": [
"chrome",
"firefox",
"internet explorer",
"edge"
]
},
"scripts": {
"test": "jest --no-cache",
"test:update": "jest --updateSnapshot",
"start": "webpack-dev-server --env.build=dev",
"start:4110": "webpack-dev-server --env.build=dev --env.config=4110",
"start:4120": "webpack-dev-server --env.build=dev --env.config=4120",
"start:4130": "webpack-dev-server --env.build=dev --env.config=4130",
"start:4140": "webpack-dev-server --env.build=dev --env.config=4140",
"start:4150": "webpack-dev-server --env.build=dev --env.config=4150",
"start:legacy": "webpack-dev-server --env.build=dev --env.legacy=true --env.REDUX_TOOLS=logger",
"start:swidget": "webpack-dev-server --port 7070 --env.build=dev --env.swidget=true --env.legacy=true --env.REDUX_TOOLS=logger",
"start:host": "webpack-dev-server --env.build=dev --env.legacy=true --env.exposed=true --env.REDUX_TOOLS=logger",
"build": "webpack --env.build=prod --env.verbose=false",
"build:ci": "webpack --env.build=prod --env.verbose=true --env.release=true",
"build:legacy": "webpack --env.build=prod --env.legacy=true",
"build:host": "webpack --env.build=prod --env.legacy=true --env.exposed=true",
"build:swidget": "webpack --env.build=prod --env.swidget=true --env.legacy=true",
"build:multi": "webpack --env.build=multi",
"build:test:functional": "tsc -p test/functional/tsconfig.json",
"lint": "nyr lint:eslint && nyr lint:stylelint && nyr lint:prettier",
"lint:staged": "lint-staged",
"lint:eslint": "eslint --ext ts,tsx src",
"lint:prettier": "prettier --check \"./**/*\"",
"lint:stylelint": "stylelint \"src/**/*.(css|scss)\" --syntax scss",
"lint:fix": "nyr lint:fix:eslint && nyr lint:fix:stylelint && nyr lint:fix:postcss && nyr lint:fix:prettier",
"lint:fix:prettier": "prettier --write \"./**/*\"",
"lint:fix:postcss": "postcss --config postcss.config.js --env sort-only --no-map --replace \"src/**/*.(css|scss)\"",
"lint:fix:stylelint": "stylelint \"src/**/*.(css|scss)\" --syntax scss --fix",
"lint:fix:eslint": "eslint --fix --ext ts,tsx src",
"clean": "rimraf dist && rimraf coverage",
"storybook": "cross-env build=dev start-storybook -p 9001 -c .build/storybook",
"storybook:static": "cross-env build=prod build-storybook -c .build/storybook -o dist/storybook",
"test:functional": "run-p build:test:functional test:functional:selenium && run-p --race start wdio",
"test:functional:headless": "run-p build:test:functional test:functional:selenium && run-p --race start wdio:headless",
"test:functional:selenium": "selenium-standalone install --silent",
"wdio": "wdio .build/wdio.conf.js",
"wdio:headless": "cross-env WDIO_HEADLESS=true wdio .build/wdio.conf.js"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"post-commit": "git update-index --again"
}
},
"repository": {
"type": "git",
"url": "Don't worry! Taken out for security!"
},
"engines": {
"node": ">=8.11.3",
"npm": ">=5.6.0"
},
"browserslist": [
"> 1%",
"last 2 versions",
"Firefox ESR",
"ie >= 11"
],
"dependencies": {
"@daimler/material-ui-comps": "0.0.4-release-84.0",
"@daimler/material-ui-theme": "0.0.9",
"@daimler/typeface-daimler-cs-web": "^1.0.0",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "^4.0.0-alpha.53",
"@switch/core": "2.0.0-beta.2",
"@types/react-csv": "^1.1.1",
"@types/react-router-dom": "^5.1.3",
"@types/redux-logger": "^3.0.7",
"axios": "^0.19.0",
"core-js": "~3.2.1",
"domtokenlist-shim": "~1.2.0",
"file-saver": "^2.0.2",
"inversify": "~4.3.0",
"joi-browser": "~13.4.0",
"jwt-decode": "^2.2.0",
"material-table": "^1.69.1",
"material-ui-dropzone": "^3.2.1",
"react": "~16.9.0",
"react-csv": "^2.0.3",
"react-dom": "~16.9.0",
"react-hot-loader": "~4.12.11",
"react-promise-tracker": "^2.0.5",
"react-redux": "^7.2.0",
"react-router-dom": "^5.1.2",
"react-switch": "^5.0.1",
"react-table": "^7.5.1",
"react-transition-group-v2": "^4.3.0",
"redux": "^4.0.5",
"redux-devtools-extension": "^2.13.8",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.3.0",
"regenerator-runtime": "~0.13.3",
"tslib": "~1.10.0",
"typesafe-actions": "^5.1.0",
"universal-cookie": "^4.0.3",
"whatwg-fetch": "~3.0.0"
},
"devDependencies": {
"@babel/core": "~7.5.5",
"@babel/preset-env": "~7.5.5",
"@babel/runtime": "~7.5.5",
"@hot-loader/react-dom": "~16.9.0",
"@storybook/addon-actions": "~5.1.11",
"@storybook/addon-info": "~5.1.11",
"@storybook/addon-knobs": "~5.1.11",
"@storybook/addon-links": "~5.1.11",
"@storybook/addons": "~5.1.11",
"@storybook/cli": "~5.1.11",
"@storybook/react": "~5.1.11",
"@types/enzyme": "~3.10.3",
"@types/jasmine": "~3.4.0",
"@types/jest": "~24.0.18",
"@types/jwt-decode": "^2.2.1",
"@types/node": "~12.0.0",
"@types/prop-types": "~15.7.1",
"@types/react": "~16.9.2",
"@types/react-dom": "~16.9.0",
"@types/react-redux": "^7.1.7",
"@types/react-test-renderer": "~16.9.0",
"@types/storybook__addon-actions": "~3.4.3",
"@types/storybook__addon-info": "~4.1.2",
"@types/storybook__addon-knobs": "~5.0.3",
"@types/storybook__addon-links": "~3.3.5",
"@types/storybook__react": "~4.0.2",
"@typescript-eslint/eslint-plugin": "~2.0.0",
"@typescript-eslint/parser": "~2.0.0",
"@wdio/cli": "~5.12.4",
"@wdio/dot-reporter": "~5.12.1",
"@wdio/jasmine-framework": "~5.12.1",
"@wdio/local-runner": "~5.12.4",
"@wdio/selenium-standalone-service": "~5.12.1",
"@wdio/spec-reporter": "~5.12.1",
"@wdio/sync": "~5.12.3",
"babel-loader": "~8.0.6",
"clean-webpack-plugin": "~3.0.0",
"copy-webpack-plugin": "~5.0.4",
"cross-env": "~5.2.0",
"css-loader": "~3.2.0",
"css-modules-typescript-loader": "~3.0.0",
"cssjson": "~2.1.3",
"duplicate-package-checker-webpack-plugin": "~3.0.0",
"enzyme": "~3.10.0",
"enzyme-adapter-react-16": "~1.14.0",
"enzyme-to-json": "~3.4.0",
"eslint": "~6.2.1",
"eslint-config-prettier": "~6.1.0",
"eslint-plugin-prettier": "~3.1.0",
"eslint-plugin-react": "~7.14.3",
"expose-loader": "~0.7.5",
"fork-ts-checker-webpack-plugin": "~1.5.0",
"html-webpack-multi-build-plugin": "~1.0.0",
"html-webpack-plugin": "~3.2.0",
"husky": "~3.0.4",
"identity-obj-proxy": "~3.0.0",
"imagemin-lint-staged": "~0.4.0",
"jasmine": "~3.4.0",
"jest": "~24.9.0",
"lint-staged": "~9.2.3",
"mini-css-extract-plugin": "~0.8.0",
"mock-local-storage": "~1.1.8",
"npm-run-all": "~4.1.5",
"nyr": "1.1.0",
"optimize-css-assets-webpack-plugin": "~5.0.3",
"postcss": "~7.0.17",
"postcss-cli": "~6.1.3",
"postcss-extend": "~1.0.5",
"postcss-import": "~12.0.1",
"postcss-import-sync": "~7.1.4",
"postcss-loader": "~3.0.0",
"postcss-nested": "~4.1.2",
"postcss-preset-env": "~6.7.0",
"postcss-remove-prefixes": "~1.2.0",
"postcss-sorting": "~5.0.1",
"postcss-unprefix": "~2.1.4",
"prettier": "~1.18.2",
"react-ace": "^7.0.4",
"react-docgen-typescript-loader": "~3.1.1",
"react-test-renderer": "~16.9.0",
"rimraf": "~3.0.0",
"simple-progress-webpack-plugin": "~1.1.2",
"style-loader": "~1.0.0",
"stylelint": "~10.1.0",
"stylelint-config-css-modules": "~1.4.0",
"stylelint-config-recommended": "~2.2.0",
"terser-webpack-plugin": "~1.4.1",
"ts-jest": "~24.0.2",
"ts-loader": "~6.0.4",
"typescript": "~3.5.3",
"url-loader": "~2.1.0",
"webapp-webpack-plugin": "~2.7.1",
"webpack": "~4.39.2",
"webpack-cli": "~3.3.7",
"webpack-dev-server": "~3.8.0",
"webpack-merge": "~4.2.1"
}
}
任何幫助將不勝感激。
互聯網上有 100 篇關於如何獲取 ReactJS 前端的帖子,作為 asp.net 核心后端的一部分,在 Z5DA5ACF461B4EFB7E726EC861065B2 子文件夾下運行沒有問題。 他們中的大多數是相同的。 其中一些確實可以正常工作,而另一些則只是垃圾。
如果您想在子文件夾中運行應用程序(后端和前端),那么唯一的方法是在 React 中硬編碼基礎 url 和公共路徑。
由於我將 React 與 typescript 和 switch 框架一起使用,我可以這樣做:default.tsx:
const baseUrl = isDevelopment ? 'http://localhost:5105' : 'http://localhost/MySubFolderName';
然后在 package.json 中:
"publicPath": "MySubFolderName",
在為生產構建后,該應用程序將在 IIS 子文件夾中正常運行。
但我的目標是讓這個應用程序在 40+ IIS 子文件夾中運行,其中許多子文件夾在同一台服務器上。 並且絕對不是為每個安裝單獨構建前端的選項。 想想維護的噩夢!!
因此,我決定編寫一個腳本來更改我所需的文件。 自動地!
我嘗試的第一個選項是在 index.html 中設置基本標簽<base href="/">
,這是互聯網上許多帖子給出的解決方案。 我的腳本打開了這個 index.html 並替換了標簽以包含子文件夾名稱: <base href="/MySubFolderName/">
。
並且“瞧瞧”,該網站運行,但是。 2 天后我終於發現這還不夠,這讓我很失望,該站點運行,但路由已損壞。 無論您使用哪種路由器 package。
因此,請注意,僅在基本標記中設置 href 是不夠的。
我的最終解決方案:已編輯:我的第一個解決方案是硬編碼基礎 url 和公共路徑,但使用我的特殊名稱:XYXYX。
經過進一步思考,我決定不對任何東西進行硬編碼,而是保持前端不變。 后端現在根據需要更新所需的文件,確保它們對於“端口”安裝或“子文件夾”安裝都是正確的。
In the back-end, in Programs.cs, create a function that will change the index.html and *app.js files depending on whether the app is installed under IIS to run on a port (ex. http://domain:端口)或子文件夾(例如 http://domain/subfolder)。
appsettings.json:
"AppAddress": "http://my.domain.name/MySubFolderName",
程序.cs:
...
IConfiguration Configuration = new ConfigurationBuilder()
.SetBasePath(pathToContentRoot)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env}.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();
try
{
var host = CreateHostBuilder(args).Build();
...
...
try
{
UpdateFilesForSubfolderOrPortUse(Configuration["AppAddress"], pathToContentRoot);
}
catch (Exception e)
{
Log.Fatal(e, $"Host terminated. Could not rewrite files! Error: {e.Message}");
return 1;
}
host.Run();
...
...
private static void UpdateFilesForSubfolderOrPortUse(string serverAddress, string pathToContentRoot)
{
string subFolder = ExtractSubfolderName(serverAddress);
// First extract the search-string from the *app.js file
string searchString = ExtractSearchStringFromJsFile(pathToContentRoot);
string oldSubFolder = "";
if (searchString != ".")
{
oldSubFolder = searchString.Substring(searchString.IndexOf("/", searchString.IndexOf("://") + 3) + 1);
}
Dictionary<string, string> searchAndReplaceDict = new Dictionary<string, string>
{
{ "HtmlSearchString1", "" },
{ "HtmlReplaceString1", "" },
{ "HtmlSearchString2", "" },
{ "HtmlReplaceString2", "" },
{ "JsSearchString1", "" },
{ "JsReplaceString1", "" },
{ "JsSearchString2", "" },
{ "JsReplaceString2", "" },
{ "JsSearchString3", "" },
{ "JsReplaceString3", "" }
};
if ((string.IsNullOrEmpty(subFolder) && searchString == ".") || (!string.IsNullOrEmpty(subFolder) && oldSubFolder == subFolder))
{
// No conversion is needed
return;
}
else if ((!string.IsNullOrEmpty(subFolder) && searchString == "."))
{
// Convert from Port to SubFolder
searchAndReplaceDict["HtmlSearchString1"] = "link href=\"";
searchAndReplaceDict["HtmlReplaceString1"] = $"link href=\"{subFolder}/";
searchAndReplaceDict["HtmlSearchString2"] = "src=\"";
searchAndReplaceDict["HtmlReplaceString2"] = $"src=\"{subFolder}/";
searchAndReplaceDict["JsSearchString1"] = "+\"fonts/";
searchAndReplaceDict["JsReplaceString1"] = "+\"/fonts/";
searchAndReplaceDict["JsSearchString2"] = "},i.p=\"";
searchAndReplaceDict["JsReplaceString2"] = "},i.p=\"" + subFolder;
searchAndReplaceDict["JsSearchString3"] = "\"http://localhost:5105\":\".";
searchAndReplaceDict["JsReplaceString3"] = $"\"http://localhost:5105\":\"{(serverAddress[serverAddress.Length - 1] == '/' ? serverAddress.Substring(0, serverAddress.Length - 1) : serverAddress)}";
}
else if ((!string.IsNullOrEmpty(subFolder) && searchString != "."))
{
// Convert from SubFolder to SubFolder
searchAndReplaceDict["HtmlSearchString1"] = oldSubFolder;
searchAndReplaceDict["HtmlReplaceString1"] = subFolder;
searchAndReplaceDict["JsSearchString1"] = "+\"fonts/";
searchAndReplaceDict["JsReplaceString1"] = "+\"/fonts/";
searchAndReplaceDict["JsSearchString2"] = $"\"{oldSubFolder}\"";
searchAndReplaceDict["JsReplaceString2"] = $"\"{subFolder}\"";
searchAndReplaceDict["JsSearchString3"] = $"/{oldSubFolder}\"";
searchAndReplaceDict["JsReplaceString3"] = $"/{subFolder}\"";
}
else
{
// Convert from SubFolder to Port
searchAndReplaceDict["HtmlSearchString1"] = $"{oldSubFolder}/";
searchAndReplaceDict["JsSearchString1"] = "+\"/fonts/";
searchAndReplaceDict["JsReplaceString1"] = "+\"fonts/";
searchAndReplaceDict["JsSearchString2"] = $"\"{oldSubFolder}\"";
searchAndReplaceDict["JsReplaceString2"] = "\"\"";
searchAndReplaceDict["JsSearchString3"] = searchString;
searchAndReplaceDict["JsReplaceString3"] = $".";
}
DoUpdateFiles(searchAndReplaceDict, pathToContentRoot);
}
private static string ExtractSubfolderName(string serverAddress)
{
string baseAddr = serverAddress.Substring(serverAddress.IndexOf("://") + 3);
int idx = baseAddr.IndexOf('/');
string subaddr = "";
if (idx > 0)
{
subaddr = baseAddr.Substring(idx + 1);
if (subaddr.Length > 0 && subaddr[subaddr.Length - 1] == '/')
{
subaddr = subaddr.Substring(0, subaddr.Length - 1);
}
}
return subaddr;
}
private static string ExtractSearchStringFromJsFile(string pathToContentRoot)
{
string rootfolder = $"{pathToContentRoot}\\App";
var exts = new[] { "app.js" };
IEnumerable<string> files = Directory
.EnumerateFiles(@rootfolder, "*.*", SearchOption.TopDirectoryOnly)
.Where(file => exts.Any(x => file.EndsWith(x, StringComparison.OrdinalIgnoreCase)));
string jsContents = File.ReadAllText(files.First());
string searchFor = "project:{baseUrl:Object.is(\"production\",\"development\")?\"http://localhost:5105\":\"";
string tmpSearchStr = jsContents.Substring(jsContents.IndexOf(searchFor) + searchFor.Length);
if (tmpSearchStr.StartsWith(".\""))
{
// This is a Port installation
tmpSearchStr = ".";
}
else
{
// This is a SubFolder installation
tmpSearchStr = tmpSearchStr.Substring(0, tmpSearchStr.IndexOf("\"}"));
}
return tmpSearchStr;
}
private static void DoUpdateFiles(Dictionary<string, string> searchAndReplaceDict, string pathToContentRoot)
{
string rootfolder = $"{pathToContentRoot}\\App";
var exts = new[] { ".html", "app.js" };
IEnumerable<string> files = Directory
.EnumerateFiles(@rootfolder, "*.*", SearchOption.TopDirectoryOnly)
.Where(file => exts.Any(x => file.EndsWith(x, StringComparison.OrdinalIgnoreCase)));
foreach (string file in files)
{
string contents = File.ReadAllText(file);
if (Path.GetExtension(@file) == ".html")
{
contents = contents.Replace(searchAndReplaceDict["HtmlSearchString1"], searchAndReplaceDict["HtmlReplaceString1"]);
if (!string.IsNullOrEmpty(searchAndReplaceDict["HtmlSearchString2"]))
{
contents = contents.Replace(searchAndReplaceDict["HtmlSearchString2"], searchAndReplaceDict["HtmlReplaceString2"]);
}
}
else if (Path.GetExtension(@file) == ".js")
{
contents = contents.Replace(searchAndReplaceDict["JsSearchString1"], searchAndReplaceDict["JsReplaceString1"]);
contents = contents.Replace(searchAndReplaceDict["JsSearchString2"], searchAndReplaceDict["JsReplaceString2"]);
contents = contents.Replace(searchAndReplaceDict["JsSearchString3"], searchAndReplaceDict["JsReplaceString3"]);
}
// Make file writable
File.SetAttributes(file, FileAttributes.Normal);
File.WriteAllText(file, contents);
}
}
現在管理員可以下載應用程序的版本並將其安裝在 IIS 子文件夾中。 該管理員還可以將所有文件從一個子文件夾/端口安裝復制到另一個。 在這兩種情況下,管理員都必須使用正確的地址更新配置文件。
現在,應用程序將在啟動期間,在它運行應用程序之前,讀取 2 個文件並替換所需的字符串。
該應用程序現在使用用於“端口”安裝的“標准”前端和用於“子文件夾”安裝的“硬編碼”前端運行,但它是動態的並根據“子文件夾名稱”。 而且我可以繼續在子文件夾或端口中添加新應用程序,而不必每次都重新構建,也不必手動更新所需的字符串以使其運行......
是的,有些人會說這是一個 hack,但在 SW 中,在沒有其他任何東西的情況下,即使是 hack 也是一個很好的解決方案!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.