﻿# ASP.NET MVC 開發心得分享 (15)：全球化和當地語系化

之前幾篇文章有講過 ASP.NET MVC 事實上共用許多 ASP.NET 原本就有的東西，這當然連 [ASP.NET 全球化和當地語系化](http://msdn.microsoft.com/zh-tw/library/c6zyy3s9.aspx)的支援也包含在內，在建置多國語言網站時我們通常會利用 **App\_GlobalResources** 儲存**全域資源檔**，與頁面有關的**本地資源檔**會放在每一個 \*.aspx 所在目錄下的 **App\_LocalResources** 目錄下，這樣的架構在 ASP.NET MVC 之中也一樣可以利用，只是需要一些小技巧才能方便在 View 中使用，今天會分享一個 HtmlHelper 幫助我們在 View 中取用資源檔的資料。

首先我要感謝 [Matt Hawley](https://web.archive.org/web/20100316195648/http://blog.eworldui.net:80/) 寫了一個超好用的 HtmlHelper 幫助大家在 ASP.NET MVC 中讀取資源檔，我們可以來看看程式碼，一共分成三個部分：

1\. 自訂 WebFormViewEngine 與 WebFormView 讓在 View 中可以取得 View 的實際所在路徑

**LocalizationWebFormView.cs**

```csharp
using System.IO;
using System.Web.Mvc;

namespace MvcApplication
{
    public class LocalizationWebFormView : WebFormView
    {
        internal const string ViewPathKey = "__ViewPath__";

        public LocalizationWebFormView(string viewPath) : base(viewPath)
        {
        }

        public LocalizationWebFormView(string viewPath, string masterPath) : base(viewPath, masterPath)
        {
        }

        public override void Render(ViewContext viewContext, TextWriter writer)
        {
            // there seems to be a bug with RenderPartial tainting the page's view data
            // so we should capture the current view path, and revert back after rendering
            string originalViewPath = (string) viewContext.ViewData[ViewPathKey];
            
            viewContext.ViewData[ViewPathKey] = ViewPath;
            base.Render(viewContext, writer);
            
            viewContext.ViewData[ViewPathKey] = originalViewPath;
        }
    }
}
```

**LocalizationWebFormViewEngine.cs**

```csharp
using System.Web.Mvc;

namespace MvcApplication
{
    public class LocalizationWebFormViewEngine : WebFormViewEngine
    {
        protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
        {
            return new LocalizationWebFormView(viewPath, masterPath);
        }

        protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
        {
            return new LocalizationWebFormView(partialPath, null);
        }
    }
}
```

2\. 建立一個 Resource HtmlHelper ，方便在 View 中直接以 Html Helper 的方式取用資源檔

**ResourceExtensions.cs**

```csharp
using System.Globalization;
using System.Web;
using System.Web.Compilation;
using System.Web.Mvc;

namespace MvcApplication
{
    public static class ResourceExtensions
    {
        public static string Resource(this Controller controller, string expression, params object[] args)
        {
            ResourceExpressionFields fields = GetResourceFields(expression, "~/");
            return GetGlobalResource(fields, args);
        }

        public static string Resource(this HtmlHelper htmlHelper, string expression, params object[] args)
        {
            string path = (string)htmlHelper.ViewData[LocalizationWebFormView.ViewPathKey];
            if (string.IsNullOrEmpty(path))
                path = "~/";

            ResourceExpressionFields fields = GetResourceFields(expression, path);
            if (!string.IsNullOrEmpty(fields.ClassKey))
                return GetGlobalResource(fields, args);

            return GetLocalResource(path, fields, args);
        }

        static string GetLocalResource(string path, ResourceExpressionFields fields, object[] args)
        {
            return string.Format((string)HttpContext.GetLocalResourceObject(path, fields.ResourceKey, CultureInfo.CurrentUICulture), args);
        }

        static string GetGlobalResource(ResourceExpressionFields fields, object[] args)
        {
            return string.Format((string)HttpContext.GetGlobalResourceObject(fields.ClassKey, fields.ResourceKey, CultureInfo.CurrentUICulture), args);
        }

        static ResourceExpressionFields GetResourceFields(string expression, string virtualPath)
        {
            var context = new ExpressionBuilderContext(virtualPath);
            var builder = new ResourceExpressionBuilder();
            return (ResourceExpressionFields)builder.ParseExpression(expression, typeof(string), context);
        }
    }
}
```

3\. 修改 Global.asax.cs 將預設的 WebFormViewEngine 換成 LocalizationWebFormViewEngine

```csharp
protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();

    RegisterRoutes(RouteTable.Routes);

    ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add(new LocalizationWebFormViewEngine());
}
```

這樣就大功告成了！接著我們看看如何在 View 中運用這個 Html Helper，如下圖示是我寫的一個範例：

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/52cd387ac5d786a6771d-image.png)

專案內容包括：

1.  由上述新增的三個檔案
2.  修改過的 Global.asax.cs
3.  新增的 **App\_GlobalResources** 目錄與一個 Resource.resx 資源檔  
    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/cf2859803fe19294a4f5-image.png)
4.  在 Views\\Home 下新增一個 **App\_LocalResources** 目錄與三個本地資源檔 (**Index.aspx.resx** , **Index.aspx.zh-TW.resx** , **Index.aspx.en-US.resx** )  
    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/0939308e7ea2db85c272-image.png)

在 **Views\\Home\\Index.aspx** 中取用資源的範例如下：

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/ea1afae64058b9702b29-image.png)

其中有三點必須注意：

1.  由於 ASP.NET MVC 的 Html Helper 透過擴充方法(Extension Method)，所以必須要載入命名空間才能取用自訂的擴充方法，如果不希望每個 View 都匯入命名空間可以參考我的另一篇文章：[ASP.NET 如何預設匯入指定的命名空間(Namespace)](/post/2008/12/24/How-to-auto-import-specific-Namespace-in-ASPNET)
2.  讀取「全域資源檔」的格式與之前在寫 ASP.NET 的時候一樣：  
    **資源檔的檔名 + 逗號 + 資源名稱**
3.  讀取「本地資源檔」的時候可以直接輸入「資源名稱」就好

若想要測試不同語系的資料，可以參考下圖或我另外兩篇文章：

-   [如何強迫 .Net 應用程式輸出英文的例外訊息](/post/2008/08/08/Force-dot-Net-Application-output-English-exception-message)
-   [如何讓 ASP.NET 依據瀏覽器指定的語系自動選用資源檔](/post/2009/06/02/Auto-language-detection-by-browser-lang-settings-in-ASPNET)

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/cebe9bab0b790e3fe534-image.png) 

**本文範例可以從這裡下載：  
**[ASP.NET MVC Globalization and Localization Sample.zip (314.66 kb)](https://stwillblogassets.blob.core.windows.net/files/ASP.NET%20MVC%20Globalization%20and%20Localization%20Sample.zip)

**相關連結**

-   [ASP.NET 全球化和當地語系化](http://msdn.microsoft.com/zh-tw/library/c6zyy3s9.aspx)
-   [ASP.NET MVC: Simplified Localization via ViewEngines](https://web.archive.org/web/20100330110001/http://blog.eworldui.net:80/post/2008/10/ASPNET-MVC-Simplified-Localization-via-ViewEngines.aspx)
-   [ASP.NET MVC 2 Custom Validation](http://haacked.com/archive/2009/11/19/aspnetmvc2-custom-validation.aspx)
-   [Localizing ASP.NET MVC Validation](http://haacked.com/archive/2009/12/07/localizing-aspnetmvc-validation.aspx)
-   [Globalizing ASP.NET MVC Client Validation](http://haacked.com/archive/2010/05/10/globalizing-mvc-validation.aspx)
