۱۳۹۱/۰۲/۰۶

ASP.NET MVC #22


تهيه سايت‌هاي چند زبانه و بومي سازي نمايش اطلاعات در ASP.NET MVC

زمانيكه دات نت فريم ورك نياز به انجام اعمال حساس به مسايل بومي را داشته باشد،‌ ابتدا به مقادير تنظيم شده دو خاصيت زير دقت مي‌كند:
الف) System.Threading.Thread.CurrentThread.CurrentCulture
بر اين اساس دات نت مي‌تواند تشخيص دهد كه براي مثال خروجي متد DateTime.Now.ToString در كانادا و آمريكا بايد با هم تفاوت داشته باشند. مثلا در آمريكا ابتدا ماه، سپس روز و در آخر سال نمايش داده مي‌شود و در كانادا ابتدا سال، بعد ماه و در آخر روز نمايش داده خواهد شد. يا نمونه‌ي ديگري از اين دست مي‌تواند نحوه نمايش علامت واحد پولي كشورها باشد.
ب) System.Threading.Thread.CurrentThread.CurrentUICulture
مقدار CurrentUICulture بر روي بارگذاري فايل‌هاي مخصوصي به نام Resource، تاثير گذار است.

اين خواص را يا به صورت دستي مي‌توان تنظيم كرد و يا ASP.NET، اين اطلاعات را از هدر Accept-Language دريافتي از مرورگر كاربر به صورت خودكار مقدار دهي مي‌كند. البته براي اين منظور نياز است يك سطر زير را به فايل وب كانفيگ برنامه اضافه كرد:

<system.web>
    <globalization culture="auto" uiCulture="auto" />

يا اگر نياز باشد تا برنامه را ملزم به نمايش اطلاعات Resource مرتبط با فرهنگ بومي خاصي كرد نيز مي‌توان در همين قسمت مقادير culture و uiCulture را دستي تنظيم نمود و يا اگر همانند برنامه‌هايي كه چند لينك را بالاي صفحه نمايش مي‌دهند كه براي مثال به نگارش‌هاي فارسي/عربي/انگليسي اشاره مي‌كند، اينكار را با كد نويسي نيز مي‌توان انجام داد:

System.Threading.Thread.CurrentThread.CurrentCulture =
                                System.Globalization.CultureInfo.CreateSpecificCulture("fa");


جهت آزمايش اين مطلب، ابتدا تنظيم globalization فوق را به فايل وب كانفيگ برنامه اضافه كنيد. سپس به مسير زير در IE مراجعه كنيد:

IE -> Tools -> Internet options -> General tab -> Languages

در اينجا مي‌توان هدر Accept-Language را مقدار دهي كرد. براي نمونه اگر مقدار زبان پيش فرض را به فرانسه تنظيم كنيم (به عنوان اولين زبان تعريف شده در ليست) و سپس سعي در نمايش مقدار decimal زير را داشته باشيم:

string.Format("{0:C}", 10.5M)

اگر زبان پيش فرض، انگليسي آمريكايي باشد، $ نمايش داده خواهد شد و اگر زبان به فرانسه تنظيم شود، يورو در كنار عدد مبلغ نمايش داده مي‌شود.
تا اينجا تنها با تنظيم culture=auto به اين نتيجه رسيده‌ايم. اما ساير قسمت‌هاي صفحه چطور؟ براي مثال برچسب‌هاي نمايش داده شده را چگونه مي‌توان به صورت خودكار بر اساس Accept-Language مرجح كاربر تنظيم كرد؟ خوشبختانه در دات نت، زير ساخت مديريت برنامه‌هاي چند زبانه به صورت توكار وجود دارد كه در ادامه به بررسي آن خواهيم پرداخت.


آشنايي با ساختار فايل‌هاي Resource


فايل‌هاي Resource يا منبع، در حقيقت فايل‌هايي هستند مبتني بر XML با پسوند resx و هدف آن‌ها ذخيره سازي رشته‌هاي متناظر با فرهنگ‌هاي مختلف مي‌باشد و براي استفاده از آن‌ها حداقل يك فايل منبع پيش فرض بايد تعريف شود. براي نمونه فايل mydata.resx را در نظر بگيريد. براي ايجاد فايل منبع اسپانيايي متناظر، بايد فايلي را به نام mydata.es.resx توليد كرد. البته نوع فرهنگ مورد استفاده را كاملتر نيز مي‌توان ذكر كرد براي مثال mydata.es-mex.resx جهت فرهنگ اسپانيايي مكزيكي بكارگرفته خواهد شد، يا mydata.fr-ca.resx به فرانسوي كانادايي اشاره مي‌كند. سپس مديريت منابع دات نت فريم ورك بر اساس مقدار CurrentUICulture جاري، اطلاعات فايل متناظري را بارگذاري خواهد كرد. اگر فايل متناظري وجود نداشت، از اطلاعات همان فايل پيش فرض استفاده مي‌گردد.
حين تهيه برنامه‌ها نيازي نيست تا مستقيما با فايل‌هاي XML منابع كار كرد. زمانيكه اولين فايل منبع توليد مي‌شود، به همراه آن يك فايل cs يا vb نيز ايجاد خواهد شد كه امكان دسترسي به كليدهاي تعريف شده در فايل‌هاي XML را به صورت strongly typed ميسر مي‌كند. اين فايل‌هاي خودكار، تنها براي فايل پيش فرض mydata.resx توليد مي‌شوند،‌از اين جهت كه تعاريف اطلاعات ساير فرهنگ‌هاي متناظر نيز بايد با همان كليدهاي فايل پيش فرض آغاز شوند. تنها «مقادير» كليدهاي تعريف شده در كلاس‌هاي منبع متفاوت هستند.
اگر به خواص فايل‌هاي resx در VS.NET دقت كنيم، نوع Build action آن‌ها به embedded resource تنظيم شده است.


مثالي جهت بررسي استفاده از فايل‌هاي Resource

يك پروژه جديد خالي ASP.NET MVC را آغاز كنيد. فايل وب كانفيگ آن‌را ويرايش كرده و تنظيمات globalization ابتداي بحث را به آن اضافه كنيد. سپس مدل، كنترلر و View متناظر با متد Index آن‌را با محتواي زير به پروژه اضافه نمائيد:

namespace MvcApplication19.Models
{
    public class Employee
    {
        public int Id { set; get; }
        public string Name { set; get; }
    }
}

using System.Web.Mvc;
using MvcApplication19.Models;

namespace MvcApplication19.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            var employee = new Employee { Name = "Name 1" };
            return View(employee);
        }
    }
}

@model MvcApplication19.Models.Employee
@{
    ViewBag.Title = "Index";
}
<h2>
    Index</h2>
<fieldset>
    <legend>Employee</legend>
    <div class="display-label">
        Name
    </div>
    <div class="display-field">
        @Html.DisplayFor(model => model.Name)
    </div>
</fieldset>
<fieldset>
    <legend>Employee Info</legend>
    @Html.DisplayForModel()
</fieldset>

قصد داريم در View فوق بر اساس uiCulture كاربر مراجعه كننده به سايت، برچسب Name را مقدار دهي كنيم. اگر كاربري از ايران مراجعه كند، «نام كارمند» نمايش داده شود و ساير كاربران، «Employee Name» را مشاهده كنند. همچنين اين تغييرات بايد بر روي متد Html.DisplayForModel نيز تاثيرگذار باشد.
براي اين منظور بر روي پوشه Views/Home كه محل قرارگيري فايل Index.cshtml فوق است كليك راست كرده و گزينه Add|New Item را انتخاب كنيد. سپس در صفحه ظاهر شده، گزينه «Resources file» را انتخاب كرده و براي مثال نام Index_cshtml.resx را وارد كنيد.
به اين ترتيب اولين فايل منبع مرتبط با View جاري كه فايل پيش فرض نيز مي‌باشد ايجاد خواهد شد. اين فايل، به همراه فايل Index_cshtml.Designer.cs توليد مي‌شود. سپس همين مراحل را طي كنيد، اما اينبار نام Index_cshtml.fa.resx را حين افزودن فايل منبع وارد نمائيد كه براي تعريف اطلاعات بومي ايران مورد استفاده قرار خواهد گرفت. فايل دومي كه اضافه شده است، فاقد فايل cs همراه مي‌باشد.
اكنون فايل Index_cshtml.resx را در VS.NET باز كنيد. از بالاي صفحه، به كمك گزينه Access modifier، سطح دسترسي متدهاي فايل cs همراه آن‌را به public تغيير دهيد. پيش فرض آن internal است كه براي كار ما مفيد نيست. از اين جهت كه امكان دسترسي به متدهاي استاتيك تعريف شده در فايل خودكار Index_cshtml.Designer.cs را در View هاي برنامه، نخواهيم داشت. سپس دو جفت «نام-مقدار» را در فايل resx وارد كنيد. مثلا نام را Name و مقدار آن‌را «Employee Name» و سپس نام ديگر را NameIsNotRight و مقدار آن‌را «Name is required» وارد نمائيد.
در ادامه فايل Index_cshtml.fa.resx را باز كنيد. در اينجا نيز دو جفت «نام-مقدار» متناظر با فايل پيش فرض منبع را بايد وارد كرد. كليدها يا نام‌ها يكي است اما قسمت مقدار اينبار بايد فارسي وارد شود. مثلا نام را Name و مقدار آن‌را «نام كارمند» وارد نمائيد. سپس كليد يا نام NameIsNotRight و مقدار «لطفا نام را وارد نمائيد» را تنظيم نمائيد.
تا اينجا كار تهيه فايل‌هاي منبع متناظر با View جاري به پايان مي‌رسد.
در ادامه با كمك فايل Index_cshtml.Designer.cs كه هربار پس از تغيير فايل resx متناظر آن به صورت خودكار توسط VS.NET توليد و به روز مي‌شود، مي‌توان به كليدها يا نام‌هايي كه تعريف كرده‌ايم، در قسمت‌هاي مختلف برنامه دست يافت. براي نمونه تعريف كليد Name در اين فايل به نحو زير است:

namespace MvcApplication19.Views.Home {
    public class Index_cshtml {
        public static string Name {
            get {
                return ResourceManager.GetString("Name", resourceCulture);
            }
        }
    }
}

بنابراين براي استفاده از آن در هر View ايي تنها كافي است بنويسيم:

@MvcApplication19.Views.Home.Index_cshtml.Name

به اين ترتيب بر اساس تنظيمات محلي كاربر، اطلاعات به صورت خودكار از فايل‌هاي Index_cshtml.fa.resx فارسي يا فايل پيش فرض Index_cshtml.resx، دريافت مي‌گردد.
علاوه بر امكان دسترسي مستقيم به كليدهاي تعريف شده در فايل‌هاي منبع، امكان استفاده از آن‌ها توسط data annotations نيز ميسر است. در اين حالت مي‌توان مثلا پيغام‌هاي اعتبار سنجي را بومي كرد يا حين استفاده از متد Html.DisplayForModel، بر روي برچسب نمايش داده شده خودكار، تاثير گذار بود. براي اينكار بايد اندكي مدل برنامه را ويرايش كرد:

using System.ComponentModel.DataAnnotations;

namespace MvcApplication19.Models
{
    public class Employee
    {
        [ScaffoldColumn(false)]
        public int Id { set; get; }

        [Display(ResourceType = typeof(MvcApplication19.Views.Home.Index_cshtml),
                 Name = "Name")]
        [Required(ErrorMessageResourceType = typeof(MvcApplication19.Views.Home.Index_cshtml),
                  ErrorMessageResourceName = "NameIsNotRight")]
        public string Name { set; get; }
    }
}

همانطور كه ملاحظه مي‌كنيد، حين تعريف ويژگي‌هاي Display يا Required، امكان تعريف نام كلاس متناظر با فايل resx خاصي وجود دارد. به علاوه ErrorMessageResourceName به نام يك كليد در اين فايل و يا پارامتر Name ويژگي Display نيز به نام كليدي در فايل منبع مشخص شده، اشاره مي‌كنند. اين اطلاعات توسط متدهاي Html.DisplayForModel، Html.ValidationMessageFor، Html.LabelFor و امثال آن به صورت خودكار مورد استفاده قرار خواهند گرفت.


نكته‌اي در مورد كش كردن اطلاعات
در اين مثال اگر فيلتر OutputCache را بر روي متد Index تعريف كنيم، حتما نياز است به هدر Accept-Language نيز دقت داشت. در غيراينصورت تمام كاربران، صرفنظر از تنظيمات بومي آن‌ها، يك صفحه را مشاهده خواهند كرد:

[OutputCache(Duration = 60, VaryByHeader = "Accept-Language")]
public ActionResult Index()