/// <summary>
 ///     Add AutoMapper with Profiles scan and Dependency Injection
 /// </summary>
 /// <param name="services">                    </param>
 /// <param name="mapperResolveType">           </param>
 /// <param name="isAssertConfigurationIsValid">
 ///     Check all auto mapper profile is valid by <c> Mapper.AssertConfigurationIsValid(); </c>
 /// </param>
 /// <param name="isCompileMappings">
 ///     AutoMapper lazily compiles the type map plans on first map. However, this behavior is
 ///     not always desirable, so you can tell AutoMapper to compile its mappings directly by
 ///     <c> Mapper.Configuration.CompileMappings(); </c>
 /// </param>
 /// <param name="assembliesToScan">
 ///     List of assembliesToScan contain AutoMapper Profile, <c> null </c> for scan current
 ///     loaded/reference in main project
 /// </param>
 public static IServiceCollection AddAutoMapper(this IServiceCollection services,
                                                Enums.MapperResolveType mapperResolveType = Enums.MapperResolveType.PerRequest,
                                                bool isAssertConfigurationIsValid         = true, bool isCompileMappings = true, params Assembly[] assembliesToScan)
 {
     return(AddAutoMapperClasses(services, null, assembliesToScan, mapperResolveType,
                                 isAssertConfigurationIsValid, isCompileMappings));
 }
 /// <summary>
 ///     Add AutoMapper with Profiles scan and Dependency Injection
 /// </summary>
 /// <param name="services">                    </param>
 /// <param name="mapperResolveType">           </param>
 /// <param name="isAssertConfigurationIsValid">
 ///     Check all auto mapper profile is valid by <c> Mapper.AssertConfigurationIsValid(); </c>
 /// </param>
 /// <param name="isCompileMappings">
 ///     AutoMapper lazily compiles the type map plans on first map. However, this behavior is
 ///     not always desirable, so you can tell AutoMapper to compile its mappings directly by
 ///     <c> Mapper.Configuration.CompileMappings(); </c>
 /// </param>
 /// <param name="profileAssemblyMarkerTypes">  </param>
 /// <returns></returns>
 public static IServiceCollection AddAutoMapper(this IServiceCollection services,
                                                Enums.MapperResolveType mapperResolveType = Enums.MapperResolveType.PerRequest,
                                                bool isAssertConfigurationIsValid         = true, bool isCompileMappings = true,
                                                params Type[] profileAssemblyMarkerTypes)
 {
     return(AddAutoMapperClasses(services, null, profileAssemblyMarkerTypes.GetAssemblies(), mapperResolveType,
                                 isAssertConfigurationIsValid, isCompileMappings));
 }
 /// <summary>
 ///     Add AutoMapper with Profiles scan and Dependency Injection
 /// </summary>
 /// <param name="services">                    </param>
 /// <param name="additionalInitAction">        </param>
 /// <param name="profileAssemblyMarkerTypes">  </param>
 /// <param name="mapperResolveType">           </param>
 /// <param name="isAssertConfigurationIsValid">
 ///     Check all auto mapper profile is valid by <c> Mapper.AssertConfigurationIsValid(); </c>
 /// </param>
 /// <param name="isCompileMappings">
 ///     AutoMapper lazily compiles the type map plans on first map. However, this behavior is
 ///     not always desirable, so you can tell AutoMapper to compile its mappings directly by
 ///     <c> Mapper.Configuration.CompileMappings(); </c>
 /// </param>
 /// <returns></returns>
 public static IServiceCollection AddAutoMapper(this IServiceCollection services,
                                                Action <IMapperConfigurationExpression> additionalInitAction, IEnumerable <Type> profileAssemblyMarkerTypes,
                                                Enums.MapperResolveType mapperResolveType = Enums.MapperResolveType.PerRequest,
                                                bool isAssertConfigurationIsValid         = true, bool isCompileMappings = true)
 {
     return(AddAutoMapperClasses(services, additionalInitAction, profileAssemblyMarkerTypes.GetAssemblies(),
                                 mapperResolveType, isAssertConfigurationIsValid, isCompileMappings));
 }
 /// <summary>
 ///     Add AutoMapper with Profiles scan and Dependency Injection
 /// </summary>
 /// <param name="services">                    </param>
 /// <param name="additionalInitAction">        </param>
 /// <param name="mapperResolveType">           </param>
 /// <param name="isAssertConfigurationIsValid">
 ///     Check all auto mapper profile is valid by <c> Mapper.AssertConfigurationIsValid(); </c>
 /// </param>
 /// <param name="isCompileMappings">
 ///     AutoMapper lazily compiles the type map plans on first map. However, this behavior is
 ///     not always desirable, so you can tell AutoMapper to compile its mappings directly by
 ///     <c> Mapper.Configuration.CompileMappings(); </c>
 /// </param>
 /// <param name="assemblies">                  </param>
 /// <returns></returns>
 public static IServiceCollection AddAutoMapper(this IServiceCollection services,
                                                Action <IMapperConfigurationExpression> additionalInitAction,
                                                Enums.MapperResolveType mapperResolveType = Enums.MapperResolveType.PerRequest,
                                                bool isAssertConfigurationIsValid         = true, bool isCompileMappings = true, params Assembly[] assemblies)
 {
     return(AddAutoMapperClasses(services, additionalInitAction, assemblies, mapperResolveType,
                                 isAssertConfigurationIsValid, isCompileMappings));
 }
        private static IServiceCollection AddAutoMapperClasses(IServiceCollection services,
                                                               Action <IMapperConfigurationExpression> additionalInitAction,
                                                               IEnumerable <Assembly> assembliesToScan,
                                                               Enums.MapperResolveType mapperResolveType = Enums.MapperResolveType.PerRequest,
                                                               bool isAssertConfigurationIsValid         = true,
                                                               bool isCompileMappings = true)
        {
            additionalInitAction = additionalInitAction ?? DefaultConfig;
            assembliesToScan     = assembliesToScan as Assembly[] ?? assembliesToScan.ToArray();

            // Scan Assemblies
            var allTypes = assembliesToScan
                           .Where(a => a.GetName().Name != nameof(AutoMapper))
                           .SelectMany(a => a.DefinedTypes)
                           .ToArray();

            var profiles =
                allTypes
                .Where(t => typeof(Profile).GetTypeInfo().IsAssignableFrom(t))
                .Where(t => !t.IsAbstract);

            Mapper.Initialize(cfg =>
            {
                additionalInitAction(cfg);

                foreach (var profile in profiles.Select(t => t.AsType()))
                {
                    cfg.AddProfile(profile);
                }
            });

            // Assert Config
            if (isAssertConfigurationIsValid)
            {
                Mapper.AssertConfigurationIsValid();
            }

            if (isCompileMappings)
            {
                Mapper.Configuration.CompileMappings();
            }

            // Dependency Injection
            var openTypes = new[]
            {
                typeof(IValueResolver <, ,>),
                typeof(IMemberValueResolver <, , ,>),
                typeof(ITypeConverter <,>)
            };

            var dependencyTypes =
                openTypes.SelectMany(
                    openType => allTypes.Where(t => t.IsClass && !t.IsAbstract &&
                                               t.AsType().IsImplementGenericInterface(openType)));

            foreach (var type in dependencyTypes)
            {
                services.AddTransient(type.AsType());
            }

            // Add Mapper to DI

            // Config is singleton
            services.AddSingleton(Mapper.Configuration);

            switch (mapperResolveType)
            {
            case Enums.MapperResolveType.PerRequest:
            {
                services.AddScoped <IMapper>(sp => new Mapper(sp.GetRequiredService <IConfigurationProvider>(),
                                                              sp.GetService));
                break;
            }

            case Enums.MapperResolveType.PreResolve:
            {
                services.AddTransient <IMapper>(sp => new Mapper(sp.GetRequiredService <IConfigurationProvider>(),
                                                                 sp.GetService));
                break;
            }

            case Enums.MapperResolveType.Singleton:
            {
                services.AddSingleton <IMapper>(sp => new Mapper(sp.GetRequiredService <IConfigurationProvider>(),
                                                                 sp.GetService));
                break;
            }

            default:
                throw new ArgumentOutOfRangeException(nameof(mapperResolveType), mapperResolveType, null);
            }

            return(services);
        }