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

.NET 8 Web API+Angular部署IIS后出现405 Method Not Allowed错误求助

.NET 8 Web API部署IIS后405 Method Not Allowed错误解决

问题背景

基于.NET 8 Web API + Angular的同解决方案应用,本地Visual Studio运行正常,部署到IIS后出现405 Method Not Allowed错误。已尝试启用请求筛选、追踪、移除webDev处理程序、移除过滤器中间件、启用全局CORS,但问题未解决,怀疑与JWT认证相关。

已尝试操作

  • 启用请求筛选
  • 启用追踪
  • 移除webDev等处理程序
  • 移除过滤器与中间件
  • 为所有源启用CORS

关键信息

Program.cs代码

var builder = WebApplication.CreateBuilder(args);

#region Configure JWT authentication

// Load configuration from appsettings.json

builder.Configuration.AddJsonFile("appsettings.json");

// Configure JWT authentication

builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;

}).AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ValidIssuer = builder.Configuration.GetSection("Jwt:Issuer").Value,
        ValidAudience = builder.Configuration.GetSection("Jwt:Audience").Value,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration.GetSection("Jwt:SecretKey").Value))
    };

    // Optionally, configure other JwtBearer options
});

builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "l", Version = "v1" });
    c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        In = ParameterLocation.Header,
        Description = "Please enter the Bearer token",
        Name = "Authorization",
        Type = SecuritySchemeType.ApiKey
    });
    c.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
    {
        new OpenApiSecurityScheme
        {
            Reference = new OpenApiReference
            {
                Type = ReferenceType.SecurityScheme,
                Id = "Bearer"
            }
        },
        Array.Empty<string>()
    }
    });
});

#endregion

builder.Services.AddResponseCompression(options =>
{
    options.Providers.Add<GzipCompressionProvider>();
    options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
        new[] { "application/json" });
});

#region Add services to the container

builder.Configuration.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json", optional: true)
            .AddEnvironmentVariables();

var connection = builder.Configuration.GetConnectionString("PublicPortalDBcontext");
var SecurityKey = builder.Configuration.GetSection("Jwt:SecretKey");
var Issuer = builder.Configuration.GetSection("Jwt:Issuer");
var audience = builder.Configuration.GetSection("Jwt:Audience");

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddControllersWithViews();
//Services Initialization

builder.Services.AddDbContext<DbContext>(options =>
        options.UseSqlServer(connection));

builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();


#endregion

#region CORS 

builder.Services.AddCors(options =>
{
    options.AddPolicy("EnableCORS", cbuilder =>
    {
        cbuilder.WithOrigins(builder.Configuration.GetSection("corsURL:devUrl").Value
                            , builder.Configuration.GetSection("corsURL:testUrl").Value
                            , builder.Configuration.GetSection("corsURL:prodUrl").Value)
        .AllowAnyHeader()
        .AllowAnyMethod()
        .AllowCredentials();
    });
});

#endregion

#region LOGGER

var logger = new LoggerConfiguration()
        .ReadFrom.Configuration(builder.Configuration)
        .Enrich.FromLogContext()
        .CreateLogger();

builder.Logging.ClearProviders();
builder.Logging.AddSerilog(logger);


#endregion LOGGER

#region Configure the HTTP request pipeline

var app = builder.Build();

// Configure the HTTP request pipeline.

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI(c =>
    {
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "Portal V1");
        c.RoutePrefix = "swagger";  // Optional: Set the Swagger UI route prefix
    });
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Error");
}

//exception handling and logging middleware

app.UseMiddleware<ExceptionHandlingMiddleware>();

app.UseCors("EnableCORS");

app.UseStaticFiles();

app.UseHttpsRedirection();

app.UseResponseCompression();

app.UseAuthentication();

app.UseAuthorization();

app.MapControllers();

app.MapFallbackToFile("/index.html");

app.Run();

#endregion

请求头

accept:
application/json, text/plain, */*
accept-encoding:
gzip, deflate, br, zstd
accept-language:
en-US,en;q=0.9,ta;q=0.8
connection:
keep-alive
content-length:
75
content-type:
application/json; charset=UTF-8
host:
localhost:85
origin:
http://localhost:85
referer:
http://localhost:85/
sec-ch-ua:
"Not)A;Brand";v="99", "Google Chrome";v="127", "Chromium";v="127"
sec-ch-ua-mobile:
?0
sec-ch-ua-platform:
"Windows"
sec-fetch-dest:
empty
sec-fetch-mode:
cors
sec-fetch-site:
same-origin
user-agent:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36
withcredentials:
true

响应头

HTTP/1.1 405 Method Not Allowed
Cache-Control: no-cache,no-store
Pragma: no-cache
Transfer-Encoding: chunked
Allow: GET, HEAD
Expires: -1
Server: Microsoft-IIS/10.0
Access-Control-Allow-Origin: *
X-Powered-By: ASP.NET
Date: Mon, 12 Aug 2024 08:11:50 GMT

解决方案步骤

1. 彻底禁用IIS WebDAV模块

WebDAV是IIS默认启用的模块,会拦截POST/PUT/DELETE等请求导致405。在项目根目录添加web.config(若无则新建),加入以下配置:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="false">
      <remove name="WebDAVModule" />
    </modules>
    <handlers>
      <remove name="WebDAV" />
      <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
    </handlers>
    <aspNetCore processPath="dotnet" arguments=".\YourAppName.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="inprocess" />
  </system.webServer>
</configuration>

替换YourAppName.dll为实际程序集名称。

2. 调整路由与中间件顺序

  • 给所有API控制器添加[Route("api/[controller]")]前缀,避免请求被Angular的MapFallbackToFile拦截
  • 确保MapFallbackToFile放在MapControllers之后,保证API请求优先匹配路由

3. 验证JWT配置与日志

  • 检查部署环境appsettings.json中JWT的Issuer、Audience、SecretKey是否与本地一致,且不为空
  • 添加JWT事件日志排查认证问题:
.AddJwtBearer(options =>
{
    // 原有TokenValidationParameters配置...

    options.Events = new JwtBearerEvents
    {
        OnAuthenticationFailed = context =>
        {
            Serilog.Log.Error($"JWT认证失败: {context.Exception.Message}");
            return Task.CompletedTask;
        },
        OnChallenge = context =>
        {
            Serilog.Log.Warning($"JWT触发Challenge: {context.Error}, {context.ErrorDescription}");
            return Task.CompletedTask;
        }
    };
});

查看Serilog日志确认是否存在认证失败情况。

4. 修复CORS配置冲突

响应头返回Access-Control-Allow-Origin: *但请求带withcredentials: true,违反CORS规则(带凭证请求不能用通配符origin)。确保部署环境appsettings.json中corsURL包含当前请求origin(http://localhost:85),或临时修改CORS策略测试:

options.AddPolicy("EnableCORS", cbuilder =>
{
    cbuilder.WithOrigins("http://localhost:85")
            .AllowAnyHeader()
            .AllowAnyMethod()
            .AllowCredentials();
});

5. 检查IIS请求筛选

在IIS管理器中进入站点 -> 请求筛选 -> HTTP方法,确保添加了所需的HTTP方法(如POST、PUT、DELETE等),避免IIS拦截这些请求。

总结

405错误多由IIS模块拦截、路由匹配错误或HTTP方法限制导致,优先排查WebDAV模块,再验证配置与中间件顺序,结合日志定位具体问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 18:17:01