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

Vue+TS+SignalR连接ASP.NET Core后端失败,CORS预检请求报错

SignalR聊天功能CORS预检重定向错误问题

技术栈

  • 前端:Vue + TypeScript + @microsoft/signalr
  • 后端:C# + ASP.NET Core + Docker

后端Program.cs代码

using DomovoiBackend.API.Auth.ServiceDecorators;
using DomovoiBackend.API.Controllers;
using DomovoiBackend.API.JsonInheritance;
using DomovoiBackend.Application;
using DomovoiBackend.Application.Services.CounterAgentServices.Interfaces;
using DomovoiBackend.Persistence;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Cors.Infrastructure;
using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSignalR();
builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1",
        new OpenApiInfo()
        {
            Title = "DomovoiBackend.API",
            Version = "v1"
        });
    options.UseAllOfToExtendReferenceSchemas();
    options.UseAllOfForInheritance();
    options.UseOneOfForPolymorphism();
});

var inheritanceConfigurations =
    new AssemblyInheritanceConfiguration()
        .CreateAllConfigurations();

builder.Services.AddControllers().AddNewtonsoftJson(
    options =>
    {
        foreach(var inheritanceConfiguration in inheritanceConfigurations)
            options.SerializerSettings.Converters.Add(inheritanceConfiguration);
    });

builder.Services.AddApplicationLayer()
    .AddMappers()
    .AddPersistence(builder.Configuration)
    .CreateDatabase()
    .FillDatabase();

builder.Services.Decorate<ICounterAgentService, AuthCounterAgentService>();

builder.Services.AddHttpContextAccessor();

builder.Services.AddDistributedMemoryCache();

builder.Services.AddAuthorization();
builder.Services.AddSession();

builder.Services.AddCors();

builder.Services
    .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {
        options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
        options.SlidingExpiration = true;
        options.LoginPath = "/CounterAgent/Login";
        options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
        options.Cookie.SameSite = SameSiteMode.Strict;
    });

builder.Services.AddAuthorizationBuilder()
    .AddPolicy("AuthenticatedCounterAgent", policy => policy.RequireClaim("CounterAgentId"));

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();
app.UseSession();

app.UseCors(policyBuilder =>
{
    policyBuilder.WithOrigins("http://localhost:5173")
        .AllowAnyHeader()
        .AllowAnyMethod()
        .AllowCredentials()
        .SetIsOriginAllowedToAllowWildcardSubdomains()
        .SetIsOriginAllowed((host) => true);
});

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.MapHub<ChatHub>("/chat");

app.Run();

public partial class Program;

ChatHub类代码

[Authorize]
public class ChatHub : Hub
{
    [Authorize]
    public async Task Send(string message, string idReceiver)
    {
        var nameSender = Context.User?.Identity?.Name;
        if (Clients.User(idReceiver) == null)
            throw new UnknownUser($"User with id \"{idReceiver}\" does not exist in the system");
        await Clients.User(idReceiver).SendAsync("Receive", message, nameSender);
        await Clients.Caller.SendAsync("NotifySendMethod", "Ok");
    }
}

前端ChatService代码

import  { HttpTransportType, HubConnection, HubConnectionBuilder } from "@microsoft/signalr";
import { inject, singleton } from "tsyringe";
import { Chat } from "../../application/useCases/chat";
import { Message } from "../../domain/chat/message";
import { MessageStatus } from "../../domain/enums/chatEnum";
import { CounterAgent } from "../../domain/counteragents/counteragent";
import { CounteragentViewModel } from "../../viewModel/CounteragentViewModel";
import { ICouterAgentMapper } from "../../mappers/interfaces/couteragentMapperInterface";

@singleton()
export class ChatService {

    private readonly _connection: HubConnection;
    private _listener!: Chat;
    
    public constructor(@inject("chatURL") private readonly _baseURL: string,
        @inject("ICouterAgentMapper") private readonly _userMapper: ICouterAgentMapper) {
        this._connection = new HubConnectionBuilder()
            .withUrl(this._baseURL, {
                headers: { 
                    'Access-Control-Allow-Origin': '*',
                 },
                withCredentials: true,
                transport: HttpTransportType.WebSockets,
            })
            .build();
        this._connection.on("Receive", (text: string, idSender: string) => this.receiveMessage(text, idSender));
    }

    public start(idCompanion :string, user: CounteragentViewModel): void {
        let counteragent = this._userMapper.mapViewModelToCouterAget(user);
        this._listener = new Chat(this, true, idCompanion, counteragent);
        this._connection.start();
    }

    public close(): void {
        this._connection.stop()
    }

    public get state(): string {
        return this._connection.state.toString();
    }

    public get messages(): Message[] {
        return this._listener.messages;
    }

    public async sendMessage(text: string): Promise<void> {
        let message = new Message("", text, this._listener.idCompanion, this._listener.user.id!);
        try {
            let response = await this._connection.invoke("Send", message.text, message.recieverId);
            message.status = MessageStatus.Send;            
        }
        catch(e){
            message.status = MessageStatus.NotSend;
            throw(e);
        }
        finally {
            this._listener.addMessage(message);
        }
    }

    public receiveMessage(text: string, idSender: string): void {
        let message = new Message("", text, idSender, this._listener.user.id!);
        message.status = MessageStatus.Recieve;
        this._listener.addMessage(message);
    } 
}

Vue组件ChatView.vue使用代码

//...
 export default defineComponent({
        components: { Header},
        data() {
            return {
                store: store,
                chatService: {} as ChatService, 
                //...
            };
        },
        computed: {
        },
        mounted() {
        //...
            this.load_data();
        },
        methods: {
            load_data(){
                let chatService = container.resolve(ChatService);
                this.chatService = chatService;
                chatService.start(this.user.id, this.store.state.user!);
                this.messages = this.chatService.messages;
            },
        //...
        },
    })
    </script>

报错信息

从源'http://localhost:5173'访问'http://localhost:8181/chat/negotiate?negotiateVersion=1'时被CORS策略阻止:预检请求的响应未通过访问控制检查:不允许对预检请求进行重定向。

@microsoft_signalr.js?v=12bba02c:2110 Uncaught (in promise) Error: 无法完成与服务器的协商:TypeError: Failed to fetch
at HttpConnection._getNegotiationResponse
(@microsoft_signalr.js?v=12bba02c:2110:29)

ChatService.ts:35 [2024-06-03T18:17:15.726Z] Error: 无法启动连接:Error: 无法完成与服务器的协商:TypeError: Failed to fetch

ChatService.ts:35 [2024-06-03T18:17:15.725Z] Error: 无法完成与服务器的协商:TypeError: Failed to fetch

已尝试调整后端CORS策略设置和前端connectionBuilder.WithUrl中的请求头,均未解决问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 20:25:53