背景:公司的电子商务网提供多语言的支持。开发语言为C#.net ,最初每个网站有一组本地化文件。分别用resx文件保存,各式例如:text.en-us.resx.
然后第一个类,实现读取.resx文件到缓存中。再次使用时直接从缓存中查询数据。
然后每到一个新的项目,直接复制此类文件,然后稍作修改,就又能在新项目中使用。然而这样不断的复制,提高了维护代价,每个不同的平台都有一个类似的类。
同时,为了提高对资源文件的管理,公司要求将本地化的文件保存到数据库中,实现统一的本地化数据管理。另外有可能需要通过创建API提供资源。而非直接数据库读取。
设计,首先定义并实现接口:
public interface IResxManager { void Init(); /// <summary> /// 资源分组,当大量文件存储时。通过分组来分批获得。 /// </summary> string ResourceGroupName { get; set; } /// <summary> /// 语言区.例如:en-us /// </summary> string CultureZone { get; set; } /// <summary> /// 获取对应key的value /// </summary> /// <param name="szKey"></param> /// <returns></returns> string GetValue(string szKey); /// <summary> /// 设置对应key的值 /// </summary> /// <param name="key"></param> /// <param name="value"></param> void SetValue(string key, string value); /// <summary> /// 获取key的值 /// </summary> /// <param name="szKey"></param> /// <returns></returns> string this[string szKey] { get; set; } /// <summary> /// 全部资源 /// </summary> NameValueCollection KeyAndValue { get; } /// <summary> /// Cache管理器 /// </summary> ICache ResourceCache { get; set; } IResouceAccess ResourceAccess { get; set; } }
除了基本的获取内容和设置内容外,重要的是:1.提供数据接口,2.缓存接口(考虑到未来可能分布式缓存)
本地化数据读取接口定义
public interface IResouceAccess { NameValueCollection ReadResource(string resourceGroupName, string cultureZone); }
缓存接口定义
public interface ICache { void Add(string szCacheKey, object oObjectToCache); void Add(string szCacheKey, object oObjectToCache, int nCacheSecs); object Read(string szCacheKey); bool Check(string szCacheKey); }
接口定义完成,开始进行配置类的开发。
允许配置:是否记录丢失的字符串,Cache 提供类,本地化资源提供类,默认语言,默认读取数据分组等信息。通过继承 IConfigurationSectionHandler 接口,实现在web.config文件中添加自定义配置项,从而避免有可能和appsettings重复。
public class ResourceClientConfig : IConfigurationSectionHandler { private static ResourceClientConfig _config; public string ResourceAccess { get; set; } NameValueCollection _nvc; public string GetSetting(string key) { return _nvc[key.ToLower()]; } public bool LogMissWord {get;set;} public bool DisplayMissWordError {get;set;} public string DefaultGroupName { get; set; } public string CacheProvider { get; set; } public string DefaultCulterZone { get; set; } internal ResourceClientConfig() { } public static ResourceClientConfig GetConfig() { if (_config == null) _config = (ResourceClientConfig)System.Configuration.ConfigurationManager.GetSection("ResourceClientConfig"); return _config; } public object Create(object parent, object configContext, System.Xml.XmlNode section) { _nvc =new NameValueCollection(); foreach (System.Xml.XmlNode node in section.ChildNodes) { if (node.Name.ToLower() == "add") { string value = node.Attributes["value"].Value; _nvc.Add(node.Attributes["key"].Value.ToLower(), value); switch (node.Attributes["key"].Value.ToLower()) { case "logmissword": LogMissWord = value.ToLower() == "true"; break; case "displaymissworderror": DisplayMissWordError = value.ToLower() == "true"; break; case "resourceaccess": ResourceAccess = value; break; case "defaultgroupname": DefaultGroupName = value; break; case "cacheprovider": CacheProvider = value; break; case "defaultculterzone": DefaultCulterZone = value; break; default: break; } } } return this; } }
本地化资源管理器实现:
/// <summary> /// Provide localization text /// </summary> public class ResxManager:IResxManager { protected NameValueCollection _KeyAndValue = new NameValueCollection(); public virtual NameValueCollection KeyAndValue { get { return _KeyAndValue; } } private string _CultureZone; public virtual string CultureZone { get { return _CultureZone; } set { _CultureZone = value; } } public ResxManager() { } public ResxManager(ICache cacheSupplyer, IResouceAccess resourceAccess) { this.ResourceCache = cacheSupplyer; this.ResourceAccess = resourceAccess; } /// <summary> /// Return value given key /// </summary> /// <param name="szKey"></param> /// <returns></returns> public virtual string GetValue(string szKey) { if (_KeyAndValue != null) { if (_KeyAndValue[szKey] != null) { return _KeyAndValue[szKey]; } else { return ""; } } else { return ""; } } /// <summary> /// Set key and value pair /// </summary> /// <param name="szKey"></param> /// <param name="szValue"></param> public virtual void SetValue(string szKey, string szValue) { if (_KeyAndValue != null) { if (_KeyAndValue[szKey] != null) { _KeyAndValue[szKey] = szValue; } else { _KeyAndValue.Add(szKey, szValue); } } } /// <summary> /// this /// </summary> /// <param name="szKey"></param> /// <returns></returns> public virtual string this[string szKey] { get { return GetValue(szKey); } set { SetValue(szKey, value); } } /// <summary> /// Init /// </summary> public virtual void Init() { NameValueCollection nvc; string szCacheKey = string.Format("Resources.{0}.{1}", ResourceGroupName, CultureZone); if (ResourceCache != null) { if (ResourceCache.Check(szCacheKey)) { nvc = (NameValueCollection)ResourceCache.Read(szCacheKey); _KeyAndValue = nvc; } else { _KeyAndValue = ResourceAccess.ReadResource(ResourceGroupName, CultureZone); ResourceCache.Add(szCacheKey, _KeyAndValue); } } else { if (_nvc == null) _nvc = new Hashtable(); if (_nvc.ContainsKey(szCacheKey)) { nvc = (NameValueCollection)_nvc[szCacheKey]; _KeyAndValue = nvc; } else { _KeyAndValue = ResourceAccess.ReadResource(ResourceGroupName, CultureZone); _nvc.Add(szCacheKey, _KeyAndValue); } } } /// <summary> /// Can be used as cache while not supply cache manager. /// </summary> private static Hashtable _nvc; public virtual ICache ResourceCache { get; set; } public virtual string ResourceGroupName { get; set; } public IResouceAccess ResourceAccess { get; set; } }
为了方便调用,创建一个工厂类,读取配置信息,并返回初始化好的资源管理器,这里通过反射得到资源提供器和缓存管理器的对象。
public class ResourceFactory { public static IResxManager GetResourceManager() { ResourceClientConfig config = ResourceClientConfig.GetConfig(); IResouceAccess reAc = (IResouceAccess)Activator.CreateInstance(Type.GetType(config.ResourceAccess)); ICache cache = null; if(!string.IsNullOrEmpty(config.CacheProvider)) cache = (ICache)Activator.CreateInstance(Type.GetType(config.CacheProvider)); IResxManager _instance = new ResxManager(cache, reAc); _instance.ResourceGroupName = config.DefaultGroupName; _instance.CultureZone = config.DefaultCulterZone; return _instance; } }
一个资源管理器就开发完成,这个类可以方便的被任意项目引用,并通过在config文件中配置,实现读取本地化资源。
然后分别编写3个项目,来提供:1.resx文件读取提供器,2.数据库读取提供器,3.API读取提供器, 3个提供器都将继承自接口 IResouceAccess
1.resx文件读取提供器
internal class ResxFileResourceAccess:IResouceAccess { public ResxFileResourceAccess() { path = ResourceClientConfig.GetConfig().GetSetting("Path"); if (string.IsNullOrEmpty(path)) throw new Exception("RESX Manager Error. get resource error. not config the resource path"); } private string path; public System.Collections.Specialized.NameValueCollection ReadResource(string resourceGroupName, string cultureZone) { NameValueCollection nvc = new NameValueCollection(); string szResxFileName = string.Format("{2}/{1}.{0}.resx", cultureZone, resourceGroupName, path); szResxFileName = System.Web.HttpContext.Current == null ? szResxFileName : System.Web.HttpContext.Current.Server.MapPath(szResxFileName); if (System.IO.File.Exists(szResxFileName) == true) { System.Diagnostics.Debug.WriteLine(szResxFileName); ResXResourceReader r = new ResXResourceReader(szResxFileName); foreach (DictionaryEntry d in r) { nvc.Add(d.Key.ToString(), d.Value.ToString()); } r.Close(); } else { System.Diagnostics.Debug.WriteLine("无法找到资源文件:" + szResxFileName); throw new Exception("Can't find the resx file. filename:" + szResxFileName); } return nvc; } }
2.数据库读取提供器
internal class ResourceAccess : IResouceAccess { public ResourceAccess() { strConn = ResourceClientConfig.GetConfig().GetSetting("StrConn"); if (string.IsNullOrEmpty(strConn)) throw new Exception("RESX Manager Error. get resource error. not config the resource path"); } private string strConn; public System.Collections.Specialized.NameValueCollection ReadResource(string resourceGroupName, string cultureZone) { NameValueCollection nvc = new NameValueCollection(); string strSql = string.Format(@"select szKey,szText,szLocalText from tb_Localization a left join (select nTextFK,szLocalText from tb_LocalizationLanguage where szCultureZone='{0}') b on a.nTextID=b.nTextFK", cultureZone); Database oDB = DatabaseFactory.CreateDatabase(strConn); DbCommand dbCommand = oDB.GetSqlStringCommand(strSql); DataSet ds = oDB.ExecuteDataSet(dbCommand); if (ds.Tables.Count > 0 && ds.Tables[0].Rows.Count > 0) { foreach (DataRow dr in ds.Tables[0].Rows) { if (!string.IsNullOrEmpty(dr["szLocalText"].ToString())) nvc.Add(dr["szKey"].ToString(), dr["szLocalText"].ToString()); else nvc.Add(dr["szKey"].ToString(), dr["szText"].ToString()); } } return nvc; } }
web.config
<ResourceClientConfig> <!--<add key="resourceaccess" value="Creative.ResourceClient.ResxFileResourceAccess, Creative.ResourceClient"></add>--> <!--<add key="resourceaccess" value="Creative.Mobile.EStore.Business.APIResxAccess, Creative.Mobile.EStore.Business"></add>--> <add key="resourceaccess" value="Creative.ResourceClient.ResourceManagerSQLData.ResourceAccess, Creative.ResourceClient.ResourceManagerSQLData"/> <add key="cacheProvider" value="Creative.Mobile.EStore.Business.ResourceCache, Creative.Mobile.EStore.Business"></add> <add key="LogMissWord" value="true"></add> <add key="DisplayMissWordError" value="true"></add> <add key="DefaultGroupName" value="estore"/> <add key="DefaultCulterZone" value="en-gb"/> <add key= "Path" value= "~/resources/"/> <add key="StrConn" value="ResourceData"/> </ResourceClientConfig>
source code: http://download.csdn.net/user/rungoosc