Я пытаюсь условно проверить поле в 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;
}
}
Не связано, но почему вы используете JS для установки значения текстового поля через список выбора? Вы можете просто использовать asp-for = "BIS232Request.JSONData.OwnershipActivity.Activity"
прямо в select.
Нашел ответ
Измененный
if (proprtyvalue.ToString() == DesiredValue.ToString() && value == null)
к
if (proprtyvalue.ToString() == DesiredValue.ToString() && value.ToString() == "N/A")
Правильный подход заключался бы в том, чтобы присвоить опции метки значение null
- <option value = "">Please Select</option>
.
Определенно, что-то вроде атрибута RequiredIf
должно работать в любом сценарии, а не только в этом конкретном случае использования.
Основываясь на исходной реализации, я бы рекомендовал расширить 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 спасибо - я отредактировал эту оплошность - я считаю (value? .ToString ()) наиболее лаконичным способом добиться этого, учитывая, что IsNullOrWhiteSpace справится с условным нулем (?)
Вы когда-нибудь это тестировали? Я не думаю, что это работает, поскольку ValidationContext не является экземпляром класса, в котором проверяется это свойство с атрибутами, а скорее самим подтвержденным свойством. Вот что говорится в официальном документе о ValidationContext: This class describes the type or member on which validation is performed.
. Поэтому type.GetProperty(PropertyName)
вернет null в приведенном выше сценарии.
@Csharpest уверен, что я использую его в своих текущих проектах
Извините за беспокойство, но можете ли вы показать мне снимок экрана во время выполнения, где он показывает, что ValidationContext является фактической моделью (родительским элементом свойства), а не самим атрибутом свойства?
@Csharpest - это довольно стандартный подход - docs.microsoft.com/en-us/aspnet/core/mvc/models/… указывает в разделе «Custom Attributes», что ValidationContext должен быть вашей моделью, поскольку они преобразуют его в тип «Movie» с помощью context.ObjectInstance. Вы смотрите на «context» или «context.ObjectInstance»?
Понятно, это сбивает с толку. Я просто попробовал еще раз, но ObjectInstance по-прежнему возвращает свойство, к которому прикреплен настраиваемый атрибут. Почему context.ObjectInstance не содержит экземпляра PageModel-Instance, где находится фактическое свойство + его атрибут?
@Csharpest Я предлагаю вам создать новый вопрос с вашим кодом, чтобы прояснить, почему эта проблема возникает у вас.
Я основывался на ответе Роба. Это универсальный валидатор вместо наследования от 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 проверяет другое свойство на наличие условия «требуется, если истинно». Кажется, только проверка того, что «это» свойство имеет значение.
Я нашел здесь сложный ответ, который буду изучать. stackoverflow.com/a/15975880/292060
Это сильно недооцененный ответ, и с первого раза он сработал фантастически. Спасибо!
Другой, более чистый и универсальный подход заключался бы в реализации более общего атрибута, а не конкретного атрибута «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;
}
}
}
Просто аннотируйте соответствующие свойства этим атрибутом, и любые валидаторы, а также пользовательские валидаторы, которые вы реализовали сами, будут вызываться только при необходимости!
Пример реализации: здесь
Примечание SIde. Ваш
ValidationAttribute
также должен реализовыватьIClientModelValidator
, если вы хотите также проверить валидацию на стороне клиента (см. Проверка модели в ASP.NET Core MVC)