I had the same problem today, as I tried to make globalization happen for a wpf app with entity framework and validation attributes.
I tried different solutions and, for now, I ended up creating that CustomResourceManager
based on the sample that Dave Sexton describes in this post: http://www.pcreview.co.uk/forums/load-resources-string-table-database-t2892227.html
Basically I have implemented my DatabaseResourceManager
and DatabaseResourceSet
derived from ResourceManager
and ResourceSet
respectively like so:
The DatabaseResourceManager
Implementation:
public class DatabaseResourceManager : ResourceManager
{
#region Singleton pattern http://msdn.microsoft.com/en-us/library/ff650316.aspx
private static volatile DatabaseResourceManager instance;
private static object syncRoot = new Object();
private DatabaseResourceManager() : base() { }
public static DatabaseResourceManager Instance
{
get
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
instance = new DatabaseResourceManager();
}
}
return instance;
}
}
#endregion
protected override ResourceSet InternalGetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents)
{
if (culture == null)
culture = CultureInfo.InvariantCulture;
return new DatabaseResourceSet(culture);
}
}
And the DatabaseResourceSet
implementation:
public class DatabaseResourceSet : ResourceSet
{
private readonly CultureInfo culture;
private static readonly Dictionary<string, Hashtable> cachedResources = new Dictionary<string, Hashtable>();
public DatabaseResourceSet(CultureInfo culture)
{
if (culture == null)
throw new ArgumentNullException("culture");
this.culture = culture;
ReadResources();
}
protected override void ReadResources()
{
if (cachedResources.ContainsKey(culture.Name))
// retrieve cached resource set
{
Table = cachedResources[culture.Name];
return;
}
using (MyDatabaseContext db = new MyDatabaseContext())
{
var translations = from t in db.Translations
where t.CultureIso == culture.Name
select t;
foreach (var translation in translations)
{
Table.Add(translation.Chave, translation.Valor);
}
}
cachedResources[culture.Name] = Table;
}
}
Since my DataBaseResourceManager has the singletone pattern implemented I can easily access it and get the data like so:
public class LocalizedRequiredAttribute : RequiredAttribute
{
public LocalizedRequiredAttribute() : base()
{
}
public override string FormatErrorMessage(string name)
{
string localErrorMessage = DatabaseResourceManager.Instance.GetString(this.ErrorMessageResourceName) ?? ErrorMessage ?? "{0} is required"; //probably DataAnnotationsResources.RequiredAttribute_ValidationError would is a better option
return string.Format(System.Globalization.CultureInfo.CurrentCulture, localErrorMessage, new object[] { name });
}
}
Now the drawbacks:
Like said in the pcreview post, winforms doesn't accept this DataBaseResourceManager
, but in my case since I'm using wpf I didn't have this problem (but had another small memory problem because of the duplication of the ResourceManager
and a DictionaryResource
) . Don't know if you can inject the resource manager in the winforms, but that goes beyond your question.
Another drawback, that you already know, is that we must derived all ValidationAttribute
used so that the FormatErrorMessage
is used. StringLengthAttribute
, RequiredAttribute
, RegularExpressionAttribute
, and so on. Which is prone to errors and clumsy. Unfortunately, Microsoft didn't gave us much room here.
Hope I helped, probably I'll review this answer in the future to share my findings on this.
Regards