Реактивная команда в 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 пути решения данной проблемы:

  1. Унаследовать ChangeUsernameCommand от ReactiveObject(ReactiveUI) и IRequest. Тогда появится реактивность для клиента, но серверу то она не нужна. Насколько я понимаю команда - POCO/DTO класс, а если его унаследовать от ReactiveObject это будет нарушение.

  2. На клиенте дублировать класс команд. И при отправке запроса трансформировать ChangeUsernameForm в ChangeUsernameCommand. Вроде бы правильное решение, но придется продублировать валидаторы и команды. Например:

public class ChangeUsernameForm : ReactiveObject
{
    public string NewUsername { get; set; }

    public string CurrentPassword { get; set; }
}

Ответы (1 шт):

Автор решения: Lapish

Спустя столько времени нашел идеальное решение, как мне кажется. Унаследовать команду от интерфейса(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>();
    }
}
→ Ссылка