如何在asp.net mvc 2应用程序中的自定义ValidationAttribute内部访问其他属性值?

我正在使用带有C#和DataAnnotations的asp.net mvc 2.

情况是这样的:我有一个针对模型类强类型化的自定义控件.此控件在视图上显示了几次,但具有不同的属性值,例如标题(例如:问题1,问题2,问题3等都是标题).我可以编写一个自定义的验证类来对整个对象进行验证,但是问题是,我无法显示特定的Html.ValidationMessage(…)标签.验证错误仅显示在视图顶部的摘要中,我希望它们显示在验证失败的特定控件的顶部和旁边.

我尝试创建像下面这样的自定义验证类,以逐个属性地进行验证,但是问题是我需要模型的两个值:Rating和Heading.实际业务验证是针对“等级”执行的,但“标题”属性向用户标识屏幕上哪个控件不正确.

    [AttributeUsage(AttributeTargets.Property)]
    public class RatingValidation : ValidationAttribute
     {

      public string Heading { get; private set; }  
      private readonly string ErrorRatingInvalid = "Rating for {0} is invalid. Rating is required and must be between 1 and 5.";

        public override string FormatErrorMessage(string name)
        {
            String errorMsg = ErrorRatingInvalid;
            return String.Format(CultureInfo.CurrentUICulture,
                                 errorMsg, 
                                 Heading);
        }

      public override bool IsValid(object value)
      {   
       bool isValidResult = true;

       if (value == null)
        return false;

        //Trying to do something like the following. This doesn't work because the 
        //attribute is applied to a property so only that property value is passed.
        //In this case, the type of myRatingObject would likely be the same as the 
        //property validated.
        var myRatingObject = TypeDescriptor.GetProperties(value);  
        this.Heading = myRatingObject.Heading;

        if( myRatingObject.Rating < 1 || myRatingObject.Rating > 5)
            isValidResult = false;

       return isValidResult;
      }
     }

我的模型类的示例:

    public class MyModel
    {
        public MyModel()
        {
            //this.IsEditable = true;
        }        
        public String Heading { get; set; }
        [RatingValidation()]
        public int Rating { get; set; }
    }

有什么想法吗?

编辑1

我想放下更多代码以更好地解释为什么在这种情况下创建单独的类验证器不能作为解决方案.

回想一下,所有这些验证都将进入强类型的自定义局部视图.更准确地说,这是一个“编辑”模板.主模型将具有与此部分视图显示的相同类型的模型的列表.出于示例目的,我修改了上面的一些代码以更好地说明这一点.我将不修改该编辑上方的代码,因此进行该编辑的讨论很有意义,并且可以看到对话的开始位置和进行的方向.

请将该代码作为演示应用程序进行测试,这相当冗长.希望它能更好地解释我的问题.

    [RatingsValidationAttribute()]
    public class PersonRating
    {
        public String Heading { get; set; }
        public int Index { get; set; }
        public int Rating { get; set; }
    }

    public class Person
    {
        public String FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }
        public List<PersonRating> Ratings { get; set; }
    }       

在我的主要观点中,我有:

        <%= Html.EditorFor(m => m.Ratings) %>

评分视图控件如下所示:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<ModelBindingResearch.Models.PersonRating>" %>
<%= Html.HiddenFor(m=>m.Heading) %>
<%= Html.HiddenFor(m=>m.Index) %>
 <%= Html.DisplayTextFor(model => model.Heading) %>
 :
 <%= Html.TextBoxFor(model => model.Rating) %>
 <%= Html.ValidationMessageFor(model => model.Rating, "*")%>
 <%= Html.ValidationMessageFor(m=>m) %>
 <br />         

验证属性类:

    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
        public class RatingsValidationAttribute : ValidationAttribute
        {
            public RatingsValidationAttribute()
            {

            }

            public int Rating { get; private set; }        
            public string Heading { get; set; }        

            private readonly string ErrorRatingInvalid = "Rating for {0} is invalid. Rating is required and must be between 1 and 5.";

            public override string FormatErrorMessage(string name)
            {
                String errorMsg = ErrorRatingInvalid;
                return String.Format(CultureInfo.CurrentUICulture,
                                     errorMsg,
                                     Heading);
            }

            public override bool IsValid(object value)
            {

                bool isValidResult = true;
                PersonRating personRating = (value as PersonRating);

                try
                {                
                    PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(value);
                    Heading = personRating.Heading;

                    if (personRating.Rating < 1 ||      //Rating must be b/t 1 & 5
                        personRating.Rating > 5)
                    {
                        isValidResult = false;
                    }
                }
                catch (Exception e)
                {
                    //log error
                }

                return isValidResult;
            }
        }    

我的测试模型活页夹.这还没有做任何事情.这只是探索性的.请注意,变量o包含完整的对象模型和所有值.该代码大量借自:ASP.NET MVC – Custom validation message for value types

    public class TestModelBinder : DefaultModelBinder
    {
        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            Object o = base.BindModel(controllerContext, bindingContext);
            Object obj = bindingContext.Model;
            Person p = (Person)o;
            bindingContext.ModelState.AddModelError("FirstName", "Custom exception thrown during binding for firstname.");
            return o;
        }

        protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, object value)
        {
            base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
            String propertyName = propertyDescriptor.Name;
            Object propertyValue = value; 
        }

        private bool IsFormatException(Exception e)
        {
            if (e == null)
                return false;
            else if (e is FormatException)
                return true;
            else
                return IsFormatException(e.InnerException);
        }
    }           

我的家庭控制器(只是我为“创建”视图添加的方法):

    public ActionResult Create()
    {
        return View(getPerson());
    }

    [HttpPost]
    public ActionResult Create(Person p)
    {
        return View(p);
    }

    private Person getPerson()
    {
        Person p = new Person();
        Address a = new Address();
        PersonRating pr1 = new PersonRating();
        PersonRating pr2 = new PersonRating();
        PersonRating pr3 = new PersonRating();
        pr1.Heading = "Initiative";
        pr1.Rating = 5;
        pr1.Index = 1;
        pr2.Heading = "Punctuality";
        pr2.Rating = 5;
        pr1.Index = 2;
        pr3.Heading = "Technical Knowledge";
        pr3.Rating = 5;
        pr3.Index = 3;

        a.Street = "555 Somewhere Dr";
        a.City = "City";
        a.State = "AL";
        p.FirstName = "Jason";
        p.LastName = "Rhevax";
        p.Age = 30;
        p.PersonAddress = a;
        p.Ratings.Add(pr1);
        p.Ratings.Add(pr2);
        p.Ratings.Add(pr3);
        return p;
    }

最后,完整的Create.aspx:

    <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<ModelBindingResearch.Models.Person>" %>

    <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
        Create
    </asp:Content>

    <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

        <h2>Create</h2>

        <% using (Html.BeginForm()) {%>
            <%= Html.ValidationSummary() %>

            <fieldset>
                <legend>Fields</legend>

                <div class="editor-label">
                    <%= Html.LabelFor(model => model.FirstName) %>
                </div>
                <div class="editor-field">
                    <%= Html.TextBoxFor(model => model.FirstName) %>
                    <%= Html.ValidationMessageFor(model => model.FirstName, "*") %>
                </div>

                <div class="editor-label">
                    <%= Html.LabelFor(model => model.LastName) %>
                </div>
                <div class="editor-field">
                    <%= Html.TextBoxFor(model => model.LastName) %>
                    <%= Html.ValidationMessageFor(model => model.LastName) %>
                </div>

                <div class="editor-label">
                    <%= Html.LabelFor(model => model.Age) %>
                </div>
                <div class="editor-field">
                    <%= Html.TextBoxFor(model => model.Age) %>
                    <%= Html.ValidationMessageFor(model => model.Age) %>
                </div>

                <div class="editor-label">
                    <%= Html.LabelFor(model => model.PersonAddress.Street) %>
                </div>
                <div class="editor-field">
                    <%= Html.TextBoxFor(model => model.PersonAddress.Street)%>
                    <%= Html.ValidationMessageFor(model => model.PersonAddress.Street)%>
                </div>

                <div class="editor-label">
                    <%= Html.LabelFor(model => model.PersonAddress.City) %>
                </div>
                <div class="editor-field">
                    <%= Html.TextBoxFor(model => model.PersonAddress.City)%>
                    <%= Html.ValidationMessageFor(model => model.PersonAddress.City)%>
                </div>

                <div class="editor-label">
                    <%= Html.LabelFor(model => model.PersonAddress.State) %>
                </div>
                <div class="editor-field">
                    <%= Html.TextBoxFor(model => model.PersonAddress.State)%>
                    <%= Html.ValidationMessageFor(model => model.PersonAddress.State)%>
                </div>     
                <div>
                    <%= Html.EditorFor(m => m.Ratings) %>
                </div>                               
                <p>
                    <input type="submit" value="Create" />
                </p>
            </fieldset>

        <% } %>

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

    </asp:Content>      

最后,我通过以下方式将模型绑定程序注册到global.aspx的application_start方法中:

ModelBinders.Binders.Add(typeof(Person), new TestModelBinder());

因此,下面的验证有效,但这是问题所在. RatingsValidationAttribute类显示的验证消息是正确的,但我只希望该消息显示在页面顶部.在控件上,我只希望“评分”文本框显示一个“ *”.您会注意到以下内容

<%= Html.ValidationMessageFor(model => model.Rating, "*")%>

不显示星号.我想这是因为该属性在类级别进行验证.在检查我的模型绑定程序中的ModelState时,实际上包含错误消息的键类似于:“ Ratings [0]”,这意味着要显示该消息,我必须使用以下命令:

 <%= Html.ValidationMessageFor(model => model)%>

首先,这很丑.其次,每个部分控件将检查多个验证,因此我希望摘要中的错误详细说明哪个标题有错误,并仅使用“ *”星号表示存在错误的控件.

抱歉,编辑时间过长.我试图以一种毫无疑问的分辨率来解决我在堆栈溢出时提出的问题.随意询问更多代码(以防万一我忘记张贴东西)或其他任何问题.

编辑3

关于在类级别创建验证属性的一般说明:当验证失败并且向ModelState添加错误时,字段名称是类的名称.由于正在验证的类是包含在视图的模型列表中的3个对象的列表,因此ModelState键类似于:Model.ListName [index].

对于在类级别评估的自定义验证类,是否可以指定与错误消息关联的键?对于字段级别的自定义验证属性,这不是问题.

编辑4

这篇博客文章http://blog.ceredir.com/index.php/2010/08/10/mvc2-cross-field-validation/解决了这个问题.我唯一的问题是它要求我为要执行的每个验证创建4个类,这太过分了.仍然是第一个工作示例,展示了一种将验证属性应用于字段/属性并可以访问该类的整个模型的方法.

现在,我唯一剩下的问题是尝试截取该框架为您做的幕后验证.我说的是您的表单上是否有一个int字段并且不输入值,该框架会添加一条验证消息,例如:

_____字段为必填字段. (其中______是int字段的名称).

如果为int字段提供文本值,它也会执行类似的操作:

值“ asdf”对于______无效.

由于我的情况是对多次迭代的模型执行这些验证,因此我需要修改这些消息以在上面的消息中显示对象的Heading属性.上面的消息将更改为:

__________字段为必填字段. -> {1}的{0}字段是必需的.

值’asdf’对于(字段名称)无效. -> {1}的{0}值对{2}无效. -> “ asdf”的额定值对技术知识无效.

我希望这是有道理的.我想我已经把整个问题弄糊涂了.我可能会回来并尝试以某种方式重新表述.如果我能得到某种反馈会很有帮助.

解决方法:

等待MVC3.我是认真的.

目前,类范围的自定义验证属性的选项非常差,没有任何很好的可扩展性点.您创建一个自定义ModelBinder几乎是费劲的,然后可以将其添加到ModelState来对验证属性进行任何复杂的操作.

就像您将要使用的属性一样,然后检测从活页夹请求什么类型,通过Reflect查找属性,然后根据需要验证/添加到模型状态.

MVC 3解决了此问题,但直到那时您仍然无法创建自己的活页夹.

上一篇:php-Laravel:如何验证变量是数组还是匹配正则表达式的字符串


下一篇:Java-Apache Commons UrlValidator