C# 如何从asp.net mvc 2应用程序中的自定义ValidationAttribute内部访问其他属性值?
我正在使用带有C#和数据注释的asp.NETMVC2 情况是这样的:我有一个针对模型类的强类型自定义控件。此控件在视图上显示多次,但具有不同的属性值,例如标题(例如:问题1、问题2、问题3等都是标题)。我可以编写一个自定义验证类来验证整个对象,但问题是,我无法获得要显示的特定Html.ValidationMessage(…)标记。验证错误只显示在视图顶部的摘要中,我希望它们同时显示在验证失败的特定控件的顶部和旁边 我曾尝试创建如下自定义验证类,以逐个属性进行验证,但问题是我需要模型的两个值:评级和标题。实际的业务验证是根据评级执行的,但Heading属性向用户标识屏幕上的哪个控件不正确C# 如何从asp.net mvc 2应用程序中的自定义ValidationAttribute内部访问其他属性值?,c#,asp.net-mvc,validation,asp.net-mvc-2,C#,Asp.net Mvc,Validation,Asp.net Mvc 2,我正在使用带有C#和数据注释的asp.NETMVC2 情况是这样的:我有一个针对模型类的强类型自定义控件。此控件在视图上显示多次,但具有不同的属性值,例如标题(例如:问题1、问题2、问题3等都是标题)。我可以编写一个自定义验证类来验证整个对象,但问题是,我无法获得要显示的特定Html.ValidationMessage(…)标记。验证错误只显示在视图顶部的摘要中,我希望它们同时显示在验证失败的特定控件的顶部和旁边 我曾尝试创建如下自定义验证类,以逐个属性进行验证,但问题是我需要模型的两个值:评级
[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;
}
}
想法
编辑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; }
}
My Home Controller(仅为创建视图添加的方法):
最后,完整显示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>
下面的验证是有效的,但问题是。RatingsValidationAttribute类显示的验证消息是正确的,但我只希望该消息显示在页面顶部。在控件上,我只想在Ratings文本框中显示一个“*”。您将注意到以下内容:
<%= Html.ValidationMessageFor(model => model.Rating, "*")%>
model.Rating,“*”>
不显示星号。我想这是因为属性在类级别进行验证。在我的模型绑定器中检查ModelState时,实际保存错误消息的键类似于:“Ratings[0]”,这意味着要显示该消息,我必须使用以下命令:
<%= Html.ValidationMessageFor(model => model)%>
model)%%>
首先,这是丑陋的。其次,每个部分控件将检查多个验证,因此我希望摘要中的错误详细说明哪个标题有错误,并使用“*”星号表示有错误的控件
我为编辑的篇幅感到抱歉。我试图以一种毫无疑问的方式来处理我在堆栈溢出问题上提出的问题。请随意询问更多代码(以防我忘记发布内容)或任何其他问题
编辑3
关于在类级别上创建验证属性的一般说明:当验证失败并且向ModelState添加错误时,字段名就是类的名称。由于要验证的类是视图模型列表中包含的3个对象的列表,因此ModelState键类似于:Model.ListName[index]
是否有方法为在类级别求值的自定义验证类指定与错误消息关联的键?对于字段级自定义验证属性,这不是问题
编辑4
这篇博文讨论了这个问题。我唯一的问题是,它要求我为每个要执行的验证创建4个类,这太过分了。尽管如此,它还是第一个工作示例,展示了将验证属性应用于字段/属性并访问该类的整个模型的方法
现在我唯一剩下的问题是试图拦截框架为您所做的一些幕后验证。我说的是,如果表单上有一个int字段,并且没有输入值,那么框架会添加一条验证消息,如:
_____;字段是必需的。(其中,u______;是int字段的名称)
如果为int字段提供文本值,它也会执行类似操作:
值“asdf”对____;无效
由于我的情况是,这些验证是在迭代多次的模型上执行的,因此我需要修改这些消息,以便在上面的消息中显示对象的Heading属性。上述消息将按如下方式更改:
“\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu{1}的{0}字段是必需的
值“asdf”对(字段名)无效。-->{1}的{0}值对{2}无效。-->“asdf”的额定值对于技术知识无效
我希望这是有道理的。我想我已经把整个问题弄得毫无意义了。我可能会回来试着重新措辞。如果我能得到一些反馈会有帮助。等待MVC 3。我是认真的 类范围的自定义验证属性的选项现在非常糟糕,没有任何真正好的扩展点。您几乎无法创建自定义ModelBinder,它可以向ModelState添加值,以完成任何复杂的验证属性 像您一样使用属性,然后检测从绑定器请求的类型,反射以查找属性,然后根据需要验证/添加到模型状态 MVC 3解决了这个问题,但在此之前,您必须创建自己的活页夹。
[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;
}
}
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;
}
<%@ 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());
<%= Html.ValidationMessageFor(model => model.Rating, "*")%>
<%= Html.ValidationMessageFor(model => model)%>
[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; }
}
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;
}