3

我有一个自定义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和提供者的文章,但找不到与我正在尝试做的类似的事情。

那么我在这里有什么选择呢?我可以朝哪个方向搜索以更接近我想做的事情?

编辑:

我看了这个问题(非常相似):我可以在 C# 中实现从派生类复制的“复制构造函数”吗? 但它所做的是属性的副本,我想避免这种情况。在这篇文章的最后一个答案中,有一些关于PopulateMetadata但我在基本提供程序中找不到该功能...

4

2 回答 2

2

我建议你看看MvcExtensions: http: //mvcextensions.github.io/

它的主要部分之一就是您正在做的事情 - 广泛的模型元数据配置/使用。您可能会在那里找到很多答案,或者只是将其视为“即用型”解决方案。

于 2013-05-18T18:26:06.427 回答
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));
    }
}
于 2013-05-21T21:55:58.373 回答