Search code examples
asp.net-mvc-3unity-containerautomapper

Automapper Random Errors


2 months ago I have asked about this problem! and the problem still exists.

I am not going to copy/paste the same problem here because I found out that the error is not for a specific Entity-DTO mapping but for any Entity-DTO which is first in a controller.

I mean if the program flow hits to a Country-CountryDto, the error says:

Missing type map configuration or unsupported mapping.

Mapping types: 
Country -> CountryDTO 
MyApp.Domain.BoundedContext.Country -> MyApp.Application.BoundedContext.CountryDTO

Destination path: 
List`1[0]

Source value: 
MyApp.Domain.BoundedContext.Country

Or if there is an account check at first hand, the error says:

Missing type map configuration or unsupported mapping.

Mapping types:
Account -> AccountDTO
MyApp.Domain.BoundedContext.Account -> MyApp.Application.BoundedContext.AccountDTO

Destination path:
AccountDTO

Source value:
MyApp.Domain.BoundedContext.Account

I also found that the error is gone whenever I rebuild the presentation layer (in this case it is an MVC 3 project) of my N-Layer solution. And then, at a random time, it happens again.

If this problem happened only in development environment it wouldn't be a big deal but after publishing the problem was still there so I am in big trouble.

I have searched through Google, Stackoverflow, Automapper Forums/Groups with no success.

I have also tested the mappings with Mapper.AssertConfigurationIsValid() and everything was fine.

My project is an MVC 3 project with Automapper 2.2 and Unity IoC..

Again, I will appreciate any idea, advice or solution.

Edit: OK, now I have a clue.. I have a profile called ManagementProfile where all my mappings are done. In the AutomapperTypeAdapterFactory() I have a code like:

public AutomapperTypeAdapterFactory()
    {
        //Scan all assemblies to find an Auto Mapper Profile
        var profiles = AppDomain.CurrentDomain.GetAssemblies()
                                .SelectMany(a => a.GetTypes())
                                .Where(t => t.BaseType == typeof(Profile));

        Mapper.Initialize(cfg =>
        {
            foreach (var item in profiles)
            {
                if (item.FullName != "AutoMapper.SelfProfiler`2")
                    cfg.AddProfile(Activator.CreateInstance(item) as Profile);
            }
        });
    }

I found that, normally, the profiles variable holds ManagementProfile but sometimes it couldn't get the information and says "Enumeration yielded no results" and I got the exception mentioned in this question.

With further investigation I see that when everything is fine the AppDomain.CurrentDomain.GetAssemblies() loads 85 assemblies and on the other hand when I get the exception it has loaded only 41 assemblies and it was obvious that one of the missing assemblies was the one that holds the DTO mappings.


Solution

  • Ok, I finally figured it out. The:

    AppDomain.CurrentDomain.GetAssemblies()
    

    piece of my code sometimes does not get my mapping assembly so while it is missing I get an error. Replacing this code by forcing the app to find all assemblies solved my problem.

    Thanks for your replies.

    Edit: As Andrew Brown asked about the code of the solution, I realized that I have not included the source code snippet. Here is the AssemblyLocator:

    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Web;
    using System.Web.Compilation;
    
    public static class AssemblyLocator
    {
        private static readonly ReadOnlyCollection<Assembly> AllAssemblies;
        private static readonly ReadOnlyCollection<Assembly> BinAssemblies;
    
        static AssemblyLocator()
        {
            AllAssemblies = new ReadOnlyCollection<Assembly>(
                BuildManager.GetReferencedAssemblies().Cast<Assembly>().ToList());
    
            IList<Assembly> binAssemblies = new List<Assembly>();
    
            string binFolder = HttpRuntime.AppDomainAppPath + "bin\\";
            IList<string> dllFiles = Directory.GetFiles(binFolder, "*.dll",
                SearchOption.TopDirectoryOnly).ToList();
    
            foreach (string dllFile in dllFiles)
            {
                AssemblyName assemblyName = AssemblyName.GetAssemblyName(dllFile);
    
                Assembly locatedAssembly = AllAssemblies.FirstOrDefault(a =>
                    AssemblyName.ReferenceMatchesDefinition(
                        a.GetName(), assemblyName));
    
                if (locatedAssembly != null)
                {
                    binAssemblies.Add(locatedAssembly);
                }
            }
    
            BinAssemblies = new ReadOnlyCollection<Assembly>(binAssemblies);
        }
    
        public static ReadOnlyCollection<Assembly> GetAssemblies()
        {
            return AllAssemblies;
        }
    
        public static ReadOnlyCollection<Assembly> GetBinFolderAssemblies()
        {
            return BinAssemblies;
        }
    }
    

    Hence, instead of using AppDomain.CurrentDomain.GetAssemblies(), I am calling the GetAssemblies() method of the provided helper class like:

    //Scan all assemblies to find an Auto Mapper Profile
    //var profiles = AppDomain.CurrentDomain.GetAssemblies()
    //                        .SelectMany(a => a.GetTypes())
    //                        .Where(t => t.BaseType == typeof(Profile));
    var profiles = AssemblyLocator.GetAssemblies().
                                   SelectMany(a => a.GetTypes()).
                                   Where(t => t.BaseType == typeof(Profile));