I wanted to create a data store that was accessible between different pages on the site. On every page I want the same code to run. Rather than putting it on every page I figured it would be best to put it into MainLayout.razor
so I can just edit it there in the one spot.
However each page has a new instance of the scoped data, and its not being shared.
If I take the code and manually insert it on each page then it works fine.
My questions are:
MainLayout
? Is it a different scope? Any other gotchas with this page?This is a Server project (not WASM)
I tried:
in Program.cs :
builder.Services.AddScoped<ScopedDataStore>();
in MainLayout.razor:
@inject ScopedDataStore ScopedData
@inject NavigationManager _nav
...
protected override async Task OnInitializedAsync()
{
ScopedData.SetCurrentUrl(_nav.Uri);
}
and ScopedDataStore:
public class ScopedDataStore
{
private string CurrentUrl="";
private string PreviousUrl="";
public void SetCurrentUrl(string x)
{
PreviousUrl = CurrentUrl;
CurrentUrl = x;
}
public string GetLastUrl()
{
return this.GetHashCode().ToString();
}
}
This is in MainLayout.razor:
<article class="content px-4">
@Body
<h1>Last URL : @ScopedData.GetLastUrl()</h1
</article>
EDIT:
To try and get around the issue I've created a component which I manually insert on each page - I can then edit the code in one spot:
@inject ScopedDataStore ScopedData
@inject NavigationManager _nav
<h3>Last URL = @(ScopedData.GetLastUrl()) </h3>
@code {
protected override Task OnInitializedAsync()
{
ScopedData.SetNavigationManager(_nav);
Console.WriteLine("GlobalComp OnInitializedAsync");
ScopedData.PushCurrentUrl(_nav.Uri);
return Task.CompletedTask;
}
}
Why does this not work in MainLayout? Is it a different scope? Any other gotchas with this page?
The reason it's not being shared is because you have configured it that way. Every navigation event is a round trip to the server because Route.Razor is statically rendered.
Layout
is also statically rendered on the server in the HttpRequest
context, The page is rendered twice, once statically on the server and then a second time in the Blazor Hub SPA context.
What is the best way to have the same code run on every page without manually adding it?
Configure interactivity to global.
Change App.razor to:
<HeadOutlet @rendermode="InteractiveServer" />
</head>
<body>
<Routes @rendermode="InteractiveServer" />
Read and understand - https://learn.microsoft.com/en-us/aspnet/core/blazor/components/render-modes?view=aspnetcore-8.0.
You can't share a Scoped service between static and interactive pages.
Any Scoped service in a static page only exists for the lifetime of the the HttpRequest. You can't get the last Url.
For the interactive pages you can either do as you've done and create a component or create a custom ComponentBase
.
Here's how to do the custom ComponentBase
.
I've added the code to SetParametersAsync
rather than OnInitializedAsync
because it then gets executed before any of the normal lifecycle code, and you are unlikely to override SetParametersAsync
and forget to call base
.
public class AppComponentBase : ComponentBase
{
private bool _isInitialized;
[Inject] protected NavigationManager NavigationManager { get; set; } = default!;
[Inject] protected ScopedDataStore ScopedDataStore { get; set; } = default!;
public override Task SetParametersAsync(ParameterView parameters)
{
parameters.SetParameterProperties(this);
if (!_isInitialized)
{
this.ScopedDataStore.SetCurrentUrl(this.NavigationManager.Uri);
_isInitialized = true;
}
return base.SetParametersAsync(ParameterView.Empty);
}
}
You can use this like:
@page "/"
@inherits AppComponentBase
@rendermode InteractiveServer
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<div class="alert alert-info m-2">@this.ScopedDataStore.GetLastUrl()</div>