在上一篇《DDD開發框架ABP之本地化/多語言支持》中,我們知道,ABP開發框架中本地化資源存儲可以采用XML文件,RESX資源文件,也提供了其他自定義的存儲方式的擴展接口。ABP框架默認實現了前面兩種方式,而數據庫存儲方式則需要自己擴展,大概是因為數據庫存儲涉及到了實體和倉儲等方面的具體內容,不適合放在基本框架里面。
以數據庫的方式存儲本地化資源,一個最明顯的好處就是方便修改,尤其是對於基於數據庫的應用系統而言,可以提供統一的維護界面。接下來我們就來一步步地實現將本地化資源存儲在數據庫中。
第一步 建立實體
如果以XML存儲資源,我們需要建立XML的資源文件,支持多少種語言則需要建立多少個資源文件。那么以數據庫存儲資源文本,自然也需要建立實體,然后實現實體的讀操作。
為了存儲資源,我們需要建立一個實體:
1 public class DBLocalization : Entity 2 { 3 [Required] 4 [StringLength(10)] 5 public virtual string Culture { get; set; } 6 [Required] 7 [StringLength(50)] 8 public virtual string Name { get; set; } 9 [Required] 10 public virtual string Value { get; set; } 11 }
其中Culture屬性為語言代碼,比如en, zh-TW, zh-CN等。Name屬性為名稱,Value屬性為對應的文本。基類Entity是ABP框架提供的通用領域實體類,默認具有整型Id,且實現了IEntity接口。
第二步 建立領域服務
領域服務提供對實體的數據操作,比如取得語言種類和語言字典。
1 public class DBLocalizationManager : DomainService 2 { 3 private readonly IRepository<DBLocalization> _localizationRepository; 4 public DBLocalizationManager(IRepository<DBLocalization> localizationRepository) 5 { 6 _localizationRepository = localizationRepository; 7 } 8 public List<CultureInfo> GetCultures() 9 { 10 return _localizationRepository.GetAllList() 11 .Select(p => new CultureInfo(p.Culture)) 12 .Distinct() 13 .ToList<CultureInfo>(); 14 } 15 public List<DBLocalization> GetDictionary() 16 { 17 return _localizationRepository.GetAllList(); 18 } 19 }
其中IRepository<DBLocalization>以構造函數方式注入。
第三步 實現本地化資源接口(ILocalizationSource)
ILocalizationSource是本地化框架的核心接口,接口方法包括:
Name:資源名稱,
Initialize():初始化方法,注冊時被ABP調用
string GetString(string name) 根據名稱取得文本
IReadOnlyList<LocalizedString> GetAllStrings() 取得當前語言的全部字典清單
ABP已經有三個實現了ILocalizationSource接口的類:NullLocalizationSource、ResourceFileLocalizationSource 和 DictionaryBasedLocalizationSource。
把資源文本存儲於數據庫中,每次取得文本時都訪問數據庫取得數據,但基於性能的考慮,顯然將資源文本提前在初始化時一次性加載到內存,應該是更好地方式。由於DictionaryBasedLocalizationSource已經實現了內存字典的通用方法,我們不太需要再另外寫一個DBLocalizationSource。查看DictionaryBasedLocalizationSource代碼,可以看到其構造函數需要傳入一個ILocalizationDictionaryProvider的實例,這個實例用於取得本地化字典的詳細內容。
接下來我們建立一個類實現ILocalizationDictionaryProvider接口:
1 public class DBLocalizationDictionaryProvider : ILocalizationDictionaryProvider 2 { 3 private DBLocalizationManager _dbLocalizationManager; 4 public IEnumerable<LocalizationDictionaryInfo> GetDictionaries(string sourceName) 5 { 6 if (_dbLocalizationManager == null) 7 { 8 if (IocManager.Instance.IsRegistered<DBLocalizationManager>()) 9 { 10 _dbLocalizationManager = IocManager.Instance.Resolve<DBLocalizationManager>(); 11 } 12 } 13 var dictionaries = new List<LocalizationDictionaryInfo>(); 14 foreach (var culture in _dbLocalizationManager.GetCultures()) 15 { 16 dictionaries.Add( 17 new LocalizationDictionaryInfo( 18 DBLocalizationDictionary.Build(culture.Name, 19 _dbLocalizationManager.GetDictionary()) , 20 isDefault: culture.Name == ZeroConsts.DefaultLanguage 21 ) 22 ); 23 } 24 return dictionaries; 25 } 26 }
該類實現了接口的GetDictionaries方法,取得字典對象DBLocalizationDictionary。方法首先利用依賴注入容器自動得到一個IDBLocalizationManager的實例。
下面是DBLocalizationDictionary字典類的實現代碼:
1 public class DBLocalizationDictionary : LocalizationDictionary 2 { 3 private DBLocalizationDictionary(CultureInfo cultureInfo) 4 : base(cultureInfo) 5 { 6 } 7 public static DBLocalizationDictionary Build(string cultureName, List<DBLocalization> dictList) 8 { 9 try 10 { 11 var dictionary = new DBLocalizationDictionary(new CultureInfo(cultureName)); 12 var dublicateNames = new List<string>(); 13 if (dictList != null && dictList.Count>0) 14 { 15 foreach (DBLocalization item in dictList.FindAll(c => c.Culture == cultureName)) 16 { 17 if (string.IsNullOrEmpty(item.Name)) 18 { 19 throw new AbpException("name of a dictionary is empty in given data."); 20 } 21 if (dictionary.Contains(item.Name)) 22 { 23 dublicateNames.Add(item.Name); 24 } 25 dictionary[item.Name] = item.Value.NormalizeLineEndings(); 26 } 27 } 28 if (dublicateNames.Count > 0) 29 { 30 throw new AbpException("A dictionary can not contain same key twice. There are some duplicated names: " + dublicateNames.JoinAsString(", ")); 31 } 32 return dictionary; 33 } 34 catch (Exception ex) 35 { 36 throw new AbpException("Invalid localization data format! ", ex); 37 } 38 } 39 }
第四步 注冊資源
在需要使用多語言本地化的模塊,我們可以在模塊的PreInitialize方法中,注冊資源。一個模塊可以在Configuration.Localization.Sources 集合中添加多個資源,只要實現了ILocalizationSource接口即可。
1 public override void PreInitialize() 2 { 3 Configuration.Localization.Sources.Add( 4 new DictionaryBasedLocalizationSource("Zero", new DBLocalizationDictionaryProvider())); 5 }
到這里,將本地化資源存儲在數據庫中就基本上已經全部實現,剩下的就是開發界面對本地化資源進行增刪改查的維護了。