DisplayName attribute from Resources?
How about writing a custom attribute:
public class LocalizedDisplayNameAttribute: DisplayNameAttribute
{
public LocalizedDisplayNameAttribute(string resourceId)
: base(GetMessageFromResource(resourceId))
{ }
private static string GetMessageFromResource(string resourceId)
{
// TODO: Return the string from the resource file
}
}
which could be used like this:
public class MyModel
{
[Required]
[LocalizedDisplayName("labelForName")]
public string Name { get; set; }
}
Get localized display name attribute from a class property which use a resource file
Finally, I found a solution on my own.
The problem
Then, letting a clear context, what we have is just a class
(from which we can extract its type), and a PropertyName
on a string, and what we want is the the localized DisplayName
of that property of that class, according to a Resource File
assigned on its decoration.
Let's suppose some elements to start. We have the class MyClass
, which has a property called MyProperty
, and which will be localized with the resource file MyResx
:
public class MyClass
{
private string myProperty;
[Display(Name = nameof(MyResx.MyProperty), ResourceType = typeof(MyResx))]
public string MyProperty
{
get { return myProperty; }
set { myProperty = value; }
}
}
The resource file MyResx
, has some localized string for the name MyProperty
, and will look like this:
The solution
// We start with the class type, and the property name on a string
Type classType = typeof(MyClass);
string nameOfTheProperty = "MyProperty";
/* Now we get the MemberInfo of our property, wich allow us to get the
* property metadata, where is the information we are looking for. */
MemberInfo propertyMetadata = classType.GetProperty(nameOfTheProperty);
/* The decorations we used, are "Custom Attributes". Now we get those
* attributes from our property metadata: */
var customAttributes = CustomAttributeData.GetCustomAttributes(propertyMetadata).FirstOrDefault();
/* If we pay attention to our decoration, we defined "Name = nameof(MyResx.MyProperty)"
* and "ResourceType = typeof(MyResx))", so, what we are looking for from our custom
* attribures are those members, Name and ResourceType: */
var customAttributeName = customAttributes.NamedArguments.FirstOrDefault(n => n.MemberName == "Name");
var name = (customAttributeName != null) ? (string)customAttributeName.TypedValue.Value : null;
var customAttributeResourceType = customAttributes.NamedArguments.FirstOrDefault(n => n.MemberName == "ResourceType");
var resourceType = (customAttributeResourceType != null) ? (Type)customAttributeResourceType.TypedValue.Value : null;
/* Now, having the resource file from the decoration, we just create an instance to
* use it: */
var decorationResx = new ComponentResourceManager(resourceType);
// And finally, from our resource file, we get our localized display name
string localizedAttribute = decorationResx.GetString(name);
Extra
I got a lot of important information from the Microsoft reference about the NamedArguments, here: https://docs.microsoft.com/en-us/dotnet/api/system.reflection.customattributedata.namedarguments?view=netcore-3.1
Localizing my display name attribute but can't reference my resource
It appears the issue is that you are specifying a locale for the resource file without having a default one present in your solution.
DisplayName.resx
would be the default language of your application and is needed for the resolution mechanism to work
DisplayName.<locale code>.resx
will then override the default locale resources for that particular locale.
Localization of DisplayNameAttribute
Here is the solution I ended up with in a separate assembly (called "Common" in my case):
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event)]
public class DisplayNameLocalizedAttribute : DisplayNameAttribute
{
public DisplayNameLocalizedAttribute(Type resourceManagerProvider, string resourceKey)
: base(Utils.LookupResource(resourceManagerProvider, resourceKey))
{
}
}
with the code to look up the resource:
internal static string LookupResource(Type resourceManagerProvider, string resourceKey)
{
foreach (PropertyInfo staticProperty in resourceManagerProvider.GetProperties(BindingFlags.Static | BindingFlags.NonPublic))
{
if (staticProperty.PropertyType == typeof(System.Resources.ResourceManager))
{
System.Resources.ResourceManager resourceManager = (System.Resources.ResourceManager)staticProperty.GetValue(null, null);
return resourceManager.GetString(resourceKey);
}
}
return resourceKey; // Fallback with the key name
}
Typical usage would be:
class Foo
{
[Common.DisplayNameLocalized(typeof(Resources.Resource), "CreationDateDisplayName"),
Common.DescriptionLocalized(typeof(Resources.Resource), "CreationDateDescription")]
public DateTime CreationDate
{
get;
set;
}
}
What is pretty much ugly as I use literal strings for resource key. Using a constant there would mean to modify Resources.Designer.cs which is probably not a good idea.
Conclusion: I am not happy with that, but I am even less happy about Microsoft who can't provide anything useful for such a common task.
Localization DisplayName Attribute
For your custom DataAnnotations attribute your need to write following code in your GetMessageFromResource method:
private static string GetMessageFromResource(string resourceId)
{
var propertyInfo = typeof(DataResource).GetProperty(resourceId, BindingFlags.Static | BindingFlags.Public);
return propertyInfo.GetValue(null, null);
}
This code should do the job assuming you have an error in your question and there are should be LocalizeDisplayNameAttribute, not the DisplayName one:
[DisplayName("NameString")]
public virtual string Name { get; set; }
Anyway I recommend using of lambda accessors for getting localized strings from resources so you can rename/navigate them using your refactoring tool.
MVC 4 setting attributes values from resources
You've got a slightly different question but the answer to the following could be applied to your question
DataAnnotations and Resources don't play nicely
Something like the following
[Display(ResourceType = typeof(Resources.LcmsBs.Models), Name = "UserName")]
ASP.NET Core DisplayAttribute Localization
You can set the ResourceType
on the DisplayAttribute
which can be used to localize your text.
Add a resource .resx
file to your project e.g. MyResources.resx
, and add a resource for your field:
Then reference the name of the field and the MyResources
type in your DisplayAttribute
[Display(Name = "RememberMe", ResourceType = typeof(MyResources))]
public bool RememberMe { get; set; }
The localized resource will be pulled through automatically (see the text box)
displayname attribute vs display attribute
They both give you the same results but the key difference I see is that you cannot specify a ResourceType
in DisplayName
attribute. For an example in MVC 2, you had to subclass the DisplayName
attribute to provide resource via localization. Display
attribute (new in MVC3 and .NET4) supports ResourceType
overload as an "out of the box" property.
Related Topics
How to Return a String Repeated X Number of Times
Reflection to Identify Extension Methods
Reading PDF Content with Itextsharp Dll in Vb.Net or C#
Trying to Run Multiple Http Requests in Parallel, But Being Limited by Windows (Registry)
Using of Inotifypropertychanged
How Do the Major C# Di/Ioc Frameworks Compare
What How to Use for Good Quality Code Coverage for C#/.Net
Mvc3 Dropdownlistfor - a Simple Example
How to Conditionally Apply a Linq Operator
How to Parse JSON Without JSON.Net Library
Dynamically Change Connection String in ASP.NET Core
No Generic Implementation of Ordereddictionary