Невозможно привязать параметр в раскрывающемся списке дочернего компонента в Blazor

Я работаю над серверным приложением Blazor ASP.NET Core (.NET 5.0). Я пытаюсь добавлять / редактировать пользователей в модальном режиме и назначать роли пользователю в другом модальном режиме. Итак, у меня есть родительский компонент и два дочерних компонента.

Родительский компонент:

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

    ......
    
}

Родительский компонент содержит объекты, которые содержат объекты данных (состояние), например. objUser, SelectedUserRole и т. Д. И методы, которые используют эти объекты для реализации бизнес-правил, например SaveUser и AddRoleToUser.

Дочерний компонент 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 в дочернем компоненте.

введите описание изображения здесь

Подобно тому, как objUser использовался в EditUserModal для хранения значений полей FirstName, LastName и Email нового / выбранного пользователя, SelectedUserRole используется в EditUserRolesModal для хранения значения выбранной роли, которая будет назначена пользователю.

Но когда нажата кнопка Назначить и событие AddRoleToUser вызывается в родительском компоненте через дочерний компонент, переменная SelectedUserRole в родительском компоненте содержит значение null.

введите описание изображения здесь

Сценарий в обоих дочерних компонентах одинаков, цель которого состоит в том, что при изменении значения параметров внутри дочернего компонента связанные значения должны быть обновлены и отражены в родительском состоянии. Однако он работает в EditUserModal компоненте, а не в EditUserRolesModal компоненте. Почему? Что я делаю неправильно?

P.S. У меня нет опыта в Blazor или веб-программировании на основе компонентов.

Изменить: я создал общедоступный репозиторий для этого проекта. Он также содержит файл сценария базы данных. Его можно найти здесь.


person Junaid    schedule 12.04.2021    source источник
comment
Я не очень внимательно анализировал ваш код, но ... Почему бы вам не поместить ваш ApplicationUser логический код (CRUD, роли обновления и т. Д.) В одно место - объект службы данных. Вы можете получить доступ к этой единственной копии правды через Dependancy Injection из всех ваших компонентов. Если вы хотите следить за этим, я отправлю пример кода в качестве ответа.   -  person MrC aka Shaun Curtis    schedule 12.04.2021
comment
@MrCakaShaunCurtis, спасибо. Я могу это сделать. Нет необходимости размещать для этого пример кода. А пока мне нужно знать ответ на свой вопрос.   -  person Junaid    schedule 12.04.2021
comment
Я добавил комментарий выше в качестве ответа. Если это решит проблему, отметьте это как ответ, чтобы закрыть вопрос.   -  person MrC aka Shaun Curtis    schedule 12.04.2021


Ответы (2)


Это виновник:

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

Когда кнопка Assign нажата, вы должны вызвать обратный вызов события AddRoleToUser, передав значение SelectedUserRole методу AddRoleToUser.

Вот полный фрагмент кода, как это сделать:

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

Примечание: это EventCallback<string> не EventCallback

Do...

<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 'делегат', передавая ему SelectedUserRole

И вот как вы определяете метод, инкапсулирующий «делегата».

private async Task AddRoleToUser(string SelectedUserRole)
{

}

Примечание: вы должны добавить атрибут type='button" к <button type='button".../>, если не ваша кнопка, получит значение по умолчанию type='submit"

Важный:

SignInManager<TUser> и UserManager<TUser> не поддерживаются в компонентах Razor.

См. источник ... . Вместо этого вы должны создать службы для требуемых рабочих мест ...

ОБНОВИТЬ:

Но я хотел бы знать, как упоминалось в моих вопросах, почему он работает с objUser в первом дочернем компоненте EditUserModal? Основываясь на вашем ответе, мне пришлось бы отправить объект objUser делегату SaveUser, но он все равно работает с методом.

Теперь это _19 _... Кажется, что это работает, так же как использование UserManager<TUser> в компоненте, похоже, работает.

Проблема с вашим кодом заключается в том, что вы изменяете состояние параметра objUser, привязывая его к текстовому элементу ввода, например:

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

Эта привязка фактически изменяет состояние параметра, но вы не должны изменять состояние параметра. Только компонент, создавший параметр, родительский компонент, должен изменять параметр. Другими словами, вы должны рассматривать параметр как автоматическое свойство ... Не меняйте его значение. А теперь, прежде чем вы будете в восторге, прочтите это:

Хорошо, я посмотрел поподробнее и посмотрел, что происходит. Ниже приводится объяснение, но прежде чем перейти к нему, я повторю свое утверждение о том, что основная проблема заключается в том, что дочерний компонент перезаписывает свое собственное значение свойства [Parameter]. Избегая этого, вы можете избежать этой проблемы.

В этом случае и в других случаях Blazor полагается на то, что назначение параметров от родителей к потомкам является безопасным (не перезаписывая информацию, которую вы хотите сохранить), и не имеет побочных эффектов, таких как запуск большего количества визуализаций (потому что тогда вы можете оказаться в бесконечном цикле. ). Вероятно, нам следует усилить руководство в документации, а не просто сказать, что не писать в свои собственные параметры, а расширить его, чтобы предостеречь от побочных эффектов в таких установщиках свойств. Использование Blazor свойств C # для представления канала связи от родительских компонентов к дочерним довольно удобно для разработчиков и хорошо выглядит в исходном коде, но возможность появления побочных эффектов проблематична. У нас уже есть анализатор времени компиляции, который предупреждает, если у вас нет правильных модификаторов доступности для свойств [Parameter] - возможно, нам следует расширить его, чтобы выдавать предупреждения / ошибки, если параметр не является простым {get; установленный; } авто свойство.

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.

Следуйте этому шаблону с компонентом EditUserModal ... Если у вас не получится, дайте мне знать.

Излишне говорить, что любые изменения, внесенные вами в параметр objUser в компоненте EditUserModal, немедленно отражаются в переменной objUser, определенной в компоненте ManageUsers, поскольку оба указывают на одно и то же место в памяти, и, следовательно, когда вы нажимаете на Save, вызывается метод SaveUser в компоненте ManageUsers, все в порядке и ...

Снова:

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

Вы же не хотите обнаруживать это при развертывании приложения, верно. Воспользуйтесь услугами ...

person enet    schedule 12.04.2021
comment
спасибо за Ваш ответ. Но я хотел бы знать, как упоминалось в моих вопросах, почему он работает с objUser в первом дочернем компоненте EditUserModal? Основываясь на вашем ответе, мне пришлось бы отправить objUser объект делегату SaveUser, но он все равно работает с методом. Вот что меня смущает .. - person Junaid; 13.04.2021
comment
Не должно работать ... Пожалуйста, выложите полный код дочернего и родительского компонентов. Но прежде чем вы это сделаете, добавьте кнопку type = в ‹button class = btn btn-primary @ onclick = SaveUser› Save ‹/button› и посмотрите, все ли «работает», как вы сказали. - person enet; 13.04.2021
comment
вы можете использовать это приложение самостоятельно. Я создал публичный репозиторий с файлом сценария SQL. Пожалуйста, проверьте это здесь: github.com/jaslam94/BlazorRolesMgmt - person Junaid; 13.04.2021
comment
Смотрите мое обновление выше ... - person enet; 14.04.2021

Я не очень внимательно анализировал ваш код, но ... Почему бы вам не поместить код логики ApplicationUser (CRUD, роли обновления и т.д.) в одно место - объект службы данных. Вы можете получить доступ к этой единственной истине посредством внедрения зависимостей из всех ваших компонентов.

Обновлять

Чтобы ответить на ваш прямой вопрос, почему objUser обновляется, а не SelectedUserRole? Очень простой ObjUser - это объект и передается по ссылке. Любые модификации, которые вы делаете на objUser, будут сделаны на оригинале обратно в родительском. С другой стороны, SelectedUserRole - это строка, и хотя это объект, он находится на полпути и рассматривается как примитивный тип. Когда SetParametersAsync выполняется в дочернем компоненте, дочернее свойство для objUser устанавливается на ссылку, а дочернее свойство для SelectedUserRole устанавливается на копию переданной строки - фактически по значению.

Вот почему я предложил перейти на сервисную модель, у вас нет таких проблем.

person MrC aka Shaun Curtis    schedule 12.04.2021
comment
спасибо, но мой вопрос фокусируется на том, почему он работает с модальным 1, а не с модальным 2? - person Junaid; 13.04.2021
comment
См. Мое обновление выше - person MrC aka Shaun Curtis; 13.04.2021