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
131 changes: 131 additions & 0 deletions src/Mapster.Fluent/Configs/FrozenTypeAdapterConfig.cs
Original file line number Diff line number Diff line change
@@ -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<TypeTuple, TypeTuple> _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<TSource, TDestination> NewConfig<TSource, TDestination>()
{
if (IsTotalFrozen ||
_frozentypes.TryGetValue(new TypeTuple(typeof(TSource), typeof(TDestination)), out _))
{
_dummyConfig.Clear();
return _dummyConfig.NewConfig<TSource, TDestination>(); ;
}

return base.NewConfig<TSource, TDestination>();
}

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 void Apply(IEnumerable<IRegister> 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<Lazy<IRegister>> registers)
{
base.Apply(registers);
}

public override IList<IRegister> Scan(params Assembly[] assemblies)
{
List<IRegister> 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;
}
}
}
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.3-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
75 changes: 64 additions & 11 deletions tests/Mapster.Fluent.Tests/MapsterDITests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<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 +253,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 +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()
{
Expand All @@ -353,7 +353,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 +385,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 +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<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 +513,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 +598,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