Je voudrais écrire mon propre classeur de modèle pour le type DateTime
. Tout d'abord, j'aimerais écrire un nouvel attribut que je peux attacher à la propriété de mon modèle, tel que:
[DateTimeFormat("d.M.yyyy")]
public DateTime Birth { get; set,}
C'est la partie facile. Mais la partie reliure est un peu plus difficile. J'aimerais ajouter un nouveau classeur pour le type DateTime
. Je peux soit
IModelBinder
et écrire ma propre méthode BindModel()
DefaultModelBinder
et surcharger la méthode BindModel()
Mon modèle a une propriété comme ci-dessus (Birth
). Ainsi, lorsque le modèle tente de lier les données de requête à cette propriété, la fonction BindModel(controllerContext, bindingContext)
de mon classeur de modèle est appelée. Tout va bien, mais. Comment obtenir les attributs de propriété de controller/bindingContext pour analyser ma date correctement ? Comment puis-je accéder à la PropertyDesciptor
de la propriété Birth
?
En raison de la séparation des préoccupations, ma classe de modèle est définie dans un assembly qui ne fait pas (et ne devrait pas) référencer System.Web.MVC Assembly. La définition d'attaches personnalisées (similaire à l'exemple de Scott Hanselman ) n'est pas permise ici.
Je ne pense pas que vous devriez mettre des attributs spécifiques aux paramètres régionaux sur un modèle.
Deux autres solutions possibles à ce problème sont:
Pour répondre à votre question, la manière d'obtenir des attributs personnalisés (pour MVC 2) consiste à écrire un AssociatedMetadataProvider .
vous pouvez modifier le classeur de modèle par défaut pour utiliser la culture utilisateur à l'aide de IModelBinder
public class DateTimeBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, value);
return value.ConvertTo(typeof(DateTime), CultureInfo.CurrentCulture);
}
}
public class NullableDateTimeBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, value);
return value == null
? null
: value.ConvertTo(typeof(DateTime), CultureInfo.CurrentCulture);
}
}
Et dans Global.Asax, ajoutez ce qui suit à Application_Start ():
ModelBinders.Binders.Add(typeof(DateTime), new DateTimeBinder());
ModelBinders.Binders.Add(typeof(DateTime?), new NullableDateTimeBinder());
Plus d’informations sur cet excellent blog qui décrivent pourquoi l’équipe de framework Mvc a implémenté une culture par défaut pour tous les utilisateurs.
J'ai moi-même eu ce très gros problème et après des heures d'essais et d'échecs, j'ai obtenu une solution efficace, comme vous l'avez demandé.
Tout d'abord, puisqu'il n'est pas possible d'avoir un liant sur une propriété, vous devez implémenter un ModelBinder complet. Puisque vous ne voulez pas que toutes les propriétés simples soient liées, vous pouvez hériter de DefaultModelBinder uniquement de la propriété qui vous tient à cœur, puis lier la propriété unique:
public class DateFiexedCultureModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.PropertyType == typeof(DateTime?))
{
try
{
var model = bindingContext.Model;
PropertyInfo property = model.GetType().GetProperty(propertyDescriptor.Name);
var value = bindingContext.ValueProvider.GetValue(propertyDescriptor.Name);
if (value != null)
{
System.Globalization.CultureInfo cultureinfo = new System.Globalization.CultureInfo("it-CH");
var date = DateTime.Parse(value.AttemptedValue, cultureinfo);
property.SetValue(model, date, null);
}
}
catch
{
//If something wrong, validation should take care
}
}
else
{
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
}
Dans mon exemple, je pars dans une culture fiexée, mais ce que vous voulez faire est possible. Vous devez créer un CustomAttribute (comme DateTimeFormatAttribute) et le placer sur votre propriété:
[DateTimeFormat("d.M.yyyy")]
public DateTime Birth { get; set,}
Désormais, dans la méthode BindProperty, au lieu de rechercher une propriété DateTime, vous pouvez rechercher une propriété avec votre DateTimeFormatAttribute, saisir le format que vous avez spécifié dans le constructeur, puis analyser la date avec DateTime.ParseExact
J'espère que cela aide, il m'a fallu très longtemps pour arriver avec cette solution. Il était en fait facile d’avoir cette solution une fois que je savais comment la rechercher :(
Vous pouvez implémenter un classeur DateTime personnalisé comme ceci, mais vous devez prendre en compte la culture et la valeur supposées de la demande du client. Pouvez-vous obtenir une date comme mm/jj/aaaa en en-US et que vous souhaitiez la convertir dans la culture système en-GB (ce qui serait comme jj/mm/aaaa) ou une culture invariante, comme nous, alors il faut l’analyser avant et utiliser la façade statique Convert pour la changer dans son comportement.
public class DateTimeModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var valueResult = bindingContext.ValueProvider
.GetValue(bindingContext.ModelName);
var modelState = new ModelState {Value = valueResult};
var resDateTime = new DateTime();
if (valueResult == null) return null;
if ((bindingContext.ModelType == typeof(DateTime)||
bindingContext.ModelType == typeof(DateTime?)))
{
if (bindingContext.ModelName != "Version")
{
try
{
resDateTime =
Convert.ToDateTime(
DateTime.Parse(valueResult.AttemptedValue, valueResult.Culture,
DateTimeStyles.AdjustToUniversal).ToUniversalTime(), CultureInfo.InvariantCulture);
}
catch (Exception e)
{
modelState.Errors.Add(EnterpriseLibraryHelper.HandleDataLayerException(e));
}
}
else
{
resDateTime =
Convert.ToDateTime(
DateTime.Parse(valueResult.AttemptedValue, valueResult.Culture), CultureInfo.InvariantCulture);
}
}
bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
return resDateTime;
}
}
Quoi qu'il en soit, la culture dépend de l'analyse DateTime dans une application sans état peut être cruelle ... surtout lorsque vous travaillez avec JSON sur des clients javascript et inversement.