Reputation: 3887
I am experiencing a weird behavior while trying to use a Scoped Service that I am initializing in the _Host.cshtml file. This is the service, which is very simple:
public interface IUserSessionService
{
void SetUserData(UserSessionData userData);
UserSessionData GetUserData();
}
public class UserSessionService : IUserSessionService
{
private UserSessionData userSessionData;
public void SetUserData(UserSessionData userData)
{
userSessionData = userData;
}
public UserSessionData GetUserData()
{
return userSessionData;
}
}
The service is registered as Scoped service in the Startup.cs
file
services.AddScoped<IUserSessionService, UserSessionService>();
The service gets user information from a database in the following fashion in the code behind the host.cshtml page:
public class _Host : PageModel
{
private readonly IUserSessionService userSessionService;
public _Host(IUserSessionService userSessionService)
{
this.userSessionService = userSessionService;
}
public async Task OnGet()
{
var authenticatedUser = Request.HttpContext.User;
var userData = await GetUserDataFromDb(authenticatedUser);
await userSessionService.SetUserData(userData);
}
}
Ideally, I would expect this to initialize the service, so that from now on I can inject it in my components and access to the user's information whenever I need it.
This is not working though: when the page loads I am able to see the user information in the screen for a second, then it disappears. Initially I thought it was caused by the page render mode ServerPrerendered
, however in Server
mode the issue persists.
The only fix I could find is to register the service as Singleton
; that is not ideal as that would be shared between all the users (at least that is my understanding!).
Is there a better place where I can store this information?
UPDATE
The issue was mostly the consequence of my bad understanding of how the Blazor circuit works :) I got it working following @enet's advice. What I did is:
HttpContext
in a HostModel
class created for _Host.cshtml
and made it available to the html code with a propertypublic class HostModel : PageModel
{
public UserSessionData UserSessionData { get; private set; }
// ... set the information up
}
@model HostModel
<component type="typeof(App)" render-mode="ServerPrerendered" param-UserSessionData="@Model.UserSessionData" />
[Parameter]
in the App.razor
@code {
[Parameter]
public UserSessionData UserSessionData { get; set; }
}
OnInitializedAsync()
method protected override async Task OnInitializedAsync()
{
await UserSessionService.SetUserData(UserSessionData);
}
Now the UserSessionData
will be available everywhere in the application through the injection of the IUserSessionService
Upvotes: 6
Views: 4805
Reputation: 4208
I can't answer your question , but I can say that when it comes to using authentication and services, things get a little dicey. I was trying a similar thing: making a service that would return user info based on authentication, and then injecting it into whatever controls needed it-- sometimes multiple Components on one page were accessing membership during initialization.
That led to a LOT of conflicts-- trying to access DbContext while it was already open, etc. Basically, race condition crashes.
My recommendation is simply not to do this at all. But if you find a solution that works, I look forward to seeing it.
Upvotes: 0
Reputation: 45586
You shouldn't use Singleton from the very reason you've mentioned.
Do public async Task OnGetAsync()
instead of public async Task OnGet()
The recommended way to pass data to your Blazor app is via parameters provided with the component html tag helper...
See complete sample how to do that here
Note: There's also a code sample in the docs how to do that. It is more or less the same like mine
UPDATE:
The following is a service I've once created. It works fine. Compare it with what you did, or even better use it instead as it actually does what your service does to a great extent.
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Security.Claims;
public class UserEditService
{
private readonly AuthenticationStateProvider authProvider;
private readonly UserManager<IdentityUser> userManager;
public UserEditService ( AuthenticationStateProvider
authProvider,
UserManager<IdentityUser> userManager)
{
// Execute only when the app is served. The app is served
// when it is initially accessed, or after a user has logged-in
this.authProvider = authProvider;
this.userManager = userManager;
}
public async Task<string> GetEmail()
{
var authenticationStateTask = await
authProvider.GetAuthenticationStateAsync();
var user = authenticationStateTask.User;
// Executed whenever the GetMail method is called
if (user.Identity.IsAuthenticated)
{
// Executed only when the user is authenticated
var userId = user.Claims.Where(c => c.Type ==
ClaimTypes.NameIdentifier).FirstOrDefault();
var applicationUser = await
userManager.FindByIdAsync(userId.Value);
return applicationUser.Email;
}
return "User not authenticated";
}
}
services.AddScoped<UserManager<IdentityUser>>();
services.AddScoped<UserEditService>();
@page "/"
@inject UserEditService UserEditService
<div>Your email: @email</div>
@code
{
private string email;
protected override async Task OnInitializedAsync()
{
email = await UserEditService.GetEmail();
}
}
Note: In my service I returns only the email field, but of course you can return the complete identity user...
Upvotes: 1