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

Blazor类构造函数注入变量始终为空?求排查解决

Blazor Server 构造注入对象为空及数据库动态选择问题排查与修复

核心问题根源

  1. 直接实例化Lookup而非依赖注入:Program.cs中var dinfo = new Lookup();调用了无参构造函数,导致_navigationManager、_contextFactory等依赖服务完全未初始化,自然为空。
  2. 启动阶段无法获取NavigationManager:NavigationManager是请求级服务,仅在处理具体用户请求时才存在,应用启动阶段(Program.cs)没有请求上下文,无法获取BaseURI。
  3. 异步操作误用.Result:直接调用异步方法的.Result会触发线程阻塞,甚至死锁,违反Blazor Server异步编程规范。
  4. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 23:34:58