Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions src/Mapster.Fluent/Configs/FrozenTypeAdapterConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
using Mapster.Models;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;

namespace Mapster.Fluent.Configs
{
public class FrozenTypeAdapterConfig : BaseTypeAdapterConfigDecorator, ITypeAdapterConfig
{

private readonly ConcurrentDictionary<TypeTuple, TypeTuple> _frozentypes = new();
private readonly ITypeAdapterConfig _dummyConfig = new TypeAdapterConfig();

public bool IsTotalFrozen { get; private set; }



private FrozenTypeAdapterConfig(ITypeAdapterConfig config, bool isTotalFrozen, bool IsGlobal = false )
{
IsTotalFrozen = isTotalFrozen;
}

public FrozenTypeAdapterConfig(bool IsGlobal = false) : base(IsGlobal)
{
}

public FrozenTypeAdapterConfig(ITypeAdapterConfig config, bool IsGlobal = false) : base(config, IsGlobal)
{
}

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 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<TSource, TDestination>()
{
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 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<ITypeAdapterConfig> action, [CallerFilePath] string key1 = "", [CallerLineNumber] int key2 = 0)
{
return base.Fork(action, key1, key2);
}

public override void Apply(IEnumerable<IRegister> 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);
}
}
}
4 changes: 2 additions & 2 deletions src/Mapster.Fluent/Mapster.Fluent.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Mapster" Version="7.4.0" />
<PackageReference Include="Mapster.DependencyInjection" Version="1.0.1" />
<PackageReference Include="DevMap.Mapster" Version="9.2.6-alpha" />
<PackageReference Include="DevMap.Mapster.DependencyInjection" Version="9.2.2-alpha" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.9" />
</ItemGroup>

Expand Down
2 changes: 1 addition & 1 deletion src/Mapster.Fluent/MapsterOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ public class MapsterOptions
/// </summary>
public bool UseServiceMapper { get; set; } = true;

public Action<IFluentMapperConfig> ConfigureAction { get; set; }
public Action<ITypeAdapterConfig> ConfigureAction { get; set; }
}
}
21 changes: 11 additions & 10 deletions src/Mapster.Fluent/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using MapsterMapper;
using Mapster.Fluent.Configs;
using MapsterMapper;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using System;
Expand All @@ -18,7 +19,7 @@ public static class ServiceCollectionExtensions
/// <returns>The service collection for method chaining.</returns>
public static IServiceCollection AddMapsterFluent(
this IServiceCollection serviceCollection,
Action<IFluentMapperConfig> configure,
Action<FrozenTypeAdapterConfig> configure,
Action<MapsterOptions> options = null)
{
if (serviceCollection == null) throw new ArgumentNullException(nameof(serviceCollection));
Expand All @@ -27,18 +28,18 @@ 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)
{
innerConfig.Scan(mapsterOptions.AssembliesToScan.ToArray());
}

serviceCollection.TryAddSingleton(config.GetInnerConfig());
serviceCollection.TryAddSingleton<ITypeAdapterConfig>(innerConfig);
if (mapsterOptions.UseServiceMapper)
{
serviceCollection.TryAddTransient<IMapper, ServiceMapper>();
Expand All @@ -60,14 +61,14 @@ public static IServiceCollection AddMapsterFluent(
/// <returns>The service collection for method chaining.</returns>
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<ITypeAdapterConfig>(config);
serviceCollection.TryAddTransient<IMapper, ServiceMapper>();
serviceCollection.TryAddSingleton<IMapContextFactory, DefaultMapContextFactory>();

Expand Down
78 changes: 66 additions & 12 deletions tests/Mapster.Fluent.Tests/MapsterDITests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using MapsterMapper;
using Mapster.Fluent.Configs;
using MapsterMapper;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Shouldly;
Expand Down Expand Up @@ -99,14 +100,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<TypeAdapterConfig>();
config1 = scope1.ServiceProvider.GetRequiredService<ITypeAdapterConfig>();
}
using (var scope2 = provider.CreateScope())
{
config2 = scope2.ServiceProvider.GetRequiredService<TypeAdapterConfig>();
config2 = scope2.ServiceProvider.GetRequiredService<ITypeAdapterConfig>();
}

// Assert
Expand Down Expand Up @@ -253,7 +254,7 @@ public void ScanMapster_WithValidAssembly_RegistersIRegisterImplementations()

var provider = services.BuildServiceProvider();
var mapper = provider.GetRequiredService<IMapper>();
var config = provider.GetRequiredService<TypeAdapterConfig>();
var config = provider.GetRequiredService<ITypeAdapterConfig>();

// Assert
mapper.ShouldNotBeNull();
Expand Down Expand Up @@ -338,7 +339,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()
{
Expand All @@ -353,7 +354,7 @@ public void AddMapsterWithConfig_WithValidConfig_RegistersMapperAndConfig()
services.AddMapsterWithConfig(existingConfig);
var provider = services.BuildServiceProvider();
var mapper = provider.GetRequiredService<IMapper>();
var config = provider.GetRequiredService<TypeAdapterConfig>();
var config = provider.GetRequiredService<ITypeAdapterConfig>();

// Assert
mapper.ShouldNotBeNull();
Expand Down Expand Up @@ -385,7 +386,7 @@ public void AddMapsterWithConfig_WithNullConfig_ThrowsArgumentNullException()
// Act & Assert
Should.Throw<ArgumentNullException>(() => services.AddMapsterWithConfig(null));
}

[Ignore("To prevent modification, access to TypeAdapterConfig should not be granted.")]
[TestMethod]
public void AddMapsterWithConfig_RegistersTypeAdapterConfigAsSingleton()
{
Expand All @@ -396,14 +397,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<TypeAdapterConfig>();
config1 = scope1.ServiceProvider.GetRequiredService<ITypeAdapterConfig>();
}
using (var scope2 = provider.CreateScope())
{
config2 = scope2.ServiceProvider.GetRequiredService<TypeAdapterConfig>();
config2 = scope2.ServiceProvider.GetRequiredService<ITypeAdapterConfig>();
}

// Assert
Expand Down Expand Up @@ -513,6 +514,59 @@ public void ScanMapster_UsesServiceMapperByDefault()
// Assert
mapper.ShouldBeOfType<ServiceMapper>();
}

[TestMethod]
public void FrozenConfigIsWork()
{
// Arrange
var services = new ServiceCollection();

// Act
services.AddMapsterFluent(
config =>
{
config.NewConfig<TestProduct, TestProductDto>()
.Map(dest => dest.DisplayName, src => "Frozen DisplayName");
config.NewConfig<TestUser, TestUserDto>()
.Map(dest => dest.FullName, src => "Frozen FullName")
.Ignore(dest => dest.Id);

config.FrozenTypes<TestProduct, TestProductDto>();
config.FrozenTypes<TestUser, TestUserDto>();

config.NewConfig<TestUser, TestUserDto>()
.Map(dest => dest.FullName, src => $"{src.LastName} {src.FirstName}")
.Ignore(dest => dest.Id);

config.NewConfig<TestProduct, TestProductDto>()
.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<IMapper>();

// Test scanned mapping (from IRegister)
var user = new TestUser { FirstName = "Integration", LastName = "Test" };
var userDto = mapper.Map<TestUserDto>(user);
userDto.FullName.ShouldBe("Frozen FullName");

// Test fluent configuration
var product = new TestProduct { Name = "Widget" };
var productDto = mapper.Map<TestProductDto>(product);
productDto.DisplayName.ShouldBe("Frozen DisplayName");

// Test ServiceMapper type
mapper.ShouldBeOfType<ServiceMapper>();
}
}


Expand Down Expand Up @@ -545,7 +599,7 @@ public class TestProductDto

public class TestUserMappingConfig : IRegister
{
public void Register(TypeAdapterConfig config)
public void Register(ITypeAdapterConfig config)
{
config.NewConfig<TestUser, TestUserDto>()
.Map(dest => dest.FullName, src => $"{src.FirstName} {src.LastName}");
Expand Down