1
votes

I want to load a bunch of automapper profiles of referenced libraries, without having to type each one out by hand.

I'm trying to take the following steps:

  1. Get all profiles from referenced assemblies
  2. Add profiles to mapper config
  3. Register mapper for DI

Step 1 works, but something goes wrong in step 2.

Current code:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {            
        var container = new UnityContainer();

        var assemblyNames = Assembly.GetExecutingAssembly().GetReferencedAssemblies()
            .Where(a => a.Name.StartsWith("OKL_KPS"));

        var assemblies = assemblyNames.Select(an => Assembly.Load(an));

        var loadedProfiles = new List<Type>();
        foreach (var assembly in assemblies)
        {
            var assemblyProfiles = assembly.ExportedTypes.Where(type => type.IsSubclassOf(typeof(Profile)));
            loadedProfiles.AddRange(assemblyProfiles);
        }

        var mapconfig = new MapperConfiguration(cfg =>
        {
            // Magic should happen here
            foreach (var profile in loadedProfiles)
            {
                var resolvedProfile = container.Resolve(profile) as Profile;
                cfg.AddProfile(resolvedProfile);
            }
        });

        container.RegisterInstance<IMapper>(mapconfig.CreateMapper());

        config.DependencyResolver = new UnityResolver(container);

        //routes here
    }
}

I also tried cfg.AddProfile((Profile)Activator.CreateInstance(profile.AssemblyQualifiedName, profile.Name).Unwrap());, but this returns the assembly name of the service I'm using it in, not the name of the library where the profile is from.

Edit

The assemblies aren't loading during the register step. To hack this there's a Dummy class in each library which are initialised before registering the profiles. Optimal solution is not needing these dummy classes, otherwise it would be cleaner to add each profile explicitly.

I also tried adding the ExportAttribute to the profile, but that didn't work either.

3
I took your code and created a sample app and it worked just fine. What is the thing goes wrong in step 2? Do you get an empty assemblyProfiles?TheRock
@TheRock the assemblies aren't loaded when adding the profiles in this way. A colleague added a dummy class to force them to load by initialising that class right before adding the profiles, but we want it to work without that dummy class.Friso

3 Answers

2
votes

You scan loaded assemblies on available properties using LINQ queries. Something like this should work:

            var profiles = AllClasses.FromLoadedAssemblies().
                Where(type => typeof(Profile).IsAssignableFrom(type));

            //add profiles to config
            var mapconfig = new MapperConfiguration(cfg =>
            {
                // Magic should happen here
                foreach (var profile in profiles)
                {
                    var resolvedProfile = container.Resolve(profile) as Profile;
                    cfg.AddProfile(resolvedProfile);
                }
            });

            //register mapper using config
            container.RegisterInstance<IMapper>(mapconfig.CreateMapper());
1
votes

You can add profiles by listing your assembly instance, assembly name or type.

Using names:

 var mapconfig = new MapperConfiguration(cfg =>
        {
           cfg.AddProfiles("Foo.YourProject.API");
           cfg.AddProfiles("Foo.YourProject.Service");
           cfg.AddProfiles("Foo.YourProject.Repository");
           ...
        });

Also check official documentation for more information.

0
votes

Within the documenation is described on how to use AutoMapper with the Microsoft DI framework. And there it simply forwards to the corresponding NuGet package and an article that describes how it searches through the current application domain for all the open types and profiles to load them.

In your classes you then have to simply inject an IMapper imapper into the constructor which then simply does what you expect.

The only next caveat I ran into was, that not all my assemblies where loaded before I called services.AddAutoMapper() in my ConfigureServices() method. But for this case I simply added a simple helper method, which will be called before DI starts to do its work:

public static void LoadAllLocalAssemblies()
{
    var entryAssembly = Assembly.GetEntryAssembly();
    var location = entryAssembly.Location;
    var path = Path.GetDirectoryName(location);

    var files = Directory.EnumerateFiles(path, "*.dll");

    foreach (var file in files)
    {
        try
        {
            Assembly.LoadFrom(file);
        }
        catch
        {
        }
    }
}

After that all assemblies are loaded into the current app domain and the NuGet package will resolve all classes that are derived from Profile. My handlers get an IMapper mapper injected within the constructor and within my method I can call mapper.Map<MyDestination>(mySource) and it works as expected.

No dummy classes, interfaces or whatsoever is needed.