#asp.net #asp.net-core #blazor #blazor-server-side #asp.net-blazor
Вопрос:
Я работаю над ASP.NET Основное серверное приложение Blazor (.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 или компонентном веб-программировании.
Изменить: Я создал общедоступный репозиторий для этого проекта. Он также содержит файл сценария базы данных. Его можно найти здесь.
Комментарии:
1. Я не очень внимательно проанализировал ваш код, но.. Почему бы вам не поместить свой
ApplicationUser
логический код (CRUD, роли обновления и т. Д.) В одно место — объект службы данных. Вы можете получить доступ к этой единственной копии истины, хотя инъекция зависимости от всех ваших компонентов. Если вы хотите продолжить, я опубликую в качестве ответа пример кода.2. @MrCakaShaunCurtis спасибо. Я могу это сделать. Нет необходимости публиковать пример кода для этого. А пока мне нужно знать ответ на свой вопрос.
3. Я добавил комментарий выше в качестве ответа. Если это решит проблему, отметьте это как ответ, чтобы закрыть вопрос.
Ответ №1:
Это и есть виновник:
<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
«делегат», передавая ему SelectedUserRole
И вот как вы определяете метод, инкапсулирующий «делегат»
private async Task AddRoleToUser(string SelectedUserRole)
{
}
Примечание: Вы должны добавить атрибут type='button"
в <button type='button".../>
,
если не ваша кнопка получает значение по умолчанию type='submit"
Важный:
SignInManager<TUser>
и UserManager<TUser>
не поддерживаются в компонентах Razor.
См. Источник…. Вместо этого вам следует создавать службы для необходимых заданий…
Обновить:
Но я хотел бы знать, как упоминалось в моих вопросах, почему он работает с objUser в первом дочернем компоненте EditUserModal? Основываясь на вашем ответе, мне пришлось бы отправить объект objUser делегату saveUser, но он все равно работает с методом.
Теперь это works
… Кажется, это работает так же UserManager<TUser>
, как, по-видимому, работает использование в компоненте.
Проблема с вашим кодом заключается в том, что вы изменяете состояние параметра objUser, привязывая его к входному текстовому элементу, например:
<input class="form-control" type="text" placeholder="First Name" @bind="objUser.FirstName" />
Эта привязка фактически изменяет состояние параметра, но вы не должны изменять состояние параметра. Только компонент, создавший параметр, родительский компонент, должен изменять параметр. Другими словами, вы должны рассматривать параметр как автоматическое свойство…Не меняйте его значение. Теперь, прежде чем вы начнете волноваться, прочтите это:
Хорошо, я посмотрел более подробно и вижу, что происходит. Ниже приведено объяснение, но прежде чем перейти к этому, я повторю свое утверждение о том, что основная проблема заключается в том, что дочерний компонент перезаписывает свое собственное значение свойства [Параметр]. Избегая этого, вы можете избежать этой проблемы.
В этом случае, как и в других, Blazor полагается на то, что назначение параметров от родителя к потомку безопасно (не перезаписывает информацию, которую вы хотите сохранить) и не имеет побочных эффектов, таких как запуск большего количества визуализаций (потому что тогда вы можете оказаться в бесконечном цикле). Вероятно, нам следует усилить руководство в документах, чтобы не просто сказать «не пишите в свои собственные параметры», а расширить его, чтобы предостеречь от побочных эффектов в таких установщиках свойств. Использование Blazor свойств C# для представления канала связи между родительскими и дочерними компонентами довольно удобно для разработчиков и хорошо смотрится в исходном коде, но возможность побочных эффектов проблематична. У нас уже есть анализатор времени компиляции, который предупреждает, если у вас нет правильных модификаторов доступности для свойств [параметра]-возможно, нам следует расширить его, чтобы выдавать предупреждения/ошибки, если параметр является чем — то иным, чем простое свойство { get; set; } auto.
Примечание: Я бы посоветовал вам прочитать весь выпуск целиком, так как это одна из самых важных вещей в Blazor, которую большинство разработчиков не в состоянии переварить и реализовать.
Вот простой фрагмент кода, описывающий, как создать двустороннюю привязку данных между родительским компонентом и его дочерним, как передавать параметры и как использовать переданные параметры.
Два дня привязки.бритва
<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; }
}
Индекс.бритва
@page "/"
<TwoWayDataBinding @bind-Value="Value"></TwoWayDataBinding>
<p>amp;nbsp;</p>
<div>
<input type="text" @bind="Value" @bind:event="oninput"/>
</div>
@code
{
private string Value { get; set; } = "Default value...";
}
Как вы можете видеть, я не изменяю параметр Value, переданный дочернему компоненту. Вместо этого я определяю локальную переменную, которую возвращаю родительскому компоненту через обратный вызов события.Метод InvokeAsync.
Следуйте этому шаблону с компонентом EditUserModal…Если у вас не получится, дайте мне знать.
Излишне говорить, что любые изменения, внесенные вами в параметр objUser
в компоненте EditUserModal, немедленно отражаются в objUser
переменной, определенной в компоненте ManageUsers, поскольку оба указывают на одно и то же место в памяти, и, таким образом, при нажатии на Save
кнопку SaveUser
вызывается метод в компоненте ManageUsers, и все кажется хорошо и …
Еще раз:
`SignInManager<TUser>` and `UserManager<TUser>` aren't supported in
Razor components.
Вы же не хотите обнаружить это при развертывании своего приложения, верно?
Пользуйтесь услугами…
Комментарии:
1. спасибо за ваш ответ. Но я хотел бы знать, как упоминалось в моих вопросах, почему он работает с
objUser
первым дочерним компонентомEditUserModal
? Основываясь на вашем ответе, мне пришлось бы отправитьobjUser
объект дляSaveUser
делегирования, но он все равно работает с методом. Вот что меня смущает..2. Это не должно сработать…Пожалуйста, опубликуйте полный код дочернего и родительского компонентов. Но прежде чем вы это сделаете, добавьте тип=»кнопка» в <класс кнопки=»btn btn-основной» @onclick=»Пользователь сохранения»>Сохраните<класс кнопки=»btn btn-основной» @onclick=»Пользователь сохранения»></кнопка> и посмотрите, работает ли он по-прежнему, как вы сказали.
3. вы можете использовать это приложение самостоятельно. Я создал общедоступный репозиторий с файлом сценария SQL. Пожалуйста, проверьте это здесь: github.com/jaslam94/BlazorRolesMgmt
Ответ №2:
Я не очень внимательно проанализировал ваш код, но.. Почему бы вам не поместить логический код пользователя приложения (CRUD, роли обновления и т. Д.) В одно место — объект службы данных. Вы можете получить доступ к этой единственной копии истины с помощью внедрения зависимостей из всех ваших компонентов.
Обновить
Чтобы ответить на ваш прямой вопрос, зачем objUser
обновляться, но нет SelectedUserRole
? Очень простым ObjUser
является объект и передается по ссылке. Любые моды, которые вы objUser
создаете, будут сделаны на оригинале в родительской версии. С другой стороны SelectedUserRole
, это строка, и хотя это объект, он немного похож на дом на полпути, и с ним обращаются как с примитивным типом. При SetParametersAsync
запуске в дочернем компоненте дочернее свойство для objUser устанавливается в ссылку, в то время как дочернее свойство для SelectedUserRole
устанавливается в копию переданной строки — фактически по значению.
Вот почему я предложил перейти на модель, основанную на услугах, у вас нет подобных проблем.
Комментарии:
1. спасибо, но мой вопрос сосредоточен на том, почему он работает с модальным 1, а не с модальным 2?
2. Смотрите мое обновление выше