MVC Editor Template for TimeSpan

I want to bind a set of text fields, hours, minutes and seconds to a TimeSpan in my model. I do not want to add hours, minutes and seconds to my model, and I would prefer not to have them as parameters passed to the action method.

I have found some discussion on using editor templates, but cannot find a good example.

+4
source share
1 answer

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;

    // 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 . , .

, , - .

, - .

+6

All Articles