Displayname Attribute from Resources

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:

How MyResx will look like

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:

Sample Image

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)

Sample Image

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



Leave a reply



Submit