Search code examples
authentication.net-coreblazorblazor-server-side

Local storage cannot be accessed even though pre-rendering is disabled and JS interop is called in OnAfterRenderAsync


I am trying to implement handling authorization tokens in a Blazor Server app based on built-in .NET Identity. I found a video tutorial which covers the subject but it is based on WASM, so I need to adapt the solution a bit.

I would like to store the access token in a local storage and to have the possiblity to read it when the page is being rendered. I have installed the Blazored LocalStorage library but unfortunatelly, I get the following error, while I am trying to use it:

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

I have checked several other posts related to the aforementioned message but no given solution is working for me.

I disabled pre-rendering. Just to be sure, I did that on several levels although it supposed to be enough to do it on the top level.

App.razor:

<Router @rendermode="new InteractiveServerRenderMode(prerender: false)" AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
    <RouteView RouteData="@routeData" DefaultLayout="@typeof(LoginLayout)" />
</Found>
<NotFound>
    <LayoutView Layout="@typeof(LoginLayout)">
        <p>Sorry, there's nothing at this address.</p>
    </LayoutView>
</NotFound>

Index.razor (page):

@page "/test"
@rendermode @(new InteractiveServerRenderMode(prerender: false))
@layout MainLayout

<AuthState />

Component itself (AuthState):

@rendermode @(new InteractiveServerRenderMode(prerender: false))

[Inject]
public required IStorageService StorageService { get; set; }

@code
{
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
       bool isPreRender = HttpContextAccessor.HttpContext is null || !HttpContextAccessor.HttpContext.Response.HasStarted;

       if (!isPreRender && !AuthStateStorage.IsInitialized)
       {
           // Following method calls GetItemAsync from Blazored LocalStorage library
           var tokens = await StorageService.GetTokens();
       }
    }
}
    

As you can see, even though I set prerender to false, I also try to access the local storage in OnAfterRenderAsync as the error message suggests to do. Additionaly, I found a suggestion to manually check whether we are in a pre-render state but it did not help as well.

Do you have any idea what I am doing wrong? Maybe, there is a better approach to store access tokens in Blazor app which is a presentation layer only, thus has no direct access to the database (it requests data from Web API).


Solution

  • Here's a quick demo showing how to detect pre-rendering and interact with the protected storage. I've used the platform ProtectedBrowserStorage rather than Blazored.

    @page "/"
    
    @using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
    
    @inject ProtectedSessionStorage ProtectedSessionStorage
    
    <PageTitle>Home</PageTitle>
    
    <h1>Hello, world!</h1>
    
    Welcome to your new app.
    
    <button class="btn btn-primary" @onclick="UpdateToken">Set Token</button>
    
    <div class="alert alert-primary m-2">@_token</div>
    
    @code {
        [CascadingParameter] private HttpContext? httpContext { get; set; }
    
        private string? _token = "Not Set";
        private static readonly string TokenName = "Token";
        private bool _isPreRender => httpContext is not null;
    
        protected override async Task OnInitializedAsync()
        {
            Console.WriteLine($"Pre-Render: {httpContext is not null}");
            // Let the component render fist time
            await Task.Delay(1);
            await this.GetToken();
        }
    
        private async Task UpdateToken()
        {
            if (_isPreRender)
                return;
    
            await this.ProtectedSessionStorage.SetAsync(TokenName, $"Set at {DateTime.Now.ToLongTimeString()}");
            await this.GetToken();
        }
    
        private async Task GetToken()
        {
            if (_isPreRender)
                return;
    
            var result = await this.ProtectedSessionStorage.GetAsync<string>(TokenName);
    
            if (result.Success)
                _token = result.Value;
            else
                _token = "Failed!!!!";
    
        }
    }