无法在 Blazor 的子组件下拉列表中绑定参数

Unable to bind the parameter in the dropdown of child component in Blazor

我正在开发 ASP.NET Core (.NET 5.0) 服务器端 Blazor 应用程序。我正在尝试 add/edit 模式中的用户并在另一个模式中为用户分配角色。所以,我有一个父组件和两个子组件。

父组件:

@if (ShowPopup)
{    
    <!-- This is the popup to create or edit user -->
    <EditUserModal ClosePopup="ClosePopup" CurrentUserRoles="" DeleteUser="DeleteUser" objUser="objUser" SaveUser="SaveUser" strError="@strError" />
}

@if (ShowUserRolesPopup)
{
    <!-- This is the popup to create or edit user roles -->
    <EditUserRolesModal AddRoleToUser="AddRoleToUser" AllRoles="AllRoles" ClosePopup="ClosePopup" CurrentUserRoles="@CurrentUserRoles" objUser="objUser" RemoveRoleFromUser="RemoveRoleFromUser" SelectedUserRole="@SelectedUserRole" strError="@strError" _allUsers="_allUsers" _userRoles="UserRoles" />
}

@code {
    // Property used to add or edit the currently selected user
    ApplicationUser objUser = new ApplicationUser();

    // Roles to display in the roles dropdown when adding a role to the user
    List<IdentityRole> AllRoles = new List<IdentityRole>();
    
    // Tracks the selected role for the current user
    string SelectedUserRole { get; set; }
    
    // To enable showing the Popup
    bool ShowPopup = false;

    // To enable showing the User Roles Popup
    bool ShowUserRolesPopup = false;

    ......
    
}

父组件包含保存数据对象(状态)的对象,例如objUserSelectedUserRole 等以及使用这些对象实现业务规则的方法,例如SaveUserAddRoleToUser.

子组件 EditUserModal 用于 add/edit 用户。它将 objUser 作为父组件的参数。 'objUser' 保存 new/selected 用户的值。

<div class="form-group">
    <input class="form-control" type="text" placeholder="First Name" @bind="objUser.FirstName" />
</div>
<div class="form-group">
    <input class="form-control" type="text" placeholder="Last Name" @bind="objUser.LastName" />
</div>
<div class="form-group">
    <input class="form-control" type="text" placeholder="Email" @bind="objUser.Email" />
</div>
<button class="btn btn-primary" @onclick="SaveUser">Save</button>

@code {

    [Parameter]
    public ApplicationUser objUser { get; set; }
    
    [Parameter]
    public EventCallback SaveUser { get; set; }
    .......

}

objUser对象和SaveUser事件回调是子组件中的参数。在子组件的表单中输入数据并保存后,在父组件中调用SaveUser,自动与父组件中的objUser对象进行数据绑定。该对象保存当前输入的数据。然后使用此对象将数据库服务调用到 create/update 用户数据。它运行完美!

这是它的样子:

objUser对象有输入的数据,一切正常。

但是 issueEditUserRolesModal 组件中,我在其中对变量做了一些类似的事情。 EditUserRolesModal 组件的代码:

<h5 class="my-3">Add Role</h5>
  <div class="row">
     <div class="col-md-10">
         <div class="form-group">
             <input class="form-control" type="text" @bind="objUser.Email" disabled />
         </div>
         <div class="form-group">
             <select class="form-control" @bind="@SelectedUserRole">
                  @foreach (var option in AllRoles)
                  {
                     <option value="@option.Name">@option.Name</option>
                  }
             </select>
         </div>
      <button class="btn btn-primary" @onclick="AddRoleToUser">Assign</button>
    </div>
</div>
    
@code {    
    [Parameter]
    public ApplicationUser objUser { get; set; }

    [Parameter]
    public string SelectedUserRole { get; set; }
    
    [Parameter]
    public List<IdentityRole> AllRoles { get; set; }
    
    [Parameter]
    public EventCallback AddRoleToUser { get; set; }
    
    ........
    
}

这里的参数SelectedUserRole用来保存要分配给用户的角色的值。它绑定到下拉列表。当我更改下拉列表的值时,调试显示子组件(参数)对象中的值已更新。以下图片展示了它:

该值设置为子组件中的 SelectedUserRole 参数对象。

很像,objUserEditUserModal 中用于保存 [=99] 的字段 FirstNameLastNameEmail 的值=] user, SelectedUserRole 用于 EditUserRolesModal 中,用于保存要分配给用户的选定角色的值。

但是当按下Assign按钮并通过子组件在父组件中调用AddRoleToUser事件时,SelectedUserRole变量包含null在父组件中。

两个子组件中的场景是一样的,目的是当子组件内部的参数值发生变化时,绑定的值应该更新并反映在父组件的状态中。但是,它适用于 EditUserModal 组件,而不适用于 EditUserRolesModal 组件。为什么?我做错了什么?

P.S。我没有 Blazor 或基于组件的 Web 编程经验。

编辑: 我已经为该项目创建了一个 public 存储库。它还包含数据库脚本文件。可以查到here.

我没有非常仔细地分析过您的代码,但是.. 为什么不将您的 ApplicationUser 逻辑代码(CRUD、更新角色等)放在一个地方 - 一个数据服务对象。您可以通过所有组件的依赖注入来访问这一事实副本。

更新

直接回答你的问题,为什么 objUser 得到更新,而不是 SelectedUserRole?很简单 ObjUser 是一个对象并通过引用传递。您在 objUser 上制作的任何模组都会在父级的原始背面制作。另一方面,SelectedUserRole 是一个字符串,虽然它是一个对象,但它有点像原始类型一样处理。当子组件中 SetParametersAsync 为 运行 时,objUser 的子 属性 设置为引用,而 SelectedUserRole 的子 属性 设置为副本传递的字符串 - 按值有效。

这就是我建议转向基于服务的模型的原因,您不会遇到这样的问题。

这是罪魁祸首:

<button class="btn btn-primary" @onclick="AddRoleToUser">Assign</button>

单击“分配”按钮时,您应该调用 AddRoleToUser 事件回调,将 SelectedUserRole 值传递给 AddRoleToUser 方法。

下面是完整的代码片段:

[Parameter]
public EventCallback<string> AddRoleToUser { get; set; }

注意:是EventCallback<string>不是EventCallback

做...

<button type='button" class="btn btn-primary" 
    @onclick="OnAddRoleToUser">Assign</button>

  private async Task OnAddRoleToUser()
  {
     // skipped checking for brevity...
    
    await AddRoleToUser.InvokeAsync(SelectedUserRole);
    
  }

请注意,OnAddRoleToUser 方法调用 AddRoleToUser 'delegate',将其传递给 SelectedUserRole

这就是您定义封装方法的方式 'delegate'

private async Task AddRoleToUser(string SelectedUserRole)
{

}

注意:您必须将属性 type='button" 添加到 <button type='button".../>, 如果不是你的按钮获得默认 type='submit"

重要:

SignInManager<TUser>UserManager<TUser> 在 Razor 组件中不受支持。

See source...。您应该改为为所需的工作创建服务...

更新:

But I would like to know, as mentioned in my questions, why does it work with objUser in the first child component EditUserModal? Based on your answer, I would have to send objUser object to SaveUser delegate, but it works with the method anyway.

现在 works... 它似乎可以工作,就像在组件中使用 UserManager<TUser> 似乎可以工作一样。

您的代码存在的问题是您通过将参数 objUser 绑定到输入文本元素来改变它的状态,如下所示:

<input class="form-control" type="text" placeholder="First Name" @bind="objUser.FirstName" /> 

这个绑定实际上改变了参数的状态,但你不应该改变参数的状态。只有创建参数的组件(父组件)才应该改变参数。换句话说,您应该将该参数视为自动 属性...不要更改其值。现在,在您兴奋之前,请阅读以下内容:

OK, I've looked in more detail and see what's going on. There's an explanation below, but before getting to that, I'll restate my claim that the core problem is the child component overwriting its own [Parameter] property value. By avoiding doing that, you can avoid this problem.

In this instance, and in others, Blazor relies on parent-to-child parameter assignment being safe (not overwriting info you want to keep) and not having side-effects such as triggering more renders (because then you could be in an infinite loop). We should probably strengthen the guidance in docs not just to say "don't write to your own parameters" and expand it to caution against having side-effects in such property setters. Blazor's use of C# properties to represent the communication channel from parent to child components is pretty convenient for developers and looks good in source code, but the capacity for side-effects is problematic. We already have a compile-time analyzer that warns if you don't have the right accessibility modifiers on [Parameter] properties - maybe we should extend it to give warnings/errors if the parameter is anything other than a simple { get; set; } auto property.

SteveSandersonMS

注意:我建议您阅读整个问题,因为这是关于 Blazor 的最重要的事情之一,大多数开发人员未能消化和实施。

下面是一段简单的代码片段,描述了如何在父组件及其子组件之间创建双向数据绑定、如何传递参数以及如何使用传入的参数。

TwoWayDataBinding.razor

<div>InternalValue: @InternalValue</div>

<div class="form-group">
    <input type="text" class="form-control" 
           @bind="InternalValue" 
           @bind:event="oninput" 
     />
</div>

@code {
    
    private string InternalValue
    {
        get => Value;
        set 
        {
            if (value != Value)
            {
                ValueChanged.InvokeAsync(value);        
            }
        }
    }

    [Parameter]
    public string Value { get; set; }

    [Parameter]
    public EventCallback<string> ValueChanged { get; set; }
      
}

Index.razor

@page "/"

<TwoWayDataBinding @bind-Value="Value"></TwoWayDataBinding>

<p>&nbsp;</p>
<div>
    <input type="text" @bind="Value" @bind:event="oninput"/>
</div>
@code
{
    private string Value { get; set; } = "Default value...";
  
}  

如您所见,我没有改变传递给子组件的 Value 参数。相反,我定义了一个局部变量,我通过 EventCallback.InvokeAsync 方法 return 返回父组件。

使用 EditUserModal 组件遵循此模式...如果您不成功,请告诉我。

不用说,您在 EditUserModal 组件中的参数 objUser 中所做的任何更改都会立即反映在 ManageUsers 组件中定义的 objUser 变量中,因为两者都指向内存中的相同位置,因此当您单击 Save 按钮时,将调用 ManageUsers 组件中的 SaveUser 方法,一切看起来都很好并且...

再次:

`SignInManager<TUser>` and `UserManager<TUser>` aren't supported in 
 Razor components.

您不想在部署应用程序时发现它,对吧。 使用服务...