我有一种复杂的模型。
我有我的 UserViewModel
它有几个属性,其中两个是 HomePhone
和 WorkPhone
。两种类型 PhoneViewModel
。在 PhoneViewModel
我有 CountryCode
, AreaCode
和 Number
所有字符串。我想做 CountryCode
可选但是 AreaCode
和 Number
强制性的。
这很好用。我的问题在于 UserViewModel
WorkPhone
是强制性的,并且 HomePhone
不是。
无论如何,我可以解散 Require
归属于 PhoneViewModel
通过设置任何属性 HomeWork
属性?
我试过这个:
[ValidateInput(false)]
但它只适用于类和方法。
码:
public class UserViewModel
{
[Required]
public string Name { get; set; }
public PhoneViewModel HomePhone { get; set; }
[Required]
public PhoneViewModel WorkPhone { get; set; }
}
public class PhoneViewModel
{
public string CountryCode { get; set; }
public string AreaCode { get; set; }
[Required]
public string Number { get; set; }
}
[2012年5月24日更新,以使想法更清晰]
我不确定这是正确的方法,但我认为你可以扩展这个概念,并可以创建一个更通用/可重用的方法。
在ASP.NET MVC中,验证发生在绑定阶段。当您将表单发布到服务器时 DefaultModelBinder
是从请求信息创建模型实例并将验证错误添加到 ModelStateDictionary
。
在你的情况下,只要绑定发生在 HomePhone
验证将启动和 我认为 我们不能通过创造来做很多事情 自定义验证属性或类似的类型。
我所想的只是根本不创建模型实例 HomePhone
表格中没有可用值时的属性 (areacode,国家代码和数字或空)当我们控制绑定时,我们控制验证,为此,我们必须创建一个 定制模型活页夹。
在里面 定制模型活页夹 我们正在检查房产是否属于 HomePhone
如果表单包含其属性的任何值,如果不包含,我们不绑定该属性,并且不会发生验证 HomePhone
。简单地说,价值 HomePhone
将在null中为null UserViewModel
。
public class CustomModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.Name == "HomePhone")
{
var form = controllerContext.HttpContext.Request.Form;
var countryCode = form["HomePhone.CountryCode"];
var areaCode = form["HomePhone.AreaCode"];
var number = form["HomePhone.Number"];
if (string.IsNullOrEmpty(countryCode) && string.IsNullOrEmpty(areaCode) && string.IsNullOrEmpty(number))
return;
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
最后,您必须在global.asax.cs中注册自定义模型绑定器。
ModelBinders.Binders.Add(typeof(UserViewModel), new CustomModelBinder());
所以你现在有一个以UserViewModel为参数的动作,
[HttpPost]
public Action Post(UserViewModel userViewModel)
{
}
我们的自定义模型活页夹发挥作用,形式不发布任何值 areacode,国家代码和号码 对于 HomePhone
,不会有任何验证错误和 userViewModel.HomePhone
一片空白。如果表单至少发布这些属性的任何一个值,则验证将发生 HomePhone
正如所料。
我一直在使用这个做动态注释的神奇的nuget: ExpressiveAnnotations
它允许你做以前不可能做的事情
[AssertThat("ReturnDate >= Today()")]
public DateTime? ReturnDate { get; set; }
甚至
public bool GoAbroad { get; set; }
[RequiredIf("GoAbroad == true")]
public string PassportNumber { get; set; }
更新:在单元测试中编译注释以确保不存在错误
正如@diego所提到的,在字符串中编写代码可能会令人生畏,但以下是我用于单元测试查找编译错误的所有验证的内容。
namespace UnitTest
{
public static class ExpressiveAnnotationTestHelpers
{
public static IEnumerable<ExpressiveAttribute> CompileExpressiveAttributes(this Type type)
{
var properties = type.GetProperties()
.Where(p => Attribute.IsDefined(p, typeof(ExpressiveAttribute)));
var attributes = new List<ExpressiveAttribute>();
foreach (var prop in properties)
{
var attribs = prop.GetCustomAttributes<ExpressiveAttribute>().ToList();
attribs.ForEach(x => x.Compile(prop.DeclaringType));
attributes.AddRange(attribs);
}
return attributes;
}
}
[TestClass]
public class ExpressiveAnnotationTests
{
[TestMethod]
public void CompileAnnotationsTest()
{
// ... or for all assemblies within current domain:
var compiled = Assembly.Load("NamespaceOfEntitiesWithExpressiveAnnotations").GetTypes()
.SelectMany(t => t.CompileExpressiveAttributes()).ToList();
Console.WriteLine($"Total entities using Expressive Annotations: {compiled.Count}");
foreach (var compileItem in compiled)
{
Console.WriteLine($"Expression: {compileItem.Expression}");
}
Assert.IsTrue(compiled.Count > 0);
}
}
}
我不会选择modelBinder;我使用自定义ValidationAttribute:
public class UserViewModel
{
[Required]
public string Name { get; set; }
public HomePhoneViewModel HomePhone { get; set; }
public WorkPhoneViewModel WorkPhone { get; set; }
}
public class HomePhoneViewModel : PhoneViewModel
{
}
public class WorkPhoneViewModel : PhoneViewModel
{
}
public class PhoneViewModel
{
public string CountryCode { get; set; }
public string AreaCode { get; set; }
[CustomRequiredPhone]
public string Number { get; set; }
}
接着:
[AttributeUsage(AttributeTargets.Property]
public class CustomRequiredPhone : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
ValidationResult validationResult = null;
// Check if Model is WorkphoneViewModel, if so, activate validation
if (validationContext.ObjectInstance.GetType() == typeof(WorkPhoneViewModel)
&& string.IsNullOrWhiteSpace((string)value) == true)
{
this.ErrorMessage = "Phone is required";
validationResult = new ValidationResult(this.ErrorMessage);
}
else
{
validationResult = ValidationResult.Success;
}
return validationResult;
}
}
如果不清楚,我会提供一个解释,但我认为这是不言自明的。
只是一些观察:如果绑定不仅仅是简单的提交,下面的代码就会出现问题。我有一个案例,在对象中有嵌套对象,它将跳过它并且caouse有些归档没有在嵌套对象中绑定。
可能的解决办法是
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
if (!propertyDescriptor.Attributes.OfType<RequiredAttribute>().Any())
{
var form = controllerContext.HttpContext.Request.Form;
if (form.AllKeys.Where(k => k.StartsWith(string.Format(propertyDescriptor.Name, "."))).Count() > 0)
{
if (form.AllKeys.Where(k => k.StartsWith(string.Format(propertyDescriptor.Name, "."))).All(
k => string.IsNullOrWhiteSpace(form[k])))
return;
}
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
非常感谢 Altaf Khatri