【问题标题】:Unable to bind the parameter in the dropdown of child component in Blazor无法在 Blazor 中的子组件下拉列表中绑定参数
【发布时间】:2021-07-07 16:11:29
【问题描述】:

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

父组件:

@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用于添加/编辑用户。它将objUser 作为父组件的参数。 'objUser' 保存新/选定用户的值。

<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对象进行数据绑定。该对象保存当前输入的数据。然后使用该对象调用数据库服务来创建/更新用户数据。它运行完美!

看起来是这样的:

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

问题 是在 EditUserRolesModal 组件中,我在其中使用变量做一些类似的事情。 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 中用于保存新/选定用户的字段FirstNameLastNameEmail 的值,SelectedUserRole 用于EditUserRolesModal 到保存要分配给用户的选定角色的值。

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

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

附:我在 Blazor 或基于组件的 Web 编程方面没有经验。

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

【问题讨论】:

  • 我没有非常仔细地分析你的代码,但是.. 你为什么不把你的 ApplicationUser 逻辑代码(CRUD、更新角色等)放在一个地方 - 一个数据服务对象。您可以通过依赖注入从所有组件中访问这一事实副本。如果您想跟进,我会发布一些示例代码作为答案。
  • @MrCakaShaunCurtis 谢谢。我能做到。无需为它发布示例代码。现在,我需要知道我的问题的答案。
  • 我添加了上面的评论作为答案。如果这解决了问题,请勾选它作为关闭问题的答案。

标签: asp.net asp.net-core blazor blazor-server-side asp.net-blazor


【解决方案1】:

这是罪魁祸首:

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

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

这里是完整的代码 sn-p 如何做到这一点:

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

注意:这是EventCallback&lt;string&gt; 而不是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 传递给它

这就是你如何定义封装“委托”的方法

private async Task AddRoleToUser(string SelectedUserRole)
{

}

注意:必须将属性type='button"添加到&lt;button type='button".../&gt;, 如果不是您的按钮,则获取默认的type='submit"

重要:

Razor 组件不支持SignInManager&lt;TUser&gt;UserManager&lt;TUser&gt;

See source...。相反,您应该为所需的作业创建服务...

更新:

但我想知道,正如我的问题中所提到的,为什么它与第一个子组件 EditUserModal 中的 objUser 一起工作?根据您的回答,我必须将 objUser 对象发送给 SaveUser 委托,但无论如何它都适用于该方法。

现在 works... 似乎可以工作,就像在组件中使用 UserManager&lt;TUser&gt; 似乎可以工作一样。

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

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

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

好的,我已经更详细地查看了,看看发生了什么。下面有一个解释,但在开始之前,我将重申我的观点,即核心问题是子组件覆盖了它自己的 [Parameter] 属性值。通过避免这样做,您可以避免这个问题。

在这种情况下,以及在其他情况下,Blazor 依赖于父子参数分配是安全的(不会覆盖您想要保留的信息)并且没有触发更多渲染等副作用(因为那样您可能会在一个无限循环)。我们可能应该加强文档中的指导,而不仅仅是说“不要写入您自己的参数”并将其扩展以警告不要在此类属性设置器中产生副作用。 Blazor 使用 C# 属性来表示从父组件到子组件的通信通道对于开发人员来说非常方便,并且在源代码中看起来不错,但副作用的能力是有问题的。我们已经有一个编译时分析器,如果您在 [Parameter] 属性上没有正确的可访问性修饰符,它会发出警告 - 如果参数不是简单的 { get; ,我们应该扩展它以给出警告/错误;放; } 自动属性。

SteveSandersonMS

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

这里有一个简单的代码sn-p,描述了如何在父组件和子组件之间创建双向数据绑定,如何传递参数,以及如何使用传入的参数。

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 方法返回给父组件。

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

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

再一次:

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

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

【讨论】:

  • 感谢您的回答。但我想知道,正如我的问题中提到的,为什么它在第一个子组件EditUserModal 中与objUser 一起使用?根据您的回答,我必须将objUser 对象发送给SaveUser 委托,但无论如何它都适用于该方法。这让我很困惑..
  • 它不应该工作......请发布子组件和父组件的完整代码。但在你这样做之前,将 type="button" 添加到 ,看看它是否仍然像你所说的那样“有效”。
  • 你可以自己使用这个应用程序。我创建了一个带有 SQL 脚本文件的公共存储库。请在此处查看:github.com/jaslam94/BlazorRolesMgmt
【解决方案2】:

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

更新

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

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

【讨论】:

  • 谢谢,但我的问题集中在为什么它适用于模态 1 而不是模态 2?
  • 查看我上面的更新
猜你喜欢
  • 2020-03-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-25
  • 2010-09-22
  • 2015-12-02
  • 1970-01-01
  • 2018-04-26
相关资源
最近更新 更多