【问题标题】:Technique for carrying metadata to View Models with AutoMapper使用 AutoMapper 将元数据传输到视图模型的技术
【发布时间】:2012-04-03 08:35:34
【问题描述】:

我使用 AutoMapper 将我的域对象映射到我的视图模型。我的域层中有元数据,我想将其转移到视图层和 ModelMetadata。 (此元数据不是 UI 逻辑,而是为我的视图提供必要的信息)。

现在,我的解决方案是使用单独的 MetadataProvider(独立于 ASP.NET MVC),并使用约定通过 AssociatedMetadataProvider 将相关元数据应用于 ModelMetadata 对象。这种方法的问题在于,在绑定域中的 ModelMetadata 时,我必须测试与使用 AutoMapping 时相同的约定,而且似乎应该有一种方法可以使这更加正交。谁能推荐一个更好的方法来完成这个?

【问题讨论】:

  • 显示一些代码,以便我们看到你在做什么。

标签: asp.net-mvc-3 asp.net-mvc-2 automapper modelmetadata modelmetadataprovider


【解决方案1】:

我使用以下方法自动将数据注释从我的实体复制到我的视图模型。这可确保实体/视图模型的 StringLength 和 Required 值等内容始终相同。

它使用 Automapper 配置工作,所以只要 AutoMapper 设置正确,如果视图模型上的属性名称不同,它就可以工作。

您需要创建自定义 ModelValidatorProvider 和自定义 ModelMetadataProvider 才能使其正常工作。我对原因的记忆有点模糊,但我相信服务器端和客户端验证都是如此,以及您基于元数据执行的任何其他格式(例如,必填字段旁边的星号)。

注意:我已经稍微简化了我的代码,因为我在下面添加了它,所以可能会有一些小问题。

元数据提供者

public class MetadataProvider : DataAnnotationsModelMetadataProvider
{        
    private IConfigurationProvider _mapper;

    public MetadataProvider(IConfigurationProvider mapper)
    {           
        _mapper = mapper;
    }

    protected override System.Web.Mvc.ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
    {           
        //Grab attributes from the entity columns and copy them to the view model
        var mappedAttributes = _mapper.GetMappedAttributes(containerType, propertyName, attributes);

        return base.CreateMetadata(mappedAttributes, containerType, modelAccessor, modelType, propertyName);

}
}

验证器提供者

public class ValidatorProvider : DataAnnotationsModelValidatorProvider
{
    private IConfigurationProvider _mapper;

    public ValidatorProvider(IConfigurationProvider mapper) 
    {
        _mapper = mapper;
    }

    protected override System.Collections.Generic.IEnumerable<ModelValidator> GetValidators(System.Web.Mvc.ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
    {   
        var mappedAttributes = _mapper.GetMappedAttributes(metadata.ContainerType, metadata.PropertyName, attributes);
        return base.GetValidators(metadata, context, mappedAttributes);
    }
}

以上 2 个类中引用的辅助方法

public static IEnumerable<Attribute> GetMappedAttributes(this IConfigurationProvider mapper, Type sourceType, string propertyName, IEnumerable<Attribute> existingAttributes)
{
    if (sourceType != null)
    {
        foreach (var typeMap in mapper.GetAllTypeMaps().Where(i => i.SourceType == sourceType))
        {
            foreach (var propertyMap in typeMap.GetPropertyMaps())
            {
                if (propertyMap.IsIgnored() || propertyMap.SourceMember == null)
                    continue;

                if (propertyMap.SourceMember.Name == propertyName)
                {
                    foreach (ValidationAttribute attribute in propertyMap.DestinationProperty.GetCustomAttributes(typeof(ValidationAttribute), true))
                    {
                        if (!existingAttributes.Any(i => i.GetType() == attribute.GetType()))
                            yield return attribute;
                    }
                }
            }
        }
    }

    if (existingAttributes != null)
    {
        foreach (var attribute in existingAttributes)
        {
            yield return attribute;
        }
    }

}

其他说明

  • 如果您使用依赖注入,请确保您的容器尚未替换内置的元数据提供程序或验证程序提供程序。在我的例子中,我使用的是 Ninject.MVC3 包,它在创建内核后绑定了其中一个包,然后我不得不在之后重新绑定它,以便实际使用我的类。我收到了有关只允许添加一次Required的例外情况,花了一天的大部分时间来追踪它。

【讨论】:

  • 这很有帮助。我现在使用类似的方法,但使用我自己的元数据源(不是 AutoMapper 的)。它也可以扩展到做你所做的事情。帮助我理解一些事情:您将metadata.ContainerType 作为源类型传递,但它似乎正在寻找您的业务对象的类型。这让我认为您正在 (a) 为您的业务对象获取 ModelMetadata 并复制视图模型属性,或者 (b) 使用 AutoMapper 将您的视图模型映射到您的业务对象(我的用例是相反的)。你能解决这个问题吗?
  • 看起来IConfigurationProvider 是可以使用的地方。查看source,对于我的场景来说,似乎更好的方法是在我的IoC 中连接到event EventHandler&lt;TypeMapCreatedEventArgs&gt; TypeMapCreated;。你试过这种方法吗? It looks like it fires every time a type is created,所以我可以将它连接到我现有的元数据提供程序中
  • 我使用 AutoMapper 映射两个方向。此代码用于将业务对象中的元数据应用到我的视图模型,但是我正在使用另一个方向的映射来查找元数据。我不知道有什么特别的原因,现在你提到它似乎有点奇怪。
  • 我之前实际上并没有见过那个事件。我可以使用它来创建和缓存元数据映射,但是仍然需要 MVC 的两个提供程序来使用它。我可以轻松地将缓存添加到我的扩展方法中,因此我并没有真正看到使用该事件的好处。如果你最终使用它,我很想听听。
  • 我想我找到了前进的方向 -- DestinationProperty 现在有一个 MemberInfo 属性,它是一个 ICustomAttributeProvider,因此代码更改为 propertyMap.DestinationProperty.MemberInfo.GetCustomAttributes(typeof (ValidationAttribute), true))
【解决方案2】:

如果您的元数据带有属性,请在 MetaDataTypes 中定义属性,然后将相同的 MetaDataType 应用于您的域类和视图模型。您可以在两个层都引用的单独 dll 中定义所有 MetaDataType。如果您的 ViewModel 类没有 MetaDataType 中使用的某些属性,则此方法存在一些问题,但这可以通过自定义 Provider 解决(如果您喜欢这种方法,我有代码)。

【讨论】:

  • 元数据来自不同的位置,我目前没有任何专用的“元数据”类(我也不想要它们)。我认为这可能是我寻找可扩展点的好地方。谢谢
  • 您可能会编写一种“代理”元数据提供程序,它只是将元数据检索从一种类型重定向到另一种类型。然后您可能会将 ViewModel 类“重定向”到域类型。
  • 这基本上就是我现在正在做的事情。问题是“代理”评估 ModelMetadata 对象以获取对映射域对象的元数据的引用。而且,了解视图是什么投影所需的逻辑与我用于 AutoMapping 的逻辑几乎相同。我想摆脱冗余。
  • 看看 automapper ITypeConverter github.com/AutoMapper/AutoMapper/wiki/Custom-type-converters 它允许您自定义映射。通过这种方式,您可以添加逻辑以从源类型中检索元数据,并且可以将其放入由该对(类型/属性名称或 PropertyInfo)索引的字典中。然后自定义元数据提供程序访问此字典以检索元数据......这是一项不平凡的工作......但在我看来是可行的
【解决方案3】:

Betty 的解决方案非常适合“继承”数据注释。我已经扩展了这个想法,还包括由 IValidatableObject 提供的验证。

public class MappedModelValidatorProvider : DataAnnotationsModelValidatorProvider
{
    private readonly IMapper _mapper;

    public MappedModelValidatorProvider(IMapper mapper)
    {
        _mapper = mapper;
    }

    protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
    {
        var mappedAttributes = _mapper.ConfigurationProvider.GetMappedAttributes(metadata.ContainerType, metadata.PropertyName, attributes);
        foreach (var validator in base.GetValidators(metadata, context, mappedAttributes))
        {
            yield return validator;
        }
        foreach (var typeMap in _mapper.ConfigurationProvider.GetAllTypeMaps().Where(i => i.SourceType == metadata.ModelType))
        {
            if (typeof(IValidatableObject).IsAssignableFrom(typeMap.DestinationType))
            {
                var model = _mapper.Map(metadata.Model, typeMap.SourceType, typeMap.DestinationType);
                var modelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, typeMap.DestinationType);
                yield return new ValidatableObjectAdapter(modelMetadata, context);
            }
        }
    }
}

然后在 Global.asax.cs 中:

ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new MappedModelValidatorProvider(Mapper.Instance));

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-20
    • 1970-01-01
    相关资源
    最近更新 更多