آی تی نرد

اشتراک اطلاعات و تجربیات در زمینه ی توسعه ی دات نت و البته شیرپوینت

تاریخ و زمان شمسی در MVC بدون نیاز به استفاده از تمپلیت

shamsi datetime

حل مشکل تاریخ و زمان شمسی در اکثر پروژه های نرم افزاری که کار میکنیم یک کار مازاد به شمار میاد که باید در گوشه ای از ذهنمون در نظر داشته باشیم و معمولا هم در انتهای پروژه نسبت به رفع اون اقدام میکنیم. البته هر کسی روشی داره و بالاخره این مشکل رو حل میکنه. خود من یادم هست که حدود 8 الی 9 سال پیش قسمت های سال و ماه و روز تاریخ شمسی رو به صورت عددی کنار هم میچسبوندم و توی جدول نگهداری میکردم که البته کار اشتباهی بود.

بهترین روش این هست که هر مقداری معادل کاربردش در سیستم و با نوع متناسب نگهداری بشه و مقادیر در زمان و لایه ی نمایش تغییر کنند. به هر حال مثلا وقتی میگیم تاریخ تولد، نوعش هم باید تاریخ باشه.

توی پروژه های MVC معمولا برای حل این مسئله یک تمپلیت برای نمایش و یکی هم برای ویرایش(که در نهایت مقدار به سرور ارسال میشود) استفاده می کنیم که در تمپلیت نمایش(DisplayTemplate) تاریخ رو تبدیل میکنیم و بعد نشون میدیم و در ویرایش(EditorTemplate) هم به همین صورت اما فقط مقدار میلادی رو توی یک فیلد مخفی نگهداری میکنیم و بعد در زمان Submit اون مقدار رو ارسال میکنیم. خود این کارها واقعا وقت گیر، حوصله بر، دست و پاگیر و اضافی هست، البته در نهایت برای انتخاب تاریخ مجبوریم از یک DatePicker در EditorTemplate استفاده کنیم ولی همانطور که در ادامه میخونید دیگر نگرانی تبدیل اون از شمسی به میلادی رو در سمت کلاینت رو نخواهیم داشت.

به نظر من بهترین کار این هست که تمامی این تبدیل ها رو به قسمتی از سیستم بدیم و بزاریم خودش تشخیص بده و مدیریت کنه و ما هم این مشغله رو از ذهنمون دور بندازیم.

توی یکی از مطالبم مقدمه ای در مورد کالچر در Asp.net Web form و سیستم Blogengine نوشتم که از همون استراتژی هم میشه توی MVC استفاده کرد به هر حال هر دو زیرساخت Asp.net رو دارند اما فقط نحوه ی پیاده سازی برخی ساختارهای زیرین در دو تکنولوژی متفاوت هست.

در MVC برای حل مشکل نمایش تاریخ شمسی از همین روش اصلاح کالچر و برای حل مشکل اصلاح تاریخ در زمان ارسال به سرور از Model Binder استفاده خواهیم کرد. پروژه ی نمونه در انتهای این مطلب هست و میتونید دانلود کنید.

حل مشکل نمایش:

ابتدا برای حل مشکل نمایش یک Custom Action Filter به نام DateTimeActionFilter ایجاد و متد OnActionExecuted رو Override میکنیم و بررسی میکنیم در صورتی که کالچر جاری فارسی بود کالچر اصلاح شده ی تاریخ را در آن قرار میدهیم.

    public class DateTimeActionFilter : ActionFilterAttribute
    {
        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            base.OnActionExecuted(filterContext);
            //if (System.Threading.Thread.CurrentThread.CurrentCulture.LCID == 1065)
            System.Threading.Thread.CurrentThread.CurrentCulture = System.Threading.Thread.CurrentThread.CurrentUICulture = new PersianCulture();
        }
    }

در کد بالا کلاس PersianCulture همان کلاسی هست که از کالچر مشتق شده و تاریخ رو اصلاح میکنه(در پروژه ی نمونه موجود هست). با اصلاح کالچر دیگر تمام تاریخ های پروژه در زمان نمایش بدون نیاز به کار اضافی دیگری شمسی نشان داده خواهد شد.

حل مشکل ویرایش و ارسال به سرور:

زمانی که در حال ویرایش یا ورود داده ی تاریخ هستید با توجه به شمسی بودن مقدار در صورت ذخیره سازی و ارسال مقادیر به سرور تاریخ همچنان به صورت شمسی ارسال خواهد شد و دیگر اعتبار یک کلاس DateTime صحیح را ندارد و در نتیجه مقدار تاریخ اشتباه ذخیره سازی خواهد شد.

به همین دلیل می بایست مقدار تاریخ شمسی را قبل از رسیدن به کنترلر مجددا به میلادی و تاریخ درست تبدیل کنیم. بدین منظور از یک Model Binder برای مقادیر نوع DateTime استفاده خواهیم کرد و پس از بررسی شمسی بودن، مقدار به میلادی برگردانده خواهد شد.

    public class DateTimeBinder : IModelBinder
    {
        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

            // ToDo: check if shamsi then do the conversion.
            var date = (DateTime)value.ConvertTo(typeof(DateTime), CultureInfo.CurrentCulture);
            date = PersianCulture.PersianToGregorianUS(date);

            return date;
        }
    }

در نتیجه مقدار تاریخ شمسی مجددا میلادی خواهد شد و سپس در اختیار کنترلر قرار خواهد گرفت.

هر دو کلاس پس از پیاده سازی می بایست در Global.asax و در رویداد Application_Start رجیستر شوند:

        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            RouteConfig.RegisterRoutes(RouteTable.Routes);

            GlobalFilters.Filters.Add(new DateTimeActionFilter());
            ModelBinders.Binders.Add(typeof(DateTime), new DateTimeBinder());
            ModelBinders.Binders.Add(typeof(DateTime?), new NullableDateTimeBinder());
        }

در کد بالا برای تاریخ نوع نول هم Binder رجیستر شده است که در پروژه ی نمونه تمام این موارد موجود می باشد.

در نهایت بدون نیاز به هیچ کد اضافه ی دیگری و بدون درگیر کردن ذهن میتوان با همان تاریخ میلادی در تمام سیستم کار و مدیریت نمایش شمسی بودن تاریخ را به این کلاسها واگذار کرد.

البته ناگفته نماند که من با همین روش سیستم اوپن سورس MVCForum رو هم شمسی کردم البته در اون فقط بحث نمایش تاریخ شمسی مطرح بود.

امیدوارم که دیگه مشکل تاریخ شمسی به صورت کامل با توجه به این مطلب و مطلب قبلی که در مورد شمسی کردن بلاگ انجین بود در پروژه های Asp.net حل شده باشه.

Sample project download, pass: itnerd.ir
ITNerd_MVCPersianDateTime.rar (1.26 mb)

نظرات (8) -

  • حسین

    11/10/1393 01:20:10 ق.ظ | پاسخ به این نظر

    سلام ، فقط نمیدونم چرا زمان اجرا این خطا رو دارم
    The given filter instance must implement one or more of the following filter interfaces: System.Web.Mvc.IAuthorizationFilter, System.Web.Mvc.IActionFilter, System.Web.Mvc.IResultFilter, System.Web.Mvc.IExceptionFilter, System.Web.Mvc.Filters.IAuthenticationFilter.

  • mehdi

    1/28/1394 01:16:43 ب.ظ | پاسخ به این نظر

    خیلی ممنون... بسیار کاربردی و مفید بود.
    اگر نخواهیم در کل پروژه استفاده بشه و فقط واسه یه اکشن خاصی استفاده بشه باید ازش چه طور استفاده کنیم؟

  • علی

    2/26/1394 10:59:54 ق.ظ | پاسخ به این نظر

    سلام، ممنون از روشی که آموزش دادین، فقط یه اشکالی داره، اون هم توی تبدیل تاریخ های مثل 31 اردیبهشت نود و چهار، که نمیتونه تبدیلش کنه و خطای
    String was not recognized as a valid datetime  رو میده
    اگه براتون ممکنه راهنمایی کنید لطفا

  • رضا

    4/23/1394 02:33:07 ب.ظ | پاسخ به این نظر

    با تشکر از زحمات شما.
    برای نمایش فقط بخش تاریخ در هنگام Dispaly  و Edit  چکار باید کرد ؟
    ممنون.

  • نا به دلایلی(نامعلوم) CultureInfo.CurrentCulture بجای fa-IR همون en-US هستش.
    میتونید تا جناب جلالی مشکل رو برطرف کنن از  new PersianCulture() استفاده کنید هر جایی که نیاز بود.

  • مسعود باقری

    2/9/1395 12:41:41 ب.ظ | پاسخ به این نظر

    سلام زمان اجرا این error رو می ده
    Specified time is not supported in this calendar. It should be between 03/22/0622 00:00:00 (Gregorian date) and 12/31/9999 23:59:59 (Gregorian date), inclusive.
    Parameter name: time

  • سید محمد هاشمی

    6/20/1395 12:50:15 ب.ظ | پاسخ به این نظر

    سلام.
    با تاریخ 31  هر ماه مشکل داره. اگر کسی میدونه مشکلش چیه راهنمایی کنه لطفا

Loading