NET Core 3.1与ReactJs 16.9.0-在IIS子文件夹中运行



我有一个。NET Core 3.1和ReactJs前端(我使用的是交换机仿生框架(,我正试图在IIS上的子文件夹(subapp(中运行它。将该应用程序作为网站安装在IIS上运行良好。应用程序运行没有问题。但是,当将应用程序作为子应用程序安装在IIS默认页面下时,对于任何需要做出反应的资源,都会给出404。这是因为React使用的baseUrl默认为根url。

到目前为止,除了将子文件夹名称添加到baseurl本身之外,我还没有找到改变这种行为的方法。这对我来说不是一个解决方案,因为这一应用程序将安装在1台服务器上IIS下的至少43个子文件夹中。我不能仅仅因为子文件夹必须更新就构建和部署43+个应用程序!

有人知道我如何让react前端识别正在使用的当前url(包括子文件夹(,而不仅仅是默认的根url吗?

我试过设置";主页":&"以及";主页":&"/"根据gaearon的帖子,在package.json中(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;

我的包.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核心后端的一部分在IIS子文件夹下毫无问题地运行的帖子。大多数都是一样的。它们中的一些确实能正常工作,而另一些则只是垃圾。

如果您想在子文件夹中运行应用程序(后端和前端(,那么唯一的方法就是在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/">

"瞧",网站开始运行了!然而,当两天后我终于发现这还不够时,我非常失望。站点正在运行,但路由已中断!无论您使用哪种路由器包。

因此,BEWARE,仅仅在基本标记中设置href是不够的。

我的最终解决方案:编辑:我的第一个解决方案是硬编码基本url和公共路径,但使用我的特殊名称:XYXYX。

经过进一步思考,我决定不硬编码任何内容,而是保持前端的原样。后端现在根据需要更新所需的文件,确保它们对于"端口"安装或"子文件夹"安装都是正确的。

在后台的Programs.cs中,创建一个函数,根据应用程序是否安装在IIS下以在端口上运行来更改index.html和*app.js文件(例如。http://domain:port)或在子文件夹中(例如。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个文件并替换所需的字符串。

该应用程序现在使用"端口"安装的"标准"前端运行,使用"硬编码"前端运行"子文件夹"安装,但它是动态的,并根据"子文件夹名称"自动更新!我可以继续在子文件夹或端口中添加新的应用程序,而不必每次都重新构建,也不必手动更新所需的字符串以使其运行。。。

是的,有些人会说这是一个黑客,但在软件中,在没有其他东西的情况下,即使是黑客也是一个很好的解决方案!

最新更新