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!
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)
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; }
}
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.
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; }
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.
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.
Works like a charm :)
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))]
}
Define ResourceType of the attribute so it looks for a resource
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.
Set the culture of current thread in the controller.
Resource.Culture = CultureInfo.GetCultureInfo("zh-CN");
Set the accessibility of the resource to public
Display the label in cshtml like this
#Html.DisplayNameFor(model => model.Age)
Related
From Microsoft MVC doc, related to Authoring Tag Helpers, I can read this:
using System;
namespace AuthoringTagHelpers.Models
{
public class WebsiteContext
{
public Version Version { get; set; }
public int CopyrightYear { get; set; }
public bool Approved { get; set; }
public int TagsToShow { get; set; }
}
}
and this:
using System;
using AuthoringTagHelpers.Models;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace AuthoringTagHelpers.TagHelpers
{
public class WebsiteInformationTagHelper : TagHelper
{
public WebsiteContext Info { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "section";
output.Content.SetHtmlContent(
$#"<ul><li><strong>Version:</strong> {Info.Version}</li>
<li><strong>Copyright Year:</strong> {Info.CopyrightYear}</li>
<li><strong>Approved:</strong> {Info.Approved}</li>
<li><strong>Number of tags to show:</strong> {Info.TagsToShow}</li></ul>");
output.TagMode = TagMode.StartTagAndEndTag;
}
}
}
I never saw this kind of code before, where public WebsiteContext Info { get; set; } can automagically instantiate an object???
How it works? Is there any documentation on it?
The answer is in the document you linked:
Note
In the Razor markup shown below:
<website-information info="new WebsiteContext {
Version = new Version(1, 3),
CopyrightYear = 1638,
Approved = true,
TagsToShow = 131 }" />
Razor knows the info attribute is a class, not a string, and you want to write C# code. Any non-string tag helper attribute should be written without the # character.
The tag helper itself doesn't know how to instantiate the instance. You have to do it manually in the Razor markup or set it to a default value in the property declaration or class constructor in order for it to be non-null. Here is an example of setting the instance in the property declaration.
public WebsiteContext { get; set; } = new WebSiteContext
{
Version = new Version(1, 3),
CopyrightYear = 1638,
Approved = true,
TagsToShow = 131
};
public WebsiteContext Info { get; set; } is not instantiating anything here. If you call the following code:
var websiteInformationTagHelper = new WebsiteInformationTagHelper();
then websiteInformationTagHelper.Info will be equal to null
Note, that it is now possible in c# to assign default values like the following which is a little bit different than what you are wondering about:
public WebsiteContext Info { get; set; } = new WebsiteContext()
Not automatically, but yes. The get and set keywords are shorthand for methods that are called after the property is accessed (get) or assigned to (set). You can add a body with a regular code block:
get { return _backingField; }
set { _backingField = value; }
The value keyword represents the value being assigned to the property and you can do most things in those blocks, same as any method, including instantiating an object.
Microsoft documentation - Auto implemented properties:
learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/auto-implemented-properties
If you're referring to instantiating the parent object, that I don't believe makes sense.
I'm developing ASP.NET MVC appliation. I've found Fluent Validation great validation tool and it works, but with my current architecture it has one drawback. The validator does not care about Metadata. I'm using Metadata on seperate class for clarity.
Model
[MetadataType(typeof(DocumentEditMetadata))]
[Validator(typeof(DocumentValidator))]
public class DocumentEditModel
{
public string DocumentNumber { get; set; }
(etc...)
}
Metadata Model
public class DocumentEditMetadata
{
[Required]
[StringLength(50)]
[Display(ResourceType = typeof(Label), Name = "DocumentNumber")]
public string DocumentNumber { get; set; }
(etc...)
}
Can anyone point a solution? I need data annotations for localization of labels (hence the DisplayAttribute).
Think you need to write your own Display name resolver for fluent validation (guess this should be placed in your global.asax).
Caution
This solution is only trying to resolve the display name.
Your other "validation" attributes (Required, StringLength) should no more be used, as you will manage that with FluentValidation.
ValidatorOptions.DisplayNameResolver = (type, memberInfo, expression) =>
{
//this will get in this case, "DocumentNumber", the property name.
//If we don't find anything in metadata / resource, that what will be displayed in the error message.
var displayName = memberInfo.Name;
//we try to find a corresponding Metadata type
var metadataType = type.GetCustomAttribute<MetadataTypeAttribute>();
if (metadataType != null)
{
var metadata = metadataType.MetadataClassType;
//we try to find a corresponding property in the metadata type
var correspondingProperty = metadata.GetProperty(memberInfo.Name);
if (correspondingProperty != null)
{
//we try to find a display attribute for the property in the metadata type
var displayAttribute = correspondingProperty.GetCustomAttribute<DisplayAttribute>();
if (displayAttribute != null)
{
//finally we got it, try to resolve the name !
displayName = displayAttribute.GetName();
}
}
}
return displayName ;
};
Personal point of view
By the way, if you just use Metadata classes for clarity, don't use them !
It may be a solution if you have no choice (when entity classes are generated from an edmx and you really want to manage the display names this way), but I would really avoid them if it's not necessary.
public class CreateHireViewModel
{
[Display(Name = nameof(CreateHireViewModel.Title), ResourceType = typeof(Resource.HireResource.Hire))]
public string Title { get; set; }
}
public class CreateHireViewModelValidator : AbstractValidator<CreateHireViewModel>
{
public CreateHireViewModelValidator(IStringLocalizer<Resource.HireResource.Hire> l)
{
RuleFor(x => x.Title).NotEmpty().WithName(l[nameof(CreateHireViewModel.Title)]);
RuleFor(x => x.Title).Length(3, 50).WithName(l[nameof(CreateHireViewModel.Title)]);
}
}
It's an EntLib-Validator-issue again. I'm playing with EntLib 5.0 in C# and .Net 4.0 on XP pro.
I have some business objects (partial classes) generated by T4 templates. So I decided to put their validation attributes in buddy-classes by using MetadataTypeAttribute as definitely recommended by the documentation of entLib 5.0 (msdn).
But the Validator object I get from the ValidatorFactory doesn't know about the validation attributes, defined in the metadata-class.
The business object is defined like this:
[MetadataType(typeof(PatientMetadata))]
public partial class Patient
{
private string _Name;
private int _DiagnosisCount;
public int DiagnosisCount
{
get
{
return _DiagnosisCount;
}
set
{
if (value != _DiagnosisCount)
{
_DiagnosisCount = value;
}
}
}
public string Name
{
get
{
return _Name;
}
set
{
if (value != _Name)
{
_Name = value;
}
}
}
}
And the metadata class like this, according to documentation:
public class PatientMetadata
{
[RangeValidator(4)]
public int DiagnosisCount { get; set; }
[StringLengthValidator(64, ErrorMessage = "Name must not exceed 64 chars.")]
public string Name { get; set; }
}
If I know try to do validation this way:
var factory = ValidationFactory.DefaultCompositeValidatorFactory;
var validator = factory.CreateValidator<Patient>();
...then watching into validator (during debugging) already says, that it's just an AndCompositeValidator without any children validators.
Again, if I put the validation attributes right in the Patient class, it works perfectly.
By now, I have no real idea, what I'm missing here, since I think doing everything according to the docs.
Thanks in advance to you guys!
The property names of the metadata class must match the property names of the main class.
In your case your metadata class should look like:
public class PatientMetadata
{
[RangeValidator(0, RangeBoundaryType.Inclusive, 10, RangeBoundaryType.Ignore)]
public int DiagnosisCount { get; set; }
[StringLengthValidator(6, ErrorMessage = "Name must not exceed 6 chars.")]
public string Name { get; set; }
}
Also, the docs indicate the accepted approach is to declare all return types as object. However, the docs also talk about using properties but in their example use fields so take it under advisement. :)
When I use DisplayAttribute in ASP.NET MVC 3 models it quickly becomes a pain writing them because we have to either hardcode the string or reference the string from a some static class that contains const strings (which is what I have now, see below). But even that is too much for me.
I would like to come up with an attribute that would be called something like [SimpleDisplay] and it would implicitly construct the string for resources by looking at
class name,
property name that the attribute is attached to.
Is this possible?
Something like this
public class Product {
[SimpleDisplay] // it will take Product and Name and do something like this Product_Name
public string Name { get; set; }
}
This is what I want to get rid of, if possible:
[Display(ResourceType = typeof(Resources.Localize), Name = ResourceStrings.product_prettyid)]
public virtual int PrettyId
{
get;
set;
}
[Display(ResourceType = typeof(Resources.Localize), Name = ResourceStrings.product_name)]
public virtual string Title
{
get;
set;
}
Now I know that it is not possible to inherit the DisplayAttribute cause it's sealed. What other options I have? Does it even make sense?
I would try creating just a standard attribute and custom DataAnnotationsModelMetadataProvider. You can override CreateMetadata method, which gets IEnumerable<Attribute>. You should than search for your attribute
attributes.OfType<SimpleDisplayAttribute>().FirstOrDefault();
and populate model metadata in any way you want.
If i have a correct understanding what you mean, you may just create a simple custom attribute like this one:
public class LocalizedDisplayNameAttribute : DisplayNameAttribute {
public LocalizedDisplayNameAttribute(string expression) : base(expression) { }
public override string DisplayName {
get {
try {
string[] vals = base.DisplayName.Split(',');
if(vals != null && vals.Length == 2)
return (string)HttpContext.GetGlobalResourceObject(vals[0].Trim(), vals[1].Trim());
} catch {}
return "{res:" + base.DisplayName + "}";
}
}
}
You may then use it as an attribute on your properies. MVC HTML extensions will pickup your custom attribute.
[LocalizedDisplayName("LBL, lbl_name1")]
public string[] Name1 { get; set; }
If I have those two classes that have two different properties but with the same name:
[RdfSerializable]
public class Type1
{
[RdfProperty(true), Name = "title"]
public string Title { get; set; }
}
[RdfSerializable]
public class Type2
{
[RdfProperty(true), Name = "title"]
public string Title { get; set; }
}
and try to serialize them to RDF and validate them with http://www.w3.org/RDF/Validator/ service. Everything is Okay and they are correct.
But after I try to generate OWL files from those classes with OntologyExtractor.exe tool I get that message:
"Ontology extraction failed. http://test.org/1.0#title is assigned to more than one type."
This is strange message as the upper classes are correct and there are some RDF specifications that has same situation with different classes that have same named properties.
I expect it is a bug in ROWLEX. Your case is a valid one, but I assume I did not prepare for it when I wrote OntologyExtractor. I will try to release a fix as soon as possible.
EDIT: ROWLEX2.1 is released, you can download it from http://rowlex.nc3a.nato.int. Version 2.1 (among others) supports now the shared property functionality. The exact code in the question would still result the same error! To overcome that, you should alter the decoration of your code as follows:
[RdfSerializable]
public class Type1
{
[RdfProperty(true, Name = "title", ExcludeFromOntology=true)]
public string Title { get; set; }
}
[RdfSerializable]
public class Type2
{
[RdfProperty(true, Name = "title",
DomainAsType = new Type[]{typeof(Type1), typeof(Type2)})]
public string Title { get; set; }
}
Using the OntologyExtractor.exe, this code will result a OWL property of with an anonymous domain class that is the UNION of Type1 and Type2.
While this is technically perfectly correct solution, setting domains on properties limit their possible future reuse. As a solution, you might want to substitute the property domain with local restrictions. You can achieve that as follows:
[RdfSerializable]
public class Type2
{
[RdfProperty(true, Name = "title",
DomainAsType = new Type[]{typeof(Type1), typeof(Type2)},
UseLocalRestrictionInsteadOfDomain = true)]
public string Title { get; set; }
}
Should you leave UseLocalRestrictionInsteadOfDomain not set, ROWLEX chooses between domain and local restriction according to the current context.