Blazor类构造函数注入变量始终为空?求排查解决
Blazor Server 构造注入对象为空及数据库动态选择问题排查与修复
核心问题根源
- 直接实例化Lookup而非依赖注入:Program.cs中
var dinfo = new Lookup();调用了无参构造函数,导致_navigationManager、_contextFactory等依赖服务完全未初始化,自然为空。 - 启动阶段无法获取NavigationManager:
NavigationManager是请求级服务,仅在处理具体用户请求时才存在,应用启动阶段(Program.cs)没有请求上下文,无法获取BaseURI。 - 异步操作误用.Result:直接调用异步方法的
.Result会触发线程阻塞,甚至死锁,违反Blazor Server异步编程规范。 - DbContext保留无参构造:自定义DbContext的无参构造会绕过依赖注入逻辑,导致实例创建不规范,同时增加误用风险。
修复步骤及代码调整
1. 修正Lookup类(移除无参构造,优化异步逻辑)
using Microsoft.AspNetCore.Components; using Microsoft.EntityFrameworkCore; namespace NewApp_Database.Data { public class Lookup { private readonly NavigationManager _navigationManager; private readonly IDbContextFactory<NewLookupDbContext> _contextFactoryNew; private readonly IDbContextFactory<LookupDbContext> _contextFactory; // 仅保留依赖注入构造函数,强制通过容器实例化 public Lookup(NavigationManager navigationManager, IDbContextFactory<NewLookupDbContext> newContextFactory, IDbContextFactory<LookupDbContext> contextFactory) { _navigationManager = navigationManager; _contextFactoryNew = newContextFactory; _contextFactory = contextFactory; } public async Task<NewDomain> LookupDomain() { // 简化主机名提取逻辑 var host = new Uri(_navigationManager.BaseUri).Host; if (!string.IsNullOrEmpty(host)) { await using var context = await _contextFactoryNew.CreateDbContextAsync(); return await context.Domain.FirstOrDefaultAsync(u => u.DomainName == host); } return null; } public async Task<NewDatabase> LookupDatabase() { var domain = await LookupDomain(); if (domain != null) { await using var context = await _contextFactoryNew.CreateDbContextAsync(); return await context.Database.FirstOrDefaultAsync(u => u.DatabaseID == domain.DatabaseID); } return null; } public async Task<NewServer> LookupServer() { var database = await LookupDatabase(); if (database != null) { await using var context = await _contextFactoryNew.CreateDbContextAsync(); return await context.Server.FirstOrDefaultAsync(u => u.ServerID == database.ServerID); } return null; } public async Task<Database> GetDatabase() { await using var context = await _contextFactory.CreateDbContextAsync(); return await context.tblDB.FirstOrDefaultAsync(u => u.fOrder == 0); } } }
2. 修正DbContext(移除无参构造,强制依赖注入)
LookupDbContext修改后
using Microsoft.EntityFrameworkCore; namespace NewApp_Database.Data { public class LookupDbContext : DbContext { public LookupDbContext(DbContextOptions<LookupDbContext> options) : base(options) { } public DbSet<Database> tblDB { get; set; } } }
NewLookupDbContext修改后
using Microsoft.EntityFrameworkCore; namespace NewApp_Database.Data { public class NewLookupDbContext : DbContext { public NewLookupDbContext(DbContextOptions<NewLookupDbContext> options) : base(options) { } public DbSet<NewDatabase> Database { get; set; } public DbSet<NewServer> Server { get; set; } public DbSet<NewDomain> Domain { get; set; } } }
3. 调整Program.cs(正确注册服务,动态处理数据库连接)
由于启动阶段无法获取请求上下文,需将数据库连接逻辑移至请求处理流程中,通过自定义DbContext工厂实现动态切换:
var builder = WebApplication.CreateBuilder(args); // 基础服务注册 builder.Services.AddAuthenticationCore(); builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); builder.Services.AddScoped<ProtectedSessionStorage>(); // 先注册Lookup用的DbContextFactory var defaultConn = builder.Configuration.GetConnectionString("DefaultConnection"); if (defaultConn.Contains("Lookup")) { builder.Services.AddDbContextFactory<NewLookupDbContext>(options => options.UseSqlServer(defaultConn)); } else { builder.Services.AddDbContextFactory<LookupDbContext>(options => options.UseSqlServer(defaultConn)); } // 注册Lookup为Scoped服务(与NavigationManager生命周期一致) builder.Services.AddScoped<Lookup>(); // 注册业务DbContext的动态工厂(示例:假设业务DbContext为AppDbContext) builder.Services.AddScoped<IDbContextFactory<AppDbContext>>(sp => { var lookup = sp.GetRequiredService<Lookup>(); var config = sp.GetRequiredService<IConfiguration>(); return new DynamicDbContextFactory(lookup, config); }); var app = builder.Build(); // 请求管道配置 app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.MapBlazorHub(); app.MapFallbackToPage("/_Host"); app.Run(); // 自定义动态DbContext工厂实现 public class DynamicDbContextFactory : IDbContextFactory<AppDbContext> { private readonly Lookup _lookup; private readonly IConfiguration _config; public DynamicDbContextFactory(Lookup lookup, IConfiguration config) { _lookup = lookup; _config = config; } public async Task<AppDbContext> CreateDbContextAsync(CancellationToken cancellationToken = default) { var cb = new SqlConnectionStringBuilder(); var defaultConn = _config.GetConnectionString("DefaultConnection"); if (defaultConn.Contains("Lookup")) { var server = await _lookup.LookupServer(); var db = await _lookup.LookupDatabase(); cb.DataSource = server?.ServerName ?? string.Empty; cb.InitialCatalog = db?.DatabaseName ?? string.Empty; cb.UserID = server?.ServerUserID ?? string.Empty; cb.Password = server?.ServerPassword ?? string.Empty; } else { var dbInfo = await _lookup.GetDatabase(); cb.DataSource = dbInfo?.DBServer ?? string.Empty; cb.InitialCatalog = dbInfo?.DBName ?? string.Empty; cb.UserID = dbInfo?.DBUserName ?? string.Empty; cb.Password = dbInfo?.DBPassWord ?? string.Empty; } var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>(); optionsBuilder.UseSqlServer(cb.ConnectionString); return new AppDbContext(optionsBuilder.Options); } }
关键注意事项
- 禁止启动阶段操作请求级服务:
NavigationManager、HttpContext等仅存在于请求上下文,启动时无法获取,必须在组件或中间件中使用。 - 全程使用await处理异步操作:绝对避免
.Result/.Wait(),否则会触发Blazor Server同步上下文死锁。 - 依赖服务必须通过容器实例化:所有需要注入依赖的类,都要通过DI容器获取实例,禁止直接
new。 - DbContext必须由DI管理:移除无参构造,确保所有DbContext实例都通过
DbContextFactory或容器创建,保证连接字符串和生命周期的正确性。
内容的提问来源于stack exchange,提问作者gbm0102
相关产品推荐
相关产品推荐

