DisplayName attribute from Resources?

130,791

Solution 1

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; }
}

Solution 2

If you use MVC 3 and .NET 4, you can use the new Display attribute in the System.ComponentModel.DataAnnotations namespace. This attribute replaces the DisplayName attribute and provides much more functionality, including localization support.

In your case, you would use it like this:

public class MyModel
{
    [Required]
    [Display(Name = "labelForName", ResourceType = typeof(Resources.Resources))]
    public string name{ get; set; }
}

As a side note, this attribute will not work with resources inside App_GlobalResources or App_LocalResources. This has to do with the custom tool (GlobalResourceProxyGenerator) these resources use. Instead make sure your resource file is set to 'Embedded resource' and use the 'ResXFileCodeGenerator' custom tool.

(As a further side note, you shouldn't be using App_GlobalResources or App_LocalResources with MVC. You can read more about why this is the case here)

Solution 3

If you open your resource file and change the access modifier to public or internal it will generate a class from your resource file which allows you to create strongly typed resource references.

Option for resource file code generation

Which means you can do something like this instead (using C# 6.0). Then you dont have to remember if firstname was lowercased or camelcased. And you can see if other properties use the same resource value with a find all references.

[Display(Name = nameof(PropertyNames.FirstName), ResourceType = typeof(PropertyNames))]
public string FirstName { get; set; }

Solution 4

Update:

I know it's too late but I'd like to add this update:

I'm using the Conventional Model Metadata Provider which presented by Phil Haacked it's more powerful and easy to apply take look at it : ConventionalModelMetadataProvider


Old Answer

Here if you wanna support many types of resources:

public class LocalizedDisplayNameAttribute : DisplayNameAttribute
{
    private readonly PropertyInfo nameProperty;

    public LocalizedDisplayNameAttribute(string displayNameKey, Type resourceType = null)
        : base(displayNameKey)
    {
        if (resourceType != null)
        {
            nameProperty = resourceType.GetProperty(base.DisplayName,
                                           BindingFlags.Static | BindingFlags.Public);
        }
    }

    public override string DisplayName
    {
        get
        {
            if (nameProperty == null)
            {
                return base.DisplayName;
            }
            return (string)nameProperty.GetValue(nameProperty.DeclaringType, null);
        }
    }
}

Then use it like this:

    [LocalizedDisplayName("Password", typeof(Res.Model.Shared.ModelProperties))]
    public string Password { get; set; }

For the full localization tutorial see this page.

Solution 5

I got Gunders answer working with my App_GlobalResources by choosing the resources properties and switch "Custom Tool" to "PublicResXFileCodeGenerator" and build action to "Embedded Resource". Please observe Gunders comment below.

enter image description here

Works like a charm :)

Share:
130,791

Related videos on Youtube

Palantir
Author by

Palantir

Updated on May 15, 2020

Comments

  • Palantir
    Palantir almost 4 years

    I have a localized application, and I am wondering if it is possible to have the DisplayName for a certain model property set from a Resource.

    I'd like to do something like this:

    public class MyModel {
      [Required]
      [DisplayName(Resources.Resources.labelForName)]
      public string name{ get; set; }
    }
    

    But I can't to it, as the compiler says: "An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type" :(

    Are there any workarounds? I am outputting labels manually, but I need these for the validator output!

  • Palantir
    Palantir about 14 years
    Yes, I worked on a similar solution this morning and it it feasible. Then I found this post which has the same approach: adamyan.blogspot.com/2010/02/…
  • kingdango
    kingdango over 12 years
    This is good for the specific example used here but it won't work for the majority of dynamic property setters that want strings.
  • 321X
    321X about 12 years
    @Gunder his post, below this one (with the most votes), is a much nicer solution. Just for people who only read the Accepted posts
  • kbvishnu
    kbvishnu over 11 years
    This is good when we have all our locale with us before deploying to production. But if I want to call db for db strings then this kind of approach is not proper. Also the disadvantage of resource file, is that it requires compilation again to effect the changes, that means you need to release another version to client. I am not saying this as a bad approach, please dont feel like that
  • Kek
    Kek over 11 years
    Just a problem : we have to know resource type in the model. I have a model DLL and a website in two distinct projects. I'd like to be able to set display names in model and set resource type in website...
  • Fischer
    Fischer over 11 years
    This actually does NOT work for accessing different translations since it will return the same value for all users no mater what. Store resourceid in a local variable and override DisplayName instead
  • Leonel Sanches da Silva
    Leonel Sanches da Silva about 11 years
    Suggestion for TODO: return Resources.Language.ResourceManager.GetString(resourceId);
  • anIBMer
    anIBMer almost 11 years
    Get Resharper, and create the resource file in your project, and it will automatically remind and then help you to move the Name into the resource file.
  • René
    René almost 11 years
    This results in a resource file that will be compiled just as if you had added a resource file outside of App_GlobalResources. So your resource file will no longer behave as a "App_GlobalResources" resource file, which is absolutely fine. But you should just be aware of it. So you no longer have the "benefits" of putting the resource file in the App_GlobalResources. You could just as well have put it somewhere else.
  • Frank Boucher
    Frank Boucher over 10 years
    You should use: [Display(Name = "labelForName", ResourceType = typeof(Resources.Resources))] as describe below...
  • Nilzor
    Nilzor about 10 years
    +1. Haack's solution is definitely the most elegant one compared to the others here. It fits very well into the convention-based style of programming in ASP.NET MVC and is easily implemented through a single nuget-command and a single line of code in Global.asax.cs.
  • philreed
    philreed over 9 years
    I think this solution requires Resources.Resources to expose potentially hundreds of static properties. Am I wrong, or is there a way around this? Something like GetResource(string key, string culture)
  • René
    René over 9 years
    Well that's how resource files work: they expose the strings as static properties. What are your concerns about that? Organization? If that's the case, you can divide your strings into multiple resource files.
  • Piotr Kula
    Piotr Kula about 9 years
    Yea this worked for the name. Now how do I replace the Required error message? Tutorials show to use the resource file directly, I dont get how we getting errors now? Darins solution is good but it means we now need to create Localised attributres... for all the attributes.. arh!
  • NoOne
    NoOne about 9 years
    To tell you the truth, missing the ResourceType = typeof(Resources.Resources) part of the built-in ASP.NET attribute would make my models much more readable... I wish MS had provided a static property for setting the default ResourceType for all their attributes. I mean, I can barely think of a project that I've worked on which required using multiple ResourceTypes... And even if one needs to do that, he could always override the default. It's definitely much better that throwing an error when the ResourceType is missing.
  • ajbeaven
    ajbeaven over 8 years
    @321X forgive my ignorance but why is this a much nicer solution? This approach gives much more flexibility and hugely improves readability.
  • Memet Olsen
    Memet Olsen about 8 years
    Calm down people. At the time of writing this answer, the DisplayAttribute didn't even exist.
  • John
    John almost 8 years
    @ajbeaven Because as Fischer pointed out, it always only ever uses one culture, the culture that the attribute was instantiated with. Therefore, it's really pointless.
  • ajbeaven
    ajbeaven almost 8 years
    @John couldn't the culture of the app determined at the start of each new thread? So if you determine the user's culture and set it (perhaps based on a URL as per most i18n solutions) then this would change for different users?
  • John
    John almost 8 years
    @ajbeaven All threads use the same instance of the attribute and also threads are reused for many requests.
  • Uwe Keim
    Uwe Keim almost 8 years
    With C# 6, instead of Name = "labelForName" you can also use Name = nameof(Resources.Resources.labelForName).
  • Dark_Knight
    Dark_Knight almost 8 years
    will this work with Winforms? I have a class which i added Display annotation from resources and i used GetAttributeFrom method from link to get the name of the property but it doesn't show the localized one!
  • Tikall
    Tikall almost 8 years
    Are you using attribute.Name or attribute.GetName() to get the localized text? Documentation for .Name says "Do not use this property to get the value of the Name property. Use the GetName method instead." msdn.microsoft.com/en-us/library/…
  • Dark_Knight
    Dark_Knight over 7 years
    Yes, i figured it out that i had to use GetName(). Thanks
  • Sandeep
    Sandeep over 5 years
    On similar lines I found this article explaining this. codedigest.com/posts/29/… Just for few of users like me who may not be sure the hard-coded string in following "labelForName" is the Name in your resource file and its value could be "Users Name" in english or "उपयोगकर्ता नाम" in hindi or "Nombre de usuario" in spanish. [Display(Name = "labelForName", ResourceType = typeof(Resources.Resources))]