/// <summary>
/// Resource provider accessing resources from the database.
/// This type is thread safe.
/// </summary>
public class DBResourceProvider : DisposableBaseType, IResourceProvider
{
public string ClassKey { get; private set; }
public IResourceFacade ResourceFacade { get; private set; }
//resource cache
private Dictionary<string, Dictionary<string, string>> _resourceCache = new Dictionary<string, Dictionary<string, string>>();
/// <summary>
/// Constructs this instance of the provider
/// supplying a resource type for the instance.
/// </summary>
/// <param name="resourceType">The resource type.</param>
public DBResourceProvider(string classKey) : this(IoC.GetInstance<IResourceFacade>())
{
Check.Assert(ResourceFacade != null, "ResourceFacade instance is null");
Debug.WriteLine(String.Format(CultureInfo.InvariantCulture, "DBResourceProvider.DBResourceProvider({0}", classKey));
ClassKey = classKey;
ResourceFacade.ResourceType = ClassKey;
}
public DBResourceProvider(IResourceFacade resourceFacade)
{
Check.Assert(resourceFacade != null, "ResourceFacade instance is null");
ResourceFacade = resourceFacade;
}
#region IResourceProvider Members
/// <summary>
/// Retrieves a resource entry based on the specified culture and
/// resource key. The resource type is based on this instance of the
/// DBResourceProvider as passed to the constructor.
/// To optimize performance, this function caches values in a dictionary
/// per culture.
/// </summary>
/// <param name="resourceKey">The resource key to find.</param>
/// <param name="culture">The culture to search with.</param>
/// <returns>If found, the resource string is returned.
/// Otherwise an empty string is returned.</returns>
public object GetObject(string resourceKey, CultureInfo culture)
{
Debug.WriteLine(String.Format(CultureInfo.InvariantCulture, "DBResourceProvider.GetObject({0}, {1}) - type:{2}", resourceKey, culture, this.ClassKey));
if (Disposed)
{
throw new ObjectDisposedException("DBResourceProvider object is already disposed.");
}
if (string.IsNullOrEmpty(resourceKey))
{
throw new ArgumentNullException("resourceKey");
}
if (culture == null)
{
culture = CultureInfo.CurrentUICulture;
}
string resourceValue = null;
Dictionary<string, string> resCacheByCulture = null;
// check the cache first
// find the dictionary for this culture
// check for the inner dictionary entry for this key
if (_resourceCache.ContainsKey(culture.Name))
{
resCacheByCulture = _resourceCache[culture.Name];
if (resCacheByCulture.ContainsKey(resourceKey))
{
resourceValue = resCacheByCulture[resourceKey];
}
}
// if not in the cache, go to the database
if (resourceValue == null)
{
resourceValue = ResourceFacade.GetResourceByCultureAndKey(culture, resourceKey);
// add this result to the cache
// find the dictionary for this culture
// add this key/value pair to the inner dictionary
lock (this)
{
if (resCacheByCulture == null)
{
resCacheByCulture = new Dictionary<string, string>();
_resourceCache.Add(culture.Name, resCacheByCulture);
}
resCacheByCulture.Add(resourceKey, resourceValue);
}
}
return resourceValue;
}
/// <summary>
/// Returns a resource reader.
/// </summary>
public System.Resources.IResourceReader ResourceReader
{
get
{
Debug.WriteLine(String.Format(CultureInfo.InvariantCulture, "DBResourceProvider.get_ResourceReader - type:{0}", this.ClassKey));
if (Disposed)
{
throw new ObjectDisposedException("DBResourceProvider object is already disposed.");
}
// this is required for implicit resources
// this is also used for the expression editor sheet
ListDictionary resourceDictionary = ResourceFacade.GetResourcesByCulture(CultureInfo.InvariantCulture);
return new DBResourceReader(resourceDictionary);
}
}
#endregion
protected override void Cleanup()
{
try
{
if (ResourceFacade != null)
GC.SuppressFinalize(ResourceFacade);
this._resourceCache.Clear();
}
finally
{
base.Cleanup();
}
}
}
+ DBResourceProviderFactory.cs
public class DBResourceProviderFactory : ResourceProviderFactory
{
public override IResourceProvider CreateGlobalResourceProvider(string classKey)
{
Debug.WriteLine(String.Format(CultureInfo.InvariantCulture, "DBResourceProviderFactory.CreateGlobalResourceProvider({0})", classKey));
return new DBResourceProvider(classKey);
}
public override IResourceProvider CreateLocalResourceProvider(string virtualPath)
{
Debug.WriteLine(String.Format(CultureInfo.InvariantCulture, "DBResourceProviderFactory.CreateLocalResourceProvider({0}", virtualPath));
// we should always get a path from the runtime
string classKey = virtualPath;
if (!string.IsNullOrEmpty(virtualPath))
{
//virtualPath = virtualPath.Remove(0, 1); // don't need it in ASP.NET MVC
classKey = virtualPath.Remove(0, virtualPath.IndexOf('/') + 1);
}
return new DBResourceProvider(classKey);
}
}
Now I build own Resource Factory for getting the Resource strings. This is a Class Diagram:
And the details of their implementation are:
+ IResourceFactory.cs
public interface IResourceFactory
{
// Account controlelr
string AdminAccountLogon_GetVirtualPath();
string AdminAccountLogon_GetUserLoginString();
string AdminAccountLogon_GetUserNameString();
string AdminAccountLogon_GetPasswordString();
string AdminAccountLogon_GetRememberMeString();
// more some thing here
.....
}
+ NMAResourceFactory.cs
public partial class NMAResourceFactory : IResourceFactory
{
public NMAResourceFactory() : this(new HttpContextWrapper(HttpContext.Current))
{
}
public NMAResourceFactory(HttpContextBase httpContext)
{
NMAContext = httpContext;
}
public HttpContextBase NMAContext { get; private set; }
}
+ My partial class:
public partial class NMAResourceFactory
{
public string AdminAccountLogon_GetVirtualPath()
{
return "/Admin/Account/Index";
}
public string AdminAccountLogon_GetUserLoginString()
{
Check.Assert(NMAContext != null, "NMAContext instance is null");
return NMAContext.GetLocalResourceObject(AdminAccountLogon_GetVirtualPath(), "UserLogin").ToString();
}
public string AdminAccountLogon_GetUserNameString()
{
Check.Assert(NMAContext != null, "NMAContext instance is null");
return NMAContext.GetLocalResourceObject(AdminAccountLogon_GetVirtualPath(), "UserName").ToString();
}
public string AdminAccountLogon_GetPasswordString()
{
Check.Assert(NMAContext != null, "NMAContext instance is null");
return NMAContext.GetLocalResourceObject(AdminAccountLogon_GetVirtualPath(), "Password").ToString();
}
public string AdminAccountLogon_GetRememberMeString()
{
Check.Assert(NMAContext != null, "NMAContext instance is null");
return NMAContext.GetLocalResourceObject(AdminAccountLogon_GetVirtualPath(), "RememberMe").ToString();
}
}
Next, I put some custom configuration in Web.config:
<system.web>
....................................
<globalization culture="en-GB" uiCulture="en-GB" resourceProviderFactoryType="NMA.Web.Core.Provider.Resources.DBResourceProviderFactory, NMA.Web" />
</system.web>
Now I want to use the new feature in .NET 4 is DataAnnotation for validating data for my ViewModel.
I found the best article about Localisation for web form at
http://adamyan.blogspot.com/2010/02/aspnet-mvc-2-localization-complete.html.
But I could not use it with my DBResourceProvider, so that I must implement the cross cutting
attribute that extend from DisplayNameAttribute's DataAnnotation. And the code as here:
public class DBLocalizedDisplayNameAttribute : DisplayNameAttribute
{
private string _displayFunctionName;
private Type _resourceFactory;
public DBLocalizedDisplayNameAttribute(Type resourceFactory, string displayFunctionName)
{
_resourceFactory = resourceFactory;
_displayFunctionName = displayFunctionName;
}
public override string DisplayName
{
get
{
Type ty = _resourceFactory;
MethodInfo[] mi = ty.GetMethods();
MethodInfo methodInfo = ty.GetMethod(_displayFunctionName);
var o = Activator.CreateInstance(ty);
var result = methodInfo.Invoke(o, null);
return result.ToString();
}
}
}
Finally I only need decorating my field in View Model as:
public class LogOnModel
{
[Required]
[DBLocalizedDisplayName(typeof(NMAResourceFactory), "AdminAccountLogon_GetUserNameString")]
public string UserName { get; set; }
[Required]
[DataType(DataType.Password)]
[DBLocalizedDisplayName(typeof(NMAResourceFactory), "AdminAccountLogon_GetPasswordString")]
public string Password { get; set; }
[DBLocalizedDisplayName(typeof(NMAResourceFactory), "AdminAccountLogon_GetRememberMeString")]
public bool RememberMe { get; set; }
}
In the View, we only need using it normally as:
<% using (Html.BeginForm("Index", "Account", FormMethod.Post, new { id = "formLogin", @class = "loginform"})) { %>
<div class="login-body">
<fieldset>
<div class="messages" style="display:none">
<div class="error"><%= Html.ValidationSummary() %></div>
</div>
<dl>
<dt>
<%= Html.LabelFor(m => m.UserName) %>
</dt>
<dd>
<%= Html.TextBoxFor(m => m.UserName, new { @Class = "inputtext", @size = "50" })%>
</dd>
</dl>
<dl>
<dt>
<%= Html.LabelFor(m => m.Password) %>
</dt>
<dd>
<%= Html.PasswordFor(m => m.Password, new { @Class = "inputtext", @size = "50" })%>
<%= Html.ValidationMessageFor(m => m.Password)%>
<div>
<%= Html.CheckBoxFor(m => m.RememberMe, new { Class = "checkbox floatleft" })%>
<%= Html.LabelFor(m => m.RememberMe)%>
</div>
<br />
<br />
</dd>
</dl>
</fieldset>
<fieldset>
<div class="floatleft" style="width: 80px;">
<div class="pributton left">
<input id="btnLogin" name="btnLogin" type="submit" value="Login" class="rightbutton" /></div>
</div>
</fieldset>
</div>
<div class="login-footer">
</div>
<% } %>
And if you want to using it from ResourceFactory from your View, you might need declaration in your view header like that:
<%
NMA.Web.Core.ResourceFactory.IResourceFactory _resourceFactory = new NMA.Web.Core.ResourceFactory.NMAResourceFactory();
%>
call it as below:
<div class="login-header">
<h3><%= _resourceFactory.AdminAccountLogon_GetUserLoginString()%></h3>
</div>
That's all. You can find all source code in NMA project http://nma.codeplex.com.
Hope this is useful for you. Reminder once again it is only my practice,
maybe we have many best solution for implemented localisation.
Good bye and see you next time!