Возможные проблемы:
- В вашем
startup.cs.Configure()
методе app.UseCors()
предшествует app.useMVC()
?
- Содержит ли URL-адрес Http-запроса косую черту
(/)
?
- Включает ли запрос Http учетные данные?
- Получает ли браузер Http-ответ от API?
- Содержит ли ответ заголовок «Access-Control-Allow-Origin»?
- Когда вы отправляете запрос от Почтальона, включает ли Http-ответ заголовок «Access-Control-Allow-Origin», а также тело, содержащее данные?
Gotchas
Firefox требует, чтобы для вашего API был установлен сертификат для отправки запроса Http с использованием протокола HTTPS.
Проверьте ваш API с помощью Postman и вашего браузера Developer Tool. Обратите внимание на 2 запроса Http. Http 200 является «предполетным», чтобы увидеть, какие варианты CORS доступны.
- Если ваш API (.NET) выдает
HTTP 500 (Internal Server Error)
, он вернет страницу исключений для разработчика, и почтальон отобразит сообщение “no ‘Access-Control-Allow-Origin’ header is present on the requested resource”
- , это неправильно .
- Фактическая проблема в этом случае заключается в том, что страницу исключений разработчика нельзя вернуть с сервера клиенту (браузеру), работающему в другом источнике.
Я бы хотел с уважением указать следующее:
- Спецификация CORS гласит, что установка происхождения для '*' (все источники) недопустима, если присутствует заголовок Access-Control-Allow-Credentials.
AllowAnyOrigin()
не рекомендуется в производственной среде, если вы не хотите, чтобы кто-либо использовал ваш API, и вы не будете вводить учетные данные.
- Вам не нужно настраивать CORS на уровне контроллера с атрибутом
EnableCors
при использовании нескольких политик CORS.
- Настроить несколько политик CORS с ASP.NET Core очень просто ( см. Учебное пособие ниже )
Следующие статьи заслуживают рассмотрения:
ASP.NET Core 2.2 не позволяет разрешать учетные данные с AllowAnyOrigin ()
Включение запросов перекрестного источника (CORS) в ASP.NET Core
ключевых точек (тдр;):
- CORS Middleware должно предшествовать любым определенным конечным точкам в конфигурации вашего приложения.
- URL должен быть указан без завершающего слеша
(/)
.
- Если браузер отправляет учетные данные, но ответ не содержит
действительный заголовок Access-Control-Allow-Credentials , браузер не
предоставьте ответ приложению, и запрос перекрестного источника завершится неудачей.
- Спецификация CORS также гласит, что установка начала координат на "*" (все
origin) недействителен, если заголовок Access-Control-Allow-Credentials имеет
подарок.
- Если браузер поддерживает CORS, он автоматически устанавливает эти заголовки для запросов из разных источников.
- Если ответ не включает заголовок Access-Control-Allow-Origin , запрос перекрестного источника завершается неудачей.
Учебное пособие по CORS: (2) Angular Clients + ASP.NET Core
Создание решения Visual Studio
md c:\s\a
cd c:\s\a
c:\s\a>dotnet new sln -n solutionName
Создание базового проекта ASP.NET
c:\s\a>md s
c:\s\a>cd s
c:\s\a\s>dotnet new webapi -o api -n api
Настройки запуска API и настройка CORS
launchSettings.json
Клон развития профиля в промежуточный профиль
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iis": {
"applicationUrl": "http://localhost:myIISApiPortNumber",
"sslPort": myIISApiSSLPortNumber
},
"iisExpress": {
"applicationUrl": "http://localhost:myIISExpressApiPortNumber",
"sslPort": myIISExpressApiSSLPortNumber
}
},
"profiles": {
"Development (IIS Express)": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "api/values",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Staging (IIS Express)": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "api/values",
"applicationUrl": "https://localhost:myIISExpressApiSSLPortNumber;http://localhost:myIISExpressApiPortNumber",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Staging"
}
},
"Production (IIS)": {
"commandName": "IIS",
"launchBrowser": true,
"launchUrl": "api/values",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Production"
},
"applicationUrl": "https:localhost:myIISApiSSLPortNumber;http://localhost:myIISApiPortNumber"
}
}
}
startup.cs
Добавить конфигурацию CORS
public class Startup
{
public IConfiguration Configuration { get; }
public IServiceCollection _services { get; private set; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
_services = services;
RegisterCorsPolicies();
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseCors("DevelopmentCorsPolicy");
app.UseDeveloperExceptionPage();
}
else if (env.IsStaging())
{
app.UseCors("StagingCorsPolicy");
}
else
{
app.UseCors("ProductionCorsPolicy");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMvc(); // CORS middleware must precede any defined endpoints
}
private void RegisterCorsPolicies()
{
string[] localHostOrigins = new string[] {
"http://localhost:4200", "http://localhost:3200"};
string[] stagingHostOrigins= new string[] {
"http://localhost:4200"};
string[] productionHostOrigins = new string[] {
"http://yourdomain.net", "http://www.yourdomain.net",
"https://yourdomain.net", "https://www.yourdomain.net"};
_services.AddCors(options => // CORS middleware must precede any defined endpoints
{
options.AddPolicy("DevelopmentCorsPolicy", builder =>
{
builder.WithOrigins(localHostOrigins)
.AllowAnyHeader().AllowAnyMethod();
});
options.AddPolicy("StagingCorsPolicy", builder =>
{
builder.WithOrigins(stagingHostOrigins)
.AllowAnyHeader().AllowAnyMethod();
});
options.AddPolicy("ProductionCorsPolicy", builder =>
{
builder.WithOrigins(productionHostOrigins)
.AllowAnyHeader().AllowAnyMethod();
});
//options.AddPolicy("AllowAllOrigins",
// builder =>
// {
// WARNING: ASP.NET Core 2.2 does not permit allowing credentials with AllowAnyOrigin()
// cref: https://docs.microsoft.com/en-us/aspnet/core/migration/21-to-22?view=aspnetcore-2.2&tabs=visual-studio
// builder.AllowAnyOrigin()
// .AllowAnyHeader().AllowAnyMethod();
// });
//options.AddPolicy("AllowSpecificMethods",
// builder =>
// {
// builder.WithOrigins(productionHostOrigins)
// .WithMethods("GET", "POST", "HEAD");
// });
//options.AddPolicy("AllowSpecificHeaders",
// builder =>
// {
// builder.WithOrigins(productionHostOrigins)
// .WithHeaders("accept", "content-type", "origin", "x-custom-header");
// });
//options.AddPolicy("ExposeResponseHeaders",
// builder =>
// {
// builder.WithOrigins(productionHostOrigins)
// .WithExposedHeaders("x-custom-header");
// });
//options.AddPolicy("AllowCredentials",
// WARNING: ASP.NET Core 2.2 does not permit allowing credentials with AllowAnyOrigin() cref: https://docs.microsoft.com/en-us/aspnet/core/migration/21-to-22?view=aspnetcore-2.2&tabs=visual-studio
// builder =>
// {
// builder.WithOrigins(productionHostOrigins)
// .AllowCredentials();
// });
//options.AddPolicy("SetPreflightExpiration",
// builder =>
// {
// builder.WithOrigins(productionHostOrigins)
// .SetPreflightMaxAge(TimeSpan.FromSeconds(2520));
// });
});
}
}
- Запуск API отладки с профилем разработки (IIS Express)
Установить точку останова на ValuesController.Get()
Тест API с почтальоном:
https://localhost:myApiPortNumber/api/values
- Заголовок и значения Access-Control-Allow-Origin должны быть в ответе
Создание приложения Angular
c:\s\a\s>ng new Spa1 --routing (will automatically create Spa folder)
Запустить приложение Spa1
c:\s\a\s>cd Spa1
c:\s\a\s\Spa1>Ng serve
Просмотр http://localhost:4200/
- Spa1 должен запуститься успешно
Реализация COR в Spa1
app.module.ts
import { HttpClientModule } from '@angular/common/http';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
HttpClientModule,
BrowserModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
app.component.ts
import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
title = 'Spa1';
values: any;
apiUrl: string = environment.apiUrl;
valuesUrl = this.apiUrl + "values";
constructor(private http: HttpClient) { }
ngOnInit() {
this.getValues();
}
getValues() {
this.http.get(this.valuesUrl).subscribe(response => {
this.values = response;
}, error => {
console.log(error);
});
}
}
app.component.html
<div style="text-align:center">
<h1>
Welcome to {{ title }}!
</h1>
</div>
<h2>Values</h2>
<p *ngFor="let value of values">
{{value}}
</p>
<router-outlet></router-outlet>
environment.ts
export const environment = {
production: false,
apiUrl: 'https://localhost:myApiPortNumber/api/'
};
Запустить приложение Spa1
c:\s\a\s\Spa1>Ng serve
Просмотр http://localhost:4200/
- Chrome & Edge: теперь должен успешно выполнять запросы CORs
- Safari: я не тестировал
- Firefox: может заблокировать запрос из-за ненадежного сертификата.
Один из способов исправления блока Firefox:
FireFox | Варианты | Конфиденциальность и безопасность | Безопасность | Сертификаты |
[Просмотр сертификатов]:
Диспетчер сертификатов | [Добавить исключение]:
add localhost
Тест CORS
Clone Spa1
c:\s\a\s>xcopy /s /i Spa1 Spa2
Название рефакторинга Spa2
app.component.ts
export class AppComponent implements OnInit {
title = 'Spa2';
}
Запустить приложение Spa2 на порт 3200
c:\s\a\s\Spa2>ng serve --port 3200
Просмотр http://localhost:3200/
- Массив значений должен отображаться на веб-странице
Прекратить отладку API с помощью профиля разработки (IIS Express)
Запуск API отладки с Промежуточный профиль (IIS Express)
Просмотр http://localhost:4200/
- Массив значений должен отображать на веб-странице.
Просмотр http://localhost:3200/
- Массив значений не должен отображать на веб-странице.
проверка ответа Http с помощью инструментов разработчика:
- Заголовок и значения Access-Control-Allow-Origin не должны быть в ответе