mezoid
mezoid

Reputation: 28730

Using reflection to bidirectionally Automap all Domain Entities to View Models in MVC3

As I've been playing with and learning ASP.Net MVC 3, I've been working with AutoMapper to map between my domain's entities and my view models.

I got tired of individually creating a map for each ViewModel I implemented. As a result I wrote some code to scan my assembly and use some reflection to create each of the required mappings. However, because I'm not very familiar with the best practices of using AutoMappers, I thought I might show everyone what I've done and ask if my approach is likely to come back to bite me.

Essentially I have a class called the AutoMappingConfigurator (used in Global.asax.cs) as follows:

public static class AutoMappingConfigurator
    {
        public static void Configure(Assembly assembly)
        {
            var autoMappingTypePairingList = new List<AutoMappingTypePairing>();

            foreach (Type t in assembly.GetTypes())
            {
                var autoMapAttribute = t
                    .GetCustomAttributes(typeof(AutoMapAttribute), true)
                    .OfType<AutoMapAttribute>()
                    .FirstOrDefault();

                if (autoMapAttribute != null)
                {
                    autoMappingTypePairingList
                .Add(new AutoMappingTypePairing(autoMapAttribute.SourceType, t));
                }
            }

            autoMappingTypePairingList
               .ForEach(mappingPair => mappingPair.CreateBidirectionalMap());
        }
    }

Essentially what it does, is scan an assembly for all types that have been marked with an AutoMapAttribute and for each one that is found it creates a bidirectional mapping.

The AutoMapAttribute is a simple attribute that I've created (based on examples I've found online) that I attach to my ViewModel to indicate which Domain Entity it maps to.

For example.

[AutoMap(typeof(Project))]
public class ProjectDetailsViewModel
{
    public int ProjectId { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
}

With respect to the bidirectional mapping, so far in my working with MVC3 I have found that I frequently seem to need to map from the Entity to ViewModel for a HttpGet and from the ViewModel to the Entity for a HttpPost.

The bidirectional mapping is implemented as an extension method as follows:

public static void CreateBidirectionalMap(this AutoMappingTypePairing mappingPair)
{
    Mapper.CreateMap(mappingPair.SourceType, mappingPair.DestinationType)
          .IgnoreProperties(mappingPair.DestinationType);

    Mapper.CreateMap(mappingPair.DestinationType, mappingPair.SourceType)
          .IgnoreProperties(mappingPair.SourceType);
}

Concerning the IgnoreProperties extension method, I found that whenever I had a view model that had a property that I wanted ignored (like when my view model has a drop down list that is not a part of the underlying domain entity) I seemed to have to create the ignore manually via the ForMember AutoMapper method. So I created another attribute to indicate which properties were to be ignored so my reflection code in AutoMappingConfigurator could do this for me automatically.

The IgnoreProperties extension method is implemented as an extension method as follows:

public static IMappingExpression IgnoreProperties(this IMappingExpression expression
                                                  , Type mappingType)
{
    var propertiesWithAutoMapIgnoreAttribute =
        mappingType.GetProperties()
            .Where(p => p.GetCustomAttributes(typeof(AutoMapIgnoreAttribute), true)
                         .OfType<AutoMapIgnoreAttribute>()
                         .Count() > 0);
    foreach (var property in propertiesWithAutoMapIgnoreAttribute)
    {
        expression.ForMember(property.Name, opt => opt.Ignore());
    }
    return expression;
}

All this allows me to write my ViewModel as follows and have it AutoMapped:

[AutoMap(typeof(EntityClass))]
private class ViewModelClass
{
    public int EntityClassId { get; set; }

    [AutoMapIgnore]
    public IEnumerable<SelectListItem> DropDownItems { get; set; }
}

private class EntityClass
{
    public int EntityClassId { get; set; }
}

While this has worked for me thus far, I am worried that it may come back to bite me due to my low level of experience with AutoMapper.

So my questions are:

Upvotes: 3

Views: 3005

Answers (3)

Christo
Christo

Reputation: 2370

If both of your models and entities have base classes you could do something like the following;

        var entityAssembly = typeof(BaseEntity).Assembly;
        var modelAssembly = typeof(BaseModel).Assembly;
        var modelNamespace = modelAssembly.GetTypes().Where(a => a.BaseType == typeof(BaseModel)).FirstOrDefault().Namespace;

        foreach (var entity in entityAssembly.GetTypes().Where(a=> a.BaseType == typeof(BaseEntity)))
        {
            var model = modelAssembly.GetType(String.Format("{0}.{1}{2}", modelNamespace, entity.Name, "Model"));
            if (model != null)
            {
                Mapper.CreateMap(entity, model);
                Mapper.CreateMap(model, entity);
            }
        }

This is a bidirectional convention over configuration implementation and if the corresponding model doesn't exist then it skips over to the next entity. Its pretty simple but is an alternative to the manual map.

NB. This makes the presumption that the model and entity names follow some sort of conventional naming. For example.

{EntityName}Model eq. Branch to BranchModel

Hope it helps. Please note this is a real basic implementation. If no models exist then the code throw an error on line #3.

Upvotes: 0

Jesper Mygind
Jesper Mygind

Reputation: 2496

I prefer one-directional view models. In other words, I use one view model when I present data to the user, and another one when I handle creates (and another one for updates and so on).

True, you will get more objects this way. The advantage is that you avoid view model properties you don't need (and thus the need to ignore them). I think a view model should always be as simple as possible (plain get/set properties and perhaps a constructor if you have a list that needs to be initialized). If you are worried about names and datatypes of "common properties" of the objects, you could (although I don't think you should) define these in an interface or a base class.

AutoMapIgnore attributes in the domain model are problematic if you want to use a certain domain model property in two view models. This I think also applies to your solution.

Finally, I don't see much benefit of using attributes instead of code lines like

Mapper.CreateMap<SourceType, DestinationType>();

Can it be any simpler? It might be a good idea though to have an extension method that ignores "unmapped properties" when you map from view model to model, allowing you to write

Mapper.CreateMap<SourceType, DestinationType>().IgnoreUnmappedProperties();

IMO this is easier than using AutoMapIgnore attributes.

Upvotes: 2

shuniar
shuniar

Reputation: 2622

I don't see anything wrong with your approach, and to answer your questions:

  • IMO, I believe the way you are setting this up is a good way to create otherwise tedious mappings between your Models/DTOs and your Entities.
  • I don't know of anything about AutoMapper that would make this a bad approach.
  • Using attributes to hook up the property ignore is a fine idea, attributes are use for just that in MVC.
  • The bidirectional mapping sounds like a good idea in most situations, but I'm curious how that would work with custom mappings.

Some things to consider:

  1. How does it handle nested mappings?
  2. How does it handle custom mappings?
    • Possibly attributes?
  3. Bidirectional mapping vs Mapping each side with Attributes
    • Which one provides more clarity?
    • Which one handles custom/nested mappings better?
  4. Is performance affected?
    • Most likely not, but can be a concern when using reflection.

Upvotes: 1

Related Questions