如何解决.NET Core 3.1 with ReactJs 16.9.0 - 在 IIS 子文件夹中运行
我有一个带有 ReactJs 前端的 .NET Core 3.1(我使用的是 switch 仿生框架),我试图在 IIS 上的子文件夹 (subapp) 中运行它。在 IIS 上安装应用程序作为网站工作正常。该应用程序运行没有问题。但是,当将应用程序安装为 IIS 默认页面下的子应用程序时,将始终在响应需要的任何资源上给出 404。发生这种情况是因为 React 使用的 baseUrl 默认为根 url。
到目前为止,除了将子文件夹名称添加到 baseurl 本身之外,我还没有找到改变这种行为的方法。这对我来说不是解决方案,因为这个应用程序将安装在 1 台服务器上的 IIS 下的至少 43 个子文件夹中。我无法仅仅因为必须更新子文件夹而构建和部署 43 多个应用程序!
有没有人知道如何让 React 前端识别正在使用的当前 url(包括子文件夹),而不仅仅是默认为根 url?
我尝试设置“主页”:“。”和 "homepage": "./" in package.json,根据 'gaearon' (https://github.com/facebook/create-react-app/issues/527) 的帖子,但它没有影响。
我的配置文件 (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,"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 核心后端的一部分,在 IIS 子文件夹下正常运行。他们中的大多数都是一样的。其中一些确实可以正常工作,而另一些则只是垃圾。
如果您想在子文件夹中运行应用程序(后端和前端),那么唯一的方法就是在 React 中硬编码基本 url 和公共路径。
由于我将 React 与 typescript 和 switch 框架一起使用,所以我可以这样做: 默认.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 天后我终于发现这还不够时,我感到非常失望。站点运行,但路由中断!无论您使用哪种路由器包。
因此,注意,仅在基本标签中设置 href 还不够。
我的最终解决方案: 编辑: 我的第一个解决方案是对基本 url 和公共路径进行硬编码,但使用我的特殊名称:XYXYX。
经过进一步思考后,我决定不对任何内容进行硬编码,而是保持前端不变。后端现在会根据需要更新所需的文件,确保它们对于“端口”安装或“子文件夹”安装是正确的。
在后端,在 Programs.cs 中,创建一个函数,该函数将根据应用程序是否安装在 IIS 下以在端口上运行来更改 index.html 和 *app.js 文件(例如 http:/ /domain:port) 或在子文件夹中(例如 http://domain/subfolder)。
appsettings.json:
"AppAddress": "http://my.domain.name/MySubFolderName",
Program.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,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 也是一个很好的解决方案!
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。