Automapper Profile registration in Web Applications

Current Situation

While searching for a proper way to register AutoMapper Profiles in a ASP.NET Web Application I found mostly code snippets like this:

   1: private static Func<Type, bool> _validProfilesExpression = t => typeof(Profile).IsAssignableFrom(t) && t.GetConstructor(Type.EmptyTypes) != null;

   2:

   3: public static void Register()

   4: {

   5:     var profiles = AppDomain.CurrentDomain

   6:                            .GetAssemblies()

   7:                            .SelectMany(a => a.GetTypes().Where(_validProfilesExpression)

   8:                            .Select(Activator.CreateInstance).Cast<Profile>()).ToList();

   9:     Register(profiles);

  10: }

  11:

  12: private static void Register(List<Profile> profiles)

  13: {

  14:     Mapper.Initialize(configuration => profiles.ForEach(configuration.AddProfile));

  15: }

Register Profiles during Application start by querying the AppDomain for classes derived from AutoMapper.Profile and adding the found profiles to AutoMapper.

This looked like a proper solution. After deploying my solution too IIS7 I recognized the application is only working for a while until already available profiles are not  found anymore.

For some reason the current AppDomain contains all assemblies available in the bin folder of the application during the very first Application start. After the Applicationpool is recycled (by default after 20 minutes of idle time) the current AppDomain contains only a subset of the assemblies available in the bin folder of the application. For that reason only some profiles are registered during Application start what can result in missing profiles.

Solution

  • Subscribe to current AppDomain assembly load event and register Profiles available on the new loaded assembly.
  • Register profiles available on assemblies already available on the current AppDomain
   1: private static Func<Type, bool> _validProfilesExpression = t => typeof(Profile).IsAssignableFrom(t) && t.GetConstructor(Type.EmptyTypes) != null;

   2:

   3: public static void Register()

   4: {

   5:     // Apply handler for assembly load to register profiles

   6:     // available on the new loaded assembly

   7:     AppDomain.CurrentDomain.AssemblyLoad += CurrentDomainAssemblyLoad;

   8:

   9:     var profiles = AppDomain.CurrentDomain

  10:                            .GetAssemblies()

  11:                            .SelectMany(a => a.GetTypes().Where(_validProfilesExpression)

  12:                            .Select(Activator.CreateInstance).Cast<Profile>()).ToList();

  13:     Register(profiles);

  14: }

  15:

  16: private static void Register(List<Profile> profiles)

  17: {

  18:     Mapper.Initialize(configuration => profiles.ForEach(configuration.AddProfile));

  19: }

  20:

  21: private static void CurrentDomainAssemblyLoad(object sender, AssemblyLoadEventArgs args)

  22: {

  23:     if (args.LoadedAssembly.GetTypes().Any(_validProfilesExpression))

  24:     {

  25:         var profiles = args.LoadedAssembly.GetTypes().Where(_validProfilesExpression).Select(Activator.CreateInstance).Cast<Profile>().ToList();

  26:         Register(profiles);

  27:     }

  28: }

Update

Registering the profiles with “Mapper.Initialize()” is a wrong approach. Change the Register method to:

private static void Register(List<Profile> profiles)
{
    profiles.ForEach(Mapper.AddProfile);
}
Advertisements