168

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!

Vadim Ovchinnikov
  • 13,327
  • 5
  • 62
  • 90
Palantir
  • 23,820
  • 10
  • 76
  • 86

6 Answers6

388

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)

Vadim Ovchinnikov
  • 13,327
  • 5
  • 62
  • 90
René
  • 9,880
  • 4
  • 43
  • 49
  • This is good for the specific example used here but it won't work for the majority of dynamic property setters that want strings. – kingdango Dec 06 '11 at 15:28
  • 1
    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 – kbvishnu Oct 15 '12 at 11:28
  • 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... – Kek Oct 23 '12 at 19:29
  • 1
    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. – anIBMer May 22 '13 at 02:14
  • 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)` – philreed Oct 28 '14 at 16:55
  • 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. – René Nov 03 '14 at 01:16
  • 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! – Piotr Kula Feb 09 '15 at 15:47
  • 17
    With C# 6, instead of `Name = "labelForName"` you can also use `Name = nameof(Resources.Resources.labelForName)`. – Uwe Keim May 31 '16 at 05:18
  • On similar lines I found this article explaining this. http://www.codedigest.com/posts/29/using-resource-file-for-dataannotations-display-attribute-with-multi-language-support-in-aspnet-mvc 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))] – Sandeep Sep 04 '18 at 19:26
115

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; }
}
Jason
  • 4,557
  • 5
  • 31
  • 40
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • Yes, I worked on a similar solution this morning and it it feasible. Then I found this post which has the same approach: http://adamyan.blogspot.com/2010/02/aspnet-mvc-2-localization-complete.html – Palantir Mar 12 '10 at 12:40
  • 26
    @Gunder his post, below this one (with the most votes), is a much nicer solution. Just for people who only read the Accepted posts – 321X May 02 '12 at 21:18
  • 1
    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 – Fischer Dec 04 '12 at 11:24
  • 5
    Suggestion for TODO: return Resources.Language.ResourceManager.GetString(resourceId); – Leonel Sanches da Silva Mar 25 '13 at 04:22
  • 4
    You should use: [Display(Name = "labelForName", ResourceType = typeof(Resources.Resources))] as describe below... – Frank Boucher Oct 17 '13 at 09:47
  • 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 `ResourceType`s... 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. – NoOne Apr 03 '15 at 19:02
  • @321X forgive my ignorance but _why_ is this a much nicer solution? This approach gives much more flexibility and hugely improves readability. – ajbeaven Dec 02 '15 at 04:50
  • Calm down people. At the time of writing this answer, the `DisplayAttribute` didn't even exist. – Memet Olsen Feb 19 '16 at 13:37
  • @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. – John May 19 '16 at 00:33
  • @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? – ajbeaven May 19 '16 at 10:14
  • @ajbeaven All threads use the same instance of the attribute and also threads are reused for many requests. – John May 20 '16 at 07:33
39

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; }
Tikall
  • 2,453
  • 25
  • 13
  • will this work with Winforms? I have a class which i added Display annotation from resources and i used GetAttributeFrom method from [link](http://stackoverflow.com/questions/7027613/how-to-retrieve-data-annotations-from-code-programmatically) to get the name of the property but it doesn't show the localized one! – Dark_Knight Jul 24 '16 at 14:54
  • 3
    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." https://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.displayattribute.name(v=vs.110).aspx – Tikall Jul 27 '16 at 10:30
  • Yes, i figured it out that i had to use GetName(). Thanks – Dark_Knight Aug 14 '16 at 18:43
20

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.

Wahid Bitar
  • 13,776
  • 13
  • 78
  • 106
  • 1
    +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. – Nilzor Feb 14 '14 at 13:08
11

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 :)

Magnus Karlsson
  • 3,549
  • 3
  • 31
  • 57
  • 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. – René May 22 '13 at 08:07
6
public class Person
{
    // Before C# 6.0
    [Display(Name = "Age", ResourceType = typeof(Testi18n.Resource))]
    public string Age { get; set; }

    // After C# 6.0
    // [Display(Name = nameof(Resource.Age), ResourceType = typeof(Resource))]
}
  1. Define ResourceType of the attribute so it looks for a resource
  2. Define Name of the attribute which is used for the key of resource, after C# 6.0, you can use nameof for strong typed support instead of hard coding the key.

  3. Set the culture of current thread in the controller.

Resource.Culture = CultureInfo.GetCultureInfo("zh-CN");

  1. Set the accessibility of the resource to public

  2. Display the label in cshtml like this

@Html.DisplayNameFor(model => model.Age)

code4j
  • 4,208
  • 5
  • 34
  • 51