【问题标题】:Blazor Server: Issue with scoped serviceBlazor 服务器:范围服务的问题
【发布时间】:2021-06-19 11:01:40
【问题描述】:

我在尝试使用我在 _Host.cshtml 文件中初始化的范围服务时遇到了奇怪的行为。 这就是服务,很简单:

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;
    }
}

服务在Startup.cs文件中注册为Scoped服务

services.AddScoped<IUserSessionService, UserSessionService>();

该服务在 host.cshtml 页面后面的代码中以下列方式从数据库中获取用户信息:

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);
    }
}

理想情况下,我希望这能够初始化服务,以便从现在开始我可以将其注入到我的组件中,并在需要时访问用户的信息。

但这不起作用:当页面加载时,我能够在屏幕上看到用户信息一秒钟,然后它就消失了。一开始我以为是页面渲染模式ServerPrerendered引起的,但是在Server模式下问题依然存在。

我能找到的唯一解决方法是将服务注册为Singleton;这并不理想,因为它将在所有用户之间共享(至少这是我的理解!)。

有没有更好的地方可以存储这些信息?


更新

这个问题主要是因为我对 Blazor 电路的工作原理理解不佳 :) 我按照@enet 的建议让它工作了。我所做的是:

  1. 从为_Host.cshtml 创建的HostModel 类中的HttpContext 获取信息,并使其可用于具有属性的html 代码
public class HostModel : PageModel
{
    public UserSessionData UserSessionData { get; private set; }
    // ... set the information up
}
  1. 在 ComponentTagHelper 中创建参数并传递属性数据
@model HostModel
<component type="typeof(App)" render-mode="ServerPrerendered" param-UserSessionData="@Model.UserSessionData" />
  1. [Parameter] 身份访问App.razor 中的信息
@code { 
    [Parameter]
    public UserSessionData UserSessionData { get; set; }
}
  1. OnInitializedAsync() 方法内的作用域服务中设置信息
    protected override async Task OnInitializedAsync()
    {
        await UserSessionService.SetUserData(UserSessionData);
    }

现在UserSessionData 将通过IUserSessionService 的注入在应用程序中随处可用

【问题讨论】:

标签: blazor blazor-server-side


【解决方案1】:

由于您提到的原因,您不应该使用 Singleton。

使用public async Task OnGetAsync() 而不是public async Task OnGet()

将数据传递到 Blazor 应用程序的推荐方法是通过组件 html 标签助手提供的参数...

  1. 将您的服务添加到范围内。
  2. 在App组件中定义参数属性,将你的服务注入到组件中,在App组件初始化的时候,读取传入的参数给你注入的服务……现在你的Spa就可以使用了。
  3. 在 _Host.cshtml 文件中将参数属性添加到您的组件 html 标签助手。

在此处查看complete sample 的操作方法

注意:文档中还有一个代码示例如何做到这一点。和我的差不多

更新:

以下是我曾经创建的服务。它工作正常。将其与您所做的进行比较,或者更好地使用它,因为它实际上在很大程度上完成了您的服务所做的事情。

UserEditService.cs

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";
               
        }
    } 

Startup.ConfigureServices

 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();
    }
   
}

注意:在我的服务中,我只返回电子邮件字段,但当然您可以返回完整的身份用户...

【讨论】:

  • 感谢您再次帮助我并为我指出您的示例:非常有帮助。如果我使用您推荐的实现将服务注入另一个服务(例如获取用户信息的存储库)中,这是否可以正常工作?
  • 我猜它会,但你不能完全肯定地说,除非我看到所有图片。注意:我将很快更新我的答案,可能会对您有所帮助...
【解决方案2】:

我无法回答您的问题,但我可以说,在使用身份验证和服务时,事情变得有点冒险。我正在尝试类似的事情:创建一个基于身份验证返回用户信息的服务,然后将其注入到任何需要它的控件中——有时一个页面上的多个组件在初始化期间访问成员资格。

这导致了很多冲突——试图在 DbContext 已经打开时访问它等等。基本上,竞争条件崩溃了。

我的建议是根本不要这样做。但是,如果您找到可行的解决方案,我期待看到它。

【讨论】:

  • 我同意这有点令人费解,但是@enet 推荐的方法效果很好。诀窍是 - 在 _host 文件中的 ComponentTagHelper 中将您需要的作为参数传递。在App.razor 中,您将其作为[参数]。在那里你覆盖了OnInitializedAsync,你在那里初始化的内容将在你的应用程序的每个组件和级别中可用。我会用解决方案更新我的问题。
猜你喜欢
  • 2021-04-23
  • 2021-04-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-12-03
  • 2021-08-30
  • 2020-09-02
相关资源
最近更新 更多