Home > database >  Trouble with Async order of execution in App.razor and other razor pages
Trouble with Async order of execution in App.razor and other razor pages

Time:11-11

I am attempting to perform a global setup in the App.razor file, which would read some data from the browser local storage and then assign it some global state class.

However, I have run into a problem with the order of tasks being fulfilled. Consider the code below:

This is what I currently have:

@inject IJSRuntime JSRuntime;

<Router AppAssembly="@typeof(App).Assembly">
    @...
</Router>

@code {
    string token { get; set; } = default!;

    protected async override Task OnInitializedAsync()
    {
        token = await JSRuntime.InvokeAsync<string>("localStorage.getItem", "token");
        stateService.token = token;
        System.Console.WriteLine("Token A : "   token);    
    }
}

The above correctly gets the local storage item, but not before the next page below loads. The problem is the page below needs access to the stateService.token set above.

and then in pages/login.razor.cs:

using Microsoft.AspNetCore.Components;

namespace Pages.Login
{
    public partial class Login : ComponentBase
    {
 
        protected override void OnAfterRender(bool firstRender)
        {
            token = stateService.Token;
            System.Console.WriteLine("Token B: "   token);        
        }
    }
}

The output is:

Token B: 
Token A: TestValueToken

But of course I would need it to be:

Token A: TestValueToken
Token B: TestValueToken

I know why the above is occuring but have no idea thow to fix it. Any suggestions would be welcomed.

CodePudding user response:

You could maybe use a semaphore, - which will let the thread in your next page "wait" until the token is loaded.

App.razor

    @code {
    string token { get; set; } = default!;
    static Semaphore semaphore;

    protected async override Task OnInitializedAsync()
    {
        if (!Semaphore.TryOpenExisting("MySemaphore", out semaphore))
        {
           semaphore = new Semaphore(1, 1, "MySemaphore");
        }
        token = await JSRuntime.InvokeAsync<string>("localStorage.getItem", "token");
        stateService.token = token;
        System.Console.WriteLine("Token A : "   token); 
        semaphore.Release();
    }
}

Next page

static Semaphore semaphore;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        semaphore = Semaphore.OpenExisting("MySemaphore");
        while (!semaphore.WaitOne(TimeSpan.FromTicks(1)))
        {
            await Task.Delay(TimeSpan.FromSeconds(0.5));
        }
        token = stateService.Token;
        System.Console.WriteLine("Token B: "   token);        
    }

WaitOne() - will block the current thread until the semaphore is released in your App.razor page.

CodePudding user response:

You can do that in various ways:

The following decribes how to do that using a service:

TokenService.cs

 public class TokenService
    {
        private IJSRuntime JSRuntime;
       
        public TokenService(IJSRuntime JSRuntime)
        {
            this.JSRuntime = JSRuntime;
        }
        public async Task<string> GetToken ()
        {
            return await JSRuntime.InvokeAsync<string> 
                                ("localStorage.getItem", "token");
        
        }
    }

Program.cs

builder.Services.AddScoped();

App.razor (I guess this is not necessary any longer)

@inject TokenService TokenService;

@code {
 protected override async Task OnInitializedAsync()
  {
         System.Console.WriteLine("Token A : "   await TokenService.GetToken());

   }

}

login.razor

@inject TokenService TokenService;

 @code {
     protected override async Task OnInitializedAsync()
            {
    
    
                System.Console.WriteLine("Token B : "   await TokenService.GetToken());
    
            }

    // Or 
  //  protected override async Task OnAfterRenderAsync(bool 
  //                                              firstRender)
  //  {
  //         System.Console.WriteLine("Token B : "   await 
  //                                TokenService2.GetToken());
  //  }

  // Not clear why you want it in OnAfterRender{Async}
    
 }

Here's another way how to do that, defining an event handler delegate that is raised when the value of the token has changed.

TokenService.cs

 public class TokenService
    {
        private string token;
        public event EventHandler TokenSet; 

        public void SetToken (string token)
        { 
            this.token = token;
            OnTokenSet(new TokenSetEventArgs(this.token));
        }

        protected void OnTokenSet(TokenSetEventArgs e)
        {
            TokenSet?.Invoke(this, e);
        }
    }

    public class TokenSetEventArgs : EventArgs
    {
        public string Token { get; set; }
        public TokenSetEventArgs(string token) => Token = token;

    }

Program.cs

builder.Services.AddScoped();

App.razor

@inject IJSRuntime JSRuntime;

@inject TokenService TokenService;


 @code {
        private string token { get; set; } 

        protected async override Task OnInitializedAsync()
        {
          await JSRuntime.InvokeVoidAsync("localStorage.setItem", 
                                                "token", "XXXX" );

          token = await JSRuntime.InvokeAsync<string> 
                               ("localStorage.getItem", "token");
            System.Console.WriteLine("Token A : "   token);
            TokenService.SetToken(token);
       }
   
    }

Login.razor

@inject TokenService TokenService;

@code
{
    protected override void OnInitialized()
    {
      // You'll also need to implement IDisposable....
        TokenService.TokenSet  = GetToken;

    }

    private void GetToken(object sender, EventArgs args)
    {

        System.Console.WriteLine("Token B : "   ((TokenSetEventArgs)(args)).Token);

    }
}
  • Related