簡體   English   中英

.NET Core 3.1 和 ReactJs 16.9.0 - 在 IIS 子文件夾中運行

[英].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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM