Powiązanie MVC DateTime z nieprawidłowym formatem daty

132

Asp.net-MVC umożliwia teraz niejawne wiązanie obiektów DateTime. Mam działanie podobne do

public ActionResult DoSomething(DateTime startDate) 
{ 
... 
}

To pomyślnie konwertuje ciąg z wywołania AJAX na DateTime. Jednak używamy formatu daty dd / MM / rrrr; MVC konwertuje na MM / dd / rrrr. Na przykład przesłanie wezwania do działania z ciągiem „09/02/2009” skutkuje datą i godziną „02/09/2009 00:00:00” lub 2 września w naszych ustawieniach lokalnych.

Nie chcę zwijać własnego segregatora ze względu na format daty. Ale wydaje się niepotrzebne, aby musieć zmieniać akcję, aby zaakceptować ciąg, a następnie użyć DateTime.Parse, jeśli MVC jest w stanie zrobić to za mnie.

Czy istnieje sposób na zmianę formatu daty używanego w domyślnym segregatorze modelu dla DateTime? Czy i tak domyślny segregator modelu nie powinien używać ustawień lokalizacji?

Sam Wessel
źródło
Hej .. Po prostu zmień format daty systemowej - DD / MM / rrrr na MM / DD / rrrr i zrobiłem to ... Mam też ten sam problem, rozwiązałem go zmieniając systemowy format daty.
banny
@banny, jeśli aplikacja jest globalna i każda może mieć inny format daty i czasu, jak możesz to zrobić? , nie powinieneś zmieniać każdego formatu daty i czasu ..
Ravi Mehta
Żadna z tych odpowiedzi nie pomaga mi. Formularz musi być zlokalizowany. Niektórzy użytkownicy mogą mieć datę w jedną stronę, inni w drugą. Konfigurowanie czegoś w pliku web.config. lub global.asax nie pomoże. Będę szukał lepszej odpowiedzi, ale jednym ze sposobów byłoby po prostu traktowanie daty jako ciągu, dopóki nie wrócę do C #.
astrosteve

Odpowiedzi:

164

Właśnie znalazłem odpowiedź na to pytanie w bardziej wyczerpujących googlach:

Melvyn Harbor ma dokładne wyjaśnienie, dlaczego MVC działa z datami w taki sposób, w jaki działa, i jak możesz to zmienić, jeśli to konieczne:

http://weblogs.asp.net/melvynharbour/archive/2008/11/21/mvc-modelbinder-and-localization.aspx

Szukając wartości do przeanalizowania, framework wygląda w określonej kolejności, a mianowicie:

  1. RouteData (nie pokazano powyżej)
  2. Ciąg zapytania URI
  3. Formularz zapytania

Jednak tylko ostatni z nich będzie świadomy kultury. Z punktu widzenia lokalizacji jest ku temu bardzo dobry powód. Wyobraź sobie, że napisałem aplikację internetową pokazującą informacje o lotach lotniczych, które publikuję w Internecie. Wyszukuję loty w określonym dniu, klikając łącze z tym dniem (może na przykład http://www.melsflighttimes.com/Flights/2008-11-21 ), a następnie chcę wysłać ten link e-mailem do mojego współpracownika w Stany Zjednoczone. Jedynym sposobem, w jaki możemy zagwarantować, że oboje będziemy patrzeć na tę samą stronę danych, jest użycie InvariantCulture. Jeśli natomiast używam formularza do rezerwacji lotu, wszystko dzieje się w ciasnym cyklu. Dane mogą szanować CurrentCulture, gdy są zapisywane w formularzu, i dlatego muszą być szanowane podczas powrotu z formularza.

Sam Wessel
źródło
Zrobi się. Ta funkcja jest wyłączona przez 48 godzin po opublikowaniu pytania.
Sam Wessel
45
Zdecydowanie nie zgodzę się, że technicznie jest to poprawne. Segregator modelu powinien ZAWSZE zachowywać się tak samo z POST i GET. Jeśli niezmienna kultura jest drogą do GET, zrób to również dla POST. Zmiana zachowania w zależności od czasownika http nie ma sensu.
Bart Calixto
mam pytanie, nasza strona internetowa jest hostowana w innym kraju, potrzebuje MM/dd/yyyyformatu, w przeciwnym razie wyświetla błąd walidacji The field BeginDate must be a date., jak mogę sprawić, by serwer zaakceptował dd/MM/yyyyformat?
shaijut
Parametr adresu URL powinien być jednoznaczny, jak przy użyciu standardowego formatowania ISO. Wtedy kultura nie miałaby znaczenia.
nforss
daje to link wyjaśniający przyczynę, ale w rzeczywistości nie zapewnia najłatwiejszego rozwiązania tego problemu (na przykład przez wysłanie ciągu ISO z datą i godziną ze skryptu), w ten sposób zawsze działa i nie musimy przejmować się ustawieniem określonej kultury w serwer lub upewnij się, że format daty i godziny jest identyczny między serwerem a klientem.
Beznadziejny
37

Ustawiłbym wasze kultury na całym świecie. ModelBinder podnieś to!

  <system.web>
    <globalization uiCulture="en-AU" culture="en-AU" />

Lub po prostu zmień to dla tej strony.
Ale myślę, że globalnie w web.config jest lepiej

Peter Gfader
źródło
27
Nie dla mnie. Data nadal jest zerowa, jeśli przekroczę 23/10/2010.
GONeale
Myślę, że to najłatwiejsze rozwiązanie. U mnie to zmienia format we wszystkich Date.ToString (). Myślę, że to zadziała też z wiązaniem, ale nie sprawdziłem, przepraszam :-(
msa.im
1
Mam ustawiony format daty serwerów deweloperskich na dd / MM / rrrr. Modelbinder użył formatu MM / dd / rrrr. Ustawienie formatu w web.config na dd / MM / rrrr wymusza teraz program wiążący model, aby używał formatu europejskiego. Moim zdaniem powinien jednak korzystać z ustawień daty serwera. W każdym razie rozwiązałeś mój problem.
Karel
To zadziałało idealnie dla mnie ... w jakiś sposób było to dziwne, ponieważ wiem, że mój serwer aplikacji znajduje się w Wielkiej Brytanii i działa na brytyjskim systemie operacyjnym ...: /
Tallmaris
Działał doskonale w MVC4 działającym na Azure Websites
Matty Bear
31

Mam ten sam problem z powiązaniem formatu krótkiej daty z właściwościami modelu DateTime. Po zapoznaniu się z wieloma różnymi przykładami (nie tylko dotyczącymi DateTime) zestawiłem następujące:

using System;
using System.Globalization;
using System.Web.Mvc;

namespace YourNamespaceHere
{
    public class CustomDateBinder : IModelBinder
    {
        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            if (controllerContext == null)
                throw new ArgumentNullException("controllerContext", "controllerContext is null.");
            if (bindingContext == null)
                throw new ArgumentNullException("bindingContext", "bindingContext is null.");

            var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

            if (value == null)
                throw new ArgumentNullException(bindingContext.ModelName);

            CultureInfo cultureInf = (CultureInfo)CultureInfo.CurrentCulture.Clone();
            cultureInf.DateTimeFormat.ShortDatePattern = "dd/MM/yyyy";

            bindingContext.ModelState.SetModelValue(bindingContext.ModelName, value);

            try
            {
                var date = value.ConvertTo(typeof(DateTime), cultureInf);

                return date;
            }
            catch (Exception ex)
            {
                bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex);
                return null;
            }
        }
    }

    public class NullableCustomDateBinder : IModelBinder
    {
        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            if (controllerContext == null)
                throw new ArgumentNullException("controllerContext", "controllerContext is null.");
            if (bindingContext == null)
                throw new ArgumentNullException("bindingContext", "bindingContext is null.");

            var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

            if (value == null) return null;

            CultureInfo cultureInf = (CultureInfo)CultureInfo.CurrentCulture.Clone();
            cultureInf.DateTimeFormat.ShortDatePattern = "dd/MM/yyyy";

            bindingContext.ModelState.SetModelValue(bindingContext.ModelName, value);

            try
            {
                var date = value.ConvertTo(typeof(DateTime), cultureInf);

                return date;
            }
            catch (Exception ex)
            {
                bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex);
                return null;
            }
        }
    }
}

Aby zachować sposób, w jaki trasy itp są rejestrowane w globalnym pliku ASAX, dodałem również nową klasę sytatic do folderu App_Start mojego projektu MVC4 o nazwie CustomModelBinderConfig:

using System;
using System.Web.Mvc;

namespace YourNamespaceHere
{
    public static class CustomModelBindersConfig
    {
        public static void RegisterCustomModelBinders()
        {
            ModelBinders.Binders.Add(typeof(DateTime), new CustomModelBinders.CustomDateBinder());
            ModelBinders.Binders.Add(typeof(DateTime?), new CustomModelBinders.NullableCustomDateBinder());
        }
    }
}

Następnie po prostu wywołuję statyczne RegisterCustomModelBinders z mojej Global ASASX Application_Start w następujący sposób:

protected void Application_Start()
{
    /* bla blah bla the usual stuff and then */

    CustomModelBindersConfig.RegisterCustomModelBinders();
}

Ważną informacją jest to, że jeśli napiszesz wartość DateTime w ukrytym polu, jak to:

@Html.HiddenFor(model => model.SomeDate) // a DateTime property
@Html.Hiddenfor(model => model) // a model that is of type DateTime

Zrobiłem to, a rzeczywista wartość na stronie była w formacie „MM / dd / rrrr gg: mm: ss tt” zamiast „dd / MM / rrrr gg: mm: ss tt”, tak jak chciałem. To spowodowało, że walidacja mojego modelu zakończyła się niepowodzeniem lub zwróceniem niewłaściwej daty (oczywiście zamieniając wartości dnia i miesiąca).

Po wielu drapaniu w głowę i nieudanych próbach rozwiązaniem było ustawienie informacji o kulturze dla każdego żądania, robiąc to w Global.ASAX:

protected void Application_BeginRequest()
{
    CultureInfo cInf = new CultureInfo("en-ZA", false);  
    // NOTE: change the culture name en-ZA to whatever culture suits your needs

    cInf.DateTimeFormat.DateSeparator = "/";
    cInf.DateTimeFormat.ShortDatePattern = "dd/MM/yyyy";
    cInf.DateTimeFormat.LongDatePattern = "dd/MM/yyyy hh:mm:ss tt";

    System.Threading.Thread.CurrentThread.CurrentCulture = cInf;
    System.Threading.Thread.CurrentThread.CurrentUICulture = cInf;
}

Nie zadziała, jeśli umieścisz go w Application_Start lub nawet Session_Start, ponieważ przypisuje go to do bieżącego wątku sesji. Jak dobrze wiesz, aplikacje internetowe są bezstanowe, więc wątek, który wcześniej obsługiwał twoje żądanie, nie jest tym samym wątkiem obsługującym twoje bieżące żądanie, dlatego informacje o kulturze trafiły do ​​wielkiego GC w cyfrowym niebie.

Podziękowania dla: Ivan Zlatev - http://ivanz.com/2010/11/03/custom-model-binding-using-imodelbinder-in-asp-net-mvc-two-gotchas/

garik - https://stackoverflow.com/a/2468447/578208

Dmitry - https://stackoverflow.com/a/11903896/578208

WernerVA
źródło
13

W MVC 3 będzie trochę inaczej.

Załóżmy, że mamy kontroler i widok z metodą Get

public ActionResult DoSomething(DateTime dateTime)
{
    return View();
}

Powinniśmy dodać ModelBinder

public class DateTimeBinder : IModelBinder
{
    #region IModelBinder Members
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        DateTime dateTime;
        if (DateTime.TryParse(controllerContext.HttpContext.Request.QueryString["dateTime"], CultureInfo.GetCultureInfo("en-GB"), DateTimeStyles.None, out dateTime))
            return dateTime;
        //else
        return new DateTime();//or another appropriate default ;
    }
    #endregion
}

i polecenie w Application_Start () w Global.asax

ModelBinders.Binders.Add(typeof(DateTime), new DateTimeBinder());
Dmitrij
źródło
To przyzwoity punkt wyjścia, ale nie implementuje się poprawnie IModelBinder, szczególnie jeśli chodzi o walidację. Działa również tylko wtedy, gdy nazwa DateTimeto dateTime .
Sam
2
Ponadto odkryłem, że DateTime?działa to tylko wtedy, gdy dodasz kolejne wywołanie do za ModelBinders.Binders.Addpomocą typeof(DateTime?).
Sam
8

Warto również zauważyć, że nawet bez tworzenia własnego segregatora modelu można przeanalizować wiele różnych formatów.

Na przykład w Stanach Zjednoczonych wszystkie poniższe ciągi są równoważne i są automatycznie przypisywane do tej samej wartości DateTime:

/ firma / prasa / maj% 2001% 202008

/ o firmie / prasa / 2008-05-01

/ firma / prasa / 05-01-2008

Zdecydowanie sugerowałbym użycie rrrr-mm-dd, ponieważ jest dużo bardziej przenośny. Naprawdę nie chcesz zajmować się obsługą wielu zlokalizowanych formatów. Jeśli ktoś zarezerwuje lot 1 maja zamiast 5 stycznia, będziesz miał duże problemy!

NB: Nie wiem dokładnie, czy rrrr-mm-dd jest powszechnie analizowane we wszystkich kulturach, więc może ktoś, kto wie, może dodać komentarz.

Simon_Weaver
źródło
3
Ponieważ nikt nie mówi, że rrrr-MM-dd nie jest uniwersalne, myślę, że tak.
deerchao
jest to moim zdaniem lepsze niż użycie segregatora wzorcowego. .datepicker ("opcja", "dateFormat", "rr-mm-dd") lub po prostu ustaw go w wartościach domyślnych.
Bart Calixto
rrrr-MM-dd to „międzynarodowy” format daty (ISO 8601). Bardzo dobrze sortuje również alfabetycznie.
Nathan Goings
6

Spróbuj użyć toISOString (). Zwraca ciąg w formacie ISO8601.

GET metoda

javascript

$.get('/example/doGet?date=' + new Date().toISOString(), function (result) {
    console.log(result);
});

do#

[HttpGet]
public JsonResult DoGet(DateTime date)
{
    return Json(date.ToString(), JsonRequestBehavior.AllowGet);
}

Metoda POST

javascript

$.post('/example/do', { date: date.toISOString() }, function (result) {
    console.log(result);
});

do#

[HttpPost]
public JsonResult Do(DateTime date)
{
     return Json(date.ToString());
}
rnofenko
źródło
5

Ustawiłem poniższą konfigurację na moim MVC4 i działa to jak urok

<globalization uiCulture="auto" culture="auto" />
JeeShen Lee
źródło
3
To zadziałało również dla mnie. Zwróć uwagę, że element globalizacji znajduje się w obszarze Konfiguracja> System.Web. msdn.microsoft.com/en-us/library/hy4kkhe0%28v=vs.85%29.aspx
Jowen
1
  public class DateTimeFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (filterContext.HttpContext.Request.RequestType == "GET")
        {

            foreach (var parameter in filterContext.ActionParameters)
            {
                var properties = parameter.Value.GetType().GetProperties();

                foreach (var property in properties)
                {
                    Type type = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;

                    if (property.PropertyType == typeof(System.DateTime) || property.PropertyType == typeof(DateTime?))
                    {
                        DateTime dateTime;

                        if (DateTime.TryParse(filterContext.HttpContext.Request.QueryString[property.Name], CultureInfo.CurrentUICulture, DateTimeStyles.None, out dateTime))
                            property.SetValue(parameter.Value, dateTime,null);
                    }
                }

            }
        }
    }
}
Tobias
źródło
To nie działa. dd-MM-yyyy nadal nie jest rozpoznawane
jao
1
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
    var str = controllerContext.HttpContext.Request.QueryString[bindingContext.ModelName];
    if (string.IsNullOrEmpty(str)) return null;
    var date = DateTime.ParseExact(str, "dd.MM.yyyy", null);
    return date;
}
Teth
źródło
1
Powinieneś wzbogacić swoją odpowiedź, dodając kilka wyjaśnień.
Alexandre Lavoie
Z pamięci nie jest to spójne ze sposobem działania wbudowanych segregatorów modelu, więc może nie mieć tego samego wtórnego zachowania, takiego jak zachowanie wpisanej wartości do weryfikacji.
Sam,
1

Ustawiłem CurrentCulturei CurrentUICulturemój niestandardowy kontroler podstawowy

    protected override void Initialize(RequestContext requestContext)
    {
        base.Initialize(requestContext);

        Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("en-GB");
        Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("en-GB");
    }
Korayem
źródło