Общая привязка TimeSpan в Asp.NET MVC 2 - PullRequest
9 голосов
/ 30 сентября 2010

У меня есть форма ввода, которая привязана к модели. Модель имеет свойство TimeSpan, но оно получает значение правильно только в том случае, если я ввожу время в формате чч: мм или чч: мм: сс. Я хочу, чтобы он захватывал значение, даже если оно записано в формате hhmm или hh.mm или hh.mm.ss или ... Я хочу, чтобы многие различные форматы были проанализированы правильно. Возможно ли это?

Спасибо!

Ответы [ 3 ]

22 голосов
/ 09 мая 2011

Я добавил несколько улучшений в код Карлеса и хотел бы поделиться ими здесь на случай, если они будут полезны для других.

  • Убедитесь, что, если ни один шаблон не успешно проанализирует время, то все равно вызовитеbase, чтобы показать ошибку проверки (в противном случае значение оставляется как TimeSpan.Zero, и ошибка проверки не возникает.)
  • Использовать цикл вместо цепочки if s.
  • Поддержкадостаточно использовать AM и PM.
  • Игнорировать пробелы.

Вот код:

public sealed class TimeSpanModelBinder : DefaultModelBinder
{
    private const DateTimeStyles _dateTimeStyles = DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeLocal | DateTimeStyles.NoCurrentDateDefault;

    protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
    {
        var form = controllerContext.HttpContext.Request.Form;

        if (propertyDescriptor.PropertyType.Equals(typeof(TimeSpan?)) || propertyDescriptor.PropertyType.Equals(typeof(TimeSpan)))
        {
            var text = form[propertyDescriptor.Name];
            TimeSpan time;
            if (text != null && TryParseTime(text, out time))
            {
                SetProperty(controllerContext, bindingContext, propertyDescriptor, time);
                return;
            }
        }

        // Either a different type, or we couldn't parse the string.
        base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
    }

    public static bool TryParseTime(string text, out TimeSpan time)
    {
        if (text == null)
            throw new ArgumentNullException("text");

        var formats = new[] {
            "HH:mm", "HH.mm", "HHmm", "HH,mm", "HH",
            "H:mm", "H.mm", "H,mm",
            "hh:mmtt", "hh.mmtt", "hhmmtt", "hh,mmtt", "hhtt",
            "h:mmtt", "h.mmtt", "hmmtt", "h,mmtt", "htt"
        };

        text = Regex.Replace(text, "([^0-9]|^)([0-9])([0-9]{2})([^0-9]|$)", "$1$2:$3$4");
        text = Regex.Replace(text, "^[0-9]$", "0$0");

        foreach (var format in formats)
        {
            DateTime value;
            if (DateTime.TryParseExact(text, format, CultureInfo.InvariantCulture, _dateTimeStyles, out value))
            {
                time = value.TimeOfDay;
                return true;
            }
        }
        time = TimeSpan.Zero;
        return false;
    }
}

Это может показаться немного чрезмерным, но я хочу, чтобы мои пользователи могли вводить что угодно и заставить мое приложение работать с ним.

Это можно применить ко всем DateTime экземплярам с помощью этого кода в Global.asax.cs:

ModelBinders.Binders.Add(typeof(TimeSpan), new TimeSpanModelBinder());

Или только на конкретный параметр метода действия:

public ActionResult Save([ModelBinder(typeof(TimeSpanModelBinder))] MyModel model)
{ ... }

А вот простой модульный тест для проверки некоторых потенциальных входов / выходов:

    [TestMethod]
    public void TimeSpanParsing()
    {
        var testData = new[] {
            new { Text = "100", Time = new TimeSpan(1, 0, 0) },
            new { Text = "10:00 PM", Time = new TimeSpan(22, 0, 0) },
            new { Text = "2", Time = new TimeSpan(2, 0, 0) },
            new { Text = "10", Time = new TimeSpan(10, 0, 0) },
            new { Text = "100PM", Time = new TimeSpan(13, 0, 0) },
            new { Text = "1000", Time = new TimeSpan(10, 0, 0) },
            new { Text = "10:00", Time = new TimeSpan(10, 0, 0) },
            new { Text = "10.00", Time = new TimeSpan(10, 0, 0) },
            new { Text = "13:00", Time = new TimeSpan(13, 0, 0) },
            new { Text = "13.00", Time = new TimeSpan(13, 0, 0) },
            new { Text = "10 PM", Time = new TimeSpan(22, 0, 0) },
            new { Text = "  10\t PM ", Time = new TimeSpan(22, 0, 0) },
            new { Text = "10PM", Time = new TimeSpan(22, 0, 0) },
            new { Text = "1PM", Time = new TimeSpan(13, 0, 0) },
            new { Text = "1 am", Time = new TimeSpan(1, 0, 0) },
            new { Text = "1 AM", Time = new TimeSpan(1, 0, 0) },
            new { Text = "1 pm", Time = new TimeSpan(13, 0, 0) },
            new { Text = "1 PM", Time = new TimeSpan(13, 0, 0) },
            new { Text = "01 PM", Time = new TimeSpan(13, 0, 0) },
            new { Text = "0100 PM", Time = new TimeSpan(13, 0, 0) },
            new { Text = "01.00 PM", Time = new TimeSpan(13, 0, 0) },
            new { Text = "01.00PM", Time = new TimeSpan(13, 0, 0) },
            new { Text = "1:00PM", Time = new TimeSpan(13, 0, 0) },
            new { Text = "1:00 PM", Time = new TimeSpan(13, 0, 0) },
            new { Text = "12,34", Time = new TimeSpan(12, 34, 0) },
            new { Text = "1012PM", Time = new TimeSpan(22, 12, 0) },
        };

        foreach (var test in testData)
        {
            try
            {
                TimeSpan time;
                Assert.IsTrue(TimeSpanModelBinder.TryParseTime(test.Text, out time), "Should parse {0}", test.Text);
                if (!Equals(time, test.Time))
                    Assert.Fail("Time parse failed.  Expected {0} but got {1}", test.Time, time);
            }
            catch (FormatException)
            {
                Assert.Fail("Received format exception with text {0}", test.Text);
            }
        }
    } 

Надеюсь, что это поможеткто-то вышел.

4 голосов
/ 30 сентября 2010

Да - напишите пользовательское связующее для вашей модели. Здесь есть тема только об этой теме на SO: ASP.NET MVC2 - Примеры привязок пользовательских моделей

3 голосов
/ 01 октября 2010

Для справки, вот как я это сделал:

using System;
using System.Globalization;
using System.Web.Mvc;

namespace Utils.ModelBinders
{
    public class CustomTimeSpanModelBinder : DefaultModelBinder
    {
        protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
        {
            var form = controllerContext.HttpContext.Request.Form;

            if (propertyDescriptor.PropertyType.Equals(typeof(TimeSpan?)))
            {
                var text = form[propertyDescriptor.Name];
                DateTime value;
                if (DateTime.TryParseExact(text, "HH:mm", CultureInfo.InvariantCulture, DateTimeStyles.None, out value))
                        SetProperty(controllerContext,bindingContext,propertyDescriptor,value.TimeOfDay);
                else if (DateTime.TryParseExact(text, "HH.mm", CultureInfo.InvariantCulture, DateTimeStyles.None, out value))
                    SetProperty(controllerContext, bindingContext, propertyDescriptor, value.TimeOfDay);
                else if (DateTime.TryParseExact(text, "HHmm", CultureInfo.InvariantCulture, DateTimeStyles.None, out value))
                    SetProperty(controllerContext, bindingContext, propertyDescriptor, value.TimeOfDay);
                else if (DateTime.TryParseExact(text, "HH,mm", CultureInfo.InvariantCulture, DateTimeStyles.None, out value))
                    SetProperty(controllerContext, bindingContext, propertyDescriptor, value.TimeOfDay);
                else if (DateTime.TryParseExact(text, "HH", CultureInfo.InvariantCulture, DateTimeStyles.None, out value))
                    SetProperty(controllerContext, bindingContext, propertyDescriptor, value.TimeOfDay);
            }
            else
            {
                base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
            }
        }
    }
}
...