You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular+.NET项目CORS问题:GET可用但PUT/POST/DELETE请求被拦截

跨域问题排查与解决方案(Angular + .NET应用)

问题背景

开发基于Angular和.NET的Web应用,已配置CORS,但仅GET请求正常,PUT/POST/DELETE等请求被浏览器CORS策略拦截,Postman中可正常执行。

浏览器报错信息

Access to XMLHttpRequest at 'https://localhost:44312/api/Collaborator/7' from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

相关代码

.NET Startup代码

using System;
using FluentValidation.AspNetCore;
using MediatR;
using Microsoft.AspNetCore.Authentication.Negotiate;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using OB.TimeSheet.Application;
using OB.TimeSheet.Application.Common.Interfaces;
using OB.TimeSheet.Infrastructure;
using OB.TimeSheet.Infrastructure.Data;
using OB.TimeSheet.src.Application.Common.Interfaces;
using OB.TimeSheet.WebUI.Services;

 namespace OB.TimeSheet.WebUI
{
    public class Startup
  {
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddApplication();
        services.AddInfrastructure(Configuration);

        services.AddDbContext<OBDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("MyDbContext")));

        services.AddScoped<IApplicationDbContext, OBDbContext>();

        services.AddRouting();

        services.AddCors(options =>
        {
            options.AddPolicy("OBTimesheetPolicy", builder =>
            {
                builder.WithOrigins("http://localhost:4200") // Add your Angular application's origin
                    .AllowAnyHeader()
                    .AllowAnyMethod()
                    .AllowCredentials();
            });
        });

        services.AddMediatR(typeof(Startup));

        services.AddSingleton<ICurrentUserService, CurrentUserService>();

        services.AddHttpContextAccessor();

        services.AddHealthChecks();

        services.AddSwaggerGen();

        services.AddControllers()
            .AddFluentValidation();

        services.AddFluentValidationAutoValidation()
            .AddFluentValidationClientsideAdapters();

        services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
            .AddNegotiate();

        services.AddAuthorization(options =>
        {
            options.AddPolicy("AllUsers", policy => policy.RequireAuthenticatedUser());
        });

        services.AddRazorPages();

        services.Configure<ApiBehaviorOptions>(options =>
        {
            options.SuppressModelStateInvalidFilter = true;
        });
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseCors("OBTimesheetPolicy");

        app.UseAuthentication();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();

            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller}/{action=Index}/{id?}");

            endpoints.MapControllerRoute(
                name: "api",
                pattern: "api/{controller}/{action=Index}/{id?}");

            endpoints.MapRazorPages();
        });

        app.UseHealthChecks("/health");

        app.UseSwagger();
        app.UseSwaggerUI(options =>
        {
            options.DocumentTitle = "OB.TimeSheet API";
            options.RoutePrefix = string.Empty;

            options.DisplayOperationId();
            options.DisplayRequestDuration();

            options.SwaggerEndpoint("/swagger/v1/swagger.json", "OB.TimeSheet API V1");
        });
    }
}

Angular ApiService代码

import { Injectable } from '@angular/core';
import { Collaborator, Team } from 'src/app/models/api/api.models';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from 'src/environments/environment';

@Injectable({
  providedIn: 'root',
})
export class ApiService {

  private readonly options = {withCredentials: true};  
  private readonly apiUrl: string = environment.apiUrl;

  constructor(private httpClient: HttpClient) {}

  getCollaborator(): Observable<Collaborator[]> {
    return this.get<Collaborator[]>('/Collaborator');
  }

  updateCollaborator(collaborator: Collaborator): Observable<void> {
    return this.put<void>(`/Collaborator/${collaborator.id}`, collaborator);
  }

  getTeam(): Observable<Team[]> {
    return this.get<Team[]>('/Team');
  }

  private get<T>(endpoint: string): Observable<T> {
    return this.httpClient.get<T>(this.apiUrl + endpoint, this.options);
  }

  private put<T>(endpoint: string, body: any): Observable<T> {
    return this.httpClient.put<T>(this.apiUrl + endpoint, body, this.options);
  }
}

排查与修复方案

核心原因

PUT/POST/DELETE等非简单请求会先发送OPTIONS预检请求,而你的.NET应用中Negotiate认证中间件可能在CORS中间件生效前就拦截了OPTIONS请求,导致响应未携带CORS相关头。

具体修复步骤

  1. 跳过OPTIONS请求的认证检查
    在ConfigureServices中修改Negotiate认证配置,让OPTIONS请求绕过认证挑战:

    services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
        .AddNegotiate(options =>
        {
            options.Events = new NegotiateEvents
            {
                OnChallenge = context =>
                {
                    if (context.Request.Method == HttpMethod.Options.Method)
                    {
                        context.HandleResponse();
                        context.Response.StatusCode = StatusCodes.Status200OK;
                        return Task.CompletedTask;
                    }
                    return Task.CompletedTask;
                }
            };
        });
    
  2. 确保CORS中间件顺序正确
    已在代码中做到UseCors在UseAuthentication和UseAuthorization之前,保持此顺序不变。

  3. 显式为API控制器启用CORS
    在API控制器类上添加[EnableCors("OBTimesheetPolicy")]属性,确保CORS策略应用到所有API端点。

  4. 清除浏览器缓存
    浏览器可能缓存旧的CORS响应,清除缓存后再测试请求。

验证方法

重启.NET应用后,在浏览器开发者工具的Network标签中查看OPTIONS请求的响应头,确认包含Access-Control-Allow-Origin、Access-Control-Allow-Methods等CORS相关字段。


内容的提问来源于stack exchange,提问作者Bernardo Pereira

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 12:07:50