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

标签 c# asp.net-mvc

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

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

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

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

无奈之下,我曾考虑尝试遍历 modelAccessor 表达式,但这充其量似乎是一种杂乱无章且极其脆弱的方法。

我已经广泛搜索了解决方案。许多人指出 Brad Wilson 关于非类模型的演讲 (http://channel9.msdn.com/Series/mvcConf/mvcConf-2011-Brad-Wilson-Advanced-MVC-3),但是,如果您查看所提供的实际代码,您会发现它太绑定(bind)到 TYPE 而不是对象 - 换句话说,并不是非常有用。其他人已指向 http://fluentvalidation.codeplex.com/ ,但这似乎只适用于验证方面,我怀疑遇到与上述相同的问题(绑定(bind)到类型而不是对象)。

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

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

这可能会作为 View 模式(或其一部分)传递给 View ,在我看来,我想使用:
@Html.EditorForModel()

有没有人找到解决方法?

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

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

    关于c# - MVC 中的动态模型(非类)元数据提供程序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20602000/

    相关文章:

    c# - 按另一个列表过滤列表 C#

    c# - Linq-to-sql 外键 id 未自动添加

    c# - 高效地写入日志文件

    asp.net-mvc - 如何在页面中显示 ASP.net MVC 项目版本?

    asp.net-mvc - ASP.NET MVC : Implementing an OpenID sign-in page ala NerdDinner v2

    asp.net-mvc - 与 jquery 选项卡一起使用时,Html.Actionlink 显示错误的 Controller 名称

    c# - Oxyplot 不在 WPF View 中显示绘图

    c# - 如何自动生成导航包含的实体?

    c# - 帮助设计订单管理器类

    asp.net - IFormFile 上传图像 ASP.NET core MVC 始终显示空值