Reputation: 106
I am trying to use the localStorageService
in my CustomAuthStateProvider
class so I can create a AuthenticationState
based on a key in local storage (just to learn and to practice).
However, when I run my application, I get an error telling me that no suitable constructor can be found for CustomAuthStateProvider
. The error makes sense but I don't understand how I can fix it and haven't found much online.
Here is the error:
Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: A suitable constructor for type 'BlazorBattles.Client.CustomAuthStateProvider' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor.
System.InvalidOperationException: A suitable constructor for type 'BlazorBattles.Client.CustomAuthStateProvider' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor.
Here is my CustomAuthStateProvider
implementing AuthenticationStateProvider
:
public class CustomAuthStateProvider : AuthenticationStateProvider
{
private readonly ILocalStorageService _localStorageService;
CustomAuthStateProvider(ILocalStorageService localStorageService)
{
_localStorageService = localStorageService;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
if (await _localStorageService.GetItemAsync<bool>("isAuthenticated"))
{
ClaimsIdentity claimsIdentity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, "Thomas")
}, "Test Authentication");
ClaimsPrincipal user = new ClaimsPrincipal(claimsIdentity);
AuthenticationState state = new AuthenticationState(user);
//Tell all the components that the Auth state has changed
NotifyAuthenticationStateChanged(Task.FromResult(state));
return (state);
}
//This will result in an unauthorised user because it does not have a claims identity
return (new AuthenticationState(new ClaimsPrincipal()));
}
}
Here is my Program.cs
using BlazorBattles.Client;
using BlazorBattles.Client.Services;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Blazored.Toast;
using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components.Authorization;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddBlazoredToast();
builder.Services.AddBlazoredLocalStorage();
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddScoped<IBananaService, BananaService>();
builder.Services.AddScoped<IUnitService, UnitService>();
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthStateProvider>();
builder.Services.AddOptions();
builder.Services.AddAuthorizationCore();
await builder.Build().RunAsync();
I am using V4.3.0 for Blazored.LocalStorage and V6 for Microsoft.AspNetCore.Components.Authorization
Thanks.
It works as expected when I remove the constructor and references to LocalStorage but when I try to inject LocalStorage to use it then I get the error. I'm not sure how to make use of the constrctor correctly in this specific case?
Update: The solution to my problem here is to add the public keyword for the constructor
Upvotes: 1
Views: 4184
Reputation: 106
The issue with my code above is that I had missed out the public keyword in my constructor and now it works as expected. A huge thank you to everyone who commented on my post and provided potential solutions, I appreciate the time you took to help me out!
Original code:
CustomAuthStateProvider(ILocalStorageService localStorageService)
{
_localStorageService = localStorageService;
}
Updated code:
public CustomAuthStateProvider(ILocalStorageService localStorageService)
{
_localStorageService = localStorageService;
}
Upvotes: 0
Reputation: 30046
I think your main issue is your custom AuthenticationStateProvider
inheritance.
Here is my "Pass Through" WASM provider that injects (but never uses) Local Storage. It just gets the user from the base code. Note it's inheritance.
public class CustomAuthenticationStateProvider
: RemoteAuthenticationService<RemoteAuthenticationState, RemoteUserAccount, MsalProviderOptions>
{
private readonly ILocalStorageService _localStorageService;
public CustomAuthenticationStateProvider(
IJSRuntime jsRuntime,
IOptionsSnapshot<RemoteAuthenticationOptions<MsalProviderOptions>> options,
NavigationManager navigation,
AccountClaimsPrincipalFactory<RemoteUserAccount> accountClaimsPrincipalFactory,
ILocalStorageService localStorageService
)
: base(jsRuntime, options, navigation, accountClaimsPrincipalFactory)
{
_localStorageService= localStorageService;
}
public async override Task<AuthenticationState> GetAuthenticationStateAsync()
{
var auth = await base.GetAuthenticationStateAsync();
return new AuthenticationState(auth.User ?? new ClaimsPrincipal());
}
}
For reference here's my Program using AzureAD for authentication.
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddHttpClient("Blazr.AzureOIDC.WASM.ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
// Supply HttpClient instances that include access tokens when making requests to the server project
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("Blazr.AzureOIDC.WASM.ServerAPI"));
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("api://api.id.uri/access_as_user");
});
builder.Services.AddBlazoredLocalStorage();
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
await builder.Build().RunAsync();
Upvotes: 2
Reputation: 11332
Try to register CustomAuthStateProvider
service like this:
// Make the same instance accessible as both AuthenticationStateProvider and CustomAuthStateProvider
builder.Services.AddScoped<CustomAuthStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(provider => provider.GetRequiredService<CustomAuthStateProvider>());
Upvotes: 2