I’m using Vue + ts + @microsoft/signalr in my project. I want to develop a chat with SignalR. I’m also using C# + ASP.NET Core on the backend.
So, I have written the following code on my backend:
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 class:
[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");
}
}
and frontend:
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);
}
}
I use ChatService in 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>
After load_data() method and connection.start() i get this message:
Access to fetch at
‘http://localhost:8181/chat/negotiate?negotiateVersion=1’ from origin
‘http://localhost:5173’ has been blocked by CORS policy: Response to
preflight request doesn’t pass access control check: Redirect is not
allowed for a preflight request.
I have used different cors policy settings on backend and different headers in connectionBuilder.WithUrl(…), but it does not help me.