最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c# - Blazor StateHasChanged Throwning Null Reference Exception on Simple Page - Stack Overflow

programmeradmin1浏览0评论

I am using Blazor with Radzen Components. I have a page setup with two tabs. The first tab lists items that can be edited. The second tab references a component used to edit the selected item.

The code under the Edit button:

private TestVS selectedSchedule = new();

private async void EditVessel(VesselListItem vessel)
{
    selectedSchedule.VesselLoadId = vessel.VesselLoadId;
    await selectedSchedule.LoadVessel();
    tabsSelectedIndex = 1;
}

When TestVS is created, the OnitializedAsync() method is executed:

protected override async Task OnInitializedAsync()
{
    try
    {
        if (CurrentUserIsValid)
        {
            // Build drop down lists
            var vList = await vesselData.GetVesselLoadList();
            if (vList is not null)
            {
                vesselList = vList.ToList();
            }
            agentList = await vesselData.GetVesselAgentList();
            destinationList = await vesselData.GetVesselDestinationList();
            shipperList = await shipperData.GetList();
            customerList = await customerData.GetList();
        }
        else
        {
            try
            {
                navigation.NavigateTo("/");
            }
            catch { }
        }
    }
    catch (Exception ex)
    {
        LogError(ex);
    }
}

This simply loads data for dropdowns on the form. The LoadVessel() method simply makes one more call to the DB to retrieve information:

public async Task LoadVessel()
{
    try
    {
        if (VesselLoadId > 0)
        {
            vesselLoad = await vesselData.GetVesselLoadById(VesselLoadId);
        }
    }
    catch (Exception ex)
    {
        LogError(ex);
    }

    // This throws a Null Reference Exception
    StateHasChanged();
}

The [...]Data objects are injected and used for database operations. This represents a standard call to get data:

public async Task<VesselLoad?> GetVesselLoadById(int id)
{
    var itemList = await _sql.LoadData<VesselLoad, dynamic>("dbo.spVesselLoad_GetById", new { VesselLoadId = id });
    if (itemList is not null)
    {
        return itemList.FirstOrDefault<VesselLoad>();
    }
    return null;
}

I use this approach throughout this application and haven't had any problems until now. I've tried creating another method call Refresh() in TestVS that simply lets me call StateHasChanged() after the LoadVessel method is complete, but it still throws the null reference exception.

I can provide more code snippets if necessary. A much more complicated setup used to work fine for this page, but has now started failing inexplicably. If anyone has a clue, I could sure use one.

I am using Blazor with Radzen Components. I have a page setup with two tabs. The first tab lists items that can be edited. The second tab references a component used to edit the selected item.

The code under the Edit button:

private TestVS selectedSchedule = new();

private async void EditVessel(VesselListItem vessel)
{
    selectedSchedule.VesselLoadId = vessel.VesselLoadId;
    await selectedSchedule.LoadVessel();
    tabsSelectedIndex = 1;
}

When TestVS is created, the OnitializedAsync() method is executed:

protected override async Task OnInitializedAsync()
{
    try
    {
        if (CurrentUserIsValid)
        {
            // Build drop down lists
            var vList = await vesselData.GetVesselLoadList();
            if (vList is not null)
            {
                vesselList = vList.ToList();
            }
            agentList = await vesselData.GetVesselAgentList();
            destinationList = await vesselData.GetVesselDestinationList();
            shipperList = await shipperData.GetList();
            customerList = await customerData.GetList();
        }
        else
        {
            try
            {
                navigation.NavigateTo("/");
            }
            catch { }
        }
    }
    catch (Exception ex)
    {
        LogError(ex);
    }
}

This simply loads data for dropdowns on the form. The LoadVessel() method simply makes one more call to the DB to retrieve information:

public async Task LoadVessel()
{
    try
    {
        if (VesselLoadId > 0)
        {
            vesselLoad = await vesselData.GetVesselLoadById(VesselLoadId);
        }
    }
    catch (Exception ex)
    {
        LogError(ex);
    }

    // This throws a Null Reference Exception
    StateHasChanged();
}

The [...]Data objects are injected and used for database operations. This represents a standard call to get data:

public async Task<VesselLoad?> GetVesselLoadById(int id)
{
    var itemList = await _sql.LoadData<VesselLoad, dynamic>("dbo.spVesselLoad_GetById", new { VesselLoadId = id });
    if (itemList is not null)
    {
        return itemList.FirstOrDefault<VesselLoad>();
    }
    return null;
}

I use this approach throughout this application and haven't had any problems until now. I've tried creating another method call Refresh() in TestVS that simply lets me call StateHasChanged() after the LoadVessel method is complete, but it still throws the null reference exception.

I can provide more code snippets if necessary. A much more complicated setup used to work fine for this page, but has now started failing inexplicably. If anyone has a clue, I could sure use one.

Share Improve this question asked Jan 31 at 16:06 Mark BonafeMark Bonafe 1,49314 silver badges24 bronze badges 8
  • Have you run it under the debugger with all exceptions being trapped to see if you can find the underlying problem? – Matthew Watson Commented Jan 31 at 16:28
  • 1) avoid async void. You don't need it here. – Henk Holterman Commented Jan 31 at 16:37
  • 2) when TestVS is a Blazor component then do not new() it. – Henk Holterman Commented Jan 31 at 16:38
  • The question is pretty hard to understand and answer. Consider creating a minimal reproducible example. – Henk Holterman Commented Jan 31 at 16:39
  • Henk, I have tried removing the new(). I get the same bad results. – Mark Bonafe Commented Jan 31 at 16:49
 |  Show 3 more comments

2 Answers 2

Reset to default 1

Your problem is that you are creating and then using a component outside the context of the Renderer. You're getting an exception because the component's RenderHandle is almost certainly null. It's assigned when the Renderer calls Attach to attach the component to the Render Tree.

Some observations:

  1. You should never new up a component like this [It's the reason for the exception].

    private TestVS selectedSchedule = new();

    If you want to add a reference to a component then, it should be nullable:

    private TestVS? selectedSchedule;
  2. You shouldn't rely on something like this in normal component design to force the component to update.

    await selectedSchedule.LoadVessel();
  3. Calling StateHasChanged in normal component logic is an indicator that your component logic isn't quite right.

Based on the code you've provided, I've created a simple Minimal Reproducible Example (MRE). I may be missing some of the context, but I think this should be enough to get you started.

In the code below I use a standard Parameter and detected the change in the OnParametersSetAsync method.

Everything works within the component lifecycle and standard event handling. There's no need for extraneous calls to StatHasChanged

SelectedForm.razor

<h3>SelectedForm</h3>

<div>Vessel ID: @_vesselId </div>

@code {
    [Parameter, EditorRequired] public int VesselId { get; set; }

    private int _vesselId;

    protected async override Task OnInitializedAsync()
    {
        // load stuff that doesn't change like dropdown lists
        await Task.Yield();
    }

    protected async override Task OnParametersSetAsync()
    {
        // Do something if vessel id is 0.

        // Detect a VesselId change and
        // Update stuff
        if(VesselId != _vesselId)
        {
            // Fake an async data get
            await Task.Yield();

            _vesselId = VesselId;
        }
    }

}

Home.razor

@page "/"

<PageTitle>Home</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.
<div class="p-2"> Value: @_vesselId</div>

<RadzenTabs @bind-SelectedIndex=@_selectedIndex>
    <Tabs>
        <RadzenTabsItem Text="Selector">
            <RadzenButton Click="() => EditVessel(1)" Text="Primary" ButtonStyle="ButtonStyle.Primary" />
        </RadzenTabsItem>
        <RadzenTabsItem Text="Editor">
            <SelectedForm VesselId="_vesselId" />
        </RadzenTabsItem>
    </Tabs>
</RadzenTabs>

@code {
    private int _selectedIndex = 0;
    private int _vesselId;

    private void EditVessel(int vessel)
    {
        // Dummy in an incrementing value
        _vesselId = _vesselId + 1;
        _selectedIndex = 1;
    }
}

I found the problem. I recently switched a security object from a singleton to scoped. A collection inside that object is, of course, now NULL whereas before it was not. The system never lies, it's just not good at telling you the full details of a problem sometimes.

Once I loaded the collection, everything works fine. I apologize for my horrible example code that should have shown the full markup. That way, some of you would have surely noticed the possibility of a problem.

Thanks, MrC, you taught me something today.

发布评论

评论列表(0)

  1. 暂无评论