diff --git a/Volo.Abp.sln b/Volo.Abp.sln index 8f561b22f5..a40ca25c23 100644 --- a/Volo.Abp.sln +++ b/Volo.Abp.sln @@ -114,7 +114,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Autofac.Tests", "t EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Castle.Core.Tests", "test\Volo.Abp.Castle.Core.Tests\Volo.Abp.Castle.Core.Tests.csproj", "{CE12E5C2-7B3E-4637-B6A3-274BB5C3DE16}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AutoMapper", "src\Volo.Abp.AutoMapper\Volo.Abp.AutoMapper.csproj", "{D2F3594F-E2B9-4338-A022-F00C4E9A14C3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AutoMapper", "src\Volo.Abp.AutoMapper\Volo.Abp.AutoMapper.csproj", "{D2F3594F-E2B9-4338-A022-F00C4E9A14C3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AutoMapper.Tests", "test\Volo.Abp.AutoMapper.Tests\Volo.Abp.AutoMapper.Tests.csproj", "{8343BE23-6A7B-4C58-BF0D-95188B11B180}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -282,6 +284,10 @@ Global {D2F3594F-E2B9-4338-A022-F00C4E9A14C3}.Debug|Any CPU.Build.0 = Debug|Any CPU {D2F3594F-E2B9-4338-A022-F00C4E9A14C3}.Release|Any CPU.ActiveCfg = Release|Any CPU {D2F3594F-E2B9-4338-A022-F00C4E9A14C3}.Release|Any CPU.Build.0 = Release|Any CPU + {8343BE23-6A7B-4C58-BF0D-95188B11B180}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8343BE23-6A7B-4C58-BF0D-95188B11B180}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8343BE23-6A7B-4C58-BF0D-95188B11B180}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8343BE23-6A7B-4C58-BF0D-95188B11B180}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -336,5 +342,6 @@ Global {D8BE64D2-BD83-40F5-9783-D7FDDF668C45} = {37087D1B-3693-4E96-983D-A69F210BDE53} {CE12E5C2-7B3E-4637-B6A3-274BB5C3DE16} = {37087D1B-3693-4E96-983D-A69F210BDE53} {D2F3594F-E2B9-4338-A022-F00C4E9A14C3} = {4C753F64-0C93-4D65-96C2-A40893AFC1E8} + {8343BE23-6A7B-4C58-BF0D-95188B11B180} = {37087D1B-3693-4E96-983D-A69F210BDE53} EndGlobalSection EndGlobal diff --git a/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperConfiguration.cs b/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperConfiguration.cs deleted file mode 100644 index 2665e5c950..0000000000 --- a/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using AutoMapper; - -namespace Volo.Abp.AutoMapper -{ - public class AbpAutoMapperConfiguration : IAbpAutoMapperConfiguration - { - public List> Configurators { get; } - - public bool UseStaticMapper { get; set; } - - public AbpAutoMapperConfiguration() - { - UseStaticMapper = true; - Configurators = new List>(); - } - } -} \ No newline at end of file diff --git a/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperModule.cs b/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperModule.cs index c8eb26fc57..5285e55887 100644 --- a/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperModule.cs +++ b/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperModule.cs @@ -1,98 +1,114 @@ using System; +using System.Linq; +using System.Reflection; using AutoMapper; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Volo.Abp.Modularity; +using Volo.Abp.Reflection; namespace Volo.Abp.AutoMapper { public class AbpAutoMapperModule : AbpModule { - //private readonly ITypeFinder _typeFinder; - - //private static volatile bool _createdMappingsBefore; - //private static readonly object SyncObj = new object(); - - //public AbpAutoMapperModule(ITypeFinder typeFinder) - //{ - // _typeFinder = typeFinder; - //} - - //public override void PreInitialize() - //{ - // IocManager.Register(); - - // Configuration.ReplaceService(); - - // Configuration.Modules.AbpAutoMapper().Configurators.Add(CreateCoreMappings); - //} - - //public override void PostInitialize() - //{ - // CreateMappings(); - //} - - //private void CreateMappings() - //{ - // lock (SyncObj) - // { - // Action configurer = configuration => - // { - // FindAndAutoMapTypes(configuration); - // foreach (var configurator in Configuration.Modules.AbpAutoMapper().Configurators) - // { - // configurator(configuration); - // } - // }; - - // if (Configuration.Modules.AbpAutoMapper().UseStaticMapper) - // { - // //We should prevent duplicate mapping in an application, since Mapper is static. - // if (!_createdMappingsBefore) - // { - // Mapper.Initialize(configurer); - // _createdMappingsBefore = true; - // } - - // IocManager.IocContainer.Register( - // Component.For().Instance(Mapper.Instance).LifestyleSingleton() - // ); - // } - // else - // { - // var config = new MapperConfiguration(configurer); - // IocManager.IocContainer.Register( - // Component.For().Instance(config.CreateMapper()).LifestyleSingleton() - // ); - // } - // } - //} - - //private void FindAndAutoMapTypes(IMapperConfigurationExpression configuration) - //{ - // var types = _typeFinder.Find(type => - // { - // var typeInfo = type.GetTypeInfo(); - // return typeInfo.IsDefined(typeof(AutoMapAttribute)) || - // typeInfo.IsDefined(typeof(AutoMapFromAttribute)) || - // typeInfo.IsDefined(typeof(AutoMapToAttribute)); - // } - // ); - - // Logger<>.DebugFormat("Found {0} classes define auto mapping attributes", types.Length); - - // foreach (var type in types) - // { - // Logger<>.Debug(type.FullName); - // configuration.CreateAutoAttributeMaps(type); - // } - //} - - //private void CreateCoreMappings(IMapperConfigurationExpression configuration) - //{ - // var localizationContext = IocManager.Resolve(); - - // configuration.CreateMap().ConvertUsing(ls => ls?.Localize(localizationContext)); - // configuration.CreateMap().ConvertUsing(ls => ls == null ? null : localizationContext.LocalizationManager.GetString(ls)); - //} + private static volatile bool _createdMappingsBefore; + private static readonly object SyncObj = new object(); + + public override void ConfigureServices(IServiceCollection services) + { + services.AddAssemblyOf(); + + var mapperAccessor = new MapperAccessor(); + services.AddSingleton(_ => mapperAccessor); + services.AddSingleton(_ => mapperAccessor); + + services.Configure(options => + { + options.Configurators.Add(CreateCoreMappings); + }); + } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + CreateMappings(context.ServiceProvider); + } + + private void CreateMappings(IServiceProvider serviceProvider) + { + lock (SyncObj) + { + using (var scope = serviceProvider.CreateScope()) + { + var options = scope.ServiceProvider.GetRequiredService>().Value; + + void ConfigureAll(IAbpAutoMapperConfigurationContext ctx) + { + FindAndAutoMapTypes(ctx); + foreach (var configurator in options.Configurators) + { + configurator(ctx); + } + } + + if (options.UseStaticMapper) + { + //We should prevent duplicate mapping in an application, since Mapper is static. + if (!_createdMappingsBefore) + { + Mapper.Initialize(mapperConfigurationExpression => + { + ConfigureAll(new AbpAutoMapperConfigurationContext(mapperConfigurationExpression, scope.ServiceProvider)); + }); + + _createdMappingsBefore = true; + } + + scope.ServiceProvider.GetRequiredService().Mapper = Mapper.Instance; + } + else + { + var config = new MapperConfiguration(mapperConfigurationExpression => + { + ConfigureAll(new AbpAutoMapperConfigurationContext(mapperConfigurationExpression, scope.ServiceProvider)); + }); + + scope.ServiceProvider.GetRequiredService().Mapper = config.CreateMapper(); + } + } + } + } + + private void FindAndAutoMapTypes(IAbpAutoMapperConfigurationContext context) + { + var typeFinder = context.ServiceProvider.GetRequiredService(); + var logger = context.ServiceProvider.GetRequiredService>(); + + var types = typeFinder.Types.Where(type => + { + var typeInfo = type.GetTypeInfo(); + return typeInfo.IsDefined(typeof(AutoMapAttribute)) || + typeInfo.IsDefined(typeof(AutoMapFromAttribute)) || + typeInfo.IsDefined(typeof(AutoMapToAttribute)); + } + ).ToArray(); + + logger.LogDebug($"Found {types.Length} classes define auto mapping attributes:"); + + foreach (var type in types) + { + logger.LogDebug(type.FullName); + context.MapperConfigurationExpression.CreateAutoAttributeMaps(type); + } + } + + private void CreateCoreMappings(IAbpAutoMapperConfigurationContext context) + { + //Will be done when localization system completed + //var localizationContext = IocManager.Resolve(); + + //configuration.CreateMap().ConvertUsing(ls => ls?.Localize(localizationContext)); + //configuration.CreateMap().ConvertUsing(ls => ls == null ? null : localizationContext.LocalizationManager.GetString(ls)); + } } } diff --git a/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperOptions.cs b/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperOptions.cs new file mode 100644 index 0000000000..38d0c37df4 --- /dev/null +++ b/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperOptions.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using AutoMapper; + +namespace Volo.Abp.AutoMapper +{ + public class AbpAutoMapperOptions + { + public List> Configurators { get; } + + public bool UseStaticMapper { get; set; } + + public AbpAutoMapperOptions() + { + UseStaticMapper = true; + Configurators = new List>(); + } + } + + public interface IAbpAutoMapperConfigurationContext + { + IMapperConfigurationExpression MapperConfigurationExpression { get; } + + IServiceProvider ServiceProvider { get; } + } + + public class AbpAutoMapperConfigurationContext : IAbpAutoMapperConfigurationContext + { + public IMapperConfigurationExpression MapperConfigurationExpression { get; } + public IServiceProvider ServiceProvider { get; } + + public AbpAutoMapperConfigurationContext( + IMapperConfigurationExpression mapperConfigurationExpression, + IServiceProvider serviceProvider) + { + MapperConfigurationExpression = mapperConfigurationExpression; + ServiceProvider = serviceProvider; + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AutoMapperObjectMapper.cs b/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AutoMapperObjectMapper.cs index dbb41da797..0c7986b24a 100644 --- a/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AutoMapperObjectMapper.cs +++ b/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AutoMapperObjectMapper.cs @@ -1,14 +1,17 @@ using AutoMapper; +using Microsoft.Extensions.DependencyInjection; +using Volo.DependencyInjection; namespace Volo.Abp.AutoMapper { + [Dependency(ServiceLifetime.Transient, ReplaceServices = true)] public class AutoMapperObjectMapper : Volo.Abp.ObjectMapping.IObjectMapper { private readonly IMapper _mapper; - public AutoMapperObjectMapper(IMapper mapper) + public AutoMapperObjectMapper(IMapperAccessor mapper) { - _mapper = mapper; + _mapper = mapper.Mapper; } public TDestination Map(object source) diff --git a/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/IAbpAutoMapperConfiguration.cs b/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/IAbpAutoMapperConfiguration.cs deleted file mode 100644 index f5e660a09f..0000000000 --- a/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/IAbpAutoMapperConfiguration.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using AutoMapper; - -namespace Volo.Abp.AutoMapper -{ - public interface IAbpAutoMapperConfiguration - { - List> Configurators { get; } - - /// - /// Use static . - /// Default: true. - /// - bool UseStaticMapper { get; set; } - } -} \ No newline at end of file diff --git a/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/IMapperAccessor.cs b/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/IMapperAccessor.cs new file mode 100644 index 0000000000..42dbc4efad --- /dev/null +++ b/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/IMapperAccessor.cs @@ -0,0 +1,9 @@ +using AutoMapper; + +namespace Volo.Abp.AutoMapper +{ + public interface IMapperAccessor + { + IMapper Mapper { get; } + } +} diff --git a/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/MapperAccessor.cs b/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/MapperAccessor.cs new file mode 100644 index 0000000000..a4be4facb4 --- /dev/null +++ b/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/MapperAccessor.cs @@ -0,0 +1,9 @@ +using AutoMapper; + +namespace Volo.Abp.AutoMapper +{ + public class MapperAccessor : IMapperAccessor + { + public IMapper Mapper { get; set; } + } +} \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Reflection/AssemblyFinder.cs b/src/Volo.Abp/Volo/Abp/Reflection/AssemblyFinder.cs index 44a8522c34..ffa4624931 100644 --- a/src/Volo.Abp/Volo/Abp/Reflection/AssemblyFinder.cs +++ b/src/Volo.Abp/Volo/Abp/Reflection/AssemblyFinder.cs @@ -5,10 +5,11 @@ using System.Linq; using System.Reflection; using System.Threading; using Volo.Abp.Modularity; +using Volo.DependencyInjection; namespace Volo.Abp.Reflection { - public class AssemblyFinder : IAssemblyFinder + public class AssemblyFinder : IAssemblyFinder, ITransientDependency { private readonly IModuleContainer _moduleContainer; diff --git a/src/Volo.Abp/Volo/Abp/Reflection/TypeFinder.cs b/src/Volo.Abp/Volo/Abp/Reflection/TypeFinder.cs index aaa59f0293..e1cfcb42bb 100644 --- a/src/Volo.Abp/Volo/Abp/Reflection/TypeFinder.cs +++ b/src/Volo.Abp/Volo/Abp/Reflection/TypeFinder.cs @@ -4,13 +4,13 @@ using System.Linq; using System.Reflection; using System.Threading; using Microsoft.Extensions.Logging; +using Volo.DependencyInjection; using Volo.ExtensionMethods.Collections.Generic; namespace Volo.Abp.Reflection { - //TODO: Write unit tests! - - public class TypeFinder : ITypeFinder + //TODO: What if we need to this type finder while registering dependencies? + public class TypeFinder : ITypeFinder, ITransientDependency { private readonly IAssemblyFinder _assemblyFinder; private readonly ILogger _logger; diff --git a/test/Volo.Abp.AutoMapper.Tests/Volo.Abp.AutoMapper.Tests.csproj b/test/Volo.Abp.AutoMapper.Tests/Volo.Abp.AutoMapper.Tests.csproj new file mode 100644 index 0000000000..1af44e987f --- /dev/null +++ b/test/Volo.Abp.AutoMapper.Tests/Volo.Abp.AutoMapper.Tests.csproj @@ -0,0 +1,24 @@ + + + + netcoreapp1.1 + Volo.Abp.AutoMapper.Tests + Volo.Abp.AutoMapper.Tests + true + 1.1.1 + false + false + false + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AbpAutoMapperModule_Registration_Tests.cs b/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AbpAutoMapperModule_Registration_Tests.cs new file mode 100644 index 0000000000..8ee6e6368a --- /dev/null +++ b/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AbpAutoMapperModule_Registration_Tests.cs @@ -0,0 +1,17 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.ObjectMapping; +using Volo.Abp.TestBase; +using Xunit; + +namespace Volo.Abp.AutoMapper +{ + public class AbpAutoMapperModule_Registration_Tests : AbpIntegratedTest + { + [Fact] + public void Should_Replace_ObjectMapper() + { + var objectMapper = ServiceProvider.GetRequiredService(); + Assert.True(objectMapper is AutoMapperObjectMapper); + } + } +} diff --git a/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AutofacTestModule.cs b/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AutofacTestModule.cs new file mode 100644 index 0000000000..052381dbd6 --- /dev/null +++ b/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AutofacTestModule.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Modularity; + +namespace Volo.Abp.AutoMapper +{ + [DependsOn(typeof(AbpAutoMapperModule))] + public class AutoMapperTestModule : AbpModule + { + public override void ConfigureServices(IServiceCollection services) + { + services.AddAssemblyOf(); + } + } +} \ No newline at end of file