英文:
Getting the identity of the logged in user?
问题
The is for a Blazor (server side) application. As I understand it, I have three objects for a logged-in user:
AuthenticationState
which I can get in my razor page as it's injected. This allows me to make calls to see if claims allow users to take actions or see data.IdentityUser
which is the ASP.NET Identity user which has their email, phone, etc. I can get that using the code below.
User which is my app user object. User.AspUserId == IdentityUser.Id
so I can read that object once I have the IdentityUser
.
private async Task<IdentityUser?> GetUser()
{
var userPrincipal = (await authenticationStateTask).User;
var userid = userPrincipal
.FindFirst(u => u.Type.Contains("nameidentifier"))?
.Value;
if (!string.IsNullOrEmpty(userid))
return await UserDbContext.Users.FirstOrDefaultAsync(u => u.Id == userid);
return null;
}
My questions are:
- Are these three objects what I should be using?
- There's an
AuthenticationStateProvider.AuthenticationStateChanged
event. Should I set this up and use it to re-read myIdentityUser
andUser
object, and otherwise I can keep those objects without re-reading them per page. - If so, where do I store these objects as they're a per-session object.
- Is my
GetUser()
code above the best way to get theIdentityUser
?
Anything I'm missing in all this?
英文:
The is for a Blazor (server side) application. As I understand it, I have three objects for a logged in user:
AuthenticationState
which I can get in my razor page as it's injected. This allows me to make calls to see if claims allow users to take actions or see data.IdentityUser
which is the ASP.NET Identity user which has their email, phone etc. I can get that using the code below.- User which is my app user object.
User.AspUserId == IdentityUser.Id
so I can read that object once I have theIdentityUser
.
private async Task<IdentityUser?> GetUser()
{
var userPrincipal = (await authenticationStateTask).User;
var userid = userPrincipal
.FindFirst(u => u.Type.Contains("nameidentifier"))?
.Value;
if (!string.IsNullOrEmpty(userid))
return await UserDbContext.Users.FirstOrDefaultAsync(u => u.Id == userid);
return null;
}
My questions are:
- Are these three objects what I should be using?
- There's a
AuthenticationStateProvider.AuthenticationStateChanged
event. Should I set this up and use it to re-read myIdentityUser
andUser
object and otherwise I can keep those objects without re-reading them per page. - If so, where do I store these objects as they're a per session object.
- Is my
GetUser()
code above the best way to get theIdentityUser
?
Anything I'm missing in all this?
答案1
得分: 1
为了回答你的问题,你需要考虑在哪里需要使用当前用户的上下文。
在组件中,这相当简单。你可以使用Task<AuthenticationState>
级联参数来获取。它是一个Task
,但几乎总是处于Completed
状态,并立即返回。
然而,如果你需要在另一个服务中使用用户上下文,你需要使用注册的AuthenticationStateProvider
服务。
以下是一个简单的用户包装服务,可以被服务或组件使用。
public class UserService : IDisposable
{
private readonly AuthenticationStateProvider _stateProvider;
private Task _task = Task.CompletedTask;
public event EventHandler<UserChangedEventArgs>? UserChanged;
public ClaimsPrincipal User { get; set; } = new ClaimsPrincipal();
public UserService(AuthenticationStateProvider authenticationStateProvider)
{
_stateProvider = authenticationStateProvider;
_stateProvider.AuthenticationStateChanged += this.OnUserChanged;
// 获取当前用户。由于这是一个异步方法,我们只需将其分配给一个类变量
_task = this.GetUserAsync(_stateProvider.GetAuthenticationStateAsync());
}
public async ValueTask<ClaimsPrincipal> GetUserAsync()
{
// 可能已完成,但我们不能确定,所以总是等待它
await _task;
return User;
}
private void OnUserChanged(Task<AuthenticationState> task)
{
// 由于这是一个fire and forget事件,我们将新任务分配给我们的类任务
// 如果需要的话,我们可以等待它
_task = this.GetChangedUserAsync(task);
}
private async Task GetChangedUserAsync(Task<AuthenticationState> task)
{
// 获取用户
await this.GetUserAsync(task);
// 获取新用户后,触发fire and forget事件
this.UserChanged?.Invoke(this, new UserChangedEventArgs(User));
}
private async Task GetUserAsync(Task<AuthenticationState> task)
{
// 将用户重置为新的ClaimsPrincipal,实际上是没有用户的
this.User = new ClaimsPrincipal();
// 获取状态对象
var state = await task;
// 更新用户
if (state is not null)
this.User = state.User;
}
public void Dispose()
=> _stateProvider.AuthenticationStateChanged -= this.OnUserChanged;
}
public class UserChangedEventArgs : EventArgs
{
public ClaimsPrincipal? User { get; private set; }
public UserChangedEventArgs(ClaimsPrincipal? user)
=> User = user;
}
供参考,这是CascadingAuthenticationState
的代码:
@implements IDisposable
@inject AuthenticationStateProvider AuthenticationStateProvider
<CascadingValue TValue="System.Threading.Tasks.Task<AuthenticationState>" Value="@_currentAuthenticationStateTask" ChildContent="@ChildContent" />
@code {
private Task<AuthenticationState>? _currentAuthenticationStateTask;
/// <summary>
/// 应提供身份验证状态的内容。
/// </summary>
[Parameter]
public RenderFragment? ChildContent { get; set; }
protected override void OnInitialized()
{
AuthenticationStateProvider.AuthenticationStateChanged += OnAuthenticationStateChanged;
_currentAuthenticationStateTask = AuthenticationStateProvider
.GetAuthenticationStateAsync();
}
private void OnAuthenticationStateChanged(Task<AuthenticationState> newAuthStateTask)
{
_ = InvokeAsync(() =>
{
_currentAuthenticationStateTask = newAuthStateTask;
StateHasChanged();
});
}
void IDisposable.Dispose()
{
AuthenticationStateProvider.AuthenticationStateChanged -= OnAuthenticationStateChanged;
}
}
你可以在这里看到原始代码 - https://github.com/dotnet/aspnetcore/blob/main/src/Components/Authorization/src/CascadingAuthenticationState.razor
英文:
To answer all your questions, you need to consider where you need to consume the current user.
In a component, that's pretty simple. You can consume the Task<AuthenticationState>
cascaded parameter. It's a Task
, but will almost always be in the Completed
state, and return immediately.
If however you need the user context in another service you need to use the registered AuthenticationStateProvider
service.
Here's a simple User wrapper Service that can be consumed by Services or components.
public class UserService : IDisposable
{
private readonly AuthenticationStateProvider _stateProvider;
private Task _task = Task.CompletedTask;
public event EventHandler<UserChangedEventArgs>? UserChanged;
public ClaimsPrincipal User { get; set; } = new ClaimsPrincipal();
public UserService(AuthenticationStateProvider authenticationStateProvider)
{
_stateProvider = authenticationStateProvider;
_stateProvider.AuthenticationStateChanged += this.OnUserChanged;
// get the current user. as this is an async method we just assign it to a class variable
_task = this.GetUserAsync(_stateProvider.GetAuthenticationStateAsync());
}
public async ValueTask<ClaimsPrincipal> GetUserAsync()
{
// probably complete, but we don't know for sure so always await it
await _task;
return User;
}
private void OnUserChanged(Task<AuthenticationState> task)
{
// As this is a fire and forget event we assign the new task to our class task
// which we can await if we need to
_task = this.GetChangedUserAsync(task);
}
private async Task GetChangedUserAsync(Task<AuthenticationState> task)
{
// get the user
await this.GetUserAsync(task);
// raise our fire and forget event now we've got the new user
this.UserChanged?.Invoke(this, new UserChangedEventArgs(User));
}
private async Task GetUserAsync(Task<AuthenticationState> task)
{
// Reset the user to a new ClaimsPrincipal which is effectivity no user
this.User = new ClaimsPrincipal();
// get the state object
var state = await task;
// update the user
if (state is not null)
this.User = state.User;
}
public void Dispose()
=> _stateProvider.AuthenticationStateChanged -= this.OnUserChanged;
}
public class UserChangedEventArgs : EventArgs
{
public ClaimsPrincipal? User { get; private set; }
public UserChangedEventArgs(ClaimsPrincipal? user)
=> User = user;
}
For reference, here's the code for CascadingAuthenticationState
:
@implements IDisposable
@inject AuthenticationStateProvider AuthenticationStateProvider
<CascadingValue TValue="System.Threading.Tasks.Task<AuthenticationState>" Value="@_currentAuthenticationStateTask" ChildContent="@ChildContent" />
@code {
private Task<AuthenticationState>? _currentAuthenticationStateTask;
/// <summary>
/// The content to which the authentication state should be provided.
/// </summary>
[Parameter]
public RenderFragment? ChildContent { get; set; }
protected override void OnInitialized()
{
AuthenticationStateProvider.AuthenticationStateChanged += OnAuthenticationStateChanged;
_currentAuthenticationStateTask = AuthenticationStateProvider
.GetAuthenticationStateAsync();
}
private void OnAuthenticationStateChanged(Task<AuthenticationState> newAuthStateTask)
{
_ = InvokeAsync(() =>
{
_currentAuthenticationStateTask = newAuthStateTask;
StateHasChanged();
});
}
void IDisposable.Dispose()
{
AuthenticationStateProvider.AuthenticationStateChanged -= OnAuthenticationStateChanged;
}
}
You see see the original here - https://github.com/dotnet/aspnetcore/blob/main/src/Components/Authorization/src/CascadingAuthenticationState.razor
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论