Use Blazored.LocalStorage in onPost of PageModel

85 Views Asked by At

I want to use LocalStorage in an onPost method of my PageModel. But I have an error : "InvalidOperationException: 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."

When I connect to my app, I want to remember the jwt in Local Storage because I connect with another API.

Login.cshtml

@page "/Login/{id:int?}"
@model MyApp.Pages.LoginModel
<body>
   <div id="login">
       <form action="/Login" method="post" asp-page-handler="OnPost">
          <label for="username">EMail</label>
          <input type="text" name="paramUsername" id="username" value="@Model.UserName">
          </br>
          <label for="password">Password</label>
          <input type="text" name="paramPassword" id="password" type="password">
          <button type="submit"> Envoyer </button>
       </form>
   </div>
</body>

Login.cshtml.cs

public class LoginModel : PageModel
{
   [Inject] public UtilisateurService utilisateurService { get; set; }
   [Inject] public AuthenticationStateProvider authStateProvider { get; set; }
   public string UserName { get; set; }
  
   public LoginModel(AuthenticationStateProvider authenticationStateProvider) 
   {
      authStateProvider = authenticationStateProvider;
   }

   public async Task<IActionResult> OnPost(string paramUsername, string paramPassword)
   {
      var user = utilisateurService.Authenticate(paramUsername, paramPassword);
      if (user != null)
      {
         CustomAuthenticationStateProvider custom = (CustomAuthenticationStateProvider)authStateProvider;
         await custom.UpdateAuthenticationState(orqualClient); // ERROR HERE
         return LocalRedirect("/index");
      }
      ModelState.AddModelError("Mdp", "Utilisateur ou mot de passe incorrect !");
      return Page();
   }
}

CustomAuthenticationStateProvider.cs

public class CustomAuthenticationStateProvider : AuthenticationStateProvider
{
   private readonly Blazored.LocalStorage.ILocalStorageService _localStorage;

   private ClaimsPrincipal _anonymous = new ClaimsPrincipal(new ClaimsIdentity()); 
   private static string CLAIMS_IDENDITY_TYPE = "auth";

   public CustomAuthenticationStateProvider(Blazored.LocalStorage.ILocalStorageService localStorage) 
   {
      _localStorage = localStorage;
   }

   [...]

   public async Task UpdateAuthenticationState(User user) {
      ClaimsPrincipal claimsPrincipal;
      
      bool hasJwt = await _localStorage.GetItemAsync<string>("account_jwt") != null; // ERROR HERE !
      if (hasJwt) 
          await _localStorage.ClearAsync();
                
       if (user != null && user.Jwt != "") {
          await _localStorage.SetItemAsync("account_jwt", user.Jwt);

          var claims = new List<Claim>
          {
             new Claim(ClaimTypes.Name, user.Email),
          };

          foreach (var role in user.Roles)
             claims.Add(new Claim(ClaimTypes.Role, role));

          var claimsIdentity = new ClaimsIdentity(claims, CLAIMS_IDENDITY_TYPE);
          claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
       } else {
          claimsPrincipal = _anonymous;
       }

       NotifyAuthenticationStateChanged(Task.FromResult(
          new AuthenticationState(claimsPrincipal)
       ));
    }
}

When I click on my button, I get this error :

InvalidOperationException: 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.
   Microsoft.AspNetCore.Components.Server.Circuits.RemoteJSRuntime.BeginInvokeJS(long asyncHandle, string identifier, string argsJson, JSCallResultType resultType, long targetInstanceId)

MyApp.Services.CustomAuthenticationStateProvider.UpdateAuthenticationState(User user) in CustomAuthentificationStateProvider.cs
     var result = await _browserStorage.GetAsync<string>("account_jwt");
MyApp.Pages.LoginModel.OnPost(string paramUsername, string paramPassword) in Login.cshtml.cs
     await custom.UpdateAuthenticationState(orqualClient);

I have tried to save my jwt in the local storage directly in the onPost method, but I have the same error.

1

There are 1 best solutions below

1
Suryateja KONDLA On
@{
    if (IsDataLoaded)
    {
        <CascadingAuthenticationState>
            <Router AppAssembly="@(typeof(Program).Assembly)">
                <Found Context="routeData">
                    <AuthorizeRouteView DefaultLayout="@typeof(MainLayout)"
                                        RouteData="@(routeData)" />
                    <FocusOnNavigate RouteData="@(routeData)"
                                     Selector="h1" />
                </Found>
                <NotFound>
                    <LayoutView Layout="@(typeof(EmptyLayout))">
                        @{
                            NavigationManager.NavigateTo("404", true);
                        }
                    </LayoutView>
                </NotFound>
            </Router>
        </CascadingAuthenticationState>
    }
}


@code
{
private bool IsDataLoaded {get; set;}

    protected override void OnAfterRender(bool firstRender)
    {
        if (!firstRender)
        {
            return;
        }

        IsDataLoaded = true;
        StateHasChanged();
    }
}

Yes your are right, JavaScript interop calls can only be performed during the OnAfterRenderAsync lifecycle method. change the code accordingly. Add a Boolean Flag and make it true in OnAfterRender