Blazor 服务器:范围服务问题

时间:2021-03-22 18:52:22

标签: blazor blazor-server-side

我在尝试使用我在 _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. 从为 HttpContext 创建的 HostModel 类中的 _Host.cshtml 获取信息,并将其提供给具有属性的 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 将在应用程序的任何地方可用

2 个答案:

答案 0 :(得分: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();
    }
   
}

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

答案 1 :(得分:0)

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

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

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