以下のアプローチを使用して、エンティティからビューモデルにデータ注釈を自動的にコピーします。これにより、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日ほどかかりました。