2010-09-30 17 views
9

Tengo un formulario de entrada vinculado a un modelo. El modelo tiene una propiedad TimeSpan, pero solo obtiene el valor correctamente si ingreso la hora como hh: mm o hh: mm: ss. Lo que quiero es que capture el valor incluso si está escrito como hhmm o hh.mm o hh.mm.ss o ... Quiero que muchos formatos diferentes sean analizados correctamente. es posible?Enlazado genérico de TimeSpan en Asp.NET MVC 2

Gracias!

Respuesta

3

Para el registro, así es como lo hice:

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); 
      } 
     } 
    } 
} 
+0

Gracias por compartir este código. ¿Podría mostrar también cómo lo especificó para ser utilizado? ¿Puede configurar esto solo para una sola propiedad en un modelo en particular, o se aplica globalmente a todas las operaciones de enlace de propiedad 'TimeSpan'? –

+1

Agregué esto al método Application_Start del archivo Global_asax.cs: ModelBinders.Binders.DefaultBinder = new CustomTimeSpanModelBinder(); Creo que también puede especificar esto acción por acción usando anotaciones. –

+1

Gracias Carles. También he publicado mi adaptación de tu código como respuesta. En mi caso, quise manejar tantas cadenas de tiempo diferentes como pudiera razonablemente proporcionarse e interpretarse. –

20

he añadido algunas mejoras al código Carles' y quería compartirlas aquí en caso de son útiles para otros.

  • asegurar que si no hay patrones de analizar correctamente el tiempo, entonces siguen llamando la base con el fin de mostrar un error de validación (de lo contrario el valor se deja como TimeSpan.Zero y generará ningún error de validación.)
  • utilizar un bucle en lugar que encadenó if s.
  • Soporta el uso de AM y PM suficiente.
  • Ignorar el espacio en blanco.

Aquí está el código:

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; 
    } 
} 

esto puede parecer un poco exagerado, pero yo quiero que mis usuarios puedan entrar en casi cualquier cosa y tienen mi aplicación funciona hacia fuera.

Se puede aplicar a todos los DateTime casos a través de este código en Global.asax.cs:

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

O simplemente en un parámetro específico método de acción:

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

y aquí está una prueba simple unidad sólo para validar algunas posibles entradas/salidas:

[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); 
      } 
     } 
    } 

Espero que ayude a alguien a salir.

+0

Y con pruebas también? Brillante. ¡Gracias! – Ted

+0

@Ted, de nada. A veces las pruebas son la mejor documentación. –

+0

Más que a veces, porque entre muchas otras cosas, generalmente mantenemos nuestras pruebas actualizadas mientras que no hay garantía de que la documentación sea correcta. Si las pruebas funcionan, entonces la "documentación real" es correcta. – Ted