diff --git a/src/AutoMapper.DI.Tests/AppDomainResolutionTests.cs b/src/AutoMapper.DI.Tests/AppDomainResolutionTests.cs index cb90ea2c68..e8f76b9377 100644 --- a/src/AutoMapper.DI.Tests/AppDomainResolutionTests.cs +++ b/src/AutoMapper.DI.Tests/AppDomainResolutionTests.cs @@ -17,10 +17,7 @@ public AppDomainResolutionTests() { IServiceCollection services = new ServiceCollection(); services.AddSingleton(NullLoggerFactory.Instance); - services.AddAutoMapper(opt => - { - opt.AddMaps(typeof(AppDomainResolutionTests)); - }); + services.AddAutoMapper(_ => { }, typeof(AppDomainResolutionTests)); _provider = services.BuildServiceProvider(); } diff --git a/src/AutoMapper.DI.Tests/AssemblyResolutionTests.cs b/src/AutoMapper.DI.Tests/AssemblyResolutionTests.cs index f4a586d01a..9cb6d5a053 100644 --- a/src/AutoMapper.DI.Tests/AssemblyResolutionTests.cs +++ b/src/AutoMapper.DI.Tests/AssemblyResolutionTests.cs @@ -23,10 +23,7 @@ private static ServiceProvider BuildServiceProvider() { IServiceCollection services = new ServiceCollection(); services.AddSingleton(NullLoggerFactory.Instance); - services.AddAutoMapper(opt => - { - opt.AddMaps(typeof(Source).GetTypeInfo().Assembly); - }); + services.AddAutoMapper(_ => { }, typeof(Source).GetTypeInfo().Assembly); var serviceProvider = services.BuildServiceProvider(); return serviceProvider; } diff --git a/src/AutoMapper.DI.Tests/AttributeTests.cs b/src/AutoMapper.DI.Tests/AttributeTests.cs index ffc109d10e..960f1fc24d 100644 --- a/src/AutoMapper.DI.Tests/AttributeTests.cs +++ b/src/AutoMapper.DI.Tests/AttributeTests.cs @@ -14,10 +14,7 @@ public void Should_not_register_static_instance_when_configured() { IServiceCollection services = new ServiceCollection(); services.AddSingleton(NullLoggerFactory.Instance); - services.AddAutoMapper(opt => - { - opt.AddMaps(typeof(Source3)); - }); + services.AddAutoMapper(_ => { }, typeof(Source3)); var serviceProvider = services.BuildServiceProvider(); diff --git a/src/AutoMapper.DI.Tests/DependencyTests.cs b/src/AutoMapper.DI.Tests/DependencyTests.cs index 7af1c4a16a..c897c4e862 100644 --- a/src/AutoMapper.DI.Tests/DependencyTests.cs +++ b/src/AutoMapper.DI.Tests/DependencyTests.cs @@ -17,10 +17,7 @@ public DependencyTests() IServiceCollection services = new ServiceCollection(); services.AddTransient(sp => new FooService(5)); services.AddSingleton(NullLoggerFactory.Instance); - services.AddAutoMapper(opt => - { - opt.AddMaps(typeof(Source), typeof(Profile)); - }); + services.AddAutoMapper(_ => { }, typeof(Source), typeof(Profile)); _provider = services.BuildServiceProvider(); _provider.GetService().AssertConfigurationIsValid(); diff --git a/src/AutoMapper.DI.Tests/Integrations/ServiceLifetimeTests.cs b/src/AutoMapper.DI.Tests/Integrations/ServiceLifetimeTests.cs index 1d0b72334e..984e2d49e4 100644 --- a/src/AutoMapper.DI.Tests/Integrations/ServiceLifetimeTests.cs +++ b/src/AutoMapper.DI.Tests/Integrations/ServiceLifetimeTests.cs @@ -50,9 +50,8 @@ public void CanUseDefaultInjectedIMapperInSingletonService() services.AddSingleton(NullLoggerFactory.Instance); services.AddAutoMapper(opt => { - opt.AddMaps(GetType().Assembly); opt.CreateMap().ReverseMap(); - }); + }, GetType().Assembly); var sp = services.BuildServiceProvider(); Bar actual; diff --git a/src/AutoMapper.DI.Tests/MultipleRegistrationTests.cs b/src/AutoMapper.DI.Tests/MultipleRegistrationTests.cs index c695235fcb..592bfddea2 100644 --- a/src/AutoMapper.DI.Tests/MultipleRegistrationTests.cs +++ b/src/AutoMapper.DI.Tests/MultipleRegistrationTests.cs @@ -30,18 +30,9 @@ public void Can_register_assembly_multiple_times() var services = new ServiceCollection(); services.AddSingleton(NullLoggerFactory.Instance); - services.AddAutoMapper(opt => - { - opt.AddMaps(typeof(MultipleRegistrationTests)); - }); - services.AddAutoMapper(opt => - { - opt.AddMaps(typeof(MultipleRegistrationTests)); - }); - services.AddAutoMapper(opt => - { - opt.AddMaps(typeof(MultipleRegistrationTests)); - }); + services.AddAutoMapper(_ => { }, typeof(MultipleRegistrationTests)); + services.AddAutoMapper(_ => { }, typeof(MultipleRegistrationTests)); + services.AddAutoMapper(_ => { }, typeof(MultipleRegistrationTests)); services.AddTransient(); var serviceProvider = services.BuildServiceProvider(); diff --git a/src/AutoMapper.DI.Tests/ScopeTests.cs b/src/AutoMapper.DI.Tests/ScopeTests.cs index bab81192e2..c7d5848e57 100644 --- a/src/AutoMapper.DI.Tests/ScopeTests.cs +++ b/src/AutoMapper.DI.Tests/ScopeTests.cs @@ -13,10 +13,7 @@ public void Can_depend_on_scoped_services_as_transient_default() { var services = new ServiceCollection(); services.AddSingleton(NullLoggerFactory.Instance); - services.AddAutoMapper(opt => - { - opt.AddMaps(typeof(Source).Assembly); - }); + services.AddAutoMapper(_ => { }, typeof(Source).Assembly); services.AddScoped(); var provider = services.BuildServiceProvider(); @@ -39,11 +36,9 @@ public void Can_depend_on_scoped_services_as_scoped() { var services = new ServiceCollection(); services.AddSingleton(NullLoggerFactory.Instance); - services.AddAutoMapper(opt => + services.AddAutoMapper(_ => { - opt.ServiceLifetime = ServiceLifetime.Scoped; - opt.AddMaps(typeof(Source).Assembly); - }); + }, [typeof(Source).Assembly], ServiceLifetime.Scoped); services.AddScoped(); var provider = services.BuildServiceProvider(); @@ -66,11 +61,7 @@ public void Cannot_correctly_resolve_scoped_services_as_singleton() { var services = new ServiceCollection(); services.AddSingleton(NullLoggerFactory.Instance); - services.AddAutoMapper(opt => - { - opt.AddMaps(typeof(Source).Assembly); - opt.ServiceLifetime = ServiceLifetime.Singleton; - }); + services.AddAutoMapper(_ => { }, [typeof(Source).Assembly], ServiceLifetime.Singleton); services.AddScoped(); var provider = services.BuildServiceProvider(); diff --git a/src/AutoMapper.DI.Tests/ServiceLifetimeTests.cs b/src/AutoMapper.DI.Tests/ServiceLifetimeTests.cs index e6f31a91ce..1c50a92016 100644 --- a/src/AutoMapper.DI.Tests/ServiceLifetimeTests.cs +++ b/src/AutoMapper.DI.Tests/ServiceLifetimeTests.cs @@ -34,10 +34,9 @@ public void AddAutoMapperExtensionDefaultWithServiceLifetime() var serviceCollection = new ServiceCollection(); //act - serviceCollection.AddAutoMapper(opt => + serviceCollection.AddAutoMapper(_ => { - opt.ServiceLifetime = ServiceLifetime.Singleton; - }); + }, new List(), ServiceLifetime.Singleton); var serviceDescriptor = serviceCollection.FirstOrDefault(sd => sd.ServiceType == typeof(IMapper)); //assert diff --git a/src/AutoMapper.DI.Tests/ServiceProviderTests.cs b/src/AutoMapper.DI.Tests/ServiceProviderTests.cs new file mode 100644 index 0000000000..4cd9ee4dba --- /dev/null +++ b/src/AutoMapper.DI.Tests/ServiceProviderTests.cs @@ -0,0 +1,48 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace AutoMapper.Extensions.Microsoft.DependencyInjection.Tests +{ + using System; + using global::Microsoft.Extensions.DependencyInjection; + using Shouldly; + using Xunit; + + public class ServiceProviderTests + { + private readonly IServiceProvider _provider; + + public ServiceProviderTests() + { + IServiceCollection services = new ServiceCollection(); + services.AddTransient(sp => new FooService(5)); + services.AddSingleton(NullLoggerFactory.Instance); + services.AddAutoMapper((sp, _) => + { + var service = sp.GetRequiredService(); + service.Modify(5); + }, typeof(Source), typeof(Profile)); + _provider = services.BuildServiceProvider(); + + _provider.GetService().AssertConfigurationIsValid(); + } + + [Fact] + public void ShouldResolveWithDependency() + { + var mapper = _provider.GetService(); + var dest = mapper.Map(new Source2()); + + dest.ResolvedValue.ShouldBe(5); + } + + [Fact] + public void ShouldConvertWithDependency() + { + var mapper = _provider.GetService(); + var dest = mapper.Map(new Source2 { ConvertedValue = 5}); + + dest.ConvertedValue.ShouldBe(10); + } + } +} diff --git a/src/AutoMapper.DI.Tests/TypeResolutionTests.cs b/src/AutoMapper.DI.Tests/TypeResolutionTests.cs index 289499eb3f..924d62d2ab 100644 --- a/src/AutoMapper.DI.Tests/TypeResolutionTests.cs +++ b/src/AutoMapper.DI.Tests/TypeResolutionTests.cs @@ -17,10 +17,9 @@ public TypeResolutionTests() { IServiceCollection services = new ServiceCollection(); services.AddSingleton(NullLoggerFactory.Instance); - services.AddAutoMapper(opt => + services.AddAutoMapper(_ => { - opt.AddMaps(typeof(Source)); - }); + }, typeof(Source)); _provider = services.BuildServiceProvider(); } diff --git a/src/AutoMapper/Configuration/MapperConfigurationExpression.cs b/src/AutoMapper/Configuration/MapperConfigurationExpression.cs index 024715204b..c4d4c51461 100644 --- a/src/AutoMapper/Configuration/MapperConfigurationExpression.cs +++ b/src/AutoMapper/Configuration/MapperConfigurationExpression.cs @@ -94,12 +94,6 @@ public interface IMapperConfigurationExpression : IProfileExpression /// Gets or sets the license key. You can find your license key in your account. /// string LicenseKey { get; set; } - - /// - /// Gets or sets the default service lifetime. Used for services registered using - /// Defaults to - /// - public ServiceLifetime ServiceLifetime { get; set; } } public sealed class MapperConfigurationExpression : Profile, IGlobalConfigurationExpression { @@ -146,30 +140,6 @@ public void CreateProfile(string profileName, Action config) Features IGlobalConfigurationExpression.Features { get; } = new(); - void IGlobalConfigurationExpression.RegisterServices(IServiceCollection services) - { - foreach (var type in _scannedAssembles.SelectMany(a => a.GetTypes().Where(type => type.IsClass && !type.IsAbstract))) - { - if (TryGetAmType(type, out var amType)) - { - // use try add to avoid double-registration - services.TryAdd(new ServiceDescriptor(type, type, ServiceLifetime)); - } - } - - return; - - bool TryGetAmType(Type type, out Type amType) - { - amType = AmTypes - .Select(type.GetGenericInterface) - .FirstOrDefault(serviceType => serviceType != null); - - return amType != null; - } - } - - public void AddProfile(Profile profile) => _profiles.Add(profile); public void AddProfile() where TProfile : Profile, new() => AddProfile(new TProfile()); diff --git a/src/AutoMapper/Internal/InternalApi.cs b/src/AutoMapper/Internal/InternalApi.cs index 197b46d125..6f5e030c3f 100644 --- a/src/AutoMapper/Internal/InternalApi.cs +++ b/src/AutoMapper/Internal/InternalApi.cs @@ -44,12 +44,6 @@ public interface IGlobalConfigurationExpression : IMapperConfigurationExpression /// Must be zero for EF6. Can be greater than zero for EF Core. /// int RecursiveQueriesMaxDepth { get; set; } - - /// - /// Registers services from assemblies used in - /// - /// Service collection - void RegisterServices(IServiceCollection services); } [EditorBrowsable(EditorBrowsableState.Never)] public interface IGlobalConfiguration : IConfigurationProvider diff --git a/src/AutoMapper/ServiceCollectionExtensions.cs b/src/AutoMapper/ServiceCollectionExtensions.cs index 136610855b..16eb00b33c 100644 --- a/src/AutoMapper/ServiceCollectionExtensions.cs +++ b/src/AutoMapper/ServiceCollectionExtensions.cs @@ -23,24 +23,53 @@ namespace Microsoft.Extensions.DependencyInjection; /// public static class ServiceCollectionExtensions { + static readonly Type[] AmTypes = [typeof(IValueResolver<,,>), typeof(IMemberValueResolver<,,,>), typeof(ITypeConverter<,>), typeof(IValueConverter<,>), typeof(IMappingAction<,>)]; public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action configAction) - => AddAutoMapperClasses(services, configAction); + => AddAutoMapperClasses(services, (sp, cfg) => configAction?.Invoke(cfg), null); - // public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action configAction) - // => AddAutoMapperClasses(services, configAction); + public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action configAction, params Assembly[] assemblies) + => AddAutoMapperClasses(services, (sp, cfg) => configAction?.Invoke(cfg), assemblies); - private static IServiceCollection AddAutoMapperClasses(IServiceCollection services, Action configAction) - { - configAction ??= _ => { }; + public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action configAction, params Assembly[] assemblies) + => AddAutoMapperClasses(services, configAction, assemblies); + + public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action configAction, IEnumerable assemblies, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + => AddAutoMapperClasses(services, (sp, cfg) => configAction?.Invoke(cfg), assemblies, serviceLifetime); + + public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action configAction, IEnumerable assemblies, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + => AddAutoMapperClasses(services, configAction, assemblies, serviceLifetime); - var config = new MapperConfigurationExpression(); + public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action configAction, params Type[] profileAssemblyMarkerTypes) + => AddAutoMapperClasses(services, (sp, cfg) => configAction?.Invoke(cfg), profileAssemblyMarkerTypes.Select(t => t.GetTypeInfo().Assembly)); - configAction(config); - - ((IGlobalConfigurationExpression)config).RegisterServices(services); + public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action configAction, params Type[] profileAssemblyMarkerTypes) + => AddAutoMapperClasses(services, configAction, profileAssemblyMarkerTypes.Select(t => t.GetTypeInfo().Assembly)); - services.AddSingleton(config); - + public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action configAction, + IEnumerable profileAssemblyMarkerTypes, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + => AddAutoMapperClasses(services, (sp, cfg) => configAction?.Invoke(cfg), profileAssemblyMarkerTypes.Select(t => t.GetTypeInfo().Assembly), serviceLifetime); + + public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action configAction, + IEnumerable profileAssemblyMarkerTypes, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + => AddAutoMapperClasses(services, configAction, profileAssemblyMarkerTypes.Select(t => t.GetTypeInfo().Assembly), serviceLifetime); + + private static IServiceCollection AddAutoMapperClasses(IServiceCollection services, Action configAction, + IEnumerable assembliesToScan, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + { + if (configAction != null) + { + services.AddOptions().Configure((options, sp) => configAction(sp, options)); + } + if (assembliesToScan != null) + { + assembliesToScan = assembliesToScan.Where(a => !a.IsDynamic && a != typeof(Mapper).Assembly).Distinct(); + services.Configure(options => options.AddMaps(assembliesToScan)); + foreach (var type in assembliesToScan.SelectMany(a => a.GetTypes().Where(type => type.IsClass && !type.IsAbstract && IsAmType(type)))) + { + // use try add to avoid double-registration + services.TryAddTransient(type); + } + } // Just return if we've already added AutoMapper to avoid double-registration if (services.Any(sd => sd.ServiceType == typeof(IMapper))) { @@ -49,11 +78,12 @@ private static IServiceCollection AddAutoMapperClasses(IServiceCollection servic services.AddSingleton(sp => { // A mapper configuration is required - var options = sp.GetRequiredService(); - return new MapperConfiguration(options, sp.GetRequiredService()); + var options = sp.GetRequiredService>(); + var loggerFactory = sp.GetRequiredService(); + return new MapperConfiguration(options.Value, loggerFactory); }); - services.Add(new(typeof(IMapper), sp => new Mapper(sp.GetRequiredService(), sp.GetService), config.ServiceLifetime)); - + services.Add(new(typeof(IMapper), sp => new Mapper(sp.GetRequiredService(), sp.GetService), serviceLifetime)); return services; + bool IsAmType(Type type) => Array.Exists(AmTypes, openType => type.GetGenericInterface(openType) != null); } } \ No newline at end of file