MediatR Blazor wasm - notification target component not updating

Created on 29 Sep 2020  路  4Comments  路  Source: jbogard/MediatR

.NET Standard 2.1
.NET Core 3.1.402
MediatR - 8.1.0

  • Start with a clean Blazor WebAssembly project in VS 2019 (16.7.4)
    When the button is clicked I get the following error message in the browser (The render handle is not yet assigned):
锘縋ublishing ping
Got ping with data 'Clicked'
updating
System.InvalidOperationException: The render handle is not yet assigned.
   at Microsoft.AspNetCore.Components.RenderHandle.ThrowNotInitialized () <0x2ebe310 + 0x0000c> in <filename unknown>:0 
   at Microsoft.AspNetCore.Components.RenderHandle.Render (Microsoft.AspNetCore.Components.RenderFragment renderFragment) <0x2bc4a60 + 0x0000a> in <filename unknown>:0 
   at Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged () <0x2bc48a0 + 0x0004e> in <filename unknown>:0 
   at BlazorApp1.Components.Component2Base.Handle (BlazorApp1.Components.Ping notification, System.Threading.CancellationToken cancellationToken) [0x00035] in 

Program.cs

register MediatR in DI

builder.Services.AddMediatR(typeof(Program));

Component1.razor

Publish notification when button is clicked

@inherits Component1Base

<h3>Component1</h3>

<button type="button" @onclick="@ButtonClicked">Click me!</button>

Component1Base.cs

    public class Component1Base : ComponentBase
    {
        [Inject] 
        public IMediator Mediator { get; set; }

        protected Task ButtonClicked(EventArgs obj)
        {
            Console.WriteLine("Publishing ping");
            var ping = new Ping("Clicked");
            Mediator.Publish(ping);
            return Task.CompletedTask;
        }
    }

Component2.razor

Handle notification and try to update the value of the input

@inherits Component2Base

<h3>Component2</h3>

<EditForm Model="@Data">
    <InputText @bind-Value="@Data"></InputText>
</EditForm>

Component2Base.cs

    public class Component2Base : ComponentBase, INotificationHandler<Ping>
    {

        protected string Data { get; set; }

        protected override void OnInitialized()
        {
            Data = "1234";
        }

        public Task Handle(Ping notification, CancellationToken cancellationToken)
        {
            Console.WriteLine($"Got ping with data '{notification.Data}'");
            try
            {
                Console.WriteLine("updating");
                Data = notification.Data;
                StateHasChanged();
                Console.WriteLine("updated");
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
            return Task.CompletedTask;
        }
    }

Ping.cs

Notification class

public class Ping : INotification
    {
        public Ping(string data)
        {
            Data = data;
        }
        public string Data { get; }
    }

Index.razor

Use the 2 components

@page "/"
@using BlazorApp1.Components

<h1>Hello, world!</h1>

Welcome to your new app.

<Component1></Component1>
<Component2></Component2>

Most helpful comment

@ryanbuening I have created a library just for this, with it you can subscribe/unsubscribe to and from MediatR notifications through an ICourier interface:
https://dev.azure.com/kandras9642/MediatR.Courier
EDIT: this would be using it in Component2Base

    public class Component2Base : ComponentBase, IDisposable
    {

       [Inject] ICourier Courier { get;set; }
        protected string Data { get; set; }

        protected override void OnInitialized()
        {
            Data = "1234";
            Courier.Subscribe<Ping>(Handle);
        }

       public void Dispose()
       {
            Courier.UnSubscribe<Ping>(Handle);
       }

        public Task Handle(Ping notification, CancellationToken cancellationToken)
        {
            Console.WriteLine($"Got ping with data '{notification.Data}'");
            try
            {
                Console.WriteLine("updating");
                Data = notification.Data;
                StateHasChanged();
                Console.WriteLine("updated");
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
            return Task.CompletedTask;
        }
    }

All 4 comments

Likely the problem here is that you are using a single class for both the razor backend and the notification handler. The .AddServices() extension method registers the handlers as Transient, which creates a new instance of the class to handle the notification. That means the one that is being used in rendering is not the same instance as the one that is handling the notification. In order to share data between the handlers and the Razor components you will need a shared dependency that both can interact with. I don't know the details of the lifecycle of the Razor components though so you will have to work that out yourself.

@orantwolinkme, if you solved this can you share your solution?

@ryanbuening I have created a library just for this, with it you can subscribe/unsubscribe to and from MediatR notifications through an ICourier interface:
https://dev.azure.com/kandras9642/MediatR.Courier
EDIT: this would be using it in Component2Base

    public class Component2Base : ComponentBase, IDisposable
    {

       [Inject] ICourier Courier { get;set; }
        protected string Data { get; set; }

        protected override void OnInitialized()
        {
            Data = "1234";
            Courier.Subscribe<Ping>(Handle);
        }

       public void Dispose()
       {
            Courier.UnSubscribe<Ping>(Handle);
       }

        public Task Handle(Ping notification, CancellationToken cancellationToken)
        {
            Console.WriteLine($"Got ping with data '{notification.Data}'");
            try
            {
                Console.WriteLine("updating");
                Data = notification.Data;
                StateHasChanged();
                Console.WriteLine("updated");
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
            return Task.CompletedTask;
        }
    }

@KuraiAndras awesome, thanks for sharing.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

FuncLun picture FuncLun  路  5Comments

zachpainter77 picture zachpainter77  路  5Comments

etechnologiesng picture etechnologiesng  路  3Comments

nevaenuf picture nevaenuf  路  4Comments

tool-cmd picture tool-cmd  路  4Comments