0

The task considers two forms:

  • operation registration
  • operation confirmation

So 1st form allows to enter the data with respective field validation based on data annotations. Meanwhile 2nd form is designed to show the same data in readonly mode for final confirmation.

There is an idea to use model validation for 1st form as well. Let's say data annotation is used for simple validations like field value length and field value content. Meanwhile model validation is going to be used for more complex data validation. For example, some extra data is required for certain operation type only. Otherwise no extra data is required. This extra data can have own validation requirements and so on. So the validation logic can be quite complex and requires information about whole model. That is why there is idea to use a model validation.

There was a recommendation how to enable a model validation in previous question.

But I have noticed some unexpected behavior. The validation is fired by submit button pressing on 1st form. Both types of validations are run (form fields data annotations and form validation). It is fine. But validation messages are shown based on field data annotations only. The validation errors based on model validation are not shown. They are available on next form only. 2nd form (confirmation) ModelState contains these errors. But let's say it is too late. The idea is to show these errors (which comes from model validation) on 1st form as well. Even more, I expect that post action (confirmation page call) should not happen in case of model validation errors.

Let me share the source code without all fields. Because the view and respective view model contain around 20 fields.

Please find below the part of view model

public class DealRegistrationViewModel : BaseViewModel 
{
        [Display(Name = "Код (для получения котировок с биржи)")]
        [StringLength(10, MinimumLength = 0)]
        [RegularExpression(@"^[a-zA-Z]+$", ErrorMessage = "Неверное значение")]
        public string Code { get; set; }

        [Required]        
        public int SelectedSecuritiesTypeId { get; set; }

        [Display(Name = "Вид")]
        public List<SecuritiesType> SecuritiesTypes { get; set; }
}

Please find below the respective part of view

<div class="row">
    <div class="col-md-4">
        <form asp-action="Confirmation">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="SecuritiesTypes" class="control-label"></label>
                <br>
                <select asp-for="SelectedSecuritiesTypeId" asp-items="@(new SelectList(Model.SecuritiesTypes, "Id", "Description"))"></select>
                <br>
                <span asp-validation-for="SelectedSecuritiesTypeId" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Code" class="control-label"></label>
                <input asp-for="Code" class="form-control"/>
                <span asp-validation-for="Code" class="text-danger"></span>
            </div>

Please find below the implementation of IModelValidator.Validate method

public IEnumerable<ModelValidationResult> Validate(ModelValidationContext context)
        {
            var model = context.Container as WAVM.DealRegistrationViewModel;

            if (model == null)
            {
                throw new ArgumentNullException("Container is null");
            }

            if (context.ModelMetadata.Name.Equals(nameof(model.Code)) == false
                && context.ModelMetadata.Name.Equals(nameof(model.SelectedSecuritiesTypeId)) == false)
            {
                return Enumerable.Empty<ModelValidationResult>();
            }

            BLI.SecuritiesType type = _securitiesService.GetItemAsync(model.SelectedSecuritiesTypeId).GetAwaiter().GetResult();

            if (type == null)            
            {
                return new List<ModelValidationResult>
                    {
                        new ModelValidationResult("", $"Не найден тип ценной бумаги по коду ({model.SelectedSecuritiesTypeId})")
                    };
            }

            if (type.IsRateRequestAllowed == true && string.IsNullOrEmpty(model.Code) == true)
            {
                if (context.ModelMetadata.Name == nameof(model.Code))
                {
                    return new List<ModelValidationResult>
                    {
                        new ModelValidationResult("", "Пустое значение поля Код не допустимо")
                    };
                }

                if (context.ModelMetadata.Name == nameof(model.SelectedSecuritiesTypeId))
                {
                    return new List<ModelValidationResult>
                    {
                        new ModelValidationResult("", "Выбранный тип ценной бумаги требует значения для поля Код")
                    };
                }
            }

            if (type.IsRateRequestAllowed == false && string.IsNullOrEmpty(model.Code) == false)
            {
                if (context.ModelMetadata.Name == nameof(model.Code))
                {
                    return new List<ModelValidationResult>
                    {
                        new ModelValidationResult("", "Значение поля Код должно быть пустым")
                    };
                }

                if (context.ModelMetadata.Name == nameof(model.SelectedSecuritiesTypeId))
                {
                    return new List<ModelValidationResult>
                    {
                        new ModelValidationResult("", "Выбранный тип ценной бумаги не допускает значения для поля Код")
                    };
                }
            }

            return Enumerable.Empty<ModelValidationResult>();
        }

The execution correctly stops on breakpoints on each "return new List"

Please find below the implementation of IModelValidatorProvider.CreateValidators method below

public void CreateValidators(ModelValidatorProviderContext context)
{
    if (context.Results.Any(v => v.Validator.GetType() == typeof(DealRegistrationViewModelValidator)) == true)
    {
        return;
    }

    if (context.ModelMetadata.ContainerType == typeof(DealRegistrationViewModel) 
            && ( context.ModelMetadata.Name == nameof(DealRegistrationViewModel.Code) 
                || context.ModelMetadata.Name == nameof(DealRegistrationViewModel.SelectedSecuritiesTypeId)) )
    {
        context.Results.Add(new ValidatorItem
        {
            Validator = new DealRegistrationViewModelValidator(_securitiesService),
            IsReusable = true
        });
    }
}
Alex
  • 31
  • 5
  • Please show the piece of code that's not behaving as intended, otherwise it's very difficult to help. – Xerillio Mar 20 '21 at 23:11
  • I have added some parts of source code like view model, view, model validator. But I am not sure which one does not work. As I mentioned the model validator is called and it is confirmed by debug. The question is why the result of form validation is not shown on the current page ? Why the form submit action is fired if there are some validation errors ? Please let me know if anything else is required. – Alex Mar 21 '21 at 18:26

0 Answers0