When I put [Required] attribute on my ViewModel's property MVC3 automatically generate error messages like:
The Price field is required.
My site's single language is Russian, so I want to have localized error messages. I can localize field's name with [Display(Name = "blablabla")], but how can I localize the field is required part?
Update:
I know, that I can change an error message for concrete field by specifying it [Required(ErrorMessage = "blablabla")], is there a way I can change it in one place for all [Required] attributes, so I could use just [Required] without additional parameters, and it took the localized error message from some ressource/config/etc?
I've created an alternative solution where you don't have to use the attributes for the localization. I've created custom model/validation meta data providers.
All you need to do is to download my code and do the following in your global.asax:
var stringProvider = new ResourceStringProvider(Resources.LocalizedStrings.ResourceManager);
ModelMetadataProviders.Current = new LocalizedModelMetadataProvider(stringProvider);
ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new LocalizedModelValidatorProvider(stringProvider));
(the ResourceStringProvider is my default implementation but it's easy to create an alternative that reads from XML files or a database)
You can read about it here:
http://blog.gauffin.org/2011/09/easy-model-and-validation-localization-in-asp-net-mvc3/
I'm going to release a nuget package as soon as I'm finished with my view localization and my alternative HTML helpers.
The Required attribute has properties that allow the message to be read from a resource string. See http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.requiredattribute.aspx for the details
Granted for this you should already have some Resource Manager which can return localized texts for string keys. My ResourceManager has static accessor for this (and registrations with Unity for DI), so there's no need to pass it.
In the global.asax:
ModelMetadataProviders.Current = new LocalizedModelMetadataProvider(); //field name localization
ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new LocalizedModelValidatorProvider()); //validation message localization
And the implementation is this:
public class LocalizedModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType,
Func<object> modelAccessor, Type modelType, string propertyName)
{
var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
if (containerType == null || propertyName == null)
return metadata;
if (metadata.DisplayName == null)
metadata.DisplayName = ResourceManager.Current.GetLocalizedAttribute(containerType, propertyName);
if (metadata.Watermark == null)
metadata.Watermark = ResourceManager.Current.GetLocalizedAttribute(containerType, propertyName, "Watermark");
if (metadata.Description == null)
metadata.Description = ResourceManager.Current.GetLocalizedAttribute(containerType, propertyName, "Description");
if (metadata.NullDisplayText == null)
metadata.NullDisplayText = ResourceManager.Current.GetLocalizedAttribute(containerType, propertyName, "NullDisplayText");
if (metadata.ShortDisplayName == null)
metadata.ShortDisplayName = ResourceManager.Current.GetLocalizedAttribute(containerType, propertyName, "ShortDisplayName");
return metadata;
}
}
public class LocalizedModelValidatorProvider : DataAnnotationsModelValidatorProvider
{
protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
{
foreach (var attribute in attributes.OfType<ValidationAttribute>())
attribute.ErrorMessage = ResourceManager.Current.GetValidationMessage(attribute);
return base.GetValidators(metadata, context, attributes);
}
}
Simply add/change the globalization tag in web.config:
<system.web>
<globalization uiCulture="your culture"/>
Related
Introduction
I have a class, which has properties localized through data annotations by a resource file, like this:
[Display(Name = nameof(ResxFile.SomeProperty), ResourceType = typeof(ResxFile)]
public string SomeProperty { get; set; }
Where ResxFile is a .resx file, and I'm using Name = nameof(ResxFile.SomeProperty) to get the name property of the resource file row (to make it strongly typed), and ResourceType = typeof(ResxFile) to indicate which is the resource file to use.
In my ResxFile, for the previous example, I would have something like:
Name | Value
------------------------------------------
SomeProperty | Some property localized
And in this way, for example, I can bind my class to a grid, and the column names will be localized according to the content of the resource file.
Question
I'm working with a kind of dynamic mapping, where I use the property names of my classes, and in general I get them with something like this: string propertyName = typeof(MyClassName).GetProperty(myPropertyName).Name
In this case, what I need, is the localized name assigned to that property, according to the resource file. To be more clear: string localizedPropertyName = typeof(MyClassName).GetProperty(myPropertyName).SomeMagic(); where localizedPropertyName would be "Some property localized"
I've been looking in CustomAttributes, but I only could get display name attributes, and some types, and that lands me in another job, which is invoke the resource file to get the value of a name.
I'm using .Net Framework 4.7.
Thanks in advance!
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://learn.microsoft.com/en-us/dotnet/api/system.reflection.customattributedata.namedarguments?view=netcore-3.1
Hopefully this helps you as in the past I have used this method to translate keys in a database. This does not cover the pulling out data from the resource file, but you can either declare [Display] attribute on a property and use the full name as the key or give a static string as the key to use later in the meta data provider.
Add your own meta data providor
public class MyMetadataProvider : DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes,
Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
var metadata = new ModelMetadata(this, containerType, modelAccessor, modelType, propertyName);
//Do what ever you want here to translate either by the property name or the display attribute key
if (propertyName != null)
{
var displayAttribute = attributes.OfType<DisplayAttribute>().FirstOrDefault();
if (displayAttribute != null)
{
//Translate using the key you provided before however you like
metadata.DisplayName = TranslateFunction(displayAttribute.Name);
}
}
return metadata;
}
}
add the translation key to the prop
[Display(Name = "ResourceKey")]
public string Something { get; set; }
Add this to application start up
protected void Application_Start(object sender, EventArgs e)
{
ModelMetadataProviders.Current = new MyMetadataProvider();
}
In an ASP.NET MVC project I'm trying to validate several fields against a regular expression. However I would like to have only one validation message displayed if any of them fails (and highlight the ones failing).
I can make a custom validation for that, and annotating one of them with the id's of the rest kind of work although only highlights the one decorated with the attribute. But it looks to me as an overkill as I just want to reduce the message to one.
In the same form I will try to do the same for two checkboxes, both must be checked.
So as far as I understand if I use the summary to put a generic message won't be able to tell if it's failing for the checkboxes or for the fields.
Is there a simple way of achieving this?
You could write a custom validation that targets your entire view model class. I wrote the following when I wanted to check that at least one property was set. You can see that this targets the class itself and should therefore give you one message.
/// <summary>
/// A configurable class wide attribute that is used to determine if at least one property of a class has received a value.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class SingleValueConfigurableAttribute : ValidationAttribute, IClientValidatable
{
public SingleValueConfigurableAttribute(string errorKey)
{ ErrorMessage = Properties.Settings.Default[errorKey].ToString(); }
public override bool IsValid(object value)
{
var typeInfo = value.GetType();
var propertyInfo = typeInfo.GetProperties();
return propertyInfo.Any(property => null != property.GetValue(value, null));
}
public override string FormatErrorMessage(string name)
{
return ErrorMessage;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
yield return new ModelClientValidationRule
{
ErrorMessage = String.IsNullOrEmpty(ErrorMessage) ? FormatErrorMessage(metadata.DisplayName) : ErrorMessage,
ValidationType = "enforcetrue"
};
}
}
In my application I have many properties like
[DisplayFormat(ApplyFormatInEditMode=false,ConvertEmptyStringToNull=false,DataFormatString="{0:0.00}")]
public decimal somedecimalvalue { get; set; }
Is there any way i can generalize this whenever a decimal property is created above format is applied to it
You can manually assign metadata for decimal properties in your models by creating custom DataAnnotationsModelMetadataProvider:
public class DecimalMetadataProvider : DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
if (propertyName == null)
return metadata;
if (metadata.ModelType == typeof(decimal))
{
// Given DisplayFormat Attribute:
// if ApplyFormatInEditMode = true
// metadata.EditFormatString = "{0:0.00}";
// for DataFormatString
metadata.DisplayFormatString = "{0:0.00}";
// for ConvertEmptyStringToNull
metadata.ConvertEmptyStringToNull = false;
}
return metadata;
}
}
And then register this provider in Global.asax.cs in Application_Start() method:
ModelMetadataProviders.Current = new DecimalMetadataProvider();
Then you can remove DisplayFormat attribute from decimal properties. Note that this won't affect other properties and you can safely add other data annotations on your decimal properties.
Read more about MetaData class and its properties.
Happy coding! :)
Cant understand exactly what you wanted to know. describe some more
I think there is no direct feature to do that. How about having one Interface and implement the same for all your entities?
You have to check out using Model Templates.
If you want to customize the way in which the decimal is displayed throughout the application then you have to create a partial view named decimal.cshtml (if Razor) and store it in the Views/Shared/DisplayTemplates folder.
In the decimal.cshtml you can control the way in which the model decimal is being displayed.
Like that, to customize the way in which it is displayed for editing then you have to create another view with the same name and dump it into the Views/Shared/EditorTemplates folder.
The MVC framework takes care of the remaining things.
Ex.
Views/Shared/DisplayTemplates/decimal.cshtml
#model decimal
<p>
#Model.ToString("0.00")
</p>
my DB model has the column name with "_" in the field name.
is there a way to dislay these values in Camel Case when displaying in the form? like
"BusinessName"
i am using asp.net MVC3 with Razor
#Html.LabelFor(model => model.business_name)
Could you not just use the DisplayAttribute?
You need a new metadataprovider which can inherit from the default one like this:
using System;
using System.Web.Mvc;
using System.Collections.Generic;
public class MyMetadataProvider : DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
if (metadata.DisplayName == null)
metadata.DisplayName = GetDisplayNameFromDBName(propertyName);
return metadata;
}
private string GetDisplayNameFromDBName(string propertyName)
{
return ...;
}
}
Register it in global.asax like this:
ModelMetadataProviders.Current = new MyMetadataProvider();
You just need to provide the implementation of GetDisplayNameFromDBName to provide the correct display name given the property name
Why don't you just use #Html.Label("Business name")?
I have created a custom ResourceProvider to pull localization information from a database. I now want to use DataAnnotation to add validation to the model.
DataAnnotation has ErrorMessageResourceType and ErrorMessageResourceName properties but ErrorMessageResourceType only accepts System.Type (i.e. a compiled resource file)
Is there any way to get DataAnnotation to use the custom ResourceProvider?
I realize this is an old question, but wanted to add a bit. I found myself in the same situation and there doesn't appear to be any documentation/blogumentation on this topic. Nevertheless, I figured out a way to use a custom resource provider, with one caveat. The caveat is that I'm in an MVC application so I still have HttpContext.GetLocalResourceObject() available. This is the method that asp.net uses to localize items. The absence of the resource object doesn't stop you from writing our own solution, even if its a direct query of the DB tables. Nevertheless, I thought it was worth pointing out.
While I'm not terribly happy with the following solution, it seems to work. For each validation attribute I want to use I inherit from said attribute and overload the IsValid(). The decoration looks like this:
[RequiredLocalized(ErrorMessageResourceType= typeof(ClassBeginValidated), ErrorMessageResourceName="Errors.GenderRequired")]
public string FirstName { get; set; }
The new attribute looks like this:
public sealed class RequiredLocalized : RequiredAttribute {
public override bool IsValid(object value) {
if ( ! (ErrorMessageResourceType == null || String.IsNullOrWhiteSpace(ErrorMessageResourceName) ) ) {
this.ErrorMessage = MVC_HtmlHelpers.Localize(this.ErrorMessageResourceType, this.ErrorMessageResourceName);
this.ErrorMessageResourceType = null;
this.ErrorMessageResourceName = null;
}
return base.IsValid(value);
}
}
Notes
You need to decorate your code with the derived attribute, not the standard one
I'm using ErrorMessageResourceType to pass the type of the class being validated. By that I mean if I'm in a customer class and validating the FirstName property I would pass typeof(customer). I'm doing this because in my database backend I'm using the full class name (namespace + classname) as a key (the same way a page URL is used in asp.net).
MVC_HtmlHelpers.Localize is just a simple wrapper for my custom resource provider
The (semi-stolen) helper code looks like this ....
public static string Localize (System.Type theType, string resourceKey) {
return Localize (theType, resourceKey, null);
}
public static string Localize (System.Type theType, string resourceKey, params object[] args) {
string resource = (HttpContext.GetLocalResourceObject(theType.FullName, resourceKey) ?? string.Empty).ToString();
return mergeTokens(resource, args);
}
private static string mergeTokens(string resource, object[] args) {
if (resource != null && args != null && args.Length > 0) {
return string.Format(resource, args);
} else {
return resource;
}
}
I have used fluent validation to achieve this. It saves me lots of time. This is what my Globalized validator looks like. It does mean that you don't use data anotations, but sometimes data anotations get a bit big and messy.
Here is an example:
(Errors.Required, Labels.Email and Errors.AlreadyRegistered are in my blobal resources folder.)
public class CreateEmployerValidator : AbstractValidator<CreateEmployerModel> {
public RegisterUserValidator() {
RuleFor(m => m.Email)
.NotEmpty()
.WithMessage(String.Format(Errors.Required, new object[] { Labels.Email }))
.EmailAddress()
.WithMessage(String.Format(Errors.Invalid, new object[] { Labels.Email }))
.Must(this.BeUniqueEmail)
.WithMessage(String.Format(Errors.AlreadyRegistered, new object[] { Labels.Email }));
}
public bool BeUniqueEmail(this IValidator validator, string email ) {
//Database request to check if email already there?
...
}
}
Like I said, it is a move away form data annotations, only because I already have too many annotations on my methods already!
I'll add my findings since I had to fight with this. Maybe it will help someone.
When you derive from RequiredAttribute, it seems to break client side validation. So to fix this I implemented IClientValidatable and implemented the GetClientValidationRules method. Resources.GetResources is static helper method I have that wraps around HttpContext.GetGlobalResourceObject.
The custom required attribute:
public class LocalizedRequiredAttribute : RequiredAttribute, IClientValidatable
{
public LocalizedRequiredAttribute(string resourceName)
{
this.ErrorMessage = Resources.GetResource(resourceName);
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
yield return new ModelClientValidationRule
{
ErrorMessage = this.ErrorMessage,
ValidationType= "required"
};
}
}
Usage:
[LocalizedRequired("SomeResourceName")]
public string SomeProperty { get; set; }
And my Resources helper if anyone is interested:
public class Resources
{
public static string GetResource(string resourceName)
{
string text = resourceName;
if (System.Web.HttpContext.Current != null)
{
var context = new HttpContextWrapper(System.Web.HttpContext.Current);
var globalResourceObject = context.GetGlobalResourceObject(null, resourceName);
if (globalResourceObject != null)
text = globalResourceObject.ToString();
}
return text;
}
}