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.
Parfait! C'est le chemin à parcourir ... –