MVC中的动态模型(非类)元数据提供程序

时间:2013-12-16 01:11:49

标签: c# asp.net-mvc

我们正在开发一个最终用户架构是动态的应用程序(我们有一个很好的商业案例 - 静态模型不能轻易处理它。)

我使用.NET DynamicObject类来允许从代码中轻松地解决这些动态模式对象,并期望这只能使用MVC模型元数据。然而,MVC元数据支持似乎受到了限制,因为它只处理每种类型定义的元数据 - 而不是每个对象,这将是这里的情况。

即使我挖掘并尝试实现我们自己的ModelMetadataProvider,似乎根本没有传入必要的信息 - GetMetadataForProperty方法特别成问题。实际上,我需要访问属性的父对象或容器对象,但传入的所有内容都是类型。

以上主要是从ModelMetadata类中的FromStringExpression方法调用的。这种方法实际上有容器(至少在这种情况下),但不通过它。当它在ViewData中找到有关存储(缓存?)的表达式的视图数据时,将执行此分支。如果失败,它会回退到通过ModelMetadata对象查找它 - 具有讽刺意味的可能对我有用。特别令人恼火的是FromStringExpression方法是静态的,所以我不能轻易地覆盖它的行为。

在绝望中我曾考虑过试图遍历modelAccessor表达式,但这看起来像是一个充其量的kludge而且非常脆弱。

我已经广泛搜索了解决方案。很多人都指出了Brad Wilson关于非类模型的讨论(http://channel9.msdn.com/Series/mvcConf/mvcConf-2011-Brad-Wilson-Advanced-MVC-3),但是如果你看一下实际的代码,你会看到TOO绑定到TYPE而不是对象 - 换句话说就是不是非常糟糕的有用。其他人已指出http://fluentvalidation.codeplex.com/,但这似乎只适用于验证方面,我怀疑同样的问题(绑定类型而不是对象)与上述相同。

例如,我可能有一个包含一系列字段对象的字典对象。这看起来像(非常简化/简化示例):

public class Entity : DynamicObject, ICustomTypeDescriptor
{
    public Guid ID { get; set; }
    public Dictionary<string, EntityProp> Props { get; set; }

    ... DynamicObject and ICustomTypeDescriptor implementation to expose Props as dynamic properties against this Entity ...
}

public class EntityProp
{
    public string Name { get; set; }
    public object Value { get; set; }
    public Type Type { get; set; }
    public bool IsRequired { get; set; }
}

这可以作为视图模式(或其一部分)传递给视图,在我看来我想使用:

@Html.EditorForModel()

有没有人找到解决方法呢?

我已经确定了两种可能的替代方法,但两者都有明显的缺点:

  • 放弃使用MVC ModelMetadata,而是构建一个直接包含必要元数据的视图模型,以及显示这些更复杂的视图模型对象所需的模板。然而,这意味着我必须以不同于“普通”对象的方式对待这些对象,在某种程度上违背了目的并增加了我们需要构建的视图模板的数量。 这是我现在倾向的方法 - 或多或少放弃与MVC ModelMetadata集成的东西
  • 为每个模板化属性生成唯一键,并将其用于属性名称而不是显示名称 - 这将允许ModelMetadataProvider查找与属性相关的元数据,而无需引用其父级。然而,这会在调试时导致相当丑陋的情况,并且再次看起来像是一个大规模的kludge。 我现在尝试了这个的简化版本,它似乎有用,但确实有一些不良行为,例如,如果我想显式绑定到模型的元素,则需要使用无意义的属性名称。
  • 在ModelMetadataProvider中返回包含属性的ModelMetadata对象集合时,将容器记录在与返回的属性关联的ModelMetadataProvider中。 我试过这个,但在这种情况下会忽略返回的属性元数据集合,而FromStringExpression方法则直接转到GetMetadataForProperty方法。

1 个答案:

答案 0 :(得分:3)

可能正在创建自定义ModelMetadataProvider:

public class CustomViewModelMetadataProvider : DataAnnotationsModelMetadataProvider
{

    public override IEnumerable<ModelMetadata> GetMetadataForProperties(object container, Type containerType)
    {
        if (containerType == null)
        {
            throw new ArgumentNullException("containerType");
        }

        return GetMetadataForPropertiesImpl(container, containerType);
    }

    private IEnumerable<ModelMetadata> GetMetadataForPropertiesImpl(object container, Type containerType)
    {
        var propertiesMetadata = new List<ModelMetadata>();
        foreach (EntityProp eprop in ((Entity)container).Props.Values)
        {
            Func<object> modelAccessor = () => eprop;
            propertiesMetadata.add(GetMetadataForProperty(modelAccessor, containerType, eprop.Name));
        }
        return propertiesMetadata;  // List returned instead of yielding, hoping not be needed to re-call this method more than once
    }

    public override ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName) {
        if (containerType == null) {
            throw new ArgumentNullException("containerType");
        }
        if (String.IsNullOrEmpty(propertyName)) {
            throw new ArgumentException(MvcResources.Common_NullOrEmpty, "propertyName");
        }

        return CreateMetadata(null, containerType, modelAccessor, modelAccessor().Type, propertyName);
    }

    protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
    {
        EntityProp eprop = modelAccessor();
        DataAnnotationsModelMetadata result;
        if (propertyName == null)
        {
            // You have the main object
            return base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
        }
        else
        {
            // You have here the property object
            result = new DataAnnotationsModelMetadata(this, containerType, () => eprop.Value, modelType, propertyName, null);
            result.IsRequired = eprop.IsRequired;
        }
        return result;
    }
}

最后,在Global.asax.cs中设置自定义提供程序:

protected void Application_Start()
{
    //...
    ModelMetadataProviders.Current = new CustomViewModelMetadataProvider();
}