背景:公司的电子商务网提供多语言的支持。开发语言为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