ASP.NET MVC 2自定义ModelValidator的问题

时间:2010-08-31 16:37:05

标签: c# .net asp.net asp.net-mvc

我正在尝试验证视图中的两个文本框字段中的任何一个都提供了值。我做了这个模型验证器:

public class RequireEitherValidator : ModelValidator
{
    private readonly string compareProperty;
    private readonly string errorMessage;

    public RequireEitherValidator(ModelMetadata metadata,
    ControllerContext context, string compareProperty, string errorMessage)
        : base(metadata, context)
    {
        this.compareProperty = compareProperty;
        this.errorMessage = errorMessage;
    }

    public override IEnumerable<ModelValidationResult> Validate(object container)
    {
        if (Metadata.Model == null)
            yield break;
        var propertyInfo = container.GetType().GetProperty(compareProperty);
        if (propertyInfo == null)
            throw new InvalidOperationException("Unknown property:" + compareProperty);

        string valueToCompare = propertyInfo.GetValue(container, null).ToString();

        if (string.IsNullOrEmpty(Metadata.Model.ToString()) && string.IsNullOrEmpty(valueToCompare))
            yield return new ModelValidationResult
            {
                Message = errorMessage
            };
    }
}

这个验证逻辑永远不会被击中,我认为这是因为没有值提供给文本框。

如果您需要它,这里是我创建的提供者和属性以及属性用法:

public class MyValidatorProvider : AssociatedValidatorProvider
{
    protected override IEnumerable<ModelValidator> GetValidators(
        ModelMetadata metadata, ControllerContext context,
        IEnumerable<Attribute> attributes)
    {
        foreach (var attrib in attributes.OfType<RequireEitherAttribute>())
            yield return new RequireEitherValidator(metadata, context,
            attrib.CompareProperty, attrib.ErrorMessage);
    }
}

public class RequireEitherAttribute : Attribute
{
    public readonly string CompareProperty;
    public string ErrorMessage { get; set; }

    public RequireEitherAttribute(string compareProperty)
    {
        CompareProperty = compareProperty;
    }
}

public class StudentLogin
{
    [DisplayName("Last Name")]
    [Required(ErrorMessage = "You must supply your last name.")]        
    public string LastName { get; set; }

    [DisplayName("Student ID")]
    [RegularExpression(@"^\d{1,8}$", ErrorMessage = "Invalid Student ID")]
    [RequireEither("SSN", ErrorMessage = "You must supply your student id or social security number.")]        
    public int? StudentId { get; set; }

    [DisplayName("Social Security Number")]
    [RegularExpression(@"^\d{9}|\d{3}-\d{2}-\d{4}$", ErrorMessage = "Invalid Social Security Number")]
    public string SSN { get; set; }
}

我的观点:

 <%Html.BeginForm(); %>
    <p>
        Please supply the following information to login:</p>
    <ol class="standard">
        <li>
            <p>
                <%=Html.LabelFor(x => x.LastName) %><br />
                <%=Html.TextBoxFor(x => x.LastName)%>
                <%=Html.ValidationMessageFor(x => x.LastName) %></p>
        </li>
        <li>
            <p>
                <%=Html.LabelFor(x => x.StudentId) %><br />
                <%=Html.TextBoxFor(x => x.StudentId) %>
                <%=Html.ValidationMessageFor(x => x.StudentId) %></p>
            <p style="margin-left: 4em;">
                - OR -</p>
            <p>
                <%=Html.LabelFor(x => x.SSN)%><br />
                <%=Html.TextBoxFor(x => x.SSN) %>
                <%=Html.ValidationMessageFor(x => x.SSN) %>
            </p>
        </li>
    </ol>
    <%=Html.SubmitButton("submit", "Login") %>
    <%Html.EndForm(); %>

3 个答案:

答案 0 :(得分:1)

解决这个问题的一种方法不仅仅是创建ValidationAttribute并在类级别应用它。

[RequireEither("StudentId", "SSN")]
public class StudentLogin

错误消息将自动显示在验证摘要中。该属性看起来像这样(我为了简洁起见,通过将所有内容视为字符串,大大简化了IsValid()中的验证逻辑:

public class RequireEither : ValidationAttribute
{
    private string firstProperty;
    private string secondProperty;

    public RequireEither(string firstProperty, string secondProperty)
    {
        this.firstProperty = firstProperty;
        this.secondProperty = secondProperty;
    }

    public override bool IsValid(object value)
    {
        var firstValue = value.GetType().GetProperty(this.firstProperty).GetValue(value, null) as string;
        var secondValue = value.GetType().GetProperty(this.secondProperty).GetValue(value, null) as string;

        if (!string.IsNullOrWhiteSpace(firstValue))
        {
            return true;
        }

        if (!string.IsNullOrWhiteSpace(secondValue))
        {
            return true;
        }
        // neither was supplied so it's not valid
        return false;
    }
}

请注意,在这种情况下,传递给IsValid()的对象是类本身的实例而不是属性。

答案 1 :(得分:1)

我喜欢Steve和Ronnie解决方案,虽然他们创建的自定义属性可能用于其他类/属性对,但我不喜欢“魔术字符串”和反映这样一个简单的情况,我通常会创建一个验证这恰好适合手头的情景。

例如,在这种情况下,我会创建类似的东西:

[AttributeUsage(AttributeTargets.Class)]
public class RequireStudentInfoAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        var student = value as StudentLogin;
        if(student == null)
        {
            return false;
        }

        if (student.StudentId.HasValue || !string.IsNullOrEmpty(student.SSN))
        {
            return true;
        }

        return false;
    }
}

只需将它应用于StudentLogin类,如:

[RequireStudentInfo]
public class StudentLogin

关于客户端验证,我通常会选择http://xval.codeplex.com/,因为它与数据注释很好地集成

答案 2 :(得分:0)

使用史蒂夫的建议只做了一些小改动:

public class RequireEitherAttribute : ValidationAttribute
{
    private string firstProperty;
    private string secondProperty;

    public RequireEitherAttribute(string firstProperty, string secondProperty)
    {
        this.firstProperty = firstProperty;
        this.secondProperty = secondProperty;
    }

    public override bool IsValid(object value)
    {
        object firstValue = value.GetType().GetProperty(firstProperty).GetValue(value, null);
        object secondValue = value.GetType().GetProperty(secondProperty).GetValue(value, null);

        return InputSupplied(firstValue) || InputSupplied(secondValue);
    }

    private bool InputSupplied(object obj)
    {
        if (obj == null)
            return false;

        if (obj is string)
        {
            string str = (string)obj;

            if (str.Trim() == string.Empty)
                return false;
        }

        return true;
    }
}

由于这不是属性级别验证,我不得不在视图中添加验证摘要。

我仍然对如何将其与客户端验证联系起来感到好奇。