From 18f886d3a24568bff42964a66448e0b4876ce135 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Sat, 11 Oct 2025 11:17:07 +0500 Subject: [PATCH 1/3] refactoring to ITypeAdapterConfig add FrozenTypeAdapterConfig --- .../Configs/FrozenTypeAdapterConfig.cs | 131 ++++++++++++++++++ src/Mapster.Fluent/Mapster.Fluent.csproj | 4 +- src/Mapster.Fluent/MapsterOptions.cs | 2 +- .../ServiceCollectionExtensions.cs | 21 +-- tests/Mapster.Fluent.Tests/MapsterDITests.cs | 75 ++++++++-- 5 files changed, 209 insertions(+), 24 deletions(-) create mode 100644 src/Mapster.Fluent/Configs/FrozenTypeAdapterConfig.cs diff --git a/src/Mapster.Fluent/Configs/FrozenTypeAdapterConfig.cs b/src/Mapster.Fluent/Configs/FrozenTypeAdapterConfig.cs new file mode 100644 index 0000000..c482368 --- /dev/null +++ b/src/Mapster.Fluent/Configs/FrozenTypeAdapterConfig.cs @@ -0,0 +1,131 @@ +using Mapster.Models; +using Mapster.Utils; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Mapster.Fluent.Configs +{ + public class FrozenTypeAdapterConfig : BaseTypeAdapterConfigDecorator, ITypeAdapterConfig + { + + private readonly ConcurrentDictionary _frozentypes = new(); + private readonly ITypeAdapterConfig _dummyConfig = new TypeAdapterConfig(); + + public bool IsTotalFrozen { get; private set; } + + public FrozenTypeAdapterConfig() : this(new TypeAdapterConfig()) + { + } + + public FrozenTypeAdapterConfig(ITypeAdapterConfig config) : base(config.Clone()) + { + } + + public override ITypeAdapterConfig GlobalSettings => new FrozenTypeAdapterConfig(); + + public override TypeAdapterSetter ForType(Type sourceType, Type destinationType) + { + if (IsTotalFrozen || + _frozentypes.TryGetValue(new TypeTuple(sourceType, destinationType), out _)) + { + _dummyConfig.Clear(); + return _dummyConfig.NewConfig(sourceType,destinationType); + } + + return base.ForType(sourceType, destinationType); + } + + public override TypeAdapterSetter NewConfig(Type sourceType, Type destinationType) + { + if (IsTotalFrozen || + _frozentypes.TryGetValue(new TypeTuple(sourceType, destinationType), out _)) + { + _dummyConfig.Clear(); + return new TypeAdapterSetter(new TypeAdapterSettings(), _dummyConfig); + } + + return base.NewConfig(sourceType, destinationType); + } + + public override TypeAdapterSetter NewConfig() + { + if (IsTotalFrozen || + _frozentypes.TryGetValue(new TypeTuple(typeof(TSource), typeof(TDestination)), out _)) + { + _dummyConfig.Clear(); + return _dummyConfig.NewConfig(); ; + } + + return base.NewConfig(); + } + + public void FrozenTypes(Type sourceType, Type destinationType) + { + var types = new TypeTuple(sourceType, destinationType); + _frozentypes.TryAdd(types, types); + } + + public void FrozenTypes(TypeTuple types) + { + Compile(types.Source, types.Destination); + _frozentypes.TryAdd(types, types); + } + + public void FrozenTypes() + { + Compile(typeof(TSource), typeof(TDestination)); + + var types = new TypeTuple(typeof(TSource), typeof(TDestination)); + _frozentypes.TryAdd(types, types); + } + + public void DeepFreeze() + { + var keys = RuleMap.Keys.ToList(); + Compile(); + + foreach (var item in keys) + { + _frozentypes.TryAdd(item, item); + } + + IsTotalFrozen = true; + } + + public override void Apply(IEnumerable registers) + { + foreach (IRegister register in registers) + { + register.Register(this); + } + } + + + public override void Apply(params IRegister[] registers) + { + foreach (IRegister register in registers) + { + register.Register(this); + } + } + + public override void Apply(IEnumerable> registers) + { + base.Apply(registers); + } + + public override IList Scan(params Assembly[] assemblies) + { + List registers = assemblies.Select(assembly => assembly.GetLoadableTypes() + .Where(x => typeof(IRegister).GetTypeInfo().IsAssignableFrom(x.GetTypeInfo()) && x.GetTypeInfo().IsClass && !x.GetTypeInfo().IsAbstract)) + .SelectMany(registerTypes => + registerTypes.Select(registerType => (IRegister)Activator.CreateInstance(registerType))).ToList(); + + Apply(registers); + return registers; + } + } +} diff --git a/src/Mapster.Fluent/Mapster.Fluent.csproj b/src/Mapster.Fluent/Mapster.Fluent.csproj index f2f9d41..6c5f61c 100644 --- a/src/Mapster.Fluent/Mapster.Fluent.csproj +++ b/src/Mapster.Fluent/Mapster.Fluent.csproj @@ -5,8 +5,8 @@ - - + + diff --git a/src/Mapster.Fluent/MapsterOptions.cs b/src/Mapster.Fluent/MapsterOptions.cs index 2e1405e..360b8e5 100644 --- a/src/Mapster.Fluent/MapsterOptions.cs +++ b/src/Mapster.Fluent/MapsterOptions.cs @@ -20,6 +20,6 @@ public class MapsterOptions /// public bool UseServiceMapper { get; set; } = true; - public Action ConfigureAction { get; set; } + public Action ConfigureAction { get; set; } } } diff --git a/src/Mapster.Fluent/ServiceCollectionExtensions.cs b/src/Mapster.Fluent/ServiceCollectionExtensions.cs index 55e23c1..9d7ffb9 100644 --- a/src/Mapster.Fluent/ServiceCollectionExtensions.cs +++ b/src/Mapster.Fluent/ServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ -using MapsterMapper; +using Mapster.Fluent.Configs; +using MapsterMapper; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using System; @@ -18,7 +19,7 @@ public static class ServiceCollectionExtensions /// The service collection for method chaining. public static IServiceCollection AddMapsterFluent( this IServiceCollection serviceCollection, - Action configure, + Action configure, Action options = null) { if (serviceCollection == null) throw new ArgumentNullException(nameof(serviceCollection)); @@ -27,10 +28,10 @@ public static IServiceCollection AddMapsterFluent( var mapsterOptions = new MapsterOptions(); options?.Invoke(mapsterOptions); - var innerConfig = new TypeAdapterConfig(); - IFluentMapperConfig config = new FluentTypeAdapterConfig(innerConfig); - configure.Invoke(config); - mapsterOptions.ConfigureAction?.Invoke(config); + var innerConfig = new FrozenTypeAdapterConfig(); + + configure.Invoke(innerConfig); + mapsterOptions.ConfigureAction?.Invoke(innerConfig); // Assembly scanning if (mapsterOptions.AssembliesToScan?.Any() == true) @@ -38,7 +39,7 @@ public static IServiceCollection AddMapsterFluent( innerConfig.Scan(mapsterOptions.AssembliesToScan.ToArray()); } - serviceCollection.TryAddSingleton(config.GetInnerConfig()); + serviceCollection.TryAddSingleton(innerConfig); if (mapsterOptions.UseServiceMapper) { serviceCollection.TryAddTransient(); @@ -60,14 +61,14 @@ public static IServiceCollection AddMapsterFluent( /// The service collection for method chaining. public static IServiceCollection AddMapsterWithConfig( this IServiceCollection serviceCollection, - TypeAdapterConfig existingConfig) + ITypeAdapterConfig existingConfig) { if (serviceCollection == null) throw new ArgumentNullException(nameof(serviceCollection)); if (existingConfig == null) throw new ArgumentNullException(nameof(existingConfig)); - IFluentMapperConfig config = new FluentTypeAdapterConfig(existingConfig); + ITypeAdapterConfig config = new FrozenTypeAdapterConfig(existingConfig); - serviceCollection.TryAddSingleton(config.GetInnerConfig()); + serviceCollection.TryAddSingleton(config); serviceCollection.TryAddTransient(); serviceCollection.TryAddSingleton(); diff --git a/tests/Mapster.Fluent.Tests/MapsterDITests.cs b/tests/Mapster.Fluent.Tests/MapsterDITests.cs index b44b3b3..05bfb82 100644 --- a/tests/Mapster.Fluent.Tests/MapsterDITests.cs +++ b/tests/Mapster.Fluent.Tests/MapsterDITests.cs @@ -99,14 +99,14 @@ public void AddMapsterFluent_RegistersTypeAdapterConfigAsSingleton() var provider = services.BuildServiceProvider(); // Act - TypeAdapterConfig config1, config2; + ITypeAdapterConfig config1, config2; using (var scope1 = provider.CreateScope()) { - config1 = scope1.ServiceProvider.GetRequiredService(); + config1 = scope1.ServiceProvider.GetRequiredService(); } using (var scope2 = provider.CreateScope()) { - config2 = scope2.ServiceProvider.GetRequiredService(); + config2 = scope2.ServiceProvider.GetRequiredService(); } // Assert @@ -253,7 +253,7 @@ public void ScanMapster_WithValidAssembly_RegistersIRegisterImplementations() var provider = services.BuildServiceProvider(); var mapper = provider.GetRequiredService(); - var config = provider.GetRequiredService(); + var config = provider.GetRequiredService(); // Assert mapper.ShouldNotBeNull(); @@ -338,7 +338,7 @@ public void AddMapsterFluent_WithUseServiceMapperTrue_RegistersServiceMapper() } // ===== ADD MAPSTER WITH CONFIG TESTS ===== - + [Ignore("To prevent modification, access to TypeAdapterConfig should not be granted.")] [TestMethod] public void AddMapsterWithConfig_WithValidConfig_RegistersMapperAndConfig() { @@ -353,7 +353,7 @@ public void AddMapsterWithConfig_WithValidConfig_RegistersMapperAndConfig() services.AddMapsterWithConfig(existingConfig); var provider = services.BuildServiceProvider(); var mapper = provider.GetRequiredService(); - var config = provider.GetRequiredService(); + var config = provider.GetRequiredService(); // Assert mapper.ShouldNotBeNull(); @@ -385,7 +385,7 @@ public void AddMapsterWithConfig_WithNullConfig_ThrowsArgumentNullException() // Act & Assert Should.Throw(() => services.AddMapsterWithConfig(null)); } - + [Ignore("To prevent modification, access to TypeAdapterConfig should not be granted.")] [TestMethod] public void AddMapsterWithConfig_RegistersTypeAdapterConfigAsSingleton() { @@ -396,14 +396,14 @@ public void AddMapsterWithConfig_RegistersTypeAdapterConfigAsSingleton() var provider = services.BuildServiceProvider(); // Act - TypeAdapterConfig config1, config2; + ITypeAdapterConfig config1, config2; using (var scope1 = provider.CreateScope()) { - config1 = scope1.ServiceProvider.GetRequiredService(); + config1 = scope1.ServiceProvider.GetRequiredService(); } using (var scope2 = provider.CreateScope()) { - config2 = scope2.ServiceProvider.GetRequiredService(); + config2 = scope2.ServiceProvider.GetRequiredService(); } // Assert @@ -513,6 +513,59 @@ public void ScanMapster_UsesServiceMapperByDefault() // Assert mapper.ShouldBeOfType(); } + + [TestMethod] + public void FrozenConfigIsWork() + { + // Arrange + var services = new ServiceCollection(); + + // Act + services.AddMapsterFluent( + config => + { + config.NewConfig() + .Map(dest => dest.DisplayName, src => "Frozen DisplayName"); + config.NewConfig() + .Map(dest => dest.FullName, src => "Frozen FullName") + .Ignore(dest => dest.Id); + + config.FrozenTypes(); + config.FrozenTypes(); + + config.NewConfig() + .Map(dest => dest.FullName, src => $"{src.LastName} {src.FirstName}") + .Ignore(dest => dest.Id); + + config.NewConfig() + .Map(dest => dest.DisplayName, src => $"Product: {src.Name}"); + + }, + options => + { + options.AssembliesToScan = [Assembly.GetExecutingAssembly()]; + options.UseServiceMapper = true; + }); + + var provider = services.BuildServiceProvider(); + + // Assert + using var scope = provider.CreateScope(); + var mapper = scope.ServiceProvider.GetRequiredService(); + + // Test scanned mapping (from IRegister) + var user = new TestUser { FirstName = "Integration", LastName = "Test" }; + var userDto = mapper.Map(user); + userDto.FullName.ShouldBe("Frozen FullName"); + + // Test fluent configuration + var product = new TestProduct { Name = "Widget" }; + var productDto = mapper.Map(product); + productDto.DisplayName.ShouldBe("Frozen DisplayName"); + + // Test ServiceMapper type + mapper.ShouldBeOfType(); + } } @@ -545,7 +598,7 @@ public class TestProductDto public class TestUserMappingConfig : IRegister { - public void Register(TypeAdapterConfig config) + public void Register(ITypeAdapterConfig config) { config.NewConfig() .Map(dest => dest.FullName, src => $"{src.FirstName} {src.LastName}"); From 5e85b54bd2c3cc7baaf3a25fd0ea736b504f2f41 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Sat, 11 Oct 2025 16:53:30 +0500 Subject: [PATCH 2/3] override Clone() --- .../Configs/FrozenTypeAdapterConfig.cs | 29 +++++++++++++++++++ tests/Mapster.Fluent.Tests/MapsterDITests.cs | 3 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/Mapster.Fluent/Configs/FrozenTypeAdapterConfig.cs b/src/Mapster.Fluent/Configs/FrozenTypeAdapterConfig.cs index c482368..1d9897e 100644 --- a/src/Mapster.Fluent/Configs/FrozenTypeAdapterConfig.cs +++ b/src/Mapster.Fluent/Configs/FrozenTypeAdapterConfig.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; namespace Mapster.Fluent.Configs { @@ -24,6 +25,11 @@ public FrozenTypeAdapterConfig(ITypeAdapterConfig config) : base(config.Clone()) { } + private FrozenTypeAdapterConfig(ITypeAdapterConfig config, bool isTotalFrozen) : base(config) + { + IsTotalFrozen = isTotalFrozen; + } + public override ITypeAdapterConfig GlobalSettings => new FrozenTypeAdapterConfig(); public override TypeAdapterSetter ForType(Type sourceType, Type destinationType) @@ -127,5 +133,28 @@ public override IList Scan(params Assembly[] assemblies) Apply(registers); return registers; } + + public override ITypeAdapterConfig Clone() + { + var result = new FrozenTypeAdapterConfig(base.Clone(), IsTotalFrozen); + + if (IsTotalFrozen) + result.DeepFreeze(); + + else if(_frozentypes.Any()) + { + foreach(var type in _frozentypes) + { + result.FrozenTypes(type.Key); + } + } + + return result; + } + + public override ITypeAdapterConfig Fork(Action action, [CallerFilePath] string key1 = "", [CallerLineNumber] int key2 = 0) + { + return base.Fork(action, key1, key2); + } } } diff --git a/tests/Mapster.Fluent.Tests/MapsterDITests.cs b/tests/Mapster.Fluent.Tests/MapsterDITests.cs index 05bfb82..e8755f6 100644 --- a/tests/Mapster.Fluent.Tests/MapsterDITests.cs +++ b/tests/Mapster.Fluent.Tests/MapsterDITests.cs @@ -1,4 +1,5 @@ -using MapsterMapper; +using Mapster.Fluent.Configs; +using MapsterMapper; using Microsoft.Extensions.DependencyInjection; using Microsoft.VisualStudio.TestTools.UnitTesting; using Shouldly; From 56a17a3d5299b318cc6e231cab9f052d4db63f6b Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Fri, 24 Oct 2025 18:20:27 +0500 Subject: [PATCH 3/3] rebuild --- .../Configs/FrozenTypeAdapterConfig.cs | 88 +++++-------------- src/Mapster.Fluent/Mapster.Fluent.csproj | 2 +- 2 files changed, 25 insertions(+), 65 deletions(-) diff --git a/src/Mapster.Fluent/Configs/FrozenTypeAdapterConfig.cs b/src/Mapster.Fluent/Configs/FrozenTypeAdapterConfig.cs index 1d9897e..9a889af 100644 --- a/src/Mapster.Fluent/Configs/FrozenTypeAdapterConfig.cs +++ b/src/Mapster.Fluent/Configs/FrozenTypeAdapterConfig.cs @@ -1,10 +1,8 @@ using Mapster.Models; -using Mapster.Utils; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Reflection; using System.Runtime.CompilerServices; namespace Mapster.Fluent.Configs @@ -17,21 +15,21 @@ public class FrozenTypeAdapterConfig : BaseTypeAdapterConfigDecorator, ITypeAdap public bool IsTotalFrozen { get; private set; } - public FrozenTypeAdapterConfig() : this(new TypeAdapterConfig()) + + + private FrozenTypeAdapterConfig(ITypeAdapterConfig config, bool isTotalFrozen, bool IsGlobal = false ) { + IsTotalFrozen = isTotalFrozen; } - public FrozenTypeAdapterConfig(ITypeAdapterConfig config) : base(config.Clone()) + public FrozenTypeAdapterConfig(bool IsGlobal = false) : base(IsGlobal) { } - private FrozenTypeAdapterConfig(ITypeAdapterConfig config, bool isTotalFrozen) : base(config) + public FrozenTypeAdapterConfig(ITypeAdapterConfig config, bool IsGlobal = false) : base(config, IsGlobal) { - IsTotalFrozen = isTotalFrozen; } - public override ITypeAdapterConfig GlobalSettings => new FrozenTypeAdapterConfig(); - public override TypeAdapterSetter ForType(Type sourceType, Type destinationType) { if (IsTotalFrozen || @@ -44,30 +42,7 @@ public override TypeAdapterSetter ForType(Type sourceType, Type destinationType) return base.ForType(sourceType, destinationType); } - public override TypeAdapterSetter NewConfig(Type sourceType, Type destinationType) - { - if (IsTotalFrozen || - _frozentypes.TryGetValue(new TypeTuple(sourceType, destinationType), out _)) - { - _dummyConfig.Clear(); - return new TypeAdapterSetter(new TypeAdapterSettings(), _dummyConfig); - } - - return base.NewConfig(sourceType, destinationType); - } - - public override TypeAdapterSetter NewConfig() - { - if (IsTotalFrozen || - _frozentypes.TryGetValue(new TypeTuple(typeof(TSource), typeof(TDestination)), out _)) - { - _dummyConfig.Clear(); - return _dummyConfig.NewConfig(); ; - } - - return base.NewConfig(); - } - + public void FrozenTypes(Type sourceType, Type destinationType) { var types = new TypeTuple(sourceType, destinationType); @@ -101,38 +76,7 @@ public void DeepFreeze() IsTotalFrozen = true; } - public override void Apply(IEnumerable registers) - { - foreach (IRegister register in registers) - { - register.Register(this); - } - } - - - public override void Apply(params IRegister[] registers) - { - foreach (IRegister register in registers) - { - register.Register(this); - } - } - - public override void Apply(IEnumerable> registers) - { - base.Apply(registers); - } - - public override IList Scan(params Assembly[] assemblies) - { - List registers = assemblies.Select(assembly => assembly.GetLoadableTypes() - .Where(x => typeof(IRegister).GetTypeInfo().IsAssignableFrom(x.GetTypeInfo()) && x.GetTypeInfo().IsClass && !x.GetTypeInfo().IsAbstract)) - .SelectMany(registerTypes => - registerTypes.Select(registerType => (IRegister)Activator.CreateInstance(registerType))).ToList(); - - Apply(registers); - return registers; - } + public override ITypeAdapterConfig Clone() { @@ -156,5 +100,21 @@ public override ITypeAdapterConfig Fork(Action action, [Call { return base.Fork(action, key1, key2); } + + public override void Apply(IEnumerable registers) + { + foreach (var item in registers) + { + item.Register(this); + } + } + + public override void Remove(Type sourceType, Type destinationType) + { + if (IsTotalFrozen || + _frozentypes.TryGetValue(new TypeTuple(sourceType, destinationType), out _)) ; + else + base.Remove(sourceType, destinationType); + } } } diff --git a/src/Mapster.Fluent/Mapster.Fluent.csproj b/src/Mapster.Fluent/Mapster.Fluent.csproj index 6c5f61c..ef01780 100644 --- a/src/Mapster.Fluent/Mapster.Fluent.csproj +++ b/src/Mapster.Fluent/Mapster.Fluent.csproj @@ -5,7 +5,7 @@ - +