使用C#客户端连接SignalR Hub时Context.User为null的原因排查
问题:SignalR C#客户端调用时Hub中Context.User为null,JavaScript客户端正常
场景说明
我有一个ASP.NET MVC应用,使用SignalR实现实时竞价功能,需要同时支持JavaScript和C#(控制器中调用)客户端。使用JavaScript客户端连接Hub时,Hub中的Context.User能正确获取到已认证用户;但通过控制器内的C#客户端调用时,Context.User始终为null。
相关代码
Startup.cs
namespace DesignAndBuilding.Web { using System.Reflection; using AutoMapper; using DesignAndBuilding.Data; using DesignAndBuilding.Data.Common; using DesignAndBuilding.Data.Common.Repositories; using DesignAndBuilding.Data.Models; using DesignAndBuilding.Data.Repositories; using DesignAndBuilding.Data.Seeding; using DesignAndBuilding.Services; using DesignAndBuilding.Services.Mapping; using DesignAndBuilding.Services.Messaging; using DesignAndBuilding.Web.Hubs; using DesignAndBuilding.Web.ViewModels; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; public class Startup { private readonly IConfiguration configuration; public Startup(IConfiguration configuration) { this.configuration = configuration; } public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddDbContext<ApplicationDbContext>( options => options.UseSqlServer(this.configuration.GetConnectionString("DefaultConnection"))); services.AddDefaultIdentity<ApplicationUser>(IdentityOptionsProvider.GetIdentityOptions) .AddRoles<ApplicationRole>().AddEntityFrameworkStores<ApplicationDbContext>(); services.AddSignalR(options => { options.EnableDetailedErrors = true; }) .AddMessagePackProtocol(); services.Configure<CookiePolicyOptions>( options => { options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); services.AddAutoMapper(typeof(Startup), typeof(MappingProfile)); services.AddMemoryCache(); services.AddControllersWithViews( options => { options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()); }).AddRazorRuntimeCompilation(); services.AddRazorPages(); services.AddDatabaseDeveloperPageExceptionFilter(); services.AddSingleton(this.configuration); // Data repositories services.AddScoped(typeof(IDeletableEntityRepository<>), typeof(EfDeletableEntityRepository<>)); services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>)); services.AddScoped<IDbQueryRunner, DbQueryRunner>(); // Application services services.AddTransient<IEmailSender, NullMessageSender>(); services.AddTransient<IBuildingsService, BuildingsService>(); services.AddTransient<IUsersService, UsersService>(); services.AddTransient<IAssignmentsService, AssignmentsService>(); services.AddTransient<IBidsService, BidsService>(); services.AddTransient<INotificationsService, NotificationsService>(); services.AddTransient<AuthenticationService>(); // Auto Mapper Configurations services.AddSingleton(provider => new MapperConfiguration(mc => { mc.AddProfile(new MappingProfile()); }).CreateMapper()); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { AutoMapperConfig.RegisterMappings(typeof(ErrorViewModel).GetTypeInfo().Assembly); // Seed data on application startup using (var serviceScope = app.ApplicationServices.CreateScope()) { var dbContext = serviceScope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); dbContext.Database.Migrate(); new ApplicationDbContextSeeder().SeedAsync(dbContext, serviceScope.ServiceProvider).GetAwaiter().GetResult(); } if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseMigrationsEndPoint(); } else { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseCookiePolicy(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints( endpoints => { endpoints.MapHub<NotificationsHub>("/notificationshub"); endpoints.MapHub<BidsHub>("/bidshub"); endpoints.MapControllerRoute("areaRoute", "{area:exists}/{controller=Home}/{action=Index}/{id?}"); endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}"); endpoints.MapRazorPages(); }); } } }
JavaScript客户端代码
console.log('Bids script loaded successfully!'); setupConnection = async function start() { let connection = null; connection = await new signalR.HubConnectionBuilder() .withUrl("/bidshub") .build(); await connection.on('newbidplaced', (obj) => { console.log(obj); }) await connection.start() .catch(err => console.error(err.toString())) .then(() => { connection.invoke('NewBid', '1', 'testUser', 5.4); }); } setupConnection();
控制器中的C#客户端代码
var connection = new HubConnectionBuilder() .AddMessagePackProtocol() .WithUrl($"https://{this.Request.Host}/bidshub", options => { options.UseDefaultCredentials = true; }) .WithAutomaticReconnect() .Build(); await connection.StartAsync(); await connection.InvokeAsync("NewBid", strId, user.Id, bidViewModel.BidPrice); await connection.DisposeAsync();
差异与问题原因
- JavaScript客户端的认证逻辑:浏览器发起SignalR连接时,会自动携带当前用户的ASP.NET Identity认证Cookie(同域请求下默认携带),SignalR服务端能通过Cookie解析出用户身份,因此
Context.User正常赋值。 - C#客户端的问题根源:控制器内创建的SignalR客户端是一个全新的独立连接,
UseDefaultCredentials = true仅适配Windows身份验证场景(如域环境),无法自动传递当前请求的ASP.NET Identity认证Cookie。没有认证凭据,服务端无法识别用户,导致Context.User为null。
解决方案
推荐方案:使用IHubContext直接调用(无需创建C#客户端)
在控制器中通过依赖注入获取IHubContext<BidsHub>,直接调用Hub方法,这样会复用当前请求的用户身份,无需额外处理认证:
// 控制器构造函数注入IHubContext private readonly IHubContext<BidsHub> _hubContext; private readonly UserManager<ApplicationUser> _userManager; public YourController(IHubContext<BidsHub> hubContext, UserManager<ApplicationUser> userManager) { _hubContext = hubContext; _userManager = userManager; } // 在Action中调用Hub逻辑 public async Task<IActionResult> PlaceBid(string strId, BidViewModel bidViewModel) { var currentUser = await _userManager.GetUserAsync(User); // 调用Hub的客户端方法推送消息 await _hubContext.Clients.All.SendAsync("newbidplaced", new { Id = strId, UserId = currentUser.Id, Price = bidViewModel.BidPrice }); // 如果需要执行Hub中NewBid方法的业务逻辑,建议抽离到服务类中,再通过IHubContext推送结果 // 避免直接在Hub中写复杂业务逻辑 return RedirectToAction("YourAction"); }
备选方案:手动传递认证Cookie(不推荐)
如果必须使用C#客户端(比如非控制器场景),可以手动从当前请求中获取认证Cookie并添加到SignalR请求头:
// 获取当前请求的Identity认证Cookie var authCookie = Request.Cookies[".AspNetCore.Identity.Application"]; var connection = new HubConnectionBuilder() .AddMessagePackProtocol() .WithUrl($"https://{Request.Host}/bidshub", options => { // 手动添加Cookie到请求头 if (!string.IsNullOrEmpty(authCookie)) { options.Headers.Add("Cookie", $".AspNetCore.Identity.Application={authCookie}"); } }) .Build(); await connection.StartAsync(); await connection.InvokeAsync("NewBid", strId, user.Id, bidViewModel.BidPrice); await connection.DisposeAsync();
注意:这种方式存在Cookie过期、跨域限制等问题,仅在特殊场景下使用。
内容的提问来源于stack exchange,提问作者Georgi Kolev
相关产品推荐
相关产品推荐

