Как правильно управлять сообщениями проверки в EditContext с помощью сервера Blazor

Я работаю над преобразованием приложения RazorPages в рендеринг сервера Blazor, приложение на самом деле представляет собой просто пользовательский интерфейс, весь доступ к данным / манипуляции с ними выполняются через API. При выполнении операций CRUD API возвращает набор объектов ошибок в стандартизированном формате.

У меня есть код в приложении RazorPages для преобразования коллекции ошибок в элементы в ModelStateDictionary, и он работает должным образом. Отображаются сообщения, и я могу вносить изменения и повторно отправлять форму.

Я добавил аналогичный код в приложение Blazor, чтобы добавить его в EditContext, но я изо всех сил пытаюсь понять, как очистить сообщения проверки, которые были добавлены моим методом расширения. Я изучал этот вопрос и все предлагаемые решения, но, похоже, у меня ничего не работает: Как сбросить пользовательские ошибки проверки при использовании editform на странице blazor razor

Это метод, который фактически выполняет манипуляции с EditContext:

        private static void AddErrors(EditContext editContext, Dictionary<string, List<string>> errorDictionary)
        {
            if (errorDictionary != null && errorDictionary.Any())
            {
                var validationMessageStore = new ValidationMessageStore(editContext);

                foreach (var error in errorDictionary)
                {
                    validationMessageStore.Add(editContext.Field(error.Key), error.Value);
                }

                editContext.NotifyValidationStateChanged();
            }
        }

Вот определение формы:

        <EditForm class="form-signin" OnValidSubmit="OnSubmit" Model="loginModel" Context="CurrentEditContext">
            <DataAnnotationsValidator />
            <SummaryOnlyValidationSummary />

            <div class="form-group">
                <InputText id="inputUsername" class="form-control" @bind-Value="loginModel.Username" autofocus placeholder="Username" @onkeyup="@(q => ResetValidation(CurrentEditContext))" />
                <ValidationMessage For="@(() => loginModel.Username)" />
            </div>

            <div class="form-group">
                <InputText type="password" id="inputPassword" class="form-control" placeholder="Password" @bind-Value="loginModel.Password" />
                <ValidationMessage For="@(() => loginModel.Password)" />
            </div>

            <div class="form-group">
                <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
            </div>
        </EditForm>

Это сигнатура метода, когда форма отправляется, и переданный editContext выглядит точным на основе сообщений проверки, которые он содержит (сообщения из обязательных атрибутов в свойствах модели верны):

protected async Task OnSubmit(EditContext editContext)

SummaryOnlyValidationSummary - это настраиваемый компонент, который работает так же, как и стандартный ValidationSummary, за исключением того, что исключает любое сообщение, которое уже отображается DataAnnotationsValidator.

Я неправильно добавляю сообщения? Если да, то есть ли какие-нибудь хорошие рекомендации относительно того, где я ошибся? Если нет, кто-нибудь знает, как правильно очистить или сбросить сообщения проверки? Я пытался всегда отправлять форму, не только когда она действительна, но даже принудительное использование editContext.Validate () не очищает сообщение проверки, добавленное моим кодом. Если это вообще помогает, в ResetValidation я создаю новый экземпляр ValidationMessageStore вне текущего editContext, и он не содержит никаких сообщений (хотя не уверен, каково ожидаемое поведение).


person Will Parsons    schedule 19.05.2020    source источник
comment
Есть ли шанс опубликовать минимально воспроизводимый пример? Эти теги смотрят несколько человек, мыслящих по поводу Blazor, и я уверен, что один из нас сможет разобраться, если вы опубликуете пример. Я ожидал, что @enet может сразу же получить ответ на этот вопрос, когда бы они ни появлялись.   -  person Nik P    schedule 19.05.2020
comment
Да, это побочный проект, так что я займусь им, когда смогу отдохнуть от настоящей работы. Я смогу создать небольшое решение, которое воспроизводит его вообще без внешних зависимостей.   -  person Will Parsons    schedule 19.05.2020
comment
@enet YW, но ты тоже заслужил. Как я и подозревал, вы получили ответ, как только увидели его.   -  person Nik P    schedule 19.05.2020


Ответы (1)


Нашел работающее решение, основанное на этом:

https://remibou.github.io/Using-the-Blazor-form-validation/

Я все еще отлаживаю, чтобы понять, почему это работает, когда я делал те же шаги, только в другой структуре кода. Очевидно, я что-то упустил, но теперь это работает!

edit - мне потребовалось время, чтобы разобраться в вещах и вернуться, чтобы обновить.

Вот компонент, который я сделал, части, касающиеся синтаксического анализа из ApiResult, очень специфичны для моего варианта использования, но остальное довольно общее и может быть легко использовано повторно:

    public class ServerModelValidator : ComponentBase
    {
        private ValidationMessageStore _messageStore;

        [CascadingParameter] EditContext CurrentEditContext { get; set; }

        /// <inheritdoc />
        protected override void OnInitialized()
        {
            if (CurrentEditContext == null)
            {
                throw new InvalidOperationException($"{nameof(ServerModelValidator)} requires a cascading parameter " +
                    $"of type {nameof(EditContext)}. For example, you can use {nameof(ServerModelValidator)} inside " +
                    $"an {nameof(EditForm)}.");
            }

            _messageStore = new ValidationMessageStore(CurrentEditContext);
            CurrentEditContext.OnValidationRequested += (s, e) => _messageStore.Clear();
            CurrentEditContext.OnFieldChanged += (s, e) => _messageStore.Clear(e.FieldIdentifier);
        }

        public void DisplayErrors(Dictionary<string, List<string>> errors)
        {
            foreach (var err in errors)
            {
                _messageStore.Add(CurrentEditContext.Field(err.Key), err.Value);
            }
            CurrentEditContext.NotifyValidationStateChanged();
        }

        public void DisplayApiErrors<T>(ApiResult apiResult) where T : ValidatedModel
        {
            if (apiResult != null && apiResult.Errors != null && apiResult.Errors.Any())
            {
                var errorDictionary = new Dictionary<string, List<string>>();

                foreach (var error in apiResult.Errors)
                {
                    var errorKeyPair = error.Message.Count(c => c == '\'') == 2
                        ? GetErrorKeyValuePair(error, typeof(T))
                        : GetErrorKeyValuePair(error);

                    // build a dictionary of property, list of validation messages
                    if (!errorDictionary.ContainsKey(errorKeyPair.Key))
                    {
                        errorDictionary.Add(errorKeyPair.Key, new List<string>());
                    }

                    errorDictionary[errorKeyPair.Key].Add(errorKeyPair.Value);
                }

                DisplayErrors(errorDictionary);
            }
        }


        public void DisplayError(string field, string validationMessage)
        {
            var dictionary = new Dictionary<string, List<string>>
            {
                { field, new List<string> { validationMessage } }
            };

            DisplayErrors(dictionary);
        }

        private KeyValuePair<string, string> GetErrorKeyValuePair(Error error)
        {
            return new KeyValuePair<string, string>(string.Empty, error.Message);
        }

        private KeyValuePair<string, string> GetErrorKeyValuePair(Error error, Type validatedModelType)
        {
            var splitMessage = error.Message.Split('\'', '\'');

            // Find a matching property on T and get it's display name
            // if no display name use the property name
            // if the property wasn't found use the name from the original error message - this shouldn't ever happen unless there is a property mismatch
            var properties = validatedModelType.GetProperties();
            var matchedProperty = properties?.FirstOrDefault(p => p.Name == splitMessage[1]);
            var displayAttribute = matchedProperty?.GetCustomAttributes(false).FirstOrDefault(a => a.GetType() == typeof(DisplayAttribute)) as DisplayAttribute;
            var displayName = (displayAttribute?.Name ?? matchedProperty?.Name) ?? splitMessage[1];

            var errorMessage = $"The {displayName} field {splitMessage[2]}.";

            return new KeyValuePair<string, string>(splitMessage[1], errorMessage);
        }
    }

Он помещается в любую EditForm:

<EditForm class="modal-form" OnValidSubmit="OnSubmit" Model="_season" Context="CurrentEditContext">
    <DataAnnotationsValidator />
    <ServerModelValidator @ref="_serverModelValidator" />

    <SummaryOnlyValidationSummary />

    <EditorWrapper For="@(() => _season.Year)">
        <InputNumber id="inputName" class="form-control" @bind-Value="_season.Year" autofocus placeholder="@DisplayName.For(() => _season.Year)" />
    </EditorWrapper>


    <EditorWrapper For="@(() => _season.Name)">
        <InputText id="inputName" class="form-control" @bind-Value="_season.Name" placeholder="@DisplayName.For(() => _season.Name)" />
    </EditorWrapper>


    <EditorWrapper For="@(() => _season.MaxEventsToScore)">
        <InputNumber id="inputMaxEventsToScore" class="form-control" @bind-Value="_season.MaxEventsToScore" placeholder="@DisplayName.For(() => _season.MaxEventsToScore)" />
    </EditorWrapper>

    <SaveButtonComponent IsSaving="_isProcessing" />
</EditForm>

и объявлен в блоке @code:

private ServerModelValidator _serverModelValidator;

и используется так:

        ApiDataResult<ApiSeason> result;

        if (SeasonId.HasValue)
        {
            // editing
            result = await _apiClient.UpdateSeasonAsync(_season.ToApiModel());
        }
        else
        {
            // creating
            result = await _apiClient.CreateSeasonAsync(_season.ToApiModel());
        }

        if (result == null)
        {
            // add an error
            _serverModelValidator.DisplayError(string.Empty, "Unknown error occurred, please try again.");
        }
        else if (result != null && result.HasErrors)
        {
            // display errors
            _serverModelValidator.DisplayApiErrors<SeasonModel>(result);
        }
person Will Parsons    schedule 19.05.2020
comment
Обновлен исходный ответ с более подробной информацией, спасибо за помощь! - person Will Parsons; 30.05.2020