views:

822

answers:

3

My current project with assemblies for the domain model, MVC web application, and unit tests. How can I set up the AutoMapper configuration so that all assemblies reference the same configuration?

I would guess that I could put items in Global.asax for the web app, but how can I use that in the unit tests? Also, if the config is in Global.asax, will the domain model pick up the map?

Many thanks,

KevDog.

+9  A: 

What we do is create a static class, something like BootStrapper, and put the initialization code in a static method in there. We're doing profiles, so you don't see much in there. Global.asax will call that at startup, domain will use it (since the configuration is singleton), and unit tests that need it call the BootStrapper.Configure() in their setup.

One final thing we do is keep a flag around on the bootstrapper, and set it to true when we configure. That way, configuration only executes once per AppDomain. That means once at startup of the global.asax (Application_Start), and once when we run unit tests.

HTH

Jimmy Bogard
Thanks Jimmy! Excellent job on this tool, it rocks on multiple levels. I like the flag on the bootstrapper. I'll be jacking this into my code tonight.
KevDog
+2  A: 

I also use a bootstrapper to handle this sort of startup task thing. Actually, I use a chain of bootstrappers because I am crazy like that. Automapper-wise, we found it was alot cleaner to make some AutoMappingBuddy classes and decorate them with an attribute. We then wire up the mappers via some reflection calls (not cheap, but they only fire once at the get go). This solution was discovered after we got sick of finding an AutoMapper issue in line 841 of a 1200+ line file.


I thought about posting the code, but I can't really call it that purdy. Anyhow, here goes:

First, a simple interface for the AutoMappingBuddies:

public interface IAutoMappingBuddy
{
    void CreateMaps();
}

Second, a little attribute to provide some glue:

public class AutoMappingBuddyAttribute : Attribute
{
    public Type MappingBuddy { get; private set; }

    public AutoMappingBuddyAttribute(Type mappingBuddyType)
    {
        if (mappingBuddyType == null) throw new ArgumentNullException("mappingBuddyType");
        MappingBuddy = mappingBuddyType;
    }

    public IAutoMappingBuddy CreateBuddy()
    {
        ConstructorInfo ci = MappingBuddy.GetConstructor(new Type[0]);
        if (ci == null)
        {
            throw new ArgumentOutOfRangeException("mappingBuddyType", string.Format("{0} does not have a parameterless constructor."));
        }
        object obj = ci.Invoke(new object[0]);
        return obj as IAutoMappingBuddy;
    }
}

Third, the AutoMappingEngine. It's where the magic happens:

public static class AutoMappingEngine
{
    public static void CreateMappings(Assembly a)
    {
        Dictionary<Type, IAutoMappingBuddy> mappingDictionary = GetMappingDictionary(a);
        foreach (Type t in a.GetTypes())
        {
            var amba =
                t.GetCustomAttributes(typeof (AutoMappingBuddyAttribute), true).OfType<AutoMappingBuddyAttribute>().
                    FirstOrDefault();
            if (amba!= null && !mappingDictionary.ContainsKey(amba.MappingBuddy))
            {
                mappingDictionary.Add(amba.MappingBuddy, amba.CreateBuddy());
            }
        }
        foreach (IAutoMappingBuddy mappingBuddy in mappingDictionary.Values)
        {
            mappingBuddy.CreateMaps();
        }
    }

    private static Dictionary<Type, IAutoMappingBuddy> GetMappingDictionary(Assembly a)
    {
        if (!assemblyMappings.ContainsKey(a))
        {
            assemblyMappings.Add(a, new Dictionary<Type, IAutoMappingBuddy>());
        }
        return assemblyMappings[a];
    }

    private static Dictionary<Assembly, Dictionary<Type, IAutoMappingBuddy>> assemblyMappings = new Dictionary<Assembly, Dictionary<Type, IAutoMappingBuddy>>();
}

Kinda slapped together in an hour or so, there are probably more elegant ways to get there.

Wyatt Barnett
Wyatt, if you get the chance to do a post on how you did this, I'd certainly be in line to read it. It sounds elegant.
KevDog
This might be something to add to AutoMapper too - for large configuration files. Great idea for v.next!
Jimmy Bogard
Code posted. @Jimmy: I could submit a patch if you would like, let me know where it should go in your codebase.
Wyatt Barnett
hi there do you have a link, no idea where your blog is and a quick search doesnt bring this post up. Cheers
Miau
Don't have a blog these days, actually have a much better version that I should pull together and get to Jimmy . . .
Wyatt Barnett
+1  A: 

I tried the code above, but could not get it to work. I modified it a little bit as follows below. I think all that's left to do is to call it via a Bootstrapper from Global.asax. Hope this helps.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

using AutoMapper;

namespace Automapping
{
    public class AutoMappingTypePairing
    {
        public Type SourceType { get; set; }
        public Type DestinationType { get; set; }
    }

    public class AutoMappingAttribute : Attribute 
    {
        public Type SourceType { get; private set; }

        public AutoMappingAttribute(Type sourceType)
        {
            if (sourceType == null) throw new ArgumentNullException("sourceType");
            SourceType = sourceType; 
        }
    }

    public static class AutoMappingEngine
    {
        public static void CreateMappings(Assembly a)
        {
            IList<AutoMappingTypePairing> autoMappingTypePairingList = new List<AutoMappingTypePairing>();

            foreach (Type t in a.GetTypes())
            {
                var amba = t.GetCustomAttributes(typeof(AutoMappingAttribute), true).OfType<AutoMappingAttribute>().FirstOrDefault();

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

            foreach (AutoMappingTypePairing mappingPair in autoMappingTypePairingList) 
            {
                Mapper.CreateMap(mappingPair.SourceType, mappingPair.DestinationType);
            }
        }
    }
}

And I use it like this to associate a source with a destination pairing:

[AutoMapping(typeof(Cms_Schema))]
public class Schema : ISchema
{
    public Int32 SchemaId { get; set; }
    public String SchemaName { get; set; }
    public Guid ApplicationId { get; set; }
}

Then to Create the mappings automagically, I do this:

        Assembly assembly = Assembly.GetAssembly(typeof([ENTER NAME OF A TYPE FROM YOUR ASSEMBLY HERE]));

        AutoMappingEngine.CreateMappings(assembly);
tschreck