Imagine that you have a legacy project Asp.NET MVC version 5, which for many years. It uses samopisny ResourceProvider, which can get a resource from the database and show it on the UI. Depending on various conditions (for example, from where the user came to the site), resources will be shown different. Now it's time to make all tightly hardcoded strings in the Data Annotation attributes, such as:
[Display(Name = "Username")] [Required(ErrorMessage = "Please enter the username")] [StringLength(64, ErrorMessage = "Username cannot exceed 64 characters")] public string Username{ get; set; }
could also get their values using ResourceProvider. How to do this using a bit of inheritance and customization available in Asp.NET MVC, I will show under the cat.
All the necessary logic to find exactly the resource that needs to be shown in the current situation is encapsulated in the
ResourceProvider class and related classes, so we just need to call its following method:
string resource = ResourceProvider.GetResource(name);
Display attribute
The DisplayAttribute , which appeared in the .NET Framework 4, unlike its predecessor,
DisplayNameAttribute , is marked as
sealed , so we cannot use the magic of inheritance in this case. Here we take advantage of the fact that the
DisplayName is also available in the
System.Web.Mvc.ModelMetadata class. Create a class that inherits from
DataAnnotationsModelMetadataProvider , override the
CreateMetadata method to replace the
DisplayName with that obtained from the
ResourceProvider . As an IoC container we use Ninject. To set the name of the resource, create your own
DisplayResourceAwareAttribute attribute.
public class DisplayResourceAwareAttribute : Attribute { public string ResourceName { get; set; } public DisplayResourceAwareAttribute() { } } public class ResourceAwareMetadataProvider : DataAnnotationsModelMetadataProvider { [Inject] public ResourceProvider ResourceProvider { get; set; } protected override ModelMetadata CreateMetadata( IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName) { var modelMetadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName); var displayNameAttr = attributes.SingleOrDefault(a => typeof(DisplayResourceAwareAttribute) == a.GetType()) as DisplayResourceAwareAttribute; if (displayNameAttr != null) { modelMetadata.DisplayName = ResourceProvider.GetResource(displayNameAttr.ResourceName); } return modelMetadata; } }
The above code looks for an attribute of the
DisplayResourceAwareAttribute type among the attributes, and if there is one, we will update the
DisplayName property with the
value obtained using the ResourceProvider.GetResource. Now we have to use the new MetadataProvider instead of the standard one. To do this, in the
Application_Start method in the Global.asax.cs file, add the following line:
protected void Application_Start() { ... ModelMetadataProviders.Current = DependencyResolver.Current.GetService<ResourceAwareMetadataProvider>(); ... }
Now, in the model, we can eliminate the explicit description of the field by replacing the standard attribute with ours:
[DisplayResourceAware(ResourceName = "UsernameResource")] [Required(ErrorMessage = "Please enter the username")] [StringLength(64, ErrorMessage = "Username cannot exceed 64 characters")] public string Username{ get; set; }
No additional changes in views, controllers are required.
Validation Attributes
With validation attributes such as
Required ,
StringLength , etc. simpler, because we can inherit our attributes from them and in the constructor set the
ErrorMessage property to the
value we need.