Реактивная команда в blazor приложении?
Разрабатываю blazor(hosted) приложение с использованием CQRS(Mediator) и луковой архитектуры(как получается). В уровне приложения есть команды, например, ChangeUsernameCommand. Валидация команды происходит также на этом уровне и даже в этом namespace. Эта же команда используется как форма EditForm(Blazor) и валидация тоже берется из уровня приложения. То есть, команда и ее валидация используются как на серверной стороне, так и на клиентской. Вроде бы все ок.
public class ChangeUsernameCommand : IRequest
{
public string NewUsername { get; set; }
public string CurrentPassword { get; set; }
}
public class ChangeUsernameCommandValidator : AbstractValidator<ChangeUsernameCommand>
{
public ChangeUsernameCommandValidator(ITranslatorService ts)
{
RuleFor(x => x.NewUsername)
.NotEmpty().WithMessage(ts.TranslateValidation("username_required"));
RuleFor(x => x.CurrentPassword)
.NotEmpty().WithMessage(ts.TranslateValidation("password_required"));
}
}
С UI или Postman отправляю запрос и все работает успешно. Но т.к я используют эту(или любую другую команду), как форму, то она не имеет реактивности(IObservable) и нет возможности подписаться на изменение любого или конкретного свойства в этом классе(команде). А мне бы хотелось показать сообщение пользователю об ошибке(400) и при первом изменении любого свойства очистить эту ошибку.
На данный момент я вижу 2 пути решения данной проблемы:
Унаследовать ChangeUsernameCommand от ReactiveObject(ReactiveUI) и IRequest. Тогда появится реактивность для клиента, но серверу то она не нужна. Насколько я понимаю команда - POCO/DTO класс, а если его унаследовать от ReactiveObject это будет нарушение.
На клиенте дублировать класс команд. И при отправке запроса трансформировать ChangeUsernameForm в ChangeUsernameCommand. Вроде бы правильное решение, но придется продублировать валидаторы и команды. Например:
public class ChangeUsernameForm : ReactiveObject
{
public string NewUsername { get; set; }
public string CurrentPassword { get; set; }
}
Ответы (1 шт):
Спустя столько времени нашел идеальное решение, как мне кажется. Унаследовать команду от интерфейса(Application), который на клиенте я буду использовать для реактивных форм и мапить эту форму обратно перед вызовом api. С таким подходом на клиенте не нужно дублировать валидацию.
На уровне приложения использую следующую структуру:
Application
Пример с ChangePassword
public partial class ChangePassword
{
public interface ICommand
{
string CurrentPassword { get; set; }
string NewPassword { get; set; }
}
}
public partial class ChangePassword
{
public class Command : IRequest, ICommand
{
public string CurrentPassword { get; set; }
public string NewPassword { get; set; }
}
}
public partial class ChangePassword
{
internal class CommandHandler : IRequestHandler<Command>
{
public async Task<Unit> Handle(Command request, CancellationToken cancellationToken)
{
//Что-то делаем
}
}
}
public partial class ChangePassword
{
public class CommandValidator : AbstractValidator<Command>
{
public CommandValidator()
{
//Валидируем....
}
}
}
Client
public class ChangePasswordForm : ReactiveObject, ChangePassword.ICommand
{
[Reactive]
public string CurrentPassword { get; set; }
[Reactive]
public string NewPassword { get; set; }
}
И эту форму мы перед вызовом нужного api маппим:
public class FormProfile : Profile
{
public FormProfile()
{
CreateMap<ChangePasswordForm, ChangePassword.Command>();
}
}
