Условная проверка в MVC.NET Core (обязательно, если)

Я пытаюсь условно проверить поле в MVC.NET Core. У меня две радиокнопки. Если я выберу Да (для Владения), я хочу сделать поле ниже обязательным (раскрывающийся список Активность)

Однако, как бы я ни старался, значение, которое нужно проверить, всегда поступает из поля «Активность», а не из поля «Владение» («N \ A» вместо «Yes»).

Может кто-нибудь, пожалуйста, скажите мне, что я делаю не так?

Представление (chtml)

<div class = " form-group">
    <div class = "bisformdynamiclabel"></div>
    <br />
    @Html.RadioButtonFor(model => model.BIS232Request.JSONData.OwnershipActivity.Ownership, "Yes", new { id = "OwnershipAnswer_true", onclick = "displayOwnershipFieldsRow(true)" })
    <label for = "OwnershipAnswer_true">Yes</label>
    @Html.RadioButtonFor(model => model.BIS232Request.JSONData.OwnershipActivity.Ownership, "No", new { id = "OwnershipAnswer_false", onclick = "displayOwnershipFieldsRow(false)" })
    <label for = "OwnershipAnswer_false">No</label>
    <span class = "alert-danger">
        @Html.ValidationMessage("OwnershipAnswer")
    </span>
</div>
<div class = "row ownershipfieldsrow">
    <div class = "col-xs-12 col-md-12">
        <div class = " form-group">
            <div class = "bisformdynamiclabel"></div>
            <br />
            <input style = "display:none" class = "form-control" type = "text" asp-for = "BIS232Request.JSONData.OwnershipActivity.Activity" />
            <select class = "form-control ownershipactivityselect" onchange = "$('#BIS232Request_JSONData_OwnershipActivity_Activity').val($(this).val());  ">
                <option value = "N/A">Please Select</option>
                <option value = "Manufacturer">Manufacturer</option>
                <option value = "Distributor">Distributor</option>
                <option value = "Exporter">Exporter</option>
                <option value = "Importer">Importer</option>
                <option value = "Other">Other</option>
            </select>
            <span asp-validation-for = "BIS232Request.JSONData.OwnershipActivity.Activity" class = "alert-danger"></span>
            <span class = "alert-danger">
                @Html.ValidationMessage("OwnershipAnswerActivity")
            </span>
        </div>
    </div>

Модель

[Required]
public string Ownership { get; set; }
[RequiredIf("Ownership", "OwnershipAnswer_true", "Activity is required if Ownership is selected")]
public string Activity { get; set; }        
public class RequiredIfAttribute : ValidationAttribute
{
    private String PropertyName { get; set; }
    private String ErrorMessage { get; set; }
    private Object DesiredValue { get; set; }

    public RequiredIfAttribute(String propertyName, Object desiredvalue, String errormessage)
    {
        this.PropertyName = propertyName;
        this.DesiredValue = desiredvalue;
        this.ErrorMessage = errormessage;
    }

    protected override ValidationResult IsValid(object value, ValidationContext context)
    {
        Object instance = context.ObjectInstance;
        Type type = instance.GetType();
        Object proprtyvalue = type.GetProperty(PropertyName).GetValue(instance, null);
        if (proprtyvalue.ToString() == DesiredValue.ToString() && value == null)
        {
            return new ValidationResult(ErrorMessage);
        }
        return ValidationResult.Success;
    }
}

Примечание SIde. Ваш ValidationAttribute также должен реализовывать IClientModelValidator, если вы хотите также проверить валидацию на стороне клиента (см. Проверка модели в ASP.NET Core MVC)

user3559349 14.09.2018 00:13

Не связано, но почему вы используете JS для установки значения текстового поля через список выбора? Вы можете просто использовать asp-for = "BIS232Request.JSONData.OwnershipActivity.Activity" прямо в select.

Chris Pratt 14.09.2018 19:07
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
13
2
13 721
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

Нашел ответ

Измененный

if (proprtyvalue.ToString() == DesiredValue.ToString() && value == null)

к

if (proprtyvalue.ToString() == DesiredValue.ToString() && value.ToString() == "N/A")

Правильный подход заключался бы в том, чтобы присвоить опции метки значение null - <option value = "">Please Select</option>.

user3559349 14.09.2018 00:15

Определенно, что-то вроде атрибута RequiredIf должно работать в любом сценарии, а не только в этом конкретном случае использования.

Chris Pratt 14.09.2018 19:05

Основываясь на исходной реализации, я бы рекомендовал расширить RequiredAttribute, а не ValidationAttribute - тогда ваше сообщение об ошибке по умолчанию и другие значения по умолчанию будут установлены в соответствии с [Обязательно]. В любом случае свойство «errormessage» является избыточным, поскольку оно уже есть у вас как свойство ValidationAttribute, а исходный код генерирует предупреждение для свойства ErrorMessage - вы также можете использовать nameof для украшения атрибута, чтобы держать вещи более жесткими в вашем код:

Моя реализация немного более конкретна, поэтому, если свойство является логическим, я могу указать, что свойство требуется (если, скажем, установлен флажок):

[AttributeUsage(AttributeTargets.Property)]
public class RequiredIfTrueAttribute : RequiredAttribute
{
    private string PropertyName { get; set; }

    public RequiredIfTrueAttribute(string propertyName)
    {
        PropertyName = propertyName;
    }

    protected override ValidationResult IsValid(object value, ValidationContext context)
    {
        object instance = context.ObjectInstance;
        Type type = instance.GetType();

        bool.TryParse(type.GetProperty(PropertyName).GetValue(instance)?.ToString(), out bool propertyValue);

        if (propertyValue && string.IsNullOrWhiteSpace(value?.ToString()))
        {
            return new ValidationResult(ErrorMessage);
        }

        return ValidationResult.Success;
    }
}

Пример использования:

public bool IsBusinessProfile { get; set; }

[RequiredIfTrue(nameof(IsBusinessProfile), ErrorMessage = "ABN is required for Business Profiles")]
public string Abn { get; set; }

Это именно то, что я искал. Когда я запустил его, я получил исключение нулевой ссылки на value, поэтому я немного изменил эту строку, чтобы сначала проверить на null, а затем проверить ToString: if (propertyValue && (value == null || string.IsNullOrWhiteSpace(value.ToString())))

FirstDivision 12.12.2019 21:55

@FirstDivision спасибо - я отредактировал эту оплошность - я считаю (value? .ToString ()) наиболее лаконичным способом добиться этого, учитывая, что IsNullOrWhiteSpace справится с условным нулем (?)

Rob 13.12.2019 04:59

Вы когда-нибудь это тестировали? Я не думаю, что это работает, поскольку ValidationContext не является экземпляром класса, в котором проверяется это свойство с атрибутами, а скорее самим подтвержденным свойством. Вот что говорится в официальном документе о ValidationContext: This class describes the type or member on which validation is performed.. Поэтому type.GetProperty(PropertyName) вернет null в приведенном выше сценарии.

Csharpest 19.12.2019 14:37

@Csharpest уверен, что я использую его в своих текущих проектах

Rob 20.12.2019 03:34

Извините за беспокойство, но можете ли вы показать мне снимок экрана во время выполнения, где он показывает, что ValidationContext является фактической моделью (родительским элементом свойства), а не самим атрибутом свойства?

Csharpest 20.12.2019 08:36

@Csharpest - это довольно стандартный подход - docs.microsoft.com/en-us/aspnet/core/mvc/models/… указывает в разделе «Custom Attributes», что ValidationContext должен быть вашей моделью, поскольку они преобразуют его в тип «Movie» с помощью context.ObjectInstance. Вы смотрите на «context» или «context.ObjectInstance»?

Rob 13.01.2020 12:39

Понятно, это сбивает с толку. Я просто попробовал еще раз, но ObjectInstance по-прежнему возвращает свойство, к которому прикреплен настраиваемый атрибут. Почему context.ObjectInstance не содержит экземпляра PageModel-Instance, где находится фактическое свойство + его атрибут?

Csharpest 13.01.2020 13:19

@Csharpest Я предлагаю вам создать новый вопрос с вашим кодом, чтобы прояснить, почему эта проблема возникает у вас.

Rob 13.01.2020 23:00

Я основывался на ответе Роба. Это универсальный валидатор вместо наследования от Required, а также обеспечивает валидацию на стороне клиента. Я использую .Net Core 3.0

using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using System;
using System.Collections.Generic;
using System.Text;

namespace System.ComponentModel.DataAnnotations
{

    [AttributeUsage(AttributeTargets.Property)]
    public class RequiredIfTrueAttribute : ValidationAttribute, IClientModelValidator
    {
        private string PropertyName { get; set; }

        public RequiredIfTrueAttribute(string propertyName)
        {
            PropertyName = propertyName;
            ErrorMessage = "The {0} field is required."; //used if error message is not set on attribute itself
        }

        protected override ValidationResult IsValid(object value, ValidationContext context)
        {
            object instance = context.ObjectInstance;
            Type type = instance.GetType();

            bool.TryParse(type.GetProperty(PropertyName).GetValue(instance)?.ToString(), out bool propertyValue);

            if (propertyValue && (value == null || string.IsNullOrWhiteSpace(value.ToString())))
            {
                return new ValidationResult(ErrorMessage);
            }

            return ValidationResult.Success;
        }

        public void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            var errorMessage = FormatErrorMessage(context.ModelMetadata.GetDisplayName());
            MergeAttribute(context.Attributes, "data-val-requirediftrue", errorMessage);
        }

        private bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
        {
            if (attributes.ContainsKey(key))
            {
                return false;
            }
            attributes.Add(key, value);
            return true;
        }
    }
}

Клиентский Javascript

//Custom validation script for the RequiredIfTrue validator
/*
 * Note that, jQuery validation registers its rules before the DOM is loaded. 
 * If you try to register your adapter after the DOM is loaded, your rules will
 * not be processed. So wrap it in a self-executing function.
 * */
(function ($) {

    var $jQval = $.validator;

   $jQval.addMethod("requirediftrue",
       function (value, element, parameters) {
            return value !== "" && value != null;
        }
    );

    var adapters = $jQval.unobtrusive.adapters;
    adapters.addBool('requirediftrue');

})(jQuery);

использование

    public bool IsSpecialField { get; set; }

    [RequiredIfTrue(nameof(IsSpecialField), ErrorMessage = "This is my custom error message")]
    [Display(Name = "Address 1")]
    public string Address1 { get; set; }

    [RequiredIfTrue(nameof(IsSpecialField))]
    public string City { get; set; }

Мне нравится ваш ответ, но я не понимаю, как javascript проверяет другое свойство на наличие условия «требуется, если истинно». Кажется, только проверка того, что «это» свойство имеет значение.

goodeye 11.02.2020 03:45

Я нашел здесь сложный ответ, который буду изучать. stackoverflow.com/a/15975880/292060

goodeye 11.02.2020 04:42

Это сильно недооцененный ответ, и с первого раза он сработал фантастически. Спасибо!

Eric Longstreet 24.08.2020 05:22

Другой, более чистый и универсальный подход заключался бы в реализации более общего атрибута, а не конкретного атрибута «requiredIf», поскольку вам придется создавать несколько настраиваемых атрибутов для каждого типа проверки, которую вы случайно используете.

К счастью, начиная с .NET Core 2, Microsoft предоставляет интерфейс IPropertyValidationFilter, который можно реализовать в настраиваемом атрибуте. Этот интерфейс определяет функцию ShouldValidateEntry, которая позволяет контролировать, должна ли текущая запись проверяться или нет; поэтому это выполняется до вызова каких-либо валидаторов.

В Framework уже есть одна реализация по умолчанию, ValidateNeverAttribute, но реализовать свою собственную, которая выполняет условную проверку другого значения, тривиально:

using System;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;

namespace Foo {
    // Implementation makes use of the IPropertyValidationFilter interface that allows
    // control over whether the attribute (and its children, if relevant) need to be
    // validated.
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
    public class ConditionalValidationAttribute : Attribute, IPropertyValidationFilter {
        public string OtherProperty { get; set; }
        public object OtherValue { get; set; }

        public ConditionalValidationAttribute(string otherProperty, object otherValue) {
            OtherProperty = otherProperty;
            OtherValue = otherValue;
        }

        public bool ShouldValidateEntry(ValidationEntry entry, ValidationEntry parentEntry) {
            // Default behaviour if no other property is set: continue validation
            if (string.IsNullOrWhiteSpace(OtherProperty)) return true;

            // Get the property specified by the name. Might not properly work with
            // nested properties.
            var prop = parentEntry.Metadata.Properties[OtherProperty]?.PropertyGetter?.Invoke(parentEntry.Model);

            return prop == OtherValue;
        }
    }
}

Просто аннотируйте соответствующие свойства этим атрибутом, и любые валидаторы, а также пользовательские валидаторы, которые вы реализовали сами, будут вызываться только при необходимости!

Пример реализации: здесь

Другие вопросы по теме