11

I am following a sample app from the NDC Oslo which is this app: https://github.com/SteveSandersonMS/presentation-2019-06-NDCOslo/tree/master/demos/MissionControl. This implements JWT as authentication and authorization. However when I tried to copy the implementation of the code to a Server Side Blazor, I'm getting an error when I try to get the JWT token stored from the local storage described below"

JavaScript interop calls cannot be issued at this time. This is because the component is being 
statically rendererd. When prerendering is enabled, JavaScript interop calls can only be performed 
during the OnAfterRenderAsync lifecycle method.

Here is my blazor code

protected override async Task OnInitializedAsync()
{
    var token = await TokenProvider.GetTokenAsync();
    Branches = await Http.GetJsonAsync<List<BranchDto>>(
        "vip/api/lookup/getbranches",
        new AuthenticationHeaderValue("Bearer", token));
}

The error comes from

public async Task<string> GetTokenAsync()
{
   //Code Omitted for brevity 
   //This line of code is equivalent to the IJSRuntime.Invoke<string>("localstorage.getitem","authToken") 
   //change to use Blazore.LocalStorage.
    var token = await _localStorageService.GetItemAsync<string>("authToken");
    return token;
 }

I tried perform the code on OnAfterRenderAsync(bool firstRender) and the error is gone but the grid which is binded to the API request has no display. The API request must fill the data source for the grid which must be OnInitializedAsync. Any workaround on this?

Update! I moved the code OnAfterRenderAsync and added the StateHasChanged Method and I got the desired Behavior. I forgot that the connection for rendering was a signalR connection.

ECie
  • 1,265
  • 4
  • 21
  • 50
  • See [“Detect when a Blazor Server app is prerendering”](https://learn.microsoft.com/en-us/aspnet/core/blazor/call-javascript-from-dotnet?view=aspnetcore-3.1#detect-when-a-blazor-server-app-is-prerendering) – poke Apr 26 '20 at 10:05
  • @poke I was wondering why the NDC example works just fine. Good catch on the pointing me out. – ECie Apr 26 '20 at 10:18
  • The demo is running on ASP.NET Core 3.0, possibly even a preview version, so it’s possible that things have slightly changed there. – poke Apr 26 '20 at 10:31
  • I agree. Since it's on their documentation, I think i would follow the workaround primarily with how the token and authorization is done – ECie Apr 26 '20 at 10:59

1 Answers1

28

As per “Detect when a Blazor Server app is prerendering”, you can only safely run interop code in the OnAfterRenderAsync lifecycle method.

However, since this runs after the render cycle, you will need to notify your component to re-render using StateHasChanged() once your asynchronous process completes:

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        var token = await TokenProvider.GetTokenAsync();
        Branches = await Http.GetJsonAsync<List<BranchDto>>(
            "vip/api/lookup/getbranches",
            new AuthenticationHeaderValue("Bearer", token));

        StateHasChanged();
    }
}
poke
  • 369,085
  • 72
  • 557
  • 602
  • Doesn't this infer an infinite loop? StateHasChanged() causes a re-render, triggers OnAfterRenderAsync(), calls StateHasChanged() ... It does in my case. How to prevent it? – flip May 17 '23 at 15:35
  • @flip `StateHasChanged()` is kind of counterintuitive and only tells the component that it *thinks* the state has changed. The component itself will decide if the state has actually changed and will re-render accordingly. In the code example above, it's possible the component decides the values are the same and that it doesn't need to re-render. In your case, it's possible that the component decides it always needs to re-render, which would cause an infinite loop. – micka190 May 17 '23 at 16:27
  • 1
    @micka190 Thanks. My component indeed changes values that trigger a re-render - I store filter- and sort settings for a table in local storage. I have now prevented the infinite loop with a "rendering" flag. – flip May 18 '23 at 07:07
  • @flip In this case, this cannot cause an infinite loop since the data is only loaded on `firstRender`. But yeah, if you would skip that check then you would continue to reload data and continue to refresh the component’s state. – poke May 25 '23 at 07:52
  • I understand better now where the issue is coming from. Thx, @poke. Initially, I did load data at `firstRender`. But because users often work with the same subset of data in this use case, I want to retain sort- and filter settings in between sessions. I read and apply them when the page is opened and data is loaded. This way users don't have to set the filter each time they open the app. To prevent this I could of course store these settings in permanent- in stead of local storage. – flip May 25 '23 at 17:04