How can I extend DataAnnotationsModelMetadata

I have a custom DataAnnotationsModelMetadataProvider

one and I would like to return an object obtained from ModelMetadata

in order to have additional properties in my razor templates.

So far, my custom provider is only overriding the function CreateMetadata

:

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);

    ModelMetadataAttribute mma;            
    foreach (Attribute a in attributes)
    {
        mma = a as ModelMetadataAttribute;
        if (mma != null)
            mma.Process(modelMetadata);                
    }

    return modelMetadata;
}        

      

so that every attribute derived from ModelMetadataAttribute

can do some custom action (in fact it only adds AdditionalValues

)

But since almost all of my attributes basically add attributes to the html elements that I generate in my razor template, I would like the ModelMetadata

view to contain the dictionnary attributes that I want to add (and some other stuff).

I need a class that inherits from DataAnnotationsModelMetadata

.

I can't just call the function base.CreateMetadata

as it won't be correctly applied to my derived class.

I was thinking about making a copy of the public properties DataAnnotationsModelMetadata

returned by the function base.CreateMetadata

into my derived class, but I might lose information to make it look unsafe.

Another way I thought was to copy / paste the base code CreateMetadata

and add my logic, but that seems ugly ... (and I only have mvc3 sources, so it could have been changed in mvc4)

Also thought about inheriting from ViewDataDictionnary

so that I can provide my own metadata class instead of the default one, but I don't know how. (also I admit that I didn't know much about this particular issue)

I have looked through a lot of articles about DataAnnotations

and suppliers but could not find something similar to what I am trying to do.

So what are my options? In what direction can I look to get closer to what I want to do?

EDIT:

I looked at this question (pretty similar): Can I get a "copy constructor" in C # that copies from a derived class? but what it does is copy the properties and I wanted to avoid that. The last answer in this post has something about PopulateMetadata

, but I cannot find this feature in the underlying provider ...

+3


source to share


2 answers


After some thinking, I actually found a solution that doesn't need to be used MvcExtensions

and copies all the information contained in the base class to the derived one.

Since DataAnnotationsModelMetadata

its base classes do nothing, but initialize some private or protected values, there is no risk of side effects for this.

public class ArtifyModelMetaDataProvider : DataAnnotationsModelMetadataProvider
{
    private static List<Tuple<FieldInfo, FieldInfo>> _fieldsMap;

    static ArtifyModelMetaDataProvider()
    {
        _fieldsMap = new List<Tuple<FieldInfo, FieldInfo>>();
        foreach (FieldInfo customFI in GetAllFields(typeof(ArtifyModelMetadata)))
            foreach (FieldInfo baseFI in GetAllFields(typeof(DataAnnotationsModelMetadata)))
                if (customFI.Name == baseFI.Name)
                    _fieldsMap.Add(new Tuple<FieldInfo, FieldInfo>(customFI, baseFI));
    }

    private static List<FieldInfo> GetAllFields(Type t)
    {
        List<FieldInfo> res = new List<FieldInfo>();

        while (t != null)
        {
            foreach (FieldInfo fi in t.GetFields(BindingFlags.NonPublic | BindingFlags.Instance))
                if (!fi.IsLiteral)
                    res.Add(fi);
            t = t.BaseType;
        }

        return res;
    }

    private static void CopyToCustomMetadata(ModelMetadata baseMetadata, ArtifyModelMetadata customMetadata)
    {
        foreach (Tuple<FieldInfo, FieldInfo> t in _fieldsMap)
            t.Item1.SetValue(customMetadata, t.Item2.GetValue(baseMetadata));
    }

    protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
    {
        ArtifyModelMetadata modelMetadata = new ArtifyModelMetadata(this, containerType, modelAccessor, modelType, propertyName);
        CopyToCustomMetadata(base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName), modelMetadata);

        ModelMetadataAttribute mma;
        Dictionary<string, string> htmlAttributes;
        object tmp;
        foreach (Attribute a in attributes)
        {
            mma = a as ModelMetadataAttribute;
            if (mma != null)
            {
                mma.Process(modelMetadata);
                htmlAttributes = mma.GetAdditionnalHtmlAttributes();

                if (htmlAttributes != null)
                {
                    foreach (KeyValuePair<string, string> p in htmlAttributes)
                    {
                        tmp = null;
                        tmp = modelMetadata.AdditionnalHtmlAttributes.TryGetValue(p.Key, out tmp);
                        if (tmp == null)
                            modelMetadata.AdditionnalHtmlAttributes.Add(p.Key, p.Value);
                        else
                            modelMetadata.AdditionnalHtmlAttributes[p.Key] = tmp.ToString() + " " + p.Value;
                    }
                }
            }
            if (mma is TooltipAttribute)
                modelMetadata.HasToolTip = true;
        }

        return modelMetadata;
    }
}

public class ArtifyModelMetadata : DataAnnotationsModelMetadata
{

    public bool HasToolTip { get; internal set; }

    public Dictionary<string, object> AdditionnalHtmlAttributes { get; private set; }

    public ArtifyModelMetadata(DataAnnotationsModelMetadataProvider provider, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
        : base(provider, containerType, modelAccessor, modelType, propertyName, null)
    {
        AdditionnalHtmlAttributes = new Dictionary<string, object>();
    }
}

      



And if you want a generic solution to get the base class fields in your derived, and you can't just use inheritance because you're in the same situation I was in, just use this class:

public abstract class GenericBaseCopy<Src, Dst> where Dst : Src
{
    private static List<Tuple<FieldInfo, FieldInfo>> _fieldsMap;

    static GenericBaseCopy()
    {
        _fieldsMap = new List<Tuple<FieldInfo, FieldInfo>>();
        foreach (FieldInfo customFI in GetAllFields(typeof(Dst)))
            foreach (FieldInfo baseFI in GetAllFields(typeof(Src)))
                if (customFI.Name == baseFI.Name)
                    _fieldsMap.Add(new Tuple<FieldInfo, FieldInfo>(customFI, baseFI));
    }

    private static List<FieldInfo> GetAllFields(Type t)
    {
        List<FieldInfo> res = new List<FieldInfo>();

        while (t != null)
        {
            foreach (FieldInfo fi in t.GetFields(BindingFlags.NonPublic | BindingFlags.Instance))
                if (!fi.IsLiteral)
                    res.Add(fi);
            t = t.BaseType;
        }

        return res;
    }

    public static void Copy(Src baseClassInstance, Dst dstClassInstance)
    {
        foreach (Tuple<FieldInfo, FieldInfo> t in _fieldsMap)
            t.Item1.SetValue(dstClassInstance, t.Item2.GetValue(baseClassInstance));
    }
}

      

0


source


I suggest you take a look at MvcExtensions

: http://mvcextensions.github.io/



One of the main parts is exactly what you are doing - extensive configuration / use of model metadata. You may find many answers there, or just accept this as a "turnkey" solution.

+2


source







All Articles