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

.NET Core部署Azure App Service后SignalR报405方法不允许错误

问题描述

本地运行.NET Core Web API的SignalR配置完全正常,但部署到Azure App Service后,连接SignalR时收到405 Method Not Allowed错误。代码配置如下:

using AutoMapper;
using Azure.Storage.Blobs;
using DinkToPdf;
using DinkToPdf.Contracts;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Identity.Client;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using System;
using System.Reflection;
using System.Text;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("CorsPolicy",
         builder => builder.AllowAnyMethod()
        .SetIsOriginAllowed(_ => true)
        .AllowAnyHeader()
        .AllowCredentials());
});

builder.Services.AddScoped<IDapperWrapper, DapperWrapper>();

//Use below code for global authorization and add AllowAnonymous to login route.
//otherwise use Authorize in the required controller and/or action
builder.Services.AddControllers(options =>
{
    options.Filters.Add(new AuthorizeFilter());
});

builder.Services.AddApiVersioning(x =>
{
    x.DefaultApiVersion = new ApiVersion(1, 0);
    x.AssumeDefaultVersionWhenUnspecified = true;
    x.ReportApiVersions = true;
});

builder.Services.AddAntiforgery(antiforgeryOptions =>
{
    antiforgeryOptions.HeaderName = "X-XSRF-TOKEN";
    antiforgeryOptions.Cookie.HttpOnly = true;
    antiforgeryOptions.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});

#region --> JWT Authentication

builder.Services.Configure<JWTSettings>(options =>
{
    builder.Configuration.GetSection("Jwt").Bind(options);

    var securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(options.Secret));
    options.ValidFor = TimeSpan.FromMinutes(Convert.ToInt32(builder.Configuration["Jwt:ValidFor"]));
    options.SigningCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
});

JWTSettings jwtSettings = builder.Configuration.GetSection("Jwt").Get<JWTSettings>();
builder.Services.AddJWTAuthentication(jwtSettings);

#endregion

builder.Services.AddSingleton<CustomIDataProtection>();

builder.Services.AddSingleton(typeof(IConverter), new SynchronizedConverter(new PdfTools()));


builder.Services.AddSignalR(o => { o.EnableDetailedErrors = true; });

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseHsts();
}

app.UseMiddleware<ExceptionMiddleware>();

app.UseHttpsRedirection();

app.UseAuthentication();

app.UseRouting();

app.UseCors("CorsPolicy");
app.UseAntiforgeryToken();

app.UseAuthorization();

app.Use(async (context, next) =>
{
    if (!context.Response.Headers.ContainsKey("X-Frame-Options"))
    {
        context.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN");
    }
    await next();
});
app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "api",
        pattern: "{controller}/{action}/{id?}");

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

    endpoints.MapControllerRoute(
      name: "api",
      pattern: "{controller}/{action}/{date}");

    endpoints.MapControllerRoute(
      name: "api",
      pattern: "{controller}/{action}/{email}");
});

if (bool.Parse(builder.Configuration["UseSwagger"]))
{
    app.UseSwagger();
    app.UseSwaggerUI(c =>
    {
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "API");
        c.DefaultModelsExpandDepth(-1);
    });
}
app.MapHub<HjsHub>("/hjsHub");
app.Run();

排查与解决步骤

一、代码层面调整

  1. 修正SignalR Hub映射顺序
    当前代码中app.MapHub<HjsHub>("/hjsHub")放在了UseEndpoints块外部,可能导致路由匹配优先级异常,控制器路由会先拦截SignalR的握手请求(如OPTIONS方法),返回405。
    修改方式:将Hub映射移至UseEndpoints内部:

    app.UseEndpoints(endpoints =>
    {
        // 保留现有控制器路由配置
        endpoints.MapControllerRoute(
            name: "api",
            pattern: "{controller}/{action}/{id?}");
        // ...其他控制器路由
    
        // 添加SignalR Hub映射
        endpoints.MapHub<HjsHub>("/hjsHub");
    });
    
  2. Antiforgery验证适配
    代码中启用了Antiforgery,SignalR连接时需携带X-XSRF-TOKEN头:

    • 客户端需添加请求头:
      const connection = new signalR.HubConnectionBuilder()
          .withUrl("/hjsHub", {
              headers: {
                  "X-XSRF-TOKEN": document.cookie.split('; ').find(row => row.startsWith('XSRF-TOKEN=')).split('=')[1]
              }
          })
          .build();
      
    • 若无需对SignalR做Antiforgery验证,可给HjsHub类添加[IgnoreAntiforgeryToken]特性。

二、Azure App Service配置调整

  1. 启用WebSockets协议
    SignalR默认依赖WebSockets传输,Azure App Service默认未启用该协议:

    • 登录Azure门户,进入目标App Service
    • 打开配置 > 常规设置
    • 将Web sockets设置为启用,保存后重启服务
  2. 统一CORS配置
    Azure门户的CORS设置会覆盖代码中的配置,避免双重配置:

    • 进入App Service的CORS设置
    • 若代码已处理CORS,选择禁用CORS;若需门户配置,添加客户端源地址并勾选允许凭据
  3. 检查HTTP版本
    确保App Service使用HTTP/2(SignalR对其支持更优):

    • 在配置 > 常规设置中,将HTTP版本设置为2.0
  4. 排查URL重写规则
    若配置了URL重写规则,确保/hjsHub路径未被拦截或重写,可在配置 > 路径映射中检查重写规则。

三、额外排查

  • 启用SignalR详细错误日志,在Azure App Service的监控 > 日志中查看具体错误详情
  • 直接访问https://<你的App Service域名>/hjsHub/negotiate,观察返回的状态码与内容,定位请求拦截点

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 19:05:36