16

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

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

4

3 回答 3

15

我使用下面的方法自动将数据注释从我的实体复制到我的视图模型。这确保了实体/视图模型的 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的例外情况,花了一天的大部分时间来追踪它。
于 2012-04-11T04:58:15.683 回答
1

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

于 2012-04-05T18:53:54.783 回答
1

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));
于 2017-03-03T09:36:26.980 回答