【问题标题】:Custom validation attribute to check for duplicates among my model properties is not firing用于检查模型属性中重复项的自定义验证属性未触发
【发布时间】:2015-07-07 17:11:33
【问题描述】:

我想向我的模型添加一个自定义验证属性,以检查是否有任何答案包含重复项。也就是说,如果用户为任何字段输入相同的答案,我想在他们输入重复答案时显示错误。

这是我的模型:

public class SecurityQuestions
{
    public int Question1Id { get; set; }
    public int Question2Id { get; set; }
    public int Question3Id { get; set; }
    public int Question4Id { get; set; }
    public int Question5Id { get; set; }
    public int Question6Id { get; set; }

    [UniqueAnswersOnly]
    public string Answer1 { get; set; }
    [UniqueAnswersOnly]
    public string Answer2 { get; set; }
    [UniqueAnswersOnly]
    public string Answer3 { get; set; }
    [UniqueAnswersOnly]
    public string Answer4 { get; set; }
    [UniqueAnswersOnly]
    public string Answer5 { get; set; }
    [UniqueAnswersOnly]
    public string Answer6 { get; set; }
}

这是我对自定义属性的尝试:

public class UniqueAnswersOnly: ValidationAttribute, IClientValidatable
    {

        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            //Get a list of all properties that are marked with [UniqueAnswersOnly]
            var props = validationContext.ObjectInstance.GetType().GetProperties().Where(
                prop => Attribute.IsDefined(prop, typeof(UniqueAnswersOnly)));

            var values = new HashSet<string>();

            //Read the values of all other properties
            foreach(var prop in props)
            {
                var pValue = (string)prop.GetValue(validationContext.ObjectInstance, null);
                if (prop.Name!=validationContext.MemberName && !values.Contains(pValue))
                {
                    values.Add(pValue);
                }
            }

            if (values.Contains(value))
            {
                return new ValidationResult("Duplicate answer", new[] { validationContext.MemberName });
            }
            return null;
        }

        public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            var rule = new ModelClientValidationRule()
            {
                ErrorMessage = metadata.DisplayName + " is required!",
                ValidationType = "duplicateanswers"
            };

            yield return rule;
        }
    }

我现在遇到的问题是验证成功,即使我输入了重复的答案。我仍然可以继续下一个对话框(如果输入重复项,我希望验证失败)。我认为这是因为我的自定义属性没有被触发或命中,因为我添加了断点但没有命中任何内容。

在我的控制器中,我有 if(ModelState.IsValid) { //continue to next dialog} 并且模型状态确实返回有效。

【问题讨论】:

  • 方法签名告诉你返回什么ValidationResult。这是自定义验证属性的类似example。问题是您需要知道属性名称,并且该问题的 IValidateObject 答案可能是您的情况更好的方法。
  • 好的,我的问题是如何返回 ValidateResult = false 而不是成功?我可以返回成功但不能返回错误?另外,我应该将哪些参数传递给我的自定义属性?我想传递我的模型列表,我可以这样做吗?
  • 如果这是一次性使用,我会考虑将您的函数添加到控制器或服务类中,并直接在操作中使用其结果。
  • 嗯,是的,但我不能以我当前的架构在客户端显示 qtips。所以我需要使用自定义属性
  • 如果成功,您将返回 null 或 ValidationResult.Success。如果验证检查失败,则返回带有错误消息的新 ValidationResult。

标签: c# asp.net-mvc validation asp.net-mvc-4 customvalidator


【解决方案1】:

您可以像这样创建自定义验证属性:

public class UniqueAnswersOnly : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        //Get a list of all properties that are marked with [UniqueAnswersOnly]
        var props = validationContext.ObjectInstance.GetType().GetProperties().Where(
            prop => Attribute.IsDefined(prop, typeof(UniqueAnswersOnly)));

        var values = new HashSet<string>();

        //Read the values of all other properties
        foreach(var prop in props)
        {
            var pValue = (string)prop.GetValue(validationContext.ObjectInstance);
            if (prop.Name!=validationContext.MemberName && !values.Contains(pValue))
            {
                values.Add(pValue);
            }
        }

        if (values.Contains(value))
        {
            return new ValidationResult("Duplicate answer", new[] { validationContext.MemberName });
        }
        return null;
    }
}

这是一个测试用例:

public class SecurityQuestions
{
    public int Question1Id { get; set; }
    public int Question2Id { get; set; }
    public int Question3Id { get; set; }
    public int Question4Id { get; set; }
    public int Question5Id { get; set; }
    public int Question6Id { get; set; }
    [UniqueAnswersOnly]
    public string Answer1 { get; set; }
    [UniqueAnswersOnly]
    public string Answer2 { get; set; }
    [UniqueAnswersOnly]
    public string Answer3 { get; set; }
    [UniqueAnswersOnly]
    public string Answer4 { get; set; }
    [UniqueAnswersOnly]
    public string Answer5 { get; set; }
    [UniqueAnswersOnly]
    public string Answer6 { get; set; }
}


class Program
{
    static void Main(string[] args)
    {
        var questions = new SecurityQuestions();
        questions.Answer1 = "Test";
        questions.Answer2 = "Test";
        questions.Answer3 = "Test3";
        questions.Answer4 = "Test4";
        questions.Answer5 = "Test5";
        questions.Answer6 = "Test6";

        var vc = new ValidationContext(questions, null, null);
        var results = new List<ValidationResult>();
        var validationResult = Validator.TryValidateObject(questions, vc, results, true);
    }
}

编辑:

我创建了一个默认的 MVC 项目并添加了您当前的代码。该验证工作正常。

我将此添加到家庭控制器中:

    public ActionResult AskQuestions()
    {
        var questions = new SecurityQuestions();

        return View(questions);
    }

    [HttpPost]
    public ActionResult CheckQuestions(SecurityQuestions questions)
    {
        if (ModelState.IsValid)
        {
            return View();
        }            
        else
        {
            return HttpNotFound();
        }
    }

AskQuestions 视图如下所示:

@model WebApplicationValidation.Models.SecurityQuestions

@{
    ViewBag.Title = "AskQuestions";
}

<h2>AskQuestions</h2>


@using (Html.BeginForm("CheckQuestions", "Home",FormMethod.Post))
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>SecurityQuestions</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <div class="form-group">
            @Html.LabelFor(model => model.Question1Id, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Question1Id, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Question1Id, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Question2Id, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Question2Id, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Question2Id, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Question3Id, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Question3Id, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Question3Id, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Question4Id, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Question4Id, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Question4Id, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Question5Id, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Question5Id, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Question5Id, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Question6Id, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Question6Id, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Question6Id, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Answer1, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Answer1, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Answer1, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Answer2, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Answer2, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Answer2, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Answer3, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Answer3, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Answer3, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Answer4, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Answer4, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Answer4, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Answer5, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Answer5, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Answer5, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Answer6, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Answer6, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Answer6, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

如果我运行应用程序,如果我输入两个相同的答案,则 ModelState.IsValid() 为 false。在验证例程中设置断点表明它正在运行。

【讨论】:

  • 好的,非常感谢!我会试一试,但我注意到的一件事是,如果我想显示自定义错误消息,我想将这些参数添加到 [UniqueAnswersOnly],我是否需要将它们传递给构造函数并以某种方式处理它们?我想将参数添加到我的属性中:ErrorMessageResourceName = "Validation_Error_DuplicateAnswers", ErrorMessageResourceType = typeof(Resources.Resources))
  • Opps,旁注:我正在使用带有 .NET 4.0 的 MVC 4,其中 GetValue 需要 2 个参数而不是 1。
  • 是的,您可以创建一个构造函数来为验证器提供参数。 public UniqueAnswersOnly(string resourceName) { } [UniqueAnswersOnly("Validation_Error_DuplicateAnswers")] public string Answer1 { get;放; }
  • 对于 UniqueAnswersOnly,我是否需要在我的控制器操作中添加检查?也就是说,当我单击提交时,我希望验证在服务器端启动,或者此验证是否已经在提交时启动?所以,如果所有字段都不唯一,我就无法进入下一页。
  • 我认为您所要做的就是将属性添加到您的模型中,然后 MVC 会为您完成所有检查。但我不是网络开发人员,所以不能 100% 确定...
【解决方案2】:

您可以在您的模型中实现IValidatableObject 接口,如下所示,并使用jQuery 来处理客户端的验证。

public class SecurityQuestions : IValidatableObject
{
    public int Question1Id { get; set; }

    public int Question2Id { get; set; }

    public int Question3Id { get; set; }

    public int Question4Id { get; set; }

    public int Question5Id { get; set; }

    public int Question6Id { get; set; }

    public string Answer1 { get; set; }

    public string Answer2 { get; set; }

    public string Answer3 { get; set; }

    public string Answer4 { get; set; }

    public string Answer5 { get; set; }

    public string Answer6 { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        var securityAnswers = new List<string>();
        securityAnswers.Add(this.Answer1);
        securityAnswers.Add(this.Answer2);
        securityAnswers.Add(this.Answer3);
        securityAnswers.Add(this.Answer4);
        securityAnswers.Add(this.Answer5);
        securityAnswers.Add(this.Answer6);

        bool hasDuplicates = securityAnswers.GroupBy(x => x).Where(g => g.Count() > 1).Any();

        if (hasDuplicates)
        {
            yield return new ValidationResult(
                "There are duplicate Answers...");
        }
    }
}

【讨论】:

    猜你喜欢
    • 2015-11-20
    • 2019-03-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-04
    相关资源
    最近更新 更多