Skip to content
Merged
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
30 changes: 26 additions & 4 deletions src/Mapr.DependencyInjection/MapperConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,18 @@ public MapperConfig(IServiceCollection services)
/// <inheritdoc />
public IMapperConfig AddMap<TSource, TDestination, TMap>() where TMap : class, IMap<TSource, TDestination>
{
_services.AddTransient<IMap<TSource, TDestination>, TMap>();
var type = typeof(TMap);
var mapAttribute = type.GetCustomAttribute<MapAttribute>() ?? new MapAttribute();

if (mapAttribute.Lifetime == MapLifetime.Singleton)
{
_services.AddSingleton<IMap<TSource, TDestination>, TMap>();
}
else
{
_services.AddTransient<IMap<TSource, TDestination>, TMap>();
}

return this;
}

Expand Down Expand Up @@ -56,8 +67,8 @@ public IMapperConfig Scan<T>()
private void RegisterDefaults()
{
_services.AddSingleton<IMapper, Mapper>();
_services.AddTransient<IMapLocator, MapLocator>();
_services.AddTransient<MapFactory>(provider => provider.GetService);
_services.AddSingleton<IMapLocator, MapLocator>();
_services.AddSingleton<MapFactory>(provider => provider.GetService);
}

private void RegisterType(Type type)
Expand All @@ -70,6 +81,17 @@ private void RegisterType(Type type)
.Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMap<,>));

foreach (var i in interfaces)
_services.AddTransient(i, type);
{
var mapAttribute = type.GetCustomAttribute<MapAttribute>() ?? new MapAttribute();

if (mapAttribute.Lifetime == MapLifetime.Singleton)
{
_services.AddSingleton(i, type);
}
else
{
_services.AddTransient(i, type);
}
}
}
}
33 changes: 33 additions & 0 deletions src/Mapr/MapAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;

namespace Mapr;

/// <summary>
/// Represents an attribute that can be placed on map classes to control their behavior
/// and registration.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class MapAttribute : Attribute
{
/// <summary>
/// Gets or sets the lifetime for the map. Determining how it will be registered in the
/// DI container. The default is <see cref="MapLifetime.Transient"/>.
/// </summary>
public MapLifetime Lifetime { get; set; } = MapLifetime.Transient;
}

/// <summary>
/// Represents the different lifetimes that can be used to register a map.
/// </summary>
public enum MapLifetime
{
/// <summary>
/// Registers the map as a singleton.
/// </summary>
Singleton,

/// <summary>
/// Registers the map as a transient.
/// </summary>
Transient
}
2 changes: 2 additions & 0 deletions src/Mapr/MapLocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ public IMap<TSource, TDestination> LocateMapFor<TSource, TDestination>()
try
{
if (_mapFactory(mapType) is not IMap<TSource, TDestination> typeMap)
{
throw new MapNotFoundException(mapType);
}

return typeMap;
}
Expand Down
102 changes: 85 additions & 17 deletions tests/Mapr.DependencyInjection.Tests/MapperConfigTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,24 @@ public void ShouldAdd_Defaults()
serviceCollection.AddMapr(_ => { });

serviceCollection.Should()
.Contain(s => s.ServiceType == typeof(IMapper) && s.ImplementationType == typeof(Mapper));
.Contain(
s => s.ServiceType == typeof(IMapper) &&
s.ImplementationType == typeof(Mapper) &&
s.Lifetime == ServiceLifetime.Singleton
);

serviceCollection.Should().Contain(
s => s.ServiceType == typeof(IMapLocator) && s.ImplementationType == typeof(MapLocator));
serviceCollection.Should()
.Contain(
s => s.ServiceType == typeof(IMapLocator) &&
s.ImplementationType == typeof(MapLocator) &&
s.Lifetime == ServiceLifetime.Singleton
);

serviceCollection.Should().Contain(s => s.ServiceType == typeof(MapFactory));
serviceCollection.Should()
.Contain(
s => s.ServiceType == typeof(MapFactory) &&
s.Lifetime == ServiceLifetime.Singleton
);
}

[Fact]
Expand All @@ -32,11 +44,26 @@ public void Scan_ShouldAddAllClassesFromAssemblyThat_InheritFromITypeMap()
config.Scan(typeof(TestMap).Assembly);
});

serviceCollection.Should().Contain(
s => s.ImplementationType == typeof(TestMap) && s.ServiceType == typeof(IMap<string, int>));
serviceCollection.Should()
.Contain(
s => s.ImplementationType == typeof(TestMap) &&
s.ServiceType == typeof(IMap<string, int>) &&
s.Lifetime == ServiceLifetime.Transient
);

serviceCollection.Should().Contain(
s => s.ImplementationType == typeof(TestMap) && s.ServiceType == typeof(IMap<int, string>));
serviceCollection.Should()
.Contain(
s => s.ImplementationType == typeof(TestMap) &&
s.ServiceType == typeof(IMap<int, string>) &&
s.Lifetime == ServiceLifetime.Transient
);

serviceCollection.Should()
.Contain(
s => s.ImplementationType == typeof(TestSingletonMap) &&
s.ServiceType == typeof(IMap<string, string>) &&
s.Lifetime == ServiceLifetime.Singleton
);
}

[Fact]
Expand All @@ -49,15 +76,30 @@ public void Scan_WithT_ShouldAddAllClassesFromAssemblyThat_InheritFromITypeMap()
config.Scan<TestMap>();
});

serviceCollection.Should().Contain(
s => s.ImplementationType == typeof(TestMap) && s.ServiceType == typeof(IMap<string, int>));
serviceCollection.Should()
.Contain(
s => s.ImplementationType == typeof(TestMap) &&
s.ServiceType == typeof(IMap<string, int>) &&
s.Lifetime == ServiceLifetime.Transient
);

serviceCollection.Should().Contain(
s => s.ImplementationType == typeof(TestMap) && s.ServiceType == typeof(IMap<int, string>));
serviceCollection.Should()
.Contain(
s => s.ImplementationType == typeof(TestMap) &&
s.ServiceType == typeof(IMap<int, string>) &&
s.Lifetime == ServiceLifetime.Transient
);

serviceCollection.Should()
.Contain(
s => s.ImplementationType == typeof(TestSingletonMap) &&
s.ServiceType == typeof(IMap<string, string>) &&
s.Lifetime == ServiceLifetime.Singleton
);
}

[Fact]
public void AddMap_ShouldAppMap()
public void AddMap_ShouldAddMap()
{
var serviceCollection = new ServiceCollection();

Expand All @@ -67,10 +109,36 @@ public void AddMap_ShouldAppMap()
.AddMap<int, string, TestMap>();
});

serviceCollection.Should().Contain(
s => s.ImplementationType == typeof(TestMap) && s.ServiceType == typeof(IMap<string, int>));
serviceCollection.Should()
.Contain(
s => s.ImplementationType == typeof(TestMap) &&
s.ServiceType == typeof(IMap<string, int>) &&
s.Lifetime == ServiceLifetime.Transient
);

serviceCollection.Should()
.Contain(
s => s.ImplementationType == typeof(TestMap) &&
s.ServiceType == typeof(IMap<int, string>) &&
s.Lifetime == ServiceLifetime.Transient
);
}

[Fact]
public void AddMap_ShouldAddSingletonMap_WhenAttributeIsPresent()
{
var serviceCollection = new ServiceCollection();

serviceCollection.Should().Contain(
s => s.ImplementationType == typeof(TestMap) && s.ServiceType == typeof(IMap<int, string>));
serviceCollection.AddMapr(config =>
{
config.AddMap<string, string, TestSingletonMap>();
});

serviceCollection.Should()
.Contain(
s => s.ImplementationType == typeof(TestSingletonMap) &&
s.ServiceType == typeof(IMap<string, string>) &&
s.Lifetime == ServiceLifetime.Singleton
);
}
}
11 changes: 11 additions & 0 deletions tests/Mapr.DependencyInjection.Tests/TestSingletonMap.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Mapr.DependencyInjection.Tests;

[Map(Lifetime = MapLifetime.Singleton)]
public class TestSingletonMap: IMap<string, string>
{
/// <inheritdoc />
public string Map(string source)
{
return source;
}
}