2010-09-30 10 views
9

Ich habe ein Eingabeformular, das an ein Modell gebunden ist. Das Modell verfügt über eine TimeSpan-Eigenschaft, aber es wird den Wert nur korrekt abgerufen, wenn ich die Zeit als hh: mm oder hh: mm: ss eingeben. Was ich will ist, dass es den Wert erfasst, auch wenn es als hhmm oder hh.mm oder hh.mm.ss geschrieben wird oder ... Ich möchte, dass viele verschiedene Formate korrekt analysiert werden. Ist das möglich?Generische TimeSpan-Bindung in Asp.NET MVC 2

Danke!

Antwort

3

Für das Protokoll, hier ist, wie ich es tat:

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

Danke für die Freigabe dieses Codes. Kannst du auch zeigen, wie du es benutzt hast? Können Sie dies nur für eine einzelne Eigenschaft in einem bestimmten Modell konfigurieren, oder gilt es global für alle "TimeSpan" -Eigenschaftsbindungsvorgänge? –

+1

Ich habe dies der Application_Start-Methode der Datei Global_asax.cs hinzugefügt: ModelBinders.Binders.DefaultBinder = new CustomTimeSpanModelBinder(); Ich denke, Sie können dies auch auf einer Aktionsbasis mit Anmerkungen festlegen. –

+1

Danke Carles. Ich habe meine Anpassung Ihres Codes als Antwort gepostet. In meinem Fall wollte ich so viele verschiedene Zeit-Strings behandeln, wie vernünftigerweise bereitgestellt und interpretiert werden konnte. –

20

ich ein paar Verbesserungen an Carles' Code hinzugefügt und wollte, dass sie hier in Fall teilen sie sind nützlich für andere.

  • Sicherstellen, dass, wenn keine Muster erfolgreich die Zeit analysieren, so ruft immer noch die Basis, um einen Überprüfungsfehler zu zeigen (andernfalls wird der Wert als TimeSpan.Zero gelassen wird und kein Überprüfungsfehler erhöht.)
  • eine Schleife verwenden eher als verkettet if s.
  • Unterstützung der Verwendung von AM und genügt.
  • Leerzeichen ignorieren.

Hier ist der Code:

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

Dieses ein wenig übertrieben erscheinen mag, aber ich möchte, dass meine Benutzer in der Lage sein, so ziemlich alles zu geben und haben es meine App funktioniert.

Es kann in Global.asax.cs über diesen Code auf alle DateTime Instanzen angewandt werden:

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

Oder einfach nur auf einem bestimmten Aktion Methodenparameter:

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

Und hier ist ein einfacher Unit-Test zu validieren nur zu einige mögliche Eingänge/Ausgänge:

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

Hoffe das hilft jemandem aus.

+0

Und mit Tests auch? Brillant. Vielen Dank! – Ted

+0

@Ted, gern geschehen. Manchmal machen Tests die beste Dokumentation. –

+0

Mehr als manchmal, weil wir unter vielen anderen Dingen unsere Tests normalerweise auf dem neuesten Stand halten, während es keine Garantie für die Dokumentation gibt. Wenn die Tests funktionieren, ist die "echte Dokumentation" korrekt. – Ted