การเชื่อม MVC DateTime ด้วยรูปแบบวันที่ไม่ถูกต้อง


132

ขณะนี้ Asp.net-MVC อนุญาตให้มีการเชื่อมโยงวัตถุ DateTime โดยนัย ฉันมีการดำเนินการตามแนวของ

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

สิ่งนี้แปลงสตริงจากการโทร ajax เป็น DateTime ได้สำเร็จ อย่างไรก็ตามเราใช้รูปแบบวันที่ dd / MM / yyyy; MVC กำลังแปลงเป็น MM / dd / yyyy ตัวอย่างเช่นการส่งคำเรียกร้องให้ดำเนินการด้วยสตริง '09 / 02/2009 'จะส่งผลให้ DateTime เป็น '02 / 09/2009 00:00:00' หรือวันที่ 2 กันยายนในการตั้งค่าท้องถิ่นของเรา

ฉันไม่ต้องการม้วนตัวยึดแบบจำลองของฉันเองเพราะรูปแบบวันที่ แต่ดูเหมือนว่าไม่จำเป็นที่จะต้องเปลี่ยนการดำเนินการเพื่อยอมรับสตริงจากนั้นใช้ DateTimeParse ว่า MVC สามารถทำสิ่งนี้ให้ฉันได้หรือไม่

มีวิธีใดในการเปลี่ยนรูปแบบวันที่ที่ใช้ในตัวยึดแบบจำลองเริ่มต้นสำหรับ DateTime หรือไม่ ตัวประสานรุ่นเริ่มต้นไม่ควรใช้การตั้งค่าการแปลของคุณต่อไปหรือไม่?


เฮ้ .. แค่เปลี่ยนรูปแบบวันที่ของระบบ - DD / MM / yyyy เป็น MM / DD / yyyy แล้วก็ทำสำเร็จ .. ฉันก็มีปัญหาเหมือนกันฉันแก้ไขได้โดยเปลี่ยนรูปแบบวันที่ของระบบ
banny

@banny หากแอปพลิเคชันเป็นสากลและอาจมีรูปแบบวันที่ไม่เหมือนกันคุณจะทำอย่างไร คุณไม่ควรไปเปลี่ยนทุกวันเวลารูปแบบ ..
Ravi Mehta

ไม่มีคำตอบใดที่ช่วยฉันได้ แบบฟอร์มต้องมีการแปล ผู้ใช้บางคนอาจมีวันที่ทางเดียวหรืออีกทางหนึ่ง การตั้งค่าบางอย่างใน web.config หรือ global.asax จะไม่ช่วย ฉันจะค้นหาคำตอบที่ดีกว่านี้ไปเรื่อย ๆ แต่วิธีหนึ่งก็คือจัดการกับวันที่เป็นสตริงจนกว่าฉันจะกลับไปที่ c #
เกิด

คำตอบ:


164

ฉันเพิ่งพบคำตอบของสิ่งนี้ด้วยการใช้ Google ที่ละเอียดถี่ถ้วนมากขึ้น:

Melvyn Harbour มีคำอธิบายอย่างละเอียดว่าเหตุใด MVC จึงทำงานกับวันที่ในแบบที่เป็นอยู่และคุณจะลบล้างสิ่งนี้ได้อย่างไรหากจำเป็น:

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

เมื่อมองหาค่าที่จะแยกวิเคราะห์เฟรมเวิร์กจะดูตามลำดับเฉพาะ ได้แก่ :

  1. RouteData (ไม่แสดงด้านบน)
  2. สตริงการสืบค้น URI
  3. แบบฟอร์มคำขอ

อย่างไรก็ตามมีเพียงสิ่งสุดท้ายเท่านั้นที่จะได้รับรู้ถึงวัฒนธรรม มีเหตุผลที่ดีมากสำหรับเรื่องนี้จากมุมมองของการแปลเป็นภาษาท้องถิ่น ลองนึกภาพว่าฉันเขียนเว็บแอปพลิเคชันที่แสดงข้อมูลเที่ยวบินของสายการบินที่ฉันเผยแพร่ทางออนไลน์ ฉันค้นหาเที่ยวบินในวันใดวันหนึ่งโดยคลิกที่ลิงค์ของวันนั้น (อาจจะเป็นhttp://www.melsflighttimes.com/Flights/2008-11-21 ) จากนั้นต้องการส่งลิงก์นั้นไปยังเพื่อนร่วมงานของฉันทางอีเมลใน สหรัฐอเมริกา. วิธีเดียวที่เราสามารถรับประกันได้ว่าเราทั้งคู่จะดูข้อมูลในหน้าเดียวกันคือถ้าใช้ InvariantCulture ในทางตรงกันข้ามหากฉันใช้แบบฟอร์มเพื่อจองเที่ยวบินทุกอย่างจะเกิดขึ้นในวงจรที่รัดกุม ข้อมูลสามารถเคารพ CurrentCulture เมื่อถูกเขียนลงในแบบฟอร์มดังนั้นจึงจำเป็นต้องเคารพข้อมูลเมื่อกลับมาจากแบบฟอร์ม


จะทำ. ฟังก์ชันดังกล่าวถูกปิดใช้งานเป็นเวลา 48 ชั่วโมงหลังจากโพสต์คำถาม
Sam Wessel

45
ฉันไม่เห็นด้วยอย่างยิ่งว่าในทางเทคนิคถูกต้อง ตัวยึดแบบจำลองควรทำงานเหมือนกันเสมอกับ POST และ GET หากวัฒนธรรมที่ไม่แปรเปลี่ยนเป็นวิธีที่จะทำให้ GET ทำโพสต์ด้วย การเปลี่ยนแปลงพฤติกรรมขึ้นอยู่กับคำกริยา http ไม่ใช่ความรู้สึก
Bart Calixto

ฉันมีคำถามเว็บไซต์ของเราโฮสต์ในประเทศอื่นต้องการMM/dd/yyyyรูปแบบอื่นที่แสดงข้อผิดพลาดในการตรวจสอบความถูกต้องThe field BeginDate must be a date.ฉันจะสร้างเซิร์ฟเวอร์เพื่อยอมรับdd/MM/yyyyรูปแบบได้อย่างไร
shaijut

พารามิเตอร์ URL ควรไม่คลุมเครือเช่นการใช้การจัดรูปแบบมาตรฐาน ISO จากนั้นการตั้งค่าวัฒนธรรมจะไม่สำคัญ
nforss

สิ่งนี้ให้ลิงค์อธิบายสาเหตุ แต่ไม่ได้ให้วิธีแก้ปัญหาที่ง่ายที่สุดสำหรับปัญหานี้ (เช่นโดยการโพสต์สตริงวันที่และเวลา ISO จากสคริปต์) วิธีนี้ใช้ได้ผลเสมอและเราไม่ต้องสนใจเกี่ยวกับการตั้งค่าวัฒนธรรมเฉพาะบน เซิร์ฟเวอร์หรือตรวจสอบให้แน่ใจว่ารูปแบบวันที่และเวลาเหมือนกันระหว่างเซิร์ฟเวอร์และไคลเอนต์
Hopeless

37

ฉันจะกำหนดวัฒนธรรมของคุณไปทั่วโลก ModelBinder หยิบมันขึ้นมา!

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

หรือคุณเพียงแค่เปลี่ยนสิ่งนี้สำหรับหน้านี้
แต่ทั่วโลกใน web.config ฉันคิดว่าดีกว่า


27
ไม่ได้สำหรับฉัน วันที่ยังคงเป็นโมฆะถ้าฉันผ่าน 23/10/2010
GONeale

ฉันคิดว่ามันเป็นทางออกที่ง่ายที่สุด สำหรับฉันมันเปลี่ยนรูปแบบใน Date ToString () ทั้งหมด ฉันคิดว่ามันจะใช้ได้กับการผูกด้วย แต่ไม่ได้ตรวจสอบขออภัย :-(
msa.im

1
ฉันตั้งค่ารูปแบบวันที่ของเซิร์ฟเวอร์ dev เป็น dd / MM / yyyy modelbinder ใช้รูปแบบ MM / dd / yyyy การตั้งค่ารูปแบบใน web.config เป็น dd / MM / yyyy ตอนนี้บังคับให้ modelbinder ใช้รูปแบบยุโรป ในความคิดของฉันควรใช้การตั้งค่าวันที่ของเซิร์ฟเวอร์ อย่างไรก็ตามคุณแก้ปัญหาของฉัน
Karel

มันทำงานได้อย่างสมบูรณ์แบบสำหรับฉัน ... อย่างไรก็ตามมันรู้สึกแปลก ๆ ตั้งแต่ฉันรู้ว่าแอปพลิเคชันเซิร์ฟเวอร์ของฉันอยู่ในสหราชอาณาจักรที่ใช้ระบบปฏิบัติการในสหราชอาณาจักร ... : /
Tallmaris

ทำงานได้อย่างสมบูรณ์แบบใน MVC4 ที่ทำงานบนเว็บไซต์ Azure
Matty Bear

31

ฉันประสบปัญหาเดียวกันกับรูปแบบวันที่แบบสั้นที่เชื่อมโยงกับคุณสมบัติของโมเดล DateTime หลังจากดูตัวอย่างต่างๆมากมาย (ไม่เพียง แต่เกี่ยวกับ DateTime) ฉันจึงรวบรวมสิ่งต่อไปนี้:

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

เพื่อให้สอดคล้องกับวิธีการกำหนดเส้นทาง ฯลฯ ในไฟล์ Global ASAX ฉันยังได้เพิ่มคลาส sytatic ใหม่ในโฟลเดอร์ App_Start ของโปรเจ็กต์ MVC4 ของฉันชื่อ 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());
        }
    }
}

จากนั้นฉันก็โทรไปที่ RegisterCustomModelBinders แบบคงที่จาก Global ASASX Application_Start ของฉันเช่นนี้:

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

    CustomModelBindersConfig.RegisterCustomModelBinders();
}

หมายเหตุสำคัญที่นี่คือถ้าคุณเขียนค่า DateTime ลงใน hiddenfield เช่นนี้:

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

ฉันทำอย่างนั้นและค่าจริงบนหน้าอยู่ในรูปแบบ "MM / dd / yyyy hh: mm: ss tt" แทนที่จะเป็น "dd / MM / yyyy hh: mm: ss tt" อย่างที่ฉันต้องการ สิ่งนี้ทำให้การตรวจสอบโมเดลของฉันล้มเหลวหรือส่งคืนวันที่ผิด (เห็นได้ชัดว่าการสลับค่าวันและเดือนรอบ ๆ )

หลังจากเกาหัวหลายครั้งและพยายามล้มเหลววิธีแก้ปัญหาคือตั้งค่าข้อมูลวัฒนธรรมสำหรับทุกคำขอโดยทำใน 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;
}

จะไม่ทำงานถ้าคุณติดไว้ใน Application_Start หรือแม้แต่ Session_Start เนื่องจากกำหนดให้กับเธรดปัจจุบันสำหรับเซสชัน อย่างที่คุณทราบกันดีว่าเว็บแอปพลิเคชันไม่มีสถานะดังนั้นเธรดที่ให้บริการคำขอของคุณก่อนหน้านี้จึงไม่ใช่เธรดเดียวกันที่ให้บริการคำขอปัจจุบันของคุณดังนั้นข้อมูลวัฒนธรรมของคุณจึงไปสู่ ​​GC ที่ยอดเยี่ยมในโลกดิจิทัล

ขอบคุณไปที่: Ivan Zlatev - http://ivanz.com/2010/11/03/custom-model-binding-using-imodelbinder-in-asp-net-mvc-two-gotchas/

การิก - https://stackoverflow.com/a/2468447/578208

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


13

มันจะแตกต่างกันเล็กน้อยใน MVC 3

สมมติว่าเรามีคอนโทรลเลอร์และมุมมองด้วย Get method

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

เราควรเพิ่ม 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
}

และคำสั่งใน Application_Start () ของ Global.asax

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

นี่เป็นจุดเริ่มต้นที่ดี แต่ไม่ได้นำไปใช้อย่างถูกต้องIModelBinderโดยเฉพาะอย่างยิ่งเกี่ยวกับการตรวจสอบความถูกต้อง นอกจากนี้ก็จะทำงานเฉพาะถ้าชื่อของที่DateTimeเป็นdateTime
แซม

2
นอกจากนี้ผมได้พบว่าDateTime?การทำงานเท่านั้นถ้าคุณเพิ่มสายอื่นไปด้วยModelBinders.Binders.Add typeof(DateTime?)
แซม

8

นอกจากนี้ยังเป็นที่น่าสังเกตว่าแม้จะไม่ได้สร้างตัวยึดแบบจำลองของคุณเองหลายรูปแบบที่แตกต่างกัน

ตัวอย่างเช่นในสหรัฐอเมริกาสตริงต่อไปนี้ทั้งหมดจะเทียบเท่าและถูกผูกไว้กับค่า DateTime เดียวกันโดยอัตโนมัติ:

/ บริษัท / ข่าว / พฤษภาคม% 2544% 202008

/ company / press / 2008-05-01

/ บริษัท / กด / 05-01-2008

ฉันขอแนะนำอย่างยิ่งให้ใช้ yyyy-mm-dd เนื่องจากพกพาได้สะดวกกว่ามาก คุณไม่ต้องการจัดการกับรูปแบบที่แปลหลายภาษา หากมีคนจองเที่ยวบินในวันที่ 1 พฤษภาคมแทนที่จะเป็นวันที่ 5 มกราคมคุณจะมีปัญหาใหญ่!

หมายเหตุ: ฉันไม่ชัดเจนว่าอาการแย่ลงถ้า yyyy-mm-dd ถูกแยกวิเคราะห์ในทุกวัฒนธรรมดังนั้นบางทีคนที่รู้สามารถเพิ่มความคิดเห็นได้


3
เนื่องจากไม่มีใครบอกว่า yyyy-MM-dd ไม่เป็นสากลฉันคิดว่ามันเป็นอย่างนั้น
กวางเชา

นี่เป็นความคิดของฉันดีกว่าการใช้ตัวยึดแบบจำลอง .datepicker ("option", "dateFormat", "yy-mm-dd") หรือเพียงแค่ตั้งค่าเป็นค่าเริ่มต้น
Bart Calixto

yyyy-MM-dd คือรูปแบบวันที่ "สากล" (ISO 8601) ยังเรียงลำดับตามตัวอักษรได้เป็นอย่างดี
Nathan Goings

6

ลองใช้ toISOString () ส่งคืนสตริงในรูปแบบ ISO8601

รับวิธีการ

จาวาสคริปต์

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

ค#

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

วิธีการโพสต์

จาวาสคริปต์

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

ค#

[HttpPost]
public JsonResult Do(DateTime date)
{
     return Json(date.ToString());
}

5

ฉันตั้งค่าการกำหนดค่าด้านล่างบน MVC4 ของฉันและทำงานได้อย่างมีเสน่ห์

<globalization uiCulture="auto" culture="auto" />

3
สิ่งนี้ได้ผลสำหรับฉันเช่นกัน โปรดทราบว่าองค์ประกอบโลกาภิวัตน์อยู่ภายใต้การกำหนดค่า> 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);
                    }
                }

            }
        }
    }
}

วิธีนี้ใช้ไม่ได้ ยังไม่รู้จัก dd-MM-yyyy
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;
}

1
คุณควรทำให้คำตอบของคุณสมบูรณ์ยิ่งขึ้นโดยการเพิ่มคำอธิบาย
Alexandre Lavoie

จากหน่วยความจำสิ่งนี้ไม่สอดคล้องกับวิธีการทำงานของตัวประสานโมเดลในตัวดังนั้นจึงอาจไม่มีลักษณะการทำงานรองเหมือนเดิมเช่นการรักษาค่าที่พิมพ์ไว้เพื่อการตรวจสอบความถูกต้อง
แซม

1

ฉันตั้งค่าCurrentCultureและCurrentUICultureตัวควบคุมฐานที่กำหนดเองของฉัน

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

        Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("en-GB");
        Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("en-GB");
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.