Подтвердить что ты не робот

Провайдер метаданных Dynamic Model (non-class) в MVC

Мы разрабатываем приложение, в котором схема конечного пользователя является динамической (для этого у нас есть хороший бизнес-сценарий - это не то, с чем легко справляется статическая модель).

Я использовал класс .NET DynamicObject, чтобы позволить этим объектам динамических схем легко обращаться с кодом, и ожидал, что это будет просто работать с метаданными модели MVC. Однако поддержка метаданных MVC, похоже, затруднена тем, что она касается только метаданных, определенных для каждого типа, а не для каждого объекта, который будет иметь место здесь.

Даже когда я откопал и попытался реализовать собственный ModelMetadataProvider, кажется, что необходимая информация просто не проходит - метод GetMetadataForProperty особенно проблематичен. Фактически мне нужно получить доступ к родительскому или контейнерному объекту для свойства, но все, что передается, это тип.

Вышеупомянутое вызывается в основном из метода FromStringExpression в классе ModelMetadata. Этот метод фактически имеет контейнер (по крайней мере, в этом случае), но не проходит его. Эта ветка выполняется, когда она находит данные представления о сохраненном выражении (кэшированном?) В ViewData. Если это не удается, оно возвращается к просмотру через объект ModelMetadata, что иронически может сработать для меня. Особенно раздражает то, что метод FromStringExpression является статическим, поэтому я не могу легко переопределить его поведение.

В отчаянии я рассмотрел попытку пересечения выражения modelAccessor, но это похоже на kludge в лучшем случае и очень хрупкое.

Я искал экстенсивно для решения этого. Многие указывают на разговор Брэда Уилсона (http://channel9.msdn.com/Series/mvcConf/mvcConf-2011-Brad-Wilson-Advanced-MVC-3) по неклассическим моделям, однако, если вы посмотрите на фактический представленный код, вы увидите, что он TOO привязан к ТИП, а не объект - другими словами, не очень полезно. Другие указали на 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​​strong >
  • Создайте уникальный ключ для каждого шаблонного свойства и используйте его для имени свойства, а не для отображаемого имени, которое позволит ModelMetadataProvider найти метаданные, связанные с этим свойством, без ссылки на его родительский элемент. Однако это привело бы к довольно уродливой ситуации при отладке и снова показалось бы крупномасштабным kludge. Я сейчас попробовал упрощенную версию этого и, похоже, работает, но имеет какое-то нежелательное поведение, например, нужно использовать бессмысленное имя свойства, если я хочу явно привязать к элементам модели.
  • В ModelMetadataProvider при возврате коллекции объектов ModelMetadata для содержащихся свойств запишите контейнер в ModelMetadataProvider, который связан с этими возвращаемыми свойствами. Я пробовал это, но эта возвращенная коллекция метаданных свойств игнорируется в этом случае, а метод FromStringExpression переходит непосредственно к методу GetMetadataForProperty.
4b9b3361

Ответ 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();
}