Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>I use the approach below to automatically copy data annotations from my entities to my view model. This ensures that things like StringLength and Required values are always the same for entity/viewmodel.</p> <p>It works using the Automapper configuration, so works if the properties are named differently on the viewmodel as long as AutoMapper is setup correctly.</p> <p>You need to create a custom ModelValidatorProvider and custom ModelMetadataProvider to get this to work. My memory on why is a little foggy, but I believe it's so both server and client side validation work, as well as any other formatting you do based on the metadata (eg an asterix next to required fields).</p> <p>Note: I have simplified my code slightly as I added it below, so there may be a few small issues.</p> <p><strong>Metadata Provider</strong></p> <pre><code>public class MetadataProvider : DataAnnotationsModelMetadataProvider { private IConfigurationProvider _mapper; public MetadataProvider(IConfigurationProvider mapper) { _mapper = mapper; } protected override System.Web.Mvc.ModelMetadata CreateMetadata(IEnumerable&lt;Attribute&gt; attributes, Type containerType, Func&lt;object&gt; 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); } } </code></pre> <p><strong>Validator Provivder</strong></p> <pre><code>public class ValidatorProvider : DataAnnotationsModelValidatorProvider { private IConfigurationProvider _mapper; public ValidatorProvider(IConfigurationProvider mapper) { _mapper = mapper; } protected override System.Collections.Generic.IEnumerable&lt;ModelValidator&gt; GetValidators(System.Web.Mvc.ModelMetadata metadata, ControllerContext context, IEnumerable&lt;Attribute&gt; attributes) { var mappedAttributes = _mapper.GetMappedAttributes(metadata.ContainerType, metadata.PropertyName, attributes); return base.GetValidators(metadata, context, mappedAttributes); } } </code></pre> <p><strong>Helper Method Referenced in above 2 classes</strong></p> <pre><code>public static IEnumerable&lt;Attribute&gt; GetMappedAttributes(this IConfigurationProvider mapper, Type sourceType, string propertyName, IEnumerable&lt;Attribute&gt; existingAttributes) { if (sourceType != null) { foreach (var typeMap in mapper.GetAllTypeMaps().Where(i =&gt; 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 =&gt; i.GetType() == attribute.GetType())) yield return attribute; } } } } } if (existingAttributes != null) { foreach (var attribute in existingAttributes) { yield return attribute; } } } </code></pre> <p><strong>Other Notes</strong></p> <ul> <li>If you're using dependency injection, make sure your container isn't already replacing the built in metadata provider or validator provider. In my case I was using the Ninject.MVC3 package which bound one of them after creating the kernel, I then had to rebind it afterwards so my class was actually used. I was getting exceptions about Required only being allowed to be added once, took most of a day to track it down.</li> </ul>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
    1. COthis is helpful. I have a similar approach that I use right now, but with my own metadata source (not AutoMapper's). It could be extended to do what yours does as well. Help me understand something: You're passing in `metadata.ContainerType` as the source type, but it seems like it would be looking for the type of your business object. This makes me think you are (a) getting ModelMetadata for your business object and copying the view model attributes, or (b) mapping your view models to your business objects with AutoMapper (my use case is the opposite). Can you clear this up?
      singulars
    2. COIt looks like the `IConfigurationProvider` is the place to work with. Looking at the [source](https://github.com/AutoMapper/AutoMapper/blob/master/src/AutoMapper/IConfigurationProvider.cs), it seems like a better approach for my scenario would be wiring up to `event EventHandler<TypeMapCreatedEventArgs> TypeMapCreated;` in my IoC. Have you tried that kind of approach? [It looks like it fires every time a type is created](https://github.com/AutoMapper/AutoMapper/blob/master/src/AutoMapper/ConfigurationStore.cs), so I can hook that into my existing metadata provider
      singulars
    3. COI map both directions with AutoMapper. This code is for applying the metadata from the business objects to my viewmodels, however I am using the mappings that go the other direction to find the metadata. No particular reason that I'm aware of, and now that you mention it is does seem a tad odd.
      singulars
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload