无法从 Angular 客户端从本地主机加载 API,而 C# 后端托管在独立单元上



我正在使用。net 6 WebAPI运行Angular客户端(v14)。这些都是运行在树莓派上的。net项目。它是一个独立的kiosk,所以前端和后端运行在同一个机器上。

我希望能够通过浏览器从同一网络上的PC访问前端。当我远程到树莓派的地址时,我可以看到Angular应用程序的加载屏幕,但它无法解析本地主机,因为它将PC的本地主机作为后端,而不是Kiosk。

我还希望能够远程访问API来控制单元的功能,这可能是通过Postman或第三方应用程序。但这可能是一个单独的问题,我可以稍后解决。

我需要angular应用程序将localhost重写为当前的IP地址。我一直在努力寻找如何做到这一点的例子,而且事情已经变得相当复杂。下面是配置的部分,我想知道是否有人可以为我指出正确的方向,或者为我指出一个如何使其工作的示例?

launchSettings.json

{
"profiles": {
"Kiosk_ClientApp": {
"commandName": "Project",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://0.0.0.0:4200"
}
}
}

nginx

server {
listen 443 ssl;
listen [::]:443 ssl;
include snippets/self-signed.conf;
include snippets/ssl-params.conf;

server_name kiosk;
location / {
proxy_pass https://localhost:4200;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection keep-alive;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_cookie_path / "/; SameSite = None' secure";
proxy_read_timeout 18000s;
proxy_send_timeout 18000s;
}
location /api { 
proxy_pass https://localhost:4901;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}

proxy.conf.json

{
"/api": {
"target": "https://0.0.0.0:4901",
"secure": true,
"changeOrigin": true,
"logLevel": "debug"
}
}

environment.ts

export const environment = {
production: false,
urlAddress: "https://localhost:4901"
};

environment.prod.ts

export const environment = {
production: true,
urlAddress: "https://localhost:4901"
};

angular.json

{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"Kiosk": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"prefix": "app",
"schematics": {},
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"progress": false,
"outputPath": "dist",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.app.json",
"assets": [
"src/assets"
],
"styles": [
"node_modules/bootstrap/dist/css/bootstrap.min.css",
"src/styles.css",
"node_modules/sweetalert2/src/sweetalert2.scss",
"node_modules/datatables.net-bs4/css/dataTables.bootstrap4.min.css",
"node_modules/datatables.net-dt/css/jquery.dataTables.css"
],
"scripts": [
"./node_modules/jquery/dist/jquery.min.js",
"./node_modules/popper.js/dist/umd/popper.min.js",
"./node_modules/bootstrap/dist/js/bootstrap.min.js",
"./node_modules/chart.js/dist/Chart.js",
"node_modules/jquery/dist/jquery.js",
"node_modules/datatables.net/js/jquery.dataTables.js",
"node_modules/datatables.net-bs4/js/dataTables.bootstrap4.min.js"
],
"vendorChunk": true,
"extractLicenses": false,
"buildOptimizer": false,
"sourceMap": true,
"optimization": false,
"namedChunks": true
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "anyComponentStyle",
"maximumWarning": "6kb"
}
]
}
},
"defaultConfiguration": ""
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "Kiosk:build",
"proxyConfig": "src/proxy.conf.json",
"ssl": true,
"sslCert": "ssl/server.crt",
"sslKey": "ssl/server.key"
},
"configurations": {
"production": {
"browserTarget": "Kiosk:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "Kiosk:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.spec.json",
"karmaConfig": "src/karma.conf.js",
"styles": [
"src/styles.css"
],
"scripts": [],
"assets": [
"src/assets"
]
}
},
"server": {
"builder": "@angular-devkit/build-angular:server",
"options": {
"outputPath": "dist-server",
"main": "src/main.ts",
"tsConfig": "src/tsconfig.server.json",
"sourceMap": true,
"optimization": false
},
"configurations": {
"dev": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true
},
"production": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true
}
},
"defaultConfiguration": ""
}
}
},
"Kiosk-e2e": {
"root": "e2e/",
"projectType": "application",
"architect": {
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "Kiosk:serve"
}
}
}
}
},
"cli": {
"analytics": false
}
}

的例子:

saveRecipe(recipe: IRecipe): Observable<number> {
this.convertRecipeToMetric(recipe);
return this.http
.post<number>(this.createCompleteRoute("api/Recipe/AddRecipe", this.envUrl.urlAddress),
recipe,
{ withCredentials: true }).pipe(map((s: number) => { return s; })
);
}
private createCompleteRoute = (route: string, envAddress: string) => {
return `${envAddress}/${route}`;
};

包。Json -脚本部分:

"scripts": {
"ng": "ng",
"start": "ng serve --proxy-config proxy.config.json",
"build": "ng build",
"build:ssr": "ng run Kiosk:server:dev",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},

c# Startup.cs

public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy(
"AllowMyOrigins",
builder =>
builder
.AllowCredentials()
.WithOrigins("https://localhost:4200")
.SetIsOriginAllowed(host => true)
.SetIsOriginAllowedToAllowWildcardSubdomains()
.AllowAnyHeader()
.AllowAnyMethod());
});
services.AddControllersWithViews();
// In production, the Angular files will be served from this directory
services.AddSpaStaticFiles(configuration => { configuration.RootPath = "ClientApp/dist"; });
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto,
});
app.UseHttpsRedirection();
app.UseStaticFiles();
if (!env.IsDevelopment())
{
app.UseSpaStaticFiles();
}
app.UseRouting();
app.UseCors("AllowMyOrigins");
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
"default",
"{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
// To learn more about options for serving an Angular SPA from ASP.NET Core,
// see https://go.microsoft.com/fwlink/?linkid=864501
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseAngularCliServer("start");
}
});
}

IP地址可以是任何东西,因为会有许多这样的单元,放置在任何地方,所以我需要动态的IP地址,而不是静态的。

如果你使用nginx作为入口代理,那么你不应该在你的前端代码中使用绝对路径。

如果你使用相对路径从javascript调用API(即"/API/something"),那么浏览器将会访问你用来加载FE的任何服务器(这可能是一个访问nginx代理的服务器URL ?)

问题似乎是Angular客户端无法向托管在本地主机上运行的独立c#后端上的API发出请求。这可能是由于CORS限制,它阻止网页向不同的域发出请求,而不是为页面提供服务的域。

要解决这个问题,你需要配置c#后端,允许来自Angular客户端域的跨域请求。一种方法是将CORS中间件添加到c#后端。下面是一个使用Microsoft.AspNetCore.Cors包实现此功能的示例:

使用NuGet安装Microsoft.AspNetCore.Cors包。在c#后端Startup.cs文件中,向ConfigureServices方法添加以下代码以配置CORS:

services.AddCors(options =>
{
options.AddDefaultPolicy(builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});

这将允许任何来源向您的API发出请求,并允许任何HTTP方法和头。

在Startup.cs的Configure方法中,添加以下代码使能CORS:

app.UseCors();

这将为应用程序中的所有端点启用CORS。

通过这些修改,你的c#后端现在应该允许来自Angular客户端域的请求。请注意,您应该谨慎地允许任何源向您的API发出请求,因为这可能会带来安全风险。一般来说,限制允许向API发出请求的源是一个好主意。

最新更新