Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Override MVC Model Display Name Annotation with Custom Functionality

I have the following method where I read from a key-value XML file. I pass in a key and am returned a value where I used to display on my view.

public static class TextManager
{
    public static string GetValue(string key)
    {
        string returnVal = null; 
        XmlSerializer serializer = new XmlSerializer(typeof(Entries));
        string path = HttpContext.Current.Server.MapPath("/App_Data/text-key-value.xml");
        if (File.Exists(path))
        {
            Entries entries = (Entries)serializer.Deserialize(File.OpenRead(path));
            var entry = entries.Where(u => u.Key == key).FirstOrDefault();
            if (entry != null)
            {
                returnVal = entry.Value;
            }
        }
        return returnVal;
    }
}

Basically I want to be able to use this method in my model class as a data-annotation that will pull directly from my site text file and set to the display name property.

For instance I want to replace

[Display(Name = "Reference Code")]
public string ReferenceCode { get; set; }

With this

[DisplaySiteText("ReferenceCodeKey")]
public string ReferenceCode { get; set; }

DisplaySiteText would pass the string reference "ReferenceCodeKey" to the GetValue method, file the reference in the file and then set the standard Display name attribute to whatever was in the file.

How do I create my own custom model annotation to do this, I've written custom validation annotations in the past by creating a class that inherits from ValidationAttribute, but I don't think that will work in this case.

like image 746
TroySteven Avatar asked Dec 17 '22 16:12

TroySteven


2 Answers

You can inherit DisplayNameAttribute for this purpose

public class DisplaySiteTextAttribute : DisplayNameAttribute
{
    private string _key;

    public DisplaySiteTextAttribute(string key)
    {
        _key = key;
    }

    public override string DisplayName
    {
        get
        {
            return TextManager.GetValue(_key);
        }
    }
}
like image 187
Alexander Avatar answered Dec 24 '22 02:12

Alexander


There are several options to customize model metadata:

  • Customize the way that framework provides metadata. (Create ModelMedatadaProvider)
  • Create new Metadata attributes. (Implement IMetadataAware)
  • Modify existing attributes. (Derive existing attributes.)

The 3rd option has been discussed in the other answer. Here in this post, I'll share first and second options.

Option 1 - Customize the way that framework provides metadata

You can change the logic of getting display text without changing the attribute.

In fact it's responsibility of ModelMetaDataProvider to get mete data for model, including display text for properties. So as an option, you can keep the Display attribute intact and instead, create a new model metadata provider and return property metadata from a different source.

To do so, you can create a new metadata provider by deriving from DataAnnotationsModelMetadataProvider. Then override GetMetadataForProperty and call base, to get metadata. Then change DisplayName based on your logic by reading from your text manager.

You also need to register the new metadata provider as ModelMetadataProviders.Current in App_Start.

using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web.Mvc;
public class MyCustomModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
    protected override ModelMetadata GetMetadataForProperty(Func<object> modelAccessor,
        Type containerType,
        PropertyDescriptor propertyDescriptor)
    {
        var metadata = base.GetMetadataForProperty(modelAccessor, 
            containerType, propertyDescriptor);
        var display = propertyDescriptor.Attributes
            .OfType<DisplayAttribute>().FirstOrDefault();
        if (display != null)
        {
            metadata.DisplayName = TextManager.GetValue(display.Name);
        }
        return metadata;
    }
}

And then register it in Application_Start():

ModelMetadataProviders.Current = new MyCustomModelMetadataProvider();

For more information take a look at DataAnnotationsModelMetadataProvider.cs source code in ASP.NET MVC sources.

This approach is useful when you want to change the way that you provide metadata for model. For example when you want to load display name and description from an external file rather than resources, without changing existing attributes.

Option 2 - Create new Metadata attributes

Another standard solution for creating metadata-aware attributes is creating an attribute and implementing IMetadataAware interface. Then in implementation of OnMetadataCreated you can easily set properties of metadata.

This approach doesn't need to register a new meta data provider. This approach is supported by the default metadata provider and is useful for creating new metadata-aware attributes:

using System;
using System.Web.Mvc;
public class CustomMetadataAttribure : Attribute, IMetadataAware
{
    public string Key { get; set; }
    public CustomMetadataAttribure(string key) => this.Key = key;
    public void OnMetadataCreated(ModelMetadata metadata)
    {
        metadata.DisplayName = TextManager.GetValue(this.Key);
    }
}

This approach is useful when you want to extend metadata attributes and add a few more attributes. For example when you want to add some attributes to control rendering. You can set ModelMetadata properties or add some new values to its AdditionalValues dictionary.

like image 44
Reza Aghaei Avatar answered Dec 24 '22 00:12

Reza Aghaei