Asp.net mvc TimeSpan的MVC编辑器模板
我想将一组文本框、小时、分钟和秒绑定到模型中的时间跨度。我不想在模型中添加小时、分钟和秒,也不希望将它们作为参数传递给action方法Asp.net mvc TimeSpan的MVC编辑器模板,asp.net-mvc,model-binding,mvc-editor-templates,Asp.net Mvc,Model Binding,Mvc Editor Templates,我想将一组文本框、小时、分钟和秒绑定到模型中的时间跨度。我不想在模型中添加小时、分钟和秒,也不希望将它们作为参数传递给action方法 我找到了一些关于使用编辑器模板的讨论,但找不到一个好的例子。有两种方法可以做到这一点。第一种方法是实现IModelBinder,这需要将TimeSpan作为单独的参数传递给操作,然后将模型的TimeSpan属性设置为: [HttpPost] public ActionResult Index(YourViewModel model, [ModelBin
我找到了一些关于使用编辑器模板的讨论,但找不到一个好的例子。有两种方法可以做到这一点。第一种方法是实现
IModelBinder
,这需要将TimeSpan
作为单独的参数传递给操作,然后将模型的TimeSpan
属性设置为:
[HttpPost]
public ActionResult Index(YourViewModel model,
[ModelBinder(typeof(TimeSpanModelBinder))] TimeSpan ts)
{
model.LengthOfTime = ts;
// do stuff
}
第二个是从DefaultModelBinder
派生,它允许您直接绑定到您的模型:
[HttpPost]
public ActionResult Index([ModelBinder(typeof(TimeSpanModelBinder))] YourViewModel model)
{
// do stuff
}
实现IModelBinder
的优点是,不需要定义自定义属性来标记属性。您也不需要在运行时动态查找模型上的属性
从DefaultModelBinder
派生的优点是,只要您使用自定义属性,它将适用于您拥有的任何模型,因此,如果您需要在多个位置使用此属性,则可以保持操作的一致性
第一种方法:实现IModelBinder
对于这种方法,实现IModelBinder
仅仅意味着为BindModel
方法创建一个实现:
public class TimeSpanModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
if (bindingContext.ModelType != typeof(TimeSpan))
return null;
int hours = 0, minutes = 0, seconds = 0;
hours = ParseTimeComponent(HoursKey, bindingContext);
minutes = ParseTimeComponent(MinutesKey, bindingContext);
seconds = ParseTimeComponent(SecondsKey, bindingContext);
return new TimeSpan(hours, minutes, seconds);
}
public int ParseTimeComponent(string component,
ModelBindingContext bindingContext)
{
int result = 0;
var val = bindingContext.ValueProvider.GetValue(component);
if (!int.TryParse(val.AttemptedValue, out result))
bindingContext.ModelState.AddModelError(component,
String.Format("The field '{0}' is required.", component));
// This is important
bindingContext.ModelState.SetModelValue(component, val);
return result;
}
private readonly string HoursKey = "Hours";
private readonly string MinutesKey = "Minutes";
private readonly string SecondsKey = "Seconds";
}
注意对bindingContext.ModelState.SetModelValue的调用。此方法确保在需要在表单上重新显示模型时,使用正确的数据重新填充模型。这一点很重要,因为它保留了默认功能,即在用户提交的数据未通过验证时再次填写所有表单字段
第二种方法:从DefaultModelBinder派生
您需要做的第一件事是创建一个自定义属性,用于标记要绑定到的模型的属性:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class TimeSpanComponentAttribute : Attribute
{
public override bool Match(object obj)
{
return obj.GetType() == typeof(TimeSpan);
}
}
然后,您可以在视图模型中这样使用它:
public class YourViewModel
{
[Required]
public string SomeRequiredProperty { get; set; }
[TimeSpanComponent]
public TimeSpan LengthOfTime { get; set; }
}
这样,我们就可以在自定义模型绑定器中查找标有该属性的属性:
public class TimeSpanModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
var model = this.CreateModel(controllerContext,
bindingContext, bindingContext.ModelType);
bindingContext.ModelMetadata.Model = model;
var target = model.GetType()
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => Attribute.IsDefined(p, typeof(TimeSpanComponentAttribute)))
.Single();
if (target == null)
throw new MemberAccessException(PropertyNotFound);
int hours = 0, minutes = 0, seconds = 0;
hours = ParseTimeComponent(HoursKey, bindingContext);
minutes = ParseTimeComponent(MinutesKey, bindingContext);
seconds = ParseTimeComponent(SecondsKey, bindingContext);
target.SetValue(model, new TimeSpan(hours, minutes, seconds));
return base.BindModel(controllerContext, bindingContext);
}
public int ParseTimeComponent(string component,
ModelBindingContext bindingContext)
{
int result = 0;
var val = bindingContext.ValueProvider.GetValue(component);
if (!int.TryParse(val.AttemptedValue, out result))
bindingContext.ModelState.AddModelError(component,
String.Format("The field '{0}' is required.", component));
// Again, this is important
bindingContext.ModelState.SetModelValue(component, val);
return result;
}
private readonly string HoursKey = "Hours";
private readonly string MinutesKey = "Minutes";
private readonly string SecondsKey = "Seconds";
private readonly string PropertyNotFound = "Could not bind to TimeSpan property. Did you forget to decorate " +
"a property with TimeSpanComponentAttribute?";
}
请注意,在BindModel
中,我们是如何根据自定义属性定位正确的属性的。另外,在我们完成了对属性的绑定之后,调用基本版本的BindModel
允许默认的模型绑定器处理所有其他内容
注
这两种方法都假定文本框的名称分别为Hours
、Minutes
和Seconds
。如果不是,只需更改3个私有字符串的值
我在这次编辑中改变了很多,所以如果我遗漏了什么,请告诉我
感谢您提出这个问题-我从中学到了很多。是否可以在模型中对多个时间跨度执行类似操作,而无需创建第二个绑定器?如果我将属性dicoration添加到metadatamodel,则第二个方法目标始终为空,我确信metadatamodel工作正常,因为显示名称正常