There are two ways to do this. The first way is to implement IModelBinder, for which you need to pass TimeSpanas a separate parameter for your action, and then set the property for it TimeSpan:
[HttpPost]
public ActionResult Index(YourViewModel model,
[ModelBinder(typeof(TimeSpanModelBinder))] TimeSpan ts)
{
model.LengthOfTime = ts;
}
DefaultModelBinder, :
[HttpPost]
public ActionResult Index([ModelBinder(typeof(TimeSpanModelBinder))] YourViewModel model)
{
}
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));
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));
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 . , .
, , - .
, - .