我在用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”的额定值对于技术知识无效。