C# 可选地将必填字段与ASP.NET MVC数据批注一起使用

C# 可选地将必填字段与ASP.NET MVC数据批注一起使用,c#,asp.net-mvc,entity-framework,asp.net-core,data-annotations,C#,Asp.net Mvc,Entity Framework,Asp.net Core,Data Annotations,我在ASP.NET MVC项目中使用EF数据注释,对于必填字段,我定义如下所示的字段: 型号: 查看: 我只想将其中一个字段设置为必填字段(如果用户填写其中一个字段,则可以,但如果他不填写任何字段,则我希望显示一条错误消息,并且不允许他提交表单),但不知道执行此操作的最合适和更智能的方法是什么?我是否应该在提交时使用JavaScript检查所有这些字段并显示必要的错误消息?或者我应该只使用数据注释来执行此操作 公共类MyModel:IValidatableObject { [必需(Error

我在ASP.NET MVC项目中使用EF数据注释,对于必填字段,我定义如下所示的字段:

型号:


查看:


我只想将其中一个字段设置为必填字段(如果用户填写其中一个字段,则可以,但如果他不填写任何字段,则我希望显示一条错误消息,并且不允许他提交表单),但不知道执行此操作的最合适和更智能的方法是什么?我是否应该在提交时使用JavaScript检查所有这些字段并显示必要的错误消息?或者我应该只使用数据注释来执行此操作

公共类MyModel:IValidatableObject
{
[必需(ErrorMessage=“必需!”);
公共字符串PhoneHome{get;set;}
[必需(ErrorMessage=“必需!”);
公共字符串电话工作{get;set;}
[必需(ErrorMessage=“必需!”);
公共字符串PhoneMobile{get;set;}
公共IEnumerable验证(ValidationContext ValidationContext)
{
如果((PhoneHome+PhoneWork+PhoneMobile)。长度<1)
{
返回新的ValidationResult(“您应该设置任何电话号码!”,new[]{“ConfirmForm”});
}
}
}

好的,所以我举了一个例子。我创建了一个自定义验证属性,该属性包含一些参数,例如需要哪些属性以及属性的数量(最小值和最大值)。命名不是最好的,但它可以完成任务(未测试)。如果不关心客户端上的valdiation,可以删除ICientValidatable(使用jquery验证,非结构化…)

该属性如下所示:

public class OptionalRequired : ValidationAttribute, IClientValidatable
{
    /// <summary>
    /// The name of the client validation rule
    /// </summary>
    private readonly string type = "optionalrequired";

    /// <summary>
    /// The (minimum) ammount of properties that are required to be filled in. Use -1 when there is no minimum. Default 1.
    /// </summary>
    public int MinimumAmmount { get; set; } = 1;
    /// <summary>
    /// The maximum ammount of properties that need to be filled in. Use -1 when there is no maximum. Default -1.
    /// </summary>
    public int MaximumAmmount { get; set; } = -1;

    /// <summary>
    /// The collection of property names
    /// </summary>
    public string[] Properties { get; set; }


    public OptionalRequired(string[] properties)
    {
        Properties = properties;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        int validPropertyValues = 0;

        // Iterate the properties in the collection
        foreach (var propertyName in Properties)
        {
            // Find the property
            var property = validationContext.ObjectType.GetProperty(propertyName);

            // When the property is not found throw an exception
            if (property == null)
                throw new ArgumentException($"Property {propertyName} not found.");

            // Get the value of the property
            var propertyValue = property.GetValue(validationContext.ObjectInstance);

            // When the value is not null and not empty (very simple validation)
            if (propertyValue != null && String.IsNullOrEmpty(propertyValue.ToString()))
                validPropertyValues++;
        }

        // Check if the minimum allowed is exceeded
        if (MinimumAmmount != -1 && validPropertyValues < MinimumAmmount)
            return new ValidationResult($"You are required to fill in a minimum of {MinimumAmmount} fields.");

        // Check if the maximum allowed is exceeded
        else if (MaximumAmmount != -1 && validPropertyValues > MaximumAmmount)
            return new ValidationResult($"You can only fill in {MaximumAmmount} of fields");

        // 
        else
            return ValidationResult.Success;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        ModelClientValidationRule rule = new ModelClientValidationRule();

        rule.ErrorMessage = "Enter your error message here or manipulate it on the client side";
        rule.ValidationParameters.Add("minimum", MinimumAmmount);
        rule.ValidationParameters.Add("maximum", MaximumAmmount);
        rule.ValidationParameters.Add("properties", string.Join(",", Properties));

        rule.ValidationType = type;

        yield return rule;
    }
}
使用此配置,您需要至少填写两个必填字段,否则将显示错误。 您可以将属性放在每个属性上,以便在所有属性上弹出错误消息。这就是您想要的

对于客户端验证,我在属性上添加了接口并设置了不同的参数,但是JavaScript本身我没有。您需要查找它(示例)


这段代码没有经过测试,但我认为它可以让您很好地了解事情是如何完成的。

为了澄清,您已经知道解决方案,但如果您想在UI或服务器端进行验证,您会遇到冲突?我认为您最好根据
Required
属性创建自己的验证规则,但需要一些额外的参数仪表和额外的验证逻辑。这将在服务器端验证,但您可以轻松地在客户端实现。@JerdineSabio实际上这并不能解决问题,因为如果我使用这种方法,用户不能只填写一个字段,而将另外两个字段留空。但我想让用户至少填写一个字段。我错了吗?@lordvlad30几年前我使用了
ExpressiveAnnotations
。在这个场景中,你的意思是在服务器端使用
ExpressiveAnnotations
,并在客户端使用Javascript进行必要的更新吗?@hexadecimal我会创建你自己的属性扩展
ValidationAttribute
System.ComponentModel.DataAnnotations
)并且为了支持客户端验证,您需要实现
IClientValidatable
接口(
System.Web.Mvc
)。但是如果您只需要一次,我可能不会对它做太多的工作,只需在检查
模型状态之前在控制器中验证它。IsValid
。谢谢您的回复。我会尝试它,但它在客户端使用我问题中的代码工作吗?它似乎工作并使
模型状态。IsValid
为false
控制器。但是我没有在ModelState中捕捉到错误消息(“您应该设置任何电话号码!”)。有什么想法吗?在另一方面,在这个场景中,投票通过了……需要(ErrorMessage=“需要!”)应该从所有3个电话属性中删除部件。这是真的吗?当然,应该从字段中删除必需的属性。
@Html.TextBoxFor(m => m.PhoneHome)
@Html.ValidationMessageFor(m => m.PhoneHome, null, new { @class = "field-validation-error" })

@Html.TextBoxFor(m => m.PhoneWork)
@Html.ValidationMessageFor(m => m.PhoneWork, null, new { @class = "field-validation-error" })

@Html.TextBoxFor(m => m.PhoneMobile )
@Html.ValidationMessageFor(m => m.PhoneMobile , null, new { @class = "field-validation-error" })
public class OptionalRequired : ValidationAttribute, IClientValidatable
{
    /// <summary>
    /// The name of the client validation rule
    /// </summary>
    private readonly string type = "optionalrequired";

    /// <summary>
    /// The (minimum) ammount of properties that are required to be filled in. Use -1 when there is no minimum. Default 1.
    /// </summary>
    public int MinimumAmmount { get; set; } = 1;
    /// <summary>
    /// The maximum ammount of properties that need to be filled in. Use -1 when there is no maximum. Default -1.
    /// </summary>
    public int MaximumAmmount { get; set; } = -1;

    /// <summary>
    /// The collection of property names
    /// </summary>
    public string[] Properties { get; set; }


    public OptionalRequired(string[] properties)
    {
        Properties = properties;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        int validPropertyValues = 0;

        // Iterate the properties in the collection
        foreach (var propertyName in Properties)
        {
            // Find the property
            var property = validationContext.ObjectType.GetProperty(propertyName);

            // When the property is not found throw an exception
            if (property == null)
                throw new ArgumentException($"Property {propertyName} not found.");

            // Get the value of the property
            var propertyValue = property.GetValue(validationContext.ObjectInstance);

            // When the value is not null and not empty (very simple validation)
            if (propertyValue != null && String.IsNullOrEmpty(propertyValue.ToString()))
                validPropertyValues++;
        }

        // Check if the minimum allowed is exceeded
        if (MinimumAmmount != -1 && validPropertyValues < MinimumAmmount)
            return new ValidationResult($"You are required to fill in a minimum of {MinimumAmmount} fields.");

        // Check if the maximum allowed is exceeded
        else if (MaximumAmmount != -1 && validPropertyValues > MaximumAmmount)
            return new ValidationResult($"You can only fill in {MaximumAmmount} of fields");

        // 
        else
            return ValidationResult.Success;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        ModelClientValidationRule rule = new ModelClientValidationRule();

        rule.ErrorMessage = "Enter your error message here or manipulate it on the client side";
        rule.ValidationParameters.Add("minimum", MinimumAmmount);
        rule.ValidationParameters.Add("maximum", MaximumAmmount);
        rule.ValidationParameters.Add("properties", string.Join(",", Properties));

        rule.ValidationType = type;

        yield return rule;
    }
}
public class Person
{
    [OptionalRequired(new string[] { nameof(MobileNumber), nameof(LandLineNumber), nameof(FaxNumber) }, MinimumAmmount = 2)]
    public string MobileNumber { get; set; }
    public string LandLineNumber { get; set; }
    public string FaxNumber { get; set; }
}