Global Resources in ASP.Net MVC Model ErrorMessage Attribute - c#

I am creating web application in ASP.Net MVC 5.
I need to add user defined languages. (So, it can work in any language).
I have added English text/messages in resources file.
While, for other languages , resources will be generated run time in App_GlobalResources folder.
With this custom resources, I can able to show labels (, buttons etc.) as per selected language.
But, I have issue with ErrorMessage , which is given as attribute in properties of model.
Model classes are in class library, and reference of class library project is added in MVC.
So, can't access resources from App_GlobalResources folder.
And, if I add resources under Project of model class, I can give customize message with following code.
[Required(ErrorMessage = "*")]
[System.Web.Mvc.Compare("Password", ErrorMessageResourceType = typeof(Resources), ErrorMessageResourceName = "PasswordCompare")]
public string ConfirmPassword { get; set; }
But, with this code, I can't use App_GlobalResources.
What will be solution in this scenario?

Finally, I got solution:
Created custom attribute class.
[Required(ErrorMessage = "*")]
[CompareCustomAttribute("Password", ClassKey = "Resources", ResourceKey = "PasswordCompare")]
public string ConfirmPassword { get; set; }
Custom Attribute class is inheriting CompareAttribute class.
public sealed class CompareCustomAttribute : System.Web.Mvc.CompareAttribute
{
public CompareCustomAttribute(string otherProperty)
: base(otherProperty)
{
}
public string ResourceKey { get; set; }
public string ClassKey { get; set; }
public override string FormatErrorMessage(string name)
{
return Convert.ToString(HttpContext.GetGlobalResourceObject(this.ClassKey, this.ResourceKey));
}
}
In overridden FormatErrorMessage method, I have put code to get customized error message from Global Resources.

Related

How to use c# 'DisplayNameAttribute' with dynamic values?

Want to display Name & country field in localized language.
In our application, we support various language based on the user preferred language, the translation has to happen for below model properties. Thought of encapsulating the translation with the "custom attribute" class but how to get the "Name", "Country" properties values in the custom attributes, so that I can find the translation word match for the given property value and assign to the 'DisplayName' property of 'DisplayNameAttribute' type.
If 'DisplayNameAttribute' type can't be used for dynamic displayname value, suggest me how the localization can be achieved using c# attributes or there better way to do localization.
P.S: our translation files are stored in the Azure bolob as .json files not as .resx files as part of app build package
class Sample
{
[LocalizedDisplayName()]
public string Name { get; set; }
[LocalizedDisplayName()]
public string Country{ get; set; }
}
internal sealed class LocalizedDisplayName : DisplayNameAttribute
{
private readonly string _dependentPropValue; //how to get this??
private string displayName;
public LocalizedDisplayName(string dependentPropValue)
{
_dependentPropValue = dependentPropValue;
}
public override string DisplayName
{
get
{
return displayName = DoTranslation(dependentPropValue);
}
}
}

How to use localization with data annotation in a separate DTO Assembly (Asp.net Core Web Api)

I have:
a Dto.Library (.Net 5 Library)
a SharedResourceLibrary with Resource.resx (.Net 5 Library)
How can i use the Resource File Messages in conjunction with Data Annotation in my DTO.Library?
The ErrorMessage should be the text from the resx files:
public class MeterForCreationDto
{
[Required(ErrorMessage = "Name must not be empty!")]
public string Name { get; set; }
[Required(ErrorMessage = "Unit must not be empty!")]
public string Unit { get; set; }
}
SharedResourceLibrary: looks like this answer #Shiran Dror
You can create a custom attribute for the properties. Something like this:
public class LocalizedDisplayNameAttribute: DisplayNameAttribute
{
public LocalizedDisplayNameAttribute(string resourceKey)
: base(GetMessageFromResource(resourceId))
{ }
private static string GetMessageFromResource(string resourceKey)
{
// return the translation out of your .rsx files
}
}
and then you need to add
public class MeterForCreationDto
{
[LocalizedDisplayName("Name")]
public string Name { get; set; }
[LocalizedDisplayName("Unit")]
public string Unit { get; set; }
}
but you need to add exactly the same key in the attribute which is in your .rsx file. If your searching for "asp.net localizeddisplayname" there are a lot of different sites with examples.
Some help for creating custom attributes:
https://learn.microsoft.com/en-gb/dotnet/standard/attributes/writing-custom-attributes
Hopefully, it helps. :)
you can use:
[Required(ErrorMessageResourceName ="NameInRecourseFile",ErrorMessageResourceType =typeof(RecourseFileName))]
also u need to make the Recourse file public from this menu:
finally u need to have a default Resource file[for your default culture] Default resource file name shouldn't have any specific culture (.en،.fr،....)
SharedService.en.resx => SharedService.resx note .en is the default culture in your app
so it will like these:
SharedService.resx[for your default culture]
SharedService.ar.resx
SharedService.fr.resx
Hope this helped you.
Best wishes.

Localise Display DataAnnotation without the Name Attribute in .NET Core 5 ViewModel

I've
[Required]
[Display(Name ="Email")]
public string Email { get; set; }
[Required]
[Display (Name = "Password")]
public string Password { get; set; }
In my ViewModel. I'm able to localize this. Additionally, I was able to put a different localization to the 'Required' message [without specifying ResourceType and ResourceName manually] than the default Microsoft message using the resource file. How I did that? Here is the link:
https://stackoverflow.com/a/41385880/931305
Now, I want to remove the 'Name' attribute of the 'Display'. Because most of the time Display Name is always going to be the same as the actual Property name. If you notice both are 'Email'/'Password'. So it will make the code looking clean.
I was able to do this in classic ASP.NET MVC. Here is the link:
https://haacked.com/archive/2011/07/14/model-metadata-and-validation-localization-using-conventions.aspx/
Now, how do I do this in .NET Core 5? I'm unable to use IValidationAttributeAdapterProvider to inject 'Display'. [I was getting all 'validation' attributes, but not Display]
The best and standard solution is to using localization in ASP.NET Core application.
In summary, the steps to localize your application are rough as follows:
Add the required localization services
Configure the localization middleware and if necessary a culture
provider
Inject IStringLocalizer into your controllers and services to
localize strings
Inject IViewLocalizer into your views to localize strings in views
Add resource files for non-default cultures
Add a mechanism for users to choose their culture
Take a look at this article for a detailed walkthrough.
You could write an attribute like so
public sealed class MyDisplayAttribute : DisplayNameAttribute, IModelAttribute
{
private string _name = string.Empty;
public MyDisplayAttribute(string displayName)
{
_name= displayName;
}
public override string DisplayName => _name;
public string Name => nameof(MyDisplayAttribute);
}
usage:
public class MyModel
{
[MyDisplay("MyString")]
public string MyString { get; set; }
}

Attributes added by custom ModelMetadataProvider are not being considered in model-binding

Disclaimer: this is a long one. Unreasonable knowledge of ASP.NET Core MVC internals is almost certainly required. Here be dragons.
Background
I am trying to implement a method for augmenting type metadata in ASP.NET MVC Core.
The reason I want to do this is that my data models are used by multiple projects, so I've shared them by placing them in a NuGet package:
// defined in NuGet package
public class MyModel
{
public int MyProperty { get; set; }
public MyNestedModel NestedModel { get; set; }
}
public class MyNestedModel
{
public bool NestedProperty { get; set; }
}
^ Listing 1
However, some of the projects will need to apply additional metadata to the model types - for example, in the case of an ASP.NET Core project, these models will be used as inputs and therefore participate in model-binding, so they require FromQuery, FromHeader etc. attributes applied. I obviously cannot do this in the package, as different consumer projects will need to apply different attributes depending on their use cases.
The simplest and mostly guaranteed-to-work way is to modify all model properties to be virtual and then have subclasses that override those properties as necessary to add attributes:
// defined in NuGet package
public class MyModel
{
public virtual int MyProperty { get; set; }
public virtual MyNestedModel NestedModel { get; set; }
}
public class MyNestedModel
{
public virtual bool NestedProperty { get; set; }
}
// defined in ASP.NET MVC project consuming above package
public class MyViewModel : MyModel
{
[FromQuery(Name = "foo")]
public override int MyProperty { get; set; }
public new // can't use override, as type is different
MyNestedViewModel NestedModel { get; set; }
}
[Bind(Prefix = "")]
public class MyNestedViewModel : MyNestedModel
{
[FromQuery(Name = "bar")]
public override bool NestedProperty { get; set; }
}
^ Listing 2
I don't want to do this, because in every model that has a child model property, that child model has to be subclassed, and then it cannot be used as an override but has to be re-declared with new - and the semantics of new won't work for me here. Also, I don't really intend for the model types to be subclassed.
I'm aware of ModelMetadataTypeAttribute but the intended usage of that attribute is on the model type to be augmented, not the type doing the augmenting. Since in my case there's no way to know ahead of time what the actual metadata types will be (as they're defined in the consuming projects), I cannot use ModelMetadataTypeAttribute. Subclasses and partial classes will also not work - ModelMetadataTypeAttribute only applies to the class specified, partial cannot span across assemblies.
Solution
Essentially, a "reverse" version of ModelMetadataTypeAttribute that is applied on the type doing the augmenting, pointing back to the model type to be augmented:
// defined in ASP.NET MVC project consuming package from Listing 1
[ReverseModelMetadataType(typeof(MyModel))]
public class MyModelMetadata
{
[FromQuery(Name = "foo")]
public int MyProperty { get; set; }
public MyNestedModel NestedModel { get; set; }
}
[ReverseModelMetadataType(typeof(NestedModel))]
[Bind(Prefix = "")]
public class NestedModelMetadata
{
[FromQuery(Name = "bar")]
public bool NestedProperty { get; set; }
}
^ Listing 3
The intention is that at runtime, the model-binding infrastructure will pick up that MyModel.MyProperty should be bound using [FromQuery(Name = "foo")], i.e. from the query string as a variable named foo. Similarly, MyModel.NestedModel.NestedProperty should be simply bound as bar from the query string.
In order to make this work I've implemented a custom ModelMetadataProvider that inherits DefaultModelMetadataProvider and overrides the CreatePropertyDetails method:
// defined in same ASP.NET Core MVC project as Listing 3
public class MyModelMetadataProvider : DefaultModelMetadataProvider
{
protected override DefaultMetadataDetails[] CreatePropertyDetails(ModelMetadataIdentity key)
{
var defaultPropertyDetails = base.CreatePropertyDetails(key);
// check if the key.ModelType should be augmented
// if so, mutate the relevant element(s) of the defaultPropertyDetails array to do the augmentation
return defaultPropertyDetails;
}
}
The mutation part effectively changes the relevant DefaultMetadataDetails.ModelAttributes member to have the additional attributes applied to the properties of the type(s) decorated with ReverseModelMetadataTypeAttribute. I then get ASP.NET MVC Core to use my provider instead of its DefaultModelMetadataProvider via the following in Startup.cs#ConfigureServices:
services.AddSingleton<IModelMetadataProvider, MyModelMetadataProvider>();
I have verified that provider is registered and the mutation code works correctly: MyModelMetadataProvider.CreatePropertyDetails is hit and the defaultPropertyDetails returned do contain the extra attributes applied by my *ModelMetadata classes from Listing 3.
Problem
Model binding does not work: ASP.NET Core MVC essentially behaves as if the attributes added via Listing 3 do not exist, and I don't know why. As far as I'm aware, the results of the calls to the various IModelMetadataProvider methods are cached then used for all subsequent lookup of metadata that's required, including for the purposes of binding models.
I'm hoping that somebody can provide some advice on how to get this to work, before I have to go through the pain of stepping through the ASP.NET Core source myself.

How to use DataAnnotations ErrorMessageResourceName with custom Resource Solution

I'm building a MVC web application with C#. Since the site will be multilingual, I've implemented my own ResourceManager. This class is responsible for fetching the required resource strings from a database/cache depending on the currents thread culture and works fine so far.
My problem is, I'd like to use the my custom ResourceManager solution to fetch validation error messages when for example using the Required Attribute on a property. Can this be done?
The RequiredAttribute allows to use a custom resource manager:
[Required(
ErrorMessageResourceType = typeof(CustomResourceManager),
ErrorMessageResourceName = "ResourceKey")]
public string Username { get; set; }
UPDATE:
Another possibility is to write your custom attribute:
public class CustomRequiredAttribute : RequiredAttribute
{
public override string FormatErrorMessage(string name)
{
return YourCustomResourceManager.GetResource(name);
}
}

Categories