2010-09-30 24 views
9

J'ai un formulaire d'entrée lié à un modèle. Le modèle a une propriété TimeSpan, mais il n'obtient la valeur correctement que si je saisis hh: mm ou hh: mm: ss. Ce que je veux, c'est qu'elle capture la valeur même si elle est écrite en tant que hhmm ou hh.mm ou hh.mm.ss ou ... Je veux que de nombreux formats différents soient correctement analysés. Est-ce possible?Liaison TimeSpan générique dans Asp.NET MVC 2

Merci!

Répondre

3

Pour mémoire, voici comment je l'ai fait:

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

Merci d'avoir partagé ce code. Pourriez-vous également montrer comment vous l'avez spécifié pour être utilisé? Pouvez-vous configurer ceci juste pour une seule propriété sur un modèle particulier, ou s'applique-t-il globalement à toutes les opérations de liaison de propriété 'TimeSpan'? –

+1

J'ai ajouté cela à la méthode Application_Start du fichier Global_asax.cs: ModelBinders.Binders.DefaultBinder = new CustomTimeSpanModelBinder(); Je pense que vous pouvez également spécifier ceci sur une base action par action en utilisant des annotations. –

+1

Merci Carles. J'ai aussi posté mon adaptation de votre code en réponse. Dans mon cas, je voulais gérer autant de différentes chaînes d'heures que ce qui pouvait raisonnablement être fourni et interprété. –

20

j'ai ajouté quelques améliorations au code de Carles et je voulais les partager ici au cas où ils sont utiles pour les autres.

  • Assurez-vous que si aucun motif parse avec succès le temps, puis appeler encore la base afin de montrer une erreur de validation (sinon la valeur est laissée TimeSpan.Zero et aucune erreur de validation élevé.)
  • Utilisez une boucle plutôt que enchaîné if s. L'utilisation de AM et PM est suffisante.
  • Ignorer les espaces.

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

Cela peut sembler un peu plus haut, mais je veux que mes utilisateurs d'être en mesure d'entrer quoi que ce soit à peu près et que mon application fonctionne dehors.

Il peut être appliqué à tous les DateTime cas par ce code dans Global.asax.cs:

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

Ou tout simplement un paramètre de méthode d'action spécifique:

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

Et voici un simple test de l'unité juste pour valider quelques entrées/sorties potentielles:

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

J'espère que cela aide quelqu'un.

+0

Et avec des tests aussi? Brillant. Merci! – Ted

+0

@Ted, je vous en prie. Parfois, les tests constituent la meilleure documentation. –

+0

Plus que de temps en temps, parce que, entre autres choses, nous maintenons généralement nos tests à jour alors qu'il n'y a aucune garantie que la documentation sera. Si les tests fonctionnent, alors la "vraie documentation" est correcte. – Ted