如何从asp.net mvc 2应用程序中的自定义ValidationAttribute内部访问其他属性值?
我正在使用带有C#和DataAnnotations的asp.net mvc 2。
情况是这样的:我有一个针对模型类强类型的自定义控件。 此控件在视图上多次显示,但具有不同的属性值,例如标题(例如:问题1,问题2,问题3等等都是标题)。 我可以编写一个自定义validation类来validation整个对象,但问题是,我无法获得要显示的特定Html.ValidationMessage(…)标记。 validation错误仅显示在视图顶部的摘要中,我希望它们显示在validation失败的特定控件的顶部和旁边。
我已经尝试创建自定义validation类,如下所示,以逐个属性validation属性,但问题是我需要两个模型的值:评级和标题。 实际业务validation是针对评级执行的,但是标题属性向用户标识屏幕上的哪个控件不正确。
[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 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
我想再放一些代码来更好地解释为什么在这种情况下创建单独的类validation器不能作为解决方案。
回想一下,所有这些validation都将进入强类型的自定义局部视图。 更准确地说,这是一个编辑模板。 主模型将具有此部分视图显示的相同类型模型的列表。 例如,我修改了上面的一些代码来更好地说明这一点。 我将此编辑上方的代码保持不变,因此导致此编辑的讨论很有意义,可以看到对话开始的位置和方向。
原谅代码作为测试它的演示应用程序相当冗长。 希望它能更好地解释我的问题。
[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 Ratings { get; set; } }
在我的主视图中我有:
m.Ratings) %>
评级视图控件如下所示:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %> m.Heading) %> m.Index) %> model.Heading) %> : model.Rating) %> model.Rating, "*")%> m) %>
validation属性类:
[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 5) { isValidResult = false; } } catch (Exception e) { //log error } return isValidResult; } }
我的测试模型粘合剂。 这还没有做任何事情。 这只是探索性的。 请注意,变量o包含完整的对象模型和所有值。 此代码大量借鉴: ASP.NET MVC – 值类型的自定义validation消息
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" %> Create Create
最后,我通过以下方式在global.aspx的application_start方法中注册了我的模型绑定器:
ModelBinders.Binders.Add(typeof(Person), new TestModelBinder());
因此,下面的validation有效,但这是问题所在。 RatingsValidationAttribute类显示的validation消息是正确的,但我只希望该消息显示在页面顶部。 在控件上,我只想在“评级”文本框中显示“*”。 你会注意到以下内容
model.Rating, "*")%>
不显示星号。 我想这是因为属性在类级别validation。 在模型绑定器中检查ModelState时,实际保存错误消息的键类似于:“Ratings [0]”,这意味着要显示该消息,我必须使用以下内容:
model)%>
首先,这很难看。 其次,每个部分控件将检查多个validation,因此我希望摘要中的错误详细说明哪个标题有错误,只需使用“*”星号来表示有错误的控件。
我为编辑的长度道歉。 我试着处理堆栈溢出问题,以至于毫无疑问解决方案是什么。 随意请求更多代码(如果我忘记发布某些内容)或任何其他问题。
编辑3
关于在类级别创建validation属性的一般说明:当validation失败并且错误被添加到ModelState时,字段名称是类的名称。 由于我的类被validation是一个包含在视图模型的List中的3个对象的List,因此ModelState键类似于:Model.ListName [index]。
有没有办法为类级别的自定义validation类指定与错误消息关联的键? 对于字段级自定义validation属性,这不是问题。
编辑4
此博客文章http://blog.ceredir.com/index.php/2010/08/10/mvc2-cross-field-validation/解决了此问题的问题。 我唯一的问题是它需要我为每个要执行的validation创建4个类,这非常过分。 尽管如此,它仍然是第一个工作示例,它显示了一种方法,可以将validation属性应用于字段/属性,并可以访问该类的整个模型。
现在我唯一的问题是试图拦截框架为你做的一些幕后validation。 我在谈论如果你的表单上有一个int字段并且没有放置值,框架会添加一条validation消息,如:
_____字段是必需的。 (其中______是int字段的名称)。
如果为int字段提供文本值,它也会执行类似的操作:
值’asdf’对______无效。
由于我在多次迭代的模型上执行这些validation的情况,我需要修改这些消息以在上面的消息中显示对象的Heading属性。 以上消息会改变如下:
__________字段是必需的。 – > {1}的{0}字段是必需的。
值’asdf’对(字段名称)无效。 – > {1}的{0}值对{2}无效。 – >’asdf’的评级值对技术知识无效。
我希望这是有道理的。 我想我已经把这整个问题弄错了。 我可能会回来试图以某种方式改写。 如果我能得到某种反馈,它会有所帮助。
等待MVC 3.我是认真的。
类广泛的自定义validation属性的选项现在非常差,没有任何非常好的可扩展性点。 您几乎无法创建自定义ModelBinder,然后可以向ModelState添加值,以执行与validation属性复杂的任何操作。
像您一样使用属性,然后检测从活页夹请求的类型,反映以查找属性,然后根据需要validation/添加到模型状态。
MVC 3解决了这个问题,但在那之前你就开始创建自己的绑定器了。
您可以使用class属性来实现此目的:
[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 ,它与ASP.NET MV C 很好地集成 。
更好地完善这个问题的措辞:
如何为给定表单字段的validation摘要显示明确的错误消息,并在validation适用的字段旁边显示单独的&不同消息?
例:
领域:评级
摘要消息:“技术知识的评级是必需的,必须在1到5之间。” 字段validation消息:“*”
希望这能说明我正在努力做的事情。 从研究中得出的答案似乎是当前的数据注释框架无法自行编写至少一个html助手:或者创建我自己的ValidationSummary或者我自己的ValidationMessage帮助器…
我想详细说明jfar的建议。 我不同意我需要等到MVC 3; 然而,他创建一个自定义模型绑定器的意义实际上是做我想做的事情的唯一方法。 我已经尝试了字段/属性级别属性和类级别属性。 这些都不足以满足我需要的那种消息。
例如,回想一下我的情况是我有一个模板控件,它是针对具有属性Index,Heading和Rating的模型强类型的。 因此,显示的这些控件的页面将类似于:
标题:技术知识评级:[文本框]
标题:领导评级:[文本框]
标题:工作道德评级:[文本框]
……等等
对于我的validation消息,我想要一些非常定制的东西。 事实certificate,我的需求对于MVC 2中开箱即用的validation来说太具体了。我需要评级字段的错误消息来引用与评级相关联的标题。 所以validation将是这样的:
工作伦理评级是必需的,必须是1到5之间的数字。工作伦理的’asdf’评级值无效。
字段级属性的问题是它们无法访问标题值。 此外,每个对象实际上包含一个IsEditable字段,如果该字段的值为false,我完全绕过validation。
类级别属性的问题是双重的。 首先,我无法控制ModelStateCollection中使用的密钥。 默认值是页面上对象的类名和索引。 这产生的结果类似于:PersonRating [0],PersonRating [1]。 这个问题是它意味着你只能在类级别validation时有1条错误消息。 如果您有2个类级别属性,则它们都将使用相同的键放入ModelStateCollection中。 我不太确定它是如何工作的,因为我不认为字典会让你这样做。 也许它无声地失败,或者第二条消息只是覆盖了第一条消息。 除此之外,我仍然需要字段本身进行css更改以表示错误。 使用类级别validation,我无法弄清楚如何执行此操作,因为密钥不引用字段…所以我不知道哪个消息与哪个字段相关,除非我进行硬字符串检查,这似乎是一个非常糟糕的解决方案。
我之前引用的博客文章提供了一个解决方案,但它需要每个属性太多的代码和工作量,而且看起来有点矫枉过正。
除了所有这些之外,MVC数据注释还具有硬连接的基本validation,可以帮助您远离自己。 这包括如果模型上的类型不可为空,则调用Requiredvalidation器。 内部MVCvalidation还会进行数据检查,以确保您不会尝试将字符串值提交给int类型。 在这两种情况下,我都没有看到一种可行的方法来纠正validation消息而不对validation文本进行硬性检查。
所以,最后,我为这个对象编写了自己的模型绑定器。 虽然这不是推荐的方式,但我感到很欣慰,因为在MVC框架中硬连线的内部validation,这确实是我案例中唯一可行的解决方案。 我的模型绑定器看起来如下所示:
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字段执行validation,并返回表示错误消息的字符串。 如果字符串为null,则getRatingModelErrorMessage方法不会返回任何错误。
我是第一个承认这不是很好的代码的人。 总是有改进的空间。 它仍然完成了工作。 还值得注意的是,在为模型上的非兼容类型(例如int)提交诸如文本值之类的值的情况下,它将不会绑定到模型。 我通过它的密钥从FormCollection中获取该值。
如果有人就其他方式提出任何建议或对一般代码发表评论,请告诉我。 我总是乐于接受建设性的批评。