wake-up-neo.net

Benutzerdefinierte DateTime-Modellbinder in Asp.net MVC

Ich würde gerne einen eigenen Modellordner für DateTime schreiben. Zunächst möchte ich ein neues Attribut schreiben, das ich an meine Modelleigenschaft anfügen kann:

[DateTimeFormat("d.M.yyyy")]
public DateTime Birth { get; set,}

Dies ist der einfache Teil. Das Binderteil ist jedoch etwas schwieriger. Ich möchte einen neuen Modellordner für den Typ DateTime hinzufügen. Ich kann entweder

  • IModelBinder interface implementieren und eigene BindModel()-Methode schreiben
  • erben Sie von DefaultModelBinder und überschreiben Sie die BindModel()-Methode

Mein Modell hat eine Eigenschaft wie oben (Birth). Wenn das Modell versucht, Anfragedaten an diese Eigenschaft zu binden, wird BindModel(controllerContext, bindingContext) meines Modellbinders aufgerufen. Alles in Ordnung, aber. Wie bekomme ich Eigenschaftsattribute von Controller/bindingContext, um mein Datum korrekt zu analysieren ? Wie komme ich zur PropertyDesciptor der Eigenschaft Birth?

Bearbeiten

Aufgrund der Trennung der Bedenken wird meine Modellklasse in einer Assembly definiert, die nicht auf System.Web.MVC Assembly verweist (und sollte). Das Festlegen von benutzerdefinierten Bindungsattributen (vergleichbar mit dem Beispiel von Scott Hanselman ) ist hier kein Thema.

22
Robert Koritnik

Ich denke nicht, dass Sie einem Modell länderspezifische Attribute hinzufügen sollten.

Zwei weitere mögliche Lösungen für dieses Problem sind:

  • Lassen Sie Ihre Seiten Datumsangaben aus dem ländereinstellungsspezifischen Format in ein generisches Format konvertieren, z. B. yyyy-mm-dd in JavaScript. (Funktioniert, erfordert jedoch JavaScript.)
  • Schreiben Sie einen Modellordner, der die aktuelle UI-Kultur beim Analysieren von Datumsangaben berücksichtigt.

Um Ihre eigentliche Frage zu beantworten, können Sie benutzerdefinierte Attribute (für MVC 2) abrufen, indem Sie einen AssociatedMetadataProvider schreiben.

3
Craig Stuntz

sie können den Standardmodellbinder zur Verwendung der Benutzerkultur mit IModelBinder ändern

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

Fügen Sie in Global.Asax Application_Start () Folgendes hinzu: 

ModelBinders.Binders.Add(typeof(DateTime), new DateTimeBinder());
ModelBinders.Binders.Add(typeof(DateTime?), new NullableDateTimeBinder());

Weitere Informationen finden Sie unter diesem ausgezeichneten Blog , in dem beschrieben wird, warum das Mvc Framework-Team allen Benutzern eine Standardkultur implementiert hat.

80
gdoron

Ich hatte selbst dieses sehr große Problem und nach Stunden des Versuchens und Scheiterns bekam ich eine funktionierende Lösung, wie Sie es wollten.

Zunächst einmal, da es nicht möglich ist, ein Bindemittel auf einem Grundstück zu haben, ist es nicht möglich, einen vollständigen ModelBinder zu implementieren. Da Sie nicht möchten, dass alle Einzeleigenschaften gebunden werden, sondern nur die, die Sie interessieren, können Sie von DefaultModelBinder erben und dann die Einzeleigenschaft binden:

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

In meinem Beispiel analysiere ich ein Datum mit einer gefesselten Kultur, aber was Sie tun möchten, ist möglich. Sie sollten ein CustomAttribute (wie DateTimeFormatAttribute) erstellen und es über Ihre Eigenschaft setzen:

[DateTimeFormat("d.M.yyyy")]
public DateTime Birth { get; set,}

In der BindProperty-Methode können Sie, anstatt nach einer DateTime-Eigenschaft zu suchen, mit DateTimeFormatAttribute nach einer Eigenschaft suchen, das im Konstruktor angegebene Format verwenden und dann das Datum mit DateTime.ParseExact analysieren 

Ich hoffe das hilft, es dauerte sehr lange, bis ich mit dieser Lösung kam. Es war eigentlich einfach, diese Lösung zu haben, sobald ich wusste, wie man sie sucht :(

14
Davide Vosti

Sie könnten eine benutzerdefinierte DateTime-Sammelmappe wie folgt implementieren, aber Sie müssen sich um die angenommene Kultur und den Wert der tatsächlichen Kundenanforderung kümmern. Vielleicht erhalten Sie ein Datum wie mm/tt/jjjj in en-US und möchten, dass es in der Systemkultur en-GB (was dd/mm/jjjj wäre) oder einer invarianten Kultur, wie wir es tun, dann Sie Sie müssen es vorher analysieren und die statische Fassadenkonvertierung verwenden, um sie in ihrem Verhalten zu ändern.

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

Wie auch immer, kulturabhängiges DateTime-Parsing in einer statuslosen Anwendung kann durch Grausamkeit bedingt sein ... Besonders wenn Sie mit JSON an Javascript-Clientseiten und rückwärts arbeiten. 

0
Phil