HairyIce
HairyIce

Reputation: 25

In Blazor Server can I force an Asynchronous method to finish before other lifecycle methods run (i.e. OnInitialized)

I have a Blazor server app where I want to keep some data between requests or refreshes, etc. It looks to me like using ProtectedLocalStorage is the best option for this. I would like to be able to Get this data from LocalStorage once when a connection is established and have it available for all pages/components during that connection.

For example, I would like to Get the data from local storage when App.Razor runs (it's my understanding this runs at the beginning of each new connection) and have that data available for the OnInitialized() method of a page component.

Something like this:

App.razor:

[Inject]
ProtectedLocalStorage LocalStorage {get; set;}
[Inject]
MyScopedServiceObject myScopedServiceObject {get; set;}

protected override async Task OnInitialized () {
  base.OnInitialized();
  // I know this isn't exactly right, but just to show what I want to do
  myScopedServiceObject.myData = await LocalStorage.GetAsync<string>("myDataKey").Value;
}

MyPageComponent.razor.cs

[Inject]
MyScopedServiceObject myScopedServiceObject {get; set;}

protected override void OnInitialized () {
  // I need the data here, but the async method to get it hasn't finished
  // before this runs
  string whereINeedTheData = myScopedServiceObject.myData;
}

The problem I have is that because the LocalStorage.GetAsync() method is asynchronous the page component's OnInitialized() method runs before that is finished so the data I need isn't available yet.

Is there a way to force the asynchronous method kicked off in App.razor to finish before my page component's OnInitialized() method runs? I know I can perform the GetAsync in the OnInitialized() method of my page component, but I'd rather have a single place that gets the data rather than code it into every page component that may need it. Or will I be forced to do this anyway because of how async works?

Upvotes: 1

Views: 663

Answers (3)

MrC aka Shaun Curtis
MrC aka Shaun Curtis

Reputation: 30310

If you want to run async code in any ComponentBase component prior to the first render, you need to run it in SetParametersAsync.

A precautionary note - you need to ensure you use the correct override pattern as shown below.

private bool _firstSet = true;

public async override Task SetParametersAsync(ParameterView parameters)
{
   // Apply the parameter changes immediately
   parameters.SetParameterProperties(this);

   if(_firstSet)
     await DoSomePreRenderWorkAsync();

   _firstSet = false;

   // Call the base with an empty ParameterView as we've already set the parameters
   // This will run the normal lifecycle events
   await base.SetParametersAsync(ParameterView.Empty);
}

Upvotes: 3

Qiang Fu
Qiang Fu

Reputation: 8811

You could try pass the values as CascadingParameter from parent component to other components. Since we cannot set "rendermode" in App.razor. I suggest you put the "get" codes in the Routes.razor or MainLayout.razor. Try following sample using Routes.razor:
MyObject.cs

    public class MyObject
    {
        public string Name { get; set; }
    }

program.cs

builder.Services.AddScoped<MyObject>();

App.razor (set global rendermode)

...
    <Routes @rendermode=@(new InteractiveServerRenderMode(false)) />
...

Routes.razor

@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedLocalStorage ProtectedLocalStore

<CascadingValue Value=@myobject>
<Router AppAssembly="typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
        <FocusOnNavigate RouteData="routeData" Selector="h1" />
    </Found>
</Router>
</CascadingValue>


@code{
    MyObject myobject = new MyObject();
    protected override async Task OnInitializedAsync()
    {
        await ProtectedLocalStore.SetAsync("para1", "Tom");
        var result = await ProtectedLocalStore.GetAsync<string>("para1");
        myobject.Name = result.Value;

    }
}

Home.razor

@page "/"
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedLocalStorage ProtectedLocalStore

@myObject.Name

@code{
    [CascadingParameter]
    MyObject? myObject { get; set; }
}

Test
enter image description here

Upvotes: 0

dani herrera
dani herrera

Reputation: 51715

Instead to use a property, you can use a method:

public class myScopedServiceObject
{

    protected readonly LocalStorage LocalStorage;
    myScopedServiceObject( LocalStorage localStorage)
    {
       LocalStorage = localStorage;
    }

    private string? CacheData;
    public async Task<string> GetData()
    {
        if (ChacheData == null) 
           CacheData = await LocalStorage.GetAsync<string>("myDataKey").Value;
        return CacheData!;
        
    }
}

MyPageComponent.razor.cs. (async now)

[Inject]
MyScopedServiceObject myScopedServiceObject {get; set;}

protected override async Task OnInitializedAsync () {
  // I need the data here, but the async method to get it hasn't finished
  // before this runs
  string whereINeedTheData = await myScopedServiceObject.GetData();
}

Upvotes: 1

Related Questions