如何扩展DataAnnotationsModelMetadata

时间:2013-05-18 17:56:19

标签: c# asp.net-mvc-4 razor

我有一个自定义DataAnnotationsModelMetadataProvider,我想返回一个派生自ModelMetadata的对象,这样我就可以在剃刀模板中添加额外的属性。

到目前为止,我的自定义提供程序仅覆盖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;
}        

以便从ModelMetadataAttribute派生的每个属性都可以执行一些自定义操作(实际上它只会添加AdditionalValues

但由于我的几乎所有属性都主要为我在剃刀模板中生成的html元素添加属性,所以我希望视图中的ModelMetadata包含我想要添加的属性的字典(和其他一些事情)。

所以我需要一个继承自DataAnnotationsModelMetadata

的类

我不能只调用base.CreateMetadata函数,因为它不会正确地转换为我的派生类。

我考虑过将DataAnnotationsModelMetadata函数返回的base.CreateMetadata公共属性的副本复制到我的派生类中,但是我可能会丢失信息,因此它似乎不安全。

我想到的另一种方法是复制/粘贴CreateMetadata的基本代码并添加我的逻辑,但它看起来很丑......(而且我只有mvc3源代码,所以它可能在mvc4中有所改变)

还考虑从ViewDataDictionnary继承,以便我可以提供自定义元数据类而不是标准元数据类,但我不知道如何执行此操作。 (我也承认我对这个特定的问题没有太多了解)

我查看了很多关于DataAnnotations和提供商的文章,但找不到类似于我正在尝试做的事情。

那么我的选择是什么?我可以在哪个方向搜索以接近我想要做的事情?

修改

我看了这个问题(非常相似):Can I achieve a 'copy constructor' in C# that copies from a derived class? 但它的作用是属性的副本,我想避免这种情况。 在这篇文章的最后一个答案中有一些关于PopulateMetadata的内容,但我在基础提供程序中找不到该函数...

2 个答案:

答案 0 :(得分:2)

我建议你看看MvcExtensionshttp://mvcextensions.github.io/

其中一个主要部分正是您正在做的事情 - 广泛的模型元数据配置/使用。您可以在那里找到很多答案,或者只是将其作为“随时可用”的解决方案。

答案 1 :(得分:0)

经过一番思考,我实际上找到了一个不需要使用MvcExtensions的解决方案,并将基类中包含的所有信息复制到派生类中。

由于DataAnnotationsModelMetadata及其基类除了初始化某些私有值或受保护值之外没有做任何事情,因此不存在副作用的风险。

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

如果你想要一个通用的解决方案来获得派生类中的基类字段,并且你不能只使用继承,因为你处于同样的情况,只需使用这个类:

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