代码之家  ›  专栏  ›  技术社区  ›  jason

如何从数据库中的自定义ValidationAttribute中访问其他属性值asp.netmvc 2应用程序?

  •  3
  • jason  · 技术社区  · 15 年前

    我在用asp.net带有C#和数据注释的mvc2。

    我已经尝试过创建像下面这样的自定义验证类来逐个属性进行验证,但问题是我需要模型的两个值:Rating和Heading。实际的业务验证是针对评级执行的,但是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) %>
    

    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);
            }
        }           
    

    我的主控制器(只是我为Create视图添加的方法):

        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;
        }
    

    最后,是创建.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>      
    

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

    所以,下面的验证是有效的,但问题是。RatingsValidationAttribute类显示的验证消息是正确的,但我只希望该消息显示在页面顶部。在控件上,我只想在Ratings文本框中显示一个“*”。你会注意到

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

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

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

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

    很抱歉编辑的篇幅太长。我试图以这样一种方式来处理我在堆栈溢出上提出的问题:毫无疑问,解决方案是什么。请随意询问更多的代码(以防我忘记发布一些东西)或任何其他问题。

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

    有没有办法为在类级别求值的自定义验证类指定与错误消息关联的键?对于字段级自定义验证属性,这不是问题。

    编辑4

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

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

    值“asdf”对\\无效。

    由于我的情况是,这些验证是在一个迭代多次的模型上执行的,因此我需要修改这些消息,以便在上面的消息中显示对象的Heading属性。以上消息会发生如下变化:

    \uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。-->{1}的{0}字段是必需的。

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

    4 回复  |  直到 9 年前
        1
  •  1
  •   John Farrell    15 年前

    类范围的自定义验证属性的选项现在非常糟糕,没有任何真正好的扩展点。您的任务几乎是创建一个定制的ModelBinder,它可以向ModelState添加值,以完成任何复杂的验证属性。

    像您一样使用属性,然后检测从绑定器请求的类型,反射以查找属性,然后根据需要验证/添加到模型状态。

    mvc3解决了这个问题,但在那之前,你一直在创建自己的活页夹。

        2
  •  1
  •   Darin Dimitrov    15 年前

    [AttributeUsage(AttributeTargets.Class)]
    public class RatingValidation : ValidationAttribute
    {
        public override bool IsValid(object value)
        {
            var model = (MyModel)value;
            // TODO: here you have the model so work with the 
            // Rating and Heading properties to perform your 
            // validation logic
    
            return true;
        }
    }
    

    你的模特:

    [RatingValidation]
    public class MyModel
    {
        public String Heading { get; set; }
        public int Rating { get; set; }
    }
    

    FluentValidation 哪一个 integrates nicely with ASP.NET MV

        3
  •  1
  •   jason    15 年前

    这个问题的措词更准确:

    如何在应用验证的字段旁边显示一条单独的错误消息以显示给定表单字段的验证摘要?


    摘要信息:“技术知识等级是必需的,必须在1到5之间。” 字段验证消息:“*”

    希望这能说明我在做什么。根据研究,答案似乎是,在当前的数据注释框架中,如果不自己滚动至少一个html助手,这是不可能的:要么创建自己的ValidationSummary,要么创建自己的ValidationMessage助手。。。

        4
  •  1
  •   jason    15 年前

    我想详细说明jfar的建议。我不同意我需要等到mvc3;然而,他创建自定义模型绑定器的观点实际上是我要做的唯一方法。我尝试了字段/属性级属性和类级属性。这些都不足以满足我需要的信息。

    例如,回想一下,我的情况是,我有一个模板控件,它是针对具有属性Index、Heading和Rating的模型的强类型控件。因此显示的这些控件的页面类似于:

    评分:[文本框]

    评分:[文本框]

    标题:职业道德 评分:[文本框]

    ……等等,等等。

    职业道德的“asdf”评级值无效。

    字段级属性的问题是它们无法访问标题值。此外,每个对象实际上都包含一个IsEditable字段,如果该字段的值为false,我将完全绕过验证。

    类级属性的问题有两个方面。首先,我无法控制ModelStateCollection中使用的键。默认值是页面上对象的类名和索引。这将产生一个类似于:PersonRating[0],PersonRating[1]的结果。问题在于,这意味着在类级验证中只能有一条错误消息。如果您有两个类级属性,那么它们都将使用相同的键放入ModelStateCollection。我不确定这是怎么回事,因为我不认为字典会让你这么做。也许它会无声地失败,或者第二条消息只是覆盖了第一条消息。除此之外,我仍然需要字段本身进行css更改以表示错误。对于类级别的验证,我不知道怎么做,因为键没有引用字段…所以我不知道哪个消息与哪个字段相关,除非我进行硬字符串检查,这似乎是一个非常糟糕的解决方案。

    我之前引用的博客文章确实提供了一个解决方案,但是它需要太多的代码和每个属性的工作量,而且看起来太过了。

            public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
            {
                Object o = base.BindModel(controllerContext, bindingContext);
                string ratingKey = bindingContext.ModelName + ".Rating";            
                PersonRating pr = (PersonRating)o;
                ValueProviderResult ratingVpr = controllerContext.
                                            Controller.
                                                ValueProvider.
                                                    GetValue(ratingKey);
                String ratingVal = ratingVpr.AttemptedValue;
                String ratingErrorMessage = getRatingModelErrorMessage(
                                                ratingKey,
                                                ratingVal,
                                                pr);
    
                if (!String.IsNullOrEmpty(ratingErrorMessage))
                {
                    bindingContext.ModelState[ratingKey].Errors.Clear();
                    bindingContext.ModelState.AddModelError(ratingKey, ratingErrorMessage);
                }
    
                return o;
    
                             }
    

    getRatingModelErrorMessage方法是一个自定义方法,它对PersonRating对象的Rating字段执行验证,并返回表示错误消息的字符串。如果字符串为null,则getRatingModelErrorMessage方法不会返回任何错误。

    让我知道,如果有任何人对其他方法,这可以做任何建议或对代码的一般意见。我总是乐于接受建设性的批评。