我有一个自定义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
的内容,但我在基础提供程序中找不到该函数...
答案 0 :(得分:2)
我建议你看看MvcExtensions
:http://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));
}
}