Search code examples
c#data-bindingblazor-server-siderazor-pages

In my Blazor server app, I want to show messages of razor.cs pages in the header. But I cannot pass the messages from the razor pages to the header


I have a Blazor server side app (.Net7) consisting of sidebar, header and body sections. In the body section there can be navigated to several razor pages over the sidebar. In all razor pages there are generated some error messages related to the page functions. All these error messages should be shown in the common header part.The header part contains the razor page "razor_header".

I have written the code below in the mentioned pages, but I cannot pass the error messages correctly from the razor pages to the header razor page. Although the error messages are generated (I can check this over a log file) I see them not immediatly in the header page. Mostly they are shown delayed, on other events. What is missing in my code? I am not so good in Blazor server side. I think I am missing some basic stuff. I thought that the binding in Blazor is working also between pages. But i think this is not the case.

My razor_header page

@inject TagService TagService
<div>
    <h2>@TagService.Message_Main</h2>        
</div>

My razor_page_1.cs (@inject TagService TagService in rel.razor page)

public async Task Task_1() 
{
//some code
if(error1_condition==true)
  {
  TagService.Message_Main=error1_text; //error1 should be shown in the header
  StateHasChanged();
  }
}

My razor_page_2.cs (@inject TagService TagService in rel.razor page)

 public async Task Task_2() 
 {
 //some code
 if(error2_condition==true)
  {
  TagService.Message_Main=error2_text; //error2 should be shown in the header
  StateHasChanged();
  }
 }

My razor_page_3.cs (@inject TagService TagService in rel.razor page)

public async Task Task_3() 
{
//some code    
if(error3_condition==true)
  {
  TagService.Message_Main=error3_text; //error3 should be shown in the header
  StateHasChanged();
  }
}

Solution

  • You need to implement what is commonly known as the Notification Pattern. You can't call StateHasChanged in a child to force a render of the parent component.

    Add an event to your TagService. It should look something like this. Note the private setter on ErrorMessage. It can only be set or reset through the methods that trigger the event.

    public class TagService
    {
        public event EventHandler? StateChanged;
    
        public string? ErrorMessage { get; private set; }
    
        public void SetErrorMessage(string? message)
        {
            this.ErrorMessage = message;
            this.StateChanged?.Invoke(this, EventArgs.Empty);
        }
    
        public void ResetErrorMessage()
        {
            this.ErrorMessage = null;
            this.StateChanged?.Invoke(this, EventArgs.Empty);
        }
    }
    

    Here's my MainLayout. Note the event handler set up for the TagService event [and IDisposable to unhook the handler when the component is disposed]. The handler simply calls StateHasChanged to update the Component UI.

    @inherits LayoutComponentBase
    @inject TagService TagService
    @implements IDisposable
    <PageTitle>SO77097917</PageTitle>
    
    <div class="page">
        <div class="sidebar">
            <NavMenu />
        </div>
    
        <main>
            <div class="top-row px-4">
                @if (this.TagService.ErrorMessage != null)
                {
                        <span class="text-danger">@this.TagService.ErrorMessage</span>
                }
                <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
            </div>
            <article class="content px-4">
                @Body
            </article>
        </main>
    </div>
    @code {
        protected override void OnInitialized()
            => this.TagService.StateChanged += this.OnStateChanged;
    
        private void OnStateChanged(object? sender, EventArgs e)
            => this.StateHasChanged();
    
        public void Dispose()
            => this.TagService.StateChanged -= this.OnStateChanged;
    }
    

    And my Index to demo it working:

    @page "/"
    @inject TagService TagService
    <PageTitle>Index</PageTitle>
    
    <h1>Hello, world!</h1>
    
    Welcome to your new app.
    
    <div class="m-2 p-2">
        <button class="btn btn-danger" @onclick="SetMessage">Set Error</button>
        <button class="btn btn-success" @onclick="ResetMessage">Clear Error</button>
    </div>
    <SurveyPrompt Title="How is Blazor working for you?" />
    
    @code {
        private void SetMessage()
            => this.TagService.SetErrorMessage($"ERROR!");
    
        private void ResetMessage()
            => this.TagService.ResetErrorMessage();
    
    }