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

Blazor Server根据ID获取用户时触发System.NullReferenceException问题

Blazor Server 获取用户时触发 NullReferenceException 问题

问题详情

开发首个 Blazor Server 应用,采用 Blazor Server 前端 + .NET 8 API 后端架构。根据 ID 获取指定用户时触发 System.NullReferenceException,硬编码 ID 测试仍报错,但已验证 API 端点功能正常。

错误堆栈信息:

System.NullReferenceException
  HResult=0x80004003
  Message=Object reference not set to an instance of an object.
  Source=CalibrationReportingToolFrontend
  StackTrace:
   at CalibrationReportingToolFrontend.Components.Pages.OtherPages.UserAdd.BuildRenderTree(RenderTreeBuilder __builder)

相关代码

Blazor 页面代码

@page "/User"
@page "/User/{Id:int}"
@rendermode InteractiveServer
@using System.Net.Http
@using Models
@using System.Text.Json;
@using System.Text
@using System.Security.Cryptography;
@inject HttpClient HttpClient
@inject NavigationManager NavManager


@if (Id == null)
{
    <h3>User Add</h3>
}
else
{
    <h3>User Edit</h3>
}

<form method="post" @onsubmit="AddUser" @formname="UserAdd" class="mt-4">
    <AntiforgeryToken />

    <div class="mb-3">
        <label for="name" class="form-label">Name:</label>
        <InputText type="text" @bind-Value="user!.Name" class="form-control" required />
    </div>

    <div class="mb-3">
        <label for="Company" class="form-label">Company:</label>
        <InputText type="text" @bind-Value="user!.Company" class="form-control" required />
    </div>

    <div class="mb-3">
        <label for="Department" class="form-label">Department:</label>
        <InputText type="text" @bind-Value="user!.Department" class="form-control" required />
    </div>

    <div class="mb-3">
        <label for="email" class="form-label">Email:</label>
        <InputText type="email" @bind-Value="user!.Email" class="form-control" required />
    </div>

    <div class="mb-3">
        <label for="Password" class="form-label">Password:</label>
        <InputText type="password" @bind-Value="user!.Password" class="form-control" required />
    </div>

    <button type="submit" class="btn btn-primary">Submit</button>
</form>


@code {
    [Parameter]
    public int? Id { get; set; }

    [SupplyParameterFromForm]
    public User? user { get; set; }

    protected override async Task OnInitializedAsync()
    {
        if (Id != null)
        {
            try{
                var response = await HttpClient.GetAsync($"https://localhost:7183/api/User/{Id}");
                response.EnsureSuccessStatusCode();

                var json = await response.Content.ReadAsStringAsync();
                user = JsonSerializer.Deserialize<User>(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
            }
            catch (Exception ex)
            {
                // Handle error
                Console.WriteLine("Error getting user: " + ex.Message);
            }
        }
        else
        {
            user ??= new();
        }
    }

    void AddUser()
    {
        try
        {
            byte[] salt = GenerateSalt();
            user.Password = HashPassword(user.Password, salt);

            var client = new HttpClient();
            client.BaseAddress = new Uri("https://localhost:7183/api/User");

            var json = JsonSerializer.Serialize(user);
            var content = new StringContent(json, Encoding.UTF8, "application/json");

            var response = client.PostAsync("User", content).Result;

            NavManager.NavigateTo("/UserOverview");
        }
        catch (Exception ex)
        {
            // Handle error
            Console.WriteLine("Error adding user: " + ex.Message);
        }
    }

    private string HashPassword(string password, byte[] salt)
    {
        using (Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(password, salt, 10000))
        {
            byte[] hash = pbkdf2.GetBytes(32); // 32 bytes for SHA256
            byte[] hashBytes = new byte[36]; // 32 bytes for hash + 4 bytes for salt

            Array.Copy(salt, 0, hashBytes, 0, 4);
            Array.Copy(hash, 0, hashBytes, 4, 32);

            return Convert.ToBase64String(hashBytes);
        }
    }

    private byte[] GenerateSalt()
    {
        byte[] salt = new byte[4]; // 4 bytes for salt
        using (RNGCryptoServiceProvider rngCsp = new RNGCryptoServiceProvider())
        {
            rngCsp.GetBytes(salt);
        }
        return salt;
    }
}

API 接口代码

group.MapGet("/{id}", async Task<Results<Ok<User>, NotFound>> (int id, DataContext db) =>
{
    return await db.User.AsNoTracking()
        .FirstOrDefaultAsync(model => model.Id == id)
        is User model
            ? TypedResults.Ok(model)
            : TypedResults.NotFound();
})
.WithName("GetUserById")
.WithOpenApi();

原因分析

  1. 异步渲染时机问题:Blazor 组件会先完成首次渲染,再执行OnInitializedAsync中的异步 API 请求。当Id不为 null 时,首次渲染时user尚未被赋值,UI 中的user!.Name等表达式会因user为 null 触发异常。
  2. 错误处理缺失:API 请求失败时,catch 块仅打印错误,未给user赋默认值,导致后续渲染仍使用 null 对象。
  3. SupplyParameterFromForm干扰:该特性会尝试从表单绑定user,但编辑场景下是从 API 获取数据,此特性可能破坏user的初始化逻辑。

解决方案

1. 确保user始终非空

在OnInitializedAsync开头先初始化user,并在 API 请求失败时保证user有默认值:

protected override async Task OnInitializedAsync()
{
    // 提前初始化,避免首次渲染null
    user ??= new User();

    if (Id != null)
    {
        try{
            var response = await HttpClient.GetAsync($"https://localhost:7183/api/User/{Id}");
            response.EnsureSuccessStatusCode();

            var json = await response.Content.ReadAsStringAsync();
            var fetchedUser = JsonSerializer.Deserialize<User>(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
            // 反序列化成功再赋值
            if(fetchedUser != null)
            {
                user = fetchedUser;
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("Error getting user: " + ex.Message);
            // 出错时重置为默认对象
            user = new User();
        }
    }
}

2. 移除SupplyParameterFromForm特性

编辑场景不需要从表单绑定user,移除该特性消除干扰:

// 移除[SupplyParameterFromForm]
public User? user { get; set; }

3. 添加加载状态(可选)

在异步加载期间显示加载提示,避免用户看到异常UI:

@if (user == null)
{
    <p>Loading user data...</p>
}
else
{
    <!-- 原表单内容 -->
}

4. 修复AddUser方法的 HttpClient 使用

避免新建 HttpClient,使用注入的实例并改为异步方法:

async Task AddUser()
{
    try
    {
        byte[] salt = GenerateSalt();
        user.Password = HashPassword(user.Password, salt);

        var json = JsonSerializer.Serialize(user);
        var content = new StringContent(json, Encoding.UTF8, "application/json");

        // 使用注入的HttpClient
        var response = await HttpClient.PostAsync("https://localhost:7183/api/User", content);
        response.EnsureSuccessStatusCode();

        NavManager.NavigateTo("/UserOverview");
    }
    catch (Exception ex)
    {
        Console.WriteLine("Error adding user: " + ex.Message);
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 17:05:55