16

AutoMapperを使用して、ドメインオブジェクトをビューモデルにマップします。ドメインレイヤーにメタデータがあり、ビューレイヤーとModelMetadataに引き継ぐ必要があります。(このメタデータはUIロジックではありませんが、ビューに必要な情報を提供します)。

今のところ、私の解決策は、(ASP.NET MVCとは独立して)別のMetadataProviderを使用し、規則を使用して、AssociatedMetadataProviderを介して関連するメタデータをModelMetadataオブジェクトに適用することです。このアプローチの問題は、AutoMappingを使用する場合と同じように、ドメインからModelMetadataをバインドするときに同じ規則をテストする必要があることです。これを、より直交させる方法があるはずです。誰かがこれを達成するためのより良い方法をお勧めできますか?

4

3 に答える 3

15

以下のアプローチを使用して、エンティティからビューモデルにデータ注釈を自動的にコピーします。これにより、StringLengthやRequired値などがentity/viewmodelで常に同じになります。

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

}

その他の注意事項

  • 依存性注入を使用している場合は、コンテナーが組み込みのメタデータプロバイダーまたはバリデータープロバイダーをまだ置き換えていないことを確認してください。私の場合、カーネルの作成後にそれらの1つをバインドするNinject.MVC3パッケージを使用していましたが、後でそれを再バインドして、クラスが実際に使用されるようにする必要がありました。必須は1回しか追加できないという例外があり、それを追跡するのに1日ほどかかりました。
于 2012-04-11T04:58:15.683 に答える
1

メタデータに属性が提供されている場合は、 MetaDataTypesで属性を定義し、ドメインクラスとビューモデルの両方に同じMetaDataTypeを適用します。すべてのMetaDataTypeは、両方のレイヤーで参照される個別のdllで定義できます。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 に答える