[多國語系] MVC ViewModel自動載入語系資源

      一般而言我們Mode的屬性若沒冠上[Display]的話,View會以該Property的名稱來顯示,而加上Display後,若需要加上語系的話,除了設定Name還要再多設定ResourceType,如下:
image

DRY原則

    上方的程式碼整個看起來就是令人眼花瞭亂,每個Property要冠上Display及設定ResourceType,且差異的部份只Name的指定,程式碼重複的部分一大堆...。根據DIY的原則....不不不..是DRY的原則,不要一直重覆你自已,我們需要更乾淨的作法

改善目標

為了保持較乾淨的類別及加速日後的開發與維護,列出以下幾點需求:
  • 不要設定語系名稱及語系型別
  • 每個屬性不要冠上[Display]
  • 自動為Model的Property跟資源檔作結合

1.先為資源名稱規則化

如下圖所示:以Model+底線+Property來命名
image

2.建立語系提供者的介面

   public interface ILocalizedStringProvider
    {
     
        string GetModelString(Type model, string propertyName);


        string GetModelString(Type model, string propertyName, string metadataName);
     
    }


3.實作語系提供者的介面-語系資源檔提供者


此提供者主要的功能為,1.建構式先將所需要的ResourceManager載入,2.根據目前傳進來的ModelType及Property名稱去ResourceManager取得對應的語系值。
     public class ResourceStringProvider : ILocalizedStringProvider
    {
        private readonly List<ResourceManager> _resourceManagers = new List<ResourceManager>();

        public ResourceStringProvider(params ResourceManager[] resourceManager)
        {
            _resourceManagers.AddRange(resourceManager);
        }

   

      
        public string GetModelString(Type model, string propertyName)
        {
            return GetString(Format(model, propertyName));
          
        }

      
        public string GetModelString(Type model, string propertyName, string metadataName)
        {
            return GetString(Format(model, propertyName, metadataName));
          
        }
      
     
        protected virtual string Format(Type type, string propertyName, params string[] extras)
        {
            var baseStr = string.Format("{0}_{1}", type.Name, propertyName);
            return extras.Aggregate(baseStr, (current, extra) => current + ("_" + extra));
        }

        protected virtual string Format(Type attributeType)
        {
            return attributeType.Name.Replace("Attribute", "");
        }

       
        protected virtual string GetString(string name)
        {
            var result = _resourceManagers.Select(resourceManager => resourceManager.GetString(name)).FirstOrDefault(value => value != null);
            return result;
        }
    }

4.覆寫DataAnnotationsModelMetadataProvider


MVC的Model Metadata提供者預設為DataAnnotationsModelMetadataProvider,藉由繼承它並改寫成我們需要的功能。如下程式主要為覆寫CreateMetadata,在判斷若未設定Display相關屬性時則到ResourceStringProvider 去找。
public class LocalizedModelMetadataProvider : DataAnnotationsModelMetadataProvider
    {
        private ILocalizedStringProvider _stringProvider;


        public LocalizedModelMetadataProvider(ILocalizedStringProvider stringProvider)
        {
            if (stringProvider == null) throw new ArgumentNullException("stringProvider");
            _stringProvider = stringProvider;
        }

  

        private ILocalizedStringProvider Provider
        {
            get
            {
                return _stringProvider;
            }
        }

       
        protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType,
                                                        Func<object> modelAccessor, Type modelType, string propertyName)
        {
            var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
            if (containerType == null || propertyName == null)
                return metadata;

            if (metadata.DisplayName == null)
            {
                metadata.DisplayName = Translate(containerType, propertyName) ;
            }

            if (metadata.Watermark == null)
                metadata.Watermark = Translate(containerType, propertyName, "Watermark");

            if (metadata.Description == null)
                metadata.Description = Translate(containerType, propertyName, "Description");

            if (metadata.NullDisplayText == null)
                metadata.NullDisplayText = Translate(containerType, propertyName, "NullDisplayText");

            if (metadata.ShortDisplayName == null)
                metadata.ShortDisplayName = Translate(containerType, propertyName, "ShortDisplayName");

            return metadata;
        }

        
        protected virtual string Translate(Type type, string propertyName)
        {
            return Provider.GetModelString(type, propertyName);
        }

       
        protected virtual string Translate(Type type, string propertyName, string metadataName)
        {
            return Provider.GetModelString(type, propertyName, metadataName);
        }
    }

5.在Global.asax將預設的MatadataProvider換掉



  • 建立ResourceStringProvider,在建構式加入Local.ResourceManager

  • 指定ModelMetadataProviders為LocalizedModelMetadataProvider

        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

            ResourceStringProvider rp = new ResourceStringProvider(Local.ResourceManager);
            ModelMetadataProviders.Current = new LocalizedModelMetadataProvider(rp);

        }



6.將Model的Display全部移除

    public class ProductModel
    {
        public int ProductId { get; set; }

        public string ProductName { get; set; }

        public DateTime CreateDate { get; set; }

        public decimal Price { get; set; }

        public int Qty { get; set; }

    }


7.大功告成-顯示成果

中文
image

英文
image

參考來源

http://blog.gauffin.org/2011/09/easy-model-and-validation-localization-in-asp-net-mvc3/
本篇原始碼
https://github.com/kimx/LocalizedModelProviderLab/

這個網誌中的熱門文章

[TFS] 分支與合併

[.NET Core] 將專案發行至IIS