baer999
baer999

Reputation: 992

Dynamic _Hosts.cshtml file in Blazor with C# code

Just used the great free Blazor code of DevExpress to implement custom <HEAD> data on runtime:

_Hosts.cshtml (see the code snippet in element):

    <!DOCTYPE html>
    <html lang="en">
    <head>
        @(await Html.RenderComponentAsync<DocumentMetadataComponent>(RenderMode.ServerPrerendered))
    </head>
    <body>
        <app>
            @(await Html.RenderComponentAsync<App>(RenderMode.ServerPrerendered))
        </app>
    <script src="_framework/blazor.server.js"></script>
</body>

Works great so far (can make title, description, etc. dynamic).

Problem now is that I have to set some variables on that top Level to overgive and interpret once. I want to know the used url and give some data dynamically.

But with my knowledge right now I just can do it twice in each component (DocumentMetadataComponent + App).

I'm getting data in MainLayout.razor:

<CascadingValue Value="StoreData" Name="StoreData">
    @Body
</CascadingValue>

@code{
    public StoreCompleteDTO StoreData { get; set; }

    protected override async Task OnInitializedAsync()
    {
        using var tl = new TimeLogger($"MainLayout.razor OnInitializedAsync()");

        StoreData = await AppState.GetStoreData(My.StoreId);
    }
}

This StoreData variable must be available in dynamic meta data, because I have to choose different CSS files for other stores...

Hope the explanation is clear.

Goal is call this just ONCE: StoreData = await AppState.GetStoreData(My.StoreId);

because it is web service call and costs time...

Thx!

Upvotes: 4

Views: 4329

Answers (1)

Quango
Quango

Reputation: 13468

I would suggest the best way to make some information available across a Blazor application is to use the service system and Dependency Injection.

This means you can control the scope of the data (global? session?) as well as make it available on any page/component you need. Writing in the form of State container will also help in usage. Here is a quick sample that just sets the page title, but you can extend it to cover <meta> for example.

    /// <summary>
    /// A state container for head tags
    /// </summary>
    public class HeadState
    {
        /// <summary>
        /// Page title
        /// </summary>
        public string Title => _title;

        // internal store
        private string _title = "";

        /// <summary>
        /// Set the page title
        /// </summary>
        /// <param name="title"></param>
        public void SetTitle(string title)
        {
            if(!string.Equals(_title,title))
            {
                _title = title;
                HeadChanged?.Invoke();
            }
        }

        /// <summary>
        /// Event raised when data changes
        /// </summary>
        public event Action HeadChanged;
    }

The HeadState class is a state container to allow us to set the page title. The title property is read-only so it has to be set via SetTitle, which triggers the HeadChanged event so consumers know it's amended. We need to register this as a scoped service in Startup.cs:

   // declare the HeadState for DI
   services.AddScoped<State.HeadState>();

To implement it, we amend the _host.cshtml to add a component that renders it in the <head> section as per your question:

<head>
    @(await Html.RenderComponentAsync<HeadComponent>(RenderMode.Server))
</head>

The HeadComponent injects the state and handles the render and updates:

@inject State.HeadState head

<title>@head.Title</title>

@code
{
    // although the title is set on loading, if it is changed by a component we
    // need to call StateHasChanged to trigger Blazor to rerender since the event
    // that triggered it is outside this component
    protected override void OnInitialized()
    {
        head.HeadChanged += () =>
        {
            StateHasChanged();
        };
    }
}

To test it on a page (or any other component!) we just inject the state object and use it

@page "/"
@inject State.HeadState head

<h1>Head Demo</h1>

<button @onclick='(()=> head.SetTitle("Hello"))'>Set title to Hello</button>
<button @onclick='(()=> head.SetTitle("World"))'>Set title to World</button>

Here we are just doing a <title> tag for demo purposes but of course you can add <meta> or other values. I'd avoid amending CSS stylesheets though as that possibly should be done via JS but I'm not 100% sure that's correct.

Upvotes: 7

Related Questions