【问题标题】:Dynamic Model (non-class) Metadata Provider in MVCMVC 中的动态模型(非类)元数据提供程序
【发布时间】:2013-12-23 19:05:41
【问题描述】:

我们正在开发一个最终用户模式是动态的应用程序(我们有一个很好的商业案例 - 这不是静态模型可以轻松处理的)。

我使用了 .NET DynamicObject 类来允许从代码中轻松处理这些动态模式对象,并希望这仅适用于 MVC 模型元数据。然而,MVC 元数据支持似乎受到了阻碍,因为它只处理按类型定义的元数据——而不是每个对象,这里就是这种情况。

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

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

无奈之下,我曾考虑尝试遍历 modelAccessor 表达式,但这似乎充其量只是一个杂物,而且极其脆弱。

我已经广泛搜索了这个问题的解决方案。许多人提到了 Brad Wilson 关于非类模型的演讲 (http://channel9.msdn.com/Series/mvcConf/mvcConf-2011-Brad-Wilson-Advanced-MVC-3),但是如果您查看提供的实际代码,您会发现它也绑定到 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 模型元数据的东西集成
  • 为每个模板化属性生成一个唯一键,并将其用于属性名称而不是显示名称 - 这将允许 ModelMetadataProvider 查找与该属性相关的元数据,而无需引用其父级。然而,这会在调试时导致相当丑陋的情况,并且再次看起来像一个大规模的kludge。 我现在尝试了这个的简化版本,它似乎可以工作,但确实有一些不良行为,例如如果我想显式绑定到模型的元素,则需要使用无意义的属性名称。
  • 在 ModelMetadataProvider 返回包​​含属性的 ModelMetadata 对象集合时,在 ModelMetadataProvider 中记录与这些返回的属性关联的容器。 我已经尝试过了,但是在这种情况下,这个返回的属性元数据集合被忽略了,FromStringExpression 方法直接转到 GetMetadataForProperty 方法。

【问题讨论】:

  • MVC 是开源的。您可以研究用自己的代码替换 ModelMetadata 功能。
  • 我实际上已经看过了。问题是在与 ModelMetaDataProvider 相关的 MVC 源中,我将覆盖/替换的实际公共方法没有传递必要的信息(即容器对象)来实际查找和返回相关元数据。
  • 只是要清楚 - GetMetadataForProperty 方法通过容器的类型,但不是容器本身。因此,如果元数据因容器(而不仅仅是其类型)而异,那么您就没有足够的信息来找到它。
  • 因此,作为开源,您可以找到调用该方法的位置并将正确的数据添加到函数调用中。
  • 认为这实际上是 MVC 设计中的一个缺陷(诚然,这可能不会影响很多人)- 将不得不看到报告它。

标签: c# asp.net-mvc


【解决方案1】:

也许创建一个自定义 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();
}

【讨论】:

  • 正如我所说,我已经(广泛地)研究过编写我们自己的 ModelMetadataProvider。问题是它没有传递做我想做的事情所需的信息(即它没有传递容器模型,只有属性值的访问器)。这意味着,在这种情况下,我最多可以为每个类而不是每个对象定义元数据。
猜你喜欢
  • 1970-01-01
  • 2013-07-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多