diff --git a/.github/workflows/BuildAndTest.yml b/.github/workflows/BuildAndTest.yml
new file mode 100644
index 0000000..286bc2a
--- /dev/null
+++ b/.github/workflows/BuildAndTest.yml
@@ -0,0 +1,27 @@
+name: Build and test
+
+on:
+ pull_request:
+ branches:
+ - main
+ push:
+ branches:
+ - main
+
+jobs:
+ build-and-test:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Use dotnet 5.0.x
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: '5.0.x'
+
+ - name: Restore
+ run: dotnet restore
+ - name: Build
+ run: dotnet build
+ - name: Test
+ run: dotnet test
\ No newline at end of file
diff --git a/Enterwell.AutoMapper.Extensions.Tests/AutoMapperExtensionsTests.cs b/Enterwell.AutoMapper.Extensions.Tests/AutoMapperExtensionsTests.cs
index d6329b3..694635b 100644
--- a/Enterwell.AutoMapper.Extensions.Tests/AutoMapperExtensionsTests.cs
+++ b/Enterwell.AutoMapper.Extensions.Tests/AutoMapperExtensionsTests.cs
@@ -1,15 +1,227 @@
using System;
+using AutoMapper;
using Xunit;
namespace Enterwell.AutoMapper.Extensions.Tests
{
+ ///
+ /// Tests for AutoMapper extensions.
+ ///
public class AutoMapperExtensionsTests
{
-
+ ///
+ /// Tests MapTo extension.
+ ///
[Fact]
- public void Test1()
+ public void AutoMapperExtensionsTests_MapTo()
{
- Assert.Equal(1,1);
+ var mapper = new MapperConfiguration(cfg =>
+ {
+ cfg.CreateMap()
+ .ForMember(dst => dst.DestinationPropOne,
+ opt => opt.MapFrom(src => src.SourcePropOne));
+ }).CreateMapper();
+
+ var source = new MockSimpleEntitySource
+ {
+ SourcePropOne = 5,
+ CommonPropOne = Guid.NewGuid().ToString()
+ };
+
+ var destination = source.MapTo(mapper);
+
+ Assert.Equal(source.SourcePropOne, destination.DestinationPropOne);
+ Assert.Equal(source.CommonPropOne, destination.CommonPropOne);
+ }
+
+ ///
+ /// Tests MapProperty extension.
+ ///
+ [Fact]
+ public void AutoMapperExtensionsTests_MapProperty()
+ {
+ var mapper = new MapperConfiguration(cfg =>
+ {
+ cfg.CreateMap()
+ .MapProperty(dst => dst.DestinationPropOne, src => src.SourcePropOne);
+ }).CreateMapper();
+
+ var commonProp = Guid.NewGuid().ToString();
+ var srcProp = 5;
+ var source = new MockSimpleEntitySource { CommonPropOne = commonProp, SourcePropOne = srcProp };
+ var dst = mapper.Map(source);
+
+ Assert.Equal(source.CommonPropOne, dst.CommonPropOne);
+ Assert.Equal(source.SourcePropOne, dst.DestinationPropOne);
+ }
+
+ ///
+ /// Tests MapPropertyFunc extension.
+ ///
+ [Fact]
+ public void AutoMapperExtensionsTests_MapPropertyFunc()
+ {
+ const int expectedDestProp = 3;
+ var mapper = new MapperConfiguration(cfg =>
+ {
+ cfg.CreateMap()
+ .MapPropertyFunc(dst => dst.DestinationPropOne, src => Math.Min(src.SourcePropOne, expectedDestProp));
+ }).CreateMapper();
+
+ var commonProp = Guid.NewGuid().ToString();
+ const int srcProp = 5;
+ var source = new MockSimpleEntitySource { CommonPropOne = commonProp, SourcePropOne = srcProp };
+ var dst = mapper.Map(source);
+
+ Assert.Equal(source.CommonPropOne, dst.CommonPropOne);
+ Assert.Equal(dst.DestinationPropOne, expectedDestProp);
+ }
+
+ ///
+ /// Tests map with additional required property that is valid.
+ ///
+ [Fact]
+ public void AutoMapperExtensionsTests_MapComposeTo_RequiredCompositePropertyValid()
+ {
+ var mapper = new MapperConfiguration(cfg =>
+ {
+ cfg.CreateMap()
+ .MapProperty(dst => dst.DestinationPropOne, src => src.SourcePropOne)
+ .MapCompositePropertyRequired(dst => dst.AdditionalRequiredProperty);
+ }).CreateMapper();
+
+ var commonProp = Guid.NewGuid().ToString();
+ int srcProp = 5;
+ var additionalProperty = DateTime.Now;
+
+ var source = new MockSimpleEntitySource
+ {
+ CommonPropOne = commonProp,
+ SourcePropOne = srcProp
+ };
+
+ var destination = source.MapComposeTo(mapper, additionalProperty);
+
+ Assert.Equal(source.CommonPropOne, destination.CommonPropOne);
+ Assert.Equal(source.SourcePropOne, destination.DestinationPropOne);
+ Assert.Equal(destination.AdditionalRequiredProperty, additionalProperty);
+ }
+
+ ///
+ /// Tests MapCompose with additional required property that is missing.
+ ///
+ [Fact]
+ public void AutoMapperExtensionsTests_MapComposeTo_RequiredCompositePropertyMissing()
+ {
+ var mapper = new MapperConfiguration(cfg =>
+ {
+ cfg.CreateMap()
+ .MapProperty(dst => dst.DestinationPropOne, src => src.SourcePropOne)
+ .MapCompositePropertyRequired(dst => dst.AdditionalRequiredProperty);
+ }).CreateMapper();
+
+ var source = new MockSimpleEntitySource();
+
+ var exception = Record.Exception(() => source.MapComposeTo(mapper));
+
+ Assert.NotNull(exception);
+ }
+
+ ///
+ /// Tests MapCompose with additional required property provided wrong type.
+ ///
+ [Fact]
+ public void AutoMapperExtensionsTests_MapComposeTo_CompositePropertyWrongType()
+ {
+ var mapper = new MapperConfiguration(cfg =>
+ {
+ cfg.CreateMap()
+ .MapProperty(dst => dst.DestinationPropOne, src => src.SourcePropOne)
+ .MapCompositePropertyRequired(dst => dst.AdditionalRequiredProperty);
+ }).CreateMapper();
+
+ const string additionalPropertyWrongType = "String instead of DateTime";
+
+ var source = new MockSimpleEntitySource();
+
+ var exception = Record.Exception(() => source.MapComposeTo(mapper, additionalPropertyWrongType));
+
+ Assert.NotNull(exception);
+ }
+
+ ///
+ /// Tests MapCompose second additional property optional.
+ ///
+ [Fact]
+ public void AutoMapperExtensionsTests_MapComposeTo_SecondPropertyOptional()
+ {
+ var mapper = new MapperConfiguration(cfg =>
+ {
+ cfg.CreateMap()
+ .MapProperty(dst => dst.DestinationPropOne, src => src.SourcePropOne)
+ .MapCompositePropertyRequired(dst => dst.AdditionalRequiredProperty)
+ .MapCompositeProperty(dst => dst.SecondAdditionalOptionalProperty, 1);
+ }).CreateMapper();
+
+ var source = new MockSimpleEntitySource();
+ var firstAdditionalProperty = DateTime.Now;
+ var destination = source.MapComposeTo(mapper, firstAdditionalProperty);
+ Assert.Equal(destination.AdditionalRequiredProperty, firstAdditionalProperty);
+ }
+
+ ///
+ /// Tests MapCompose two additional properties required.
+ ///
+ [Fact]
+ public void AutoMapperExtensionsTests_MapComposeTo_TwoAdditionalPropertiesRequired()
+ {
+ var mapper = new MapperConfiguration(cfg =>
+ {
+ cfg.CreateMap()
+ .MapProperty(dst => dst.DestinationPropOne, src => src.SourcePropOne)
+ .MapCompositePropertyRequired(dst => dst.AdditionalRequiredProperty)
+ .MapCompositePropertyRequired(dst => dst.SecondAdditionalOptionalProperty, 1);
+ }).CreateMapper();
+
+ var source = new MockSimpleEntitySource();
+ var firstAdditionalProperty = DateTime.Now;
+ var secondAdditionalProperty = Guid.NewGuid().ToString();
+ var destination = source.MapComposeTo(mapper, firstAdditionalProperty, secondAdditionalProperty);
+ Assert.Equal(destination.AdditionalRequiredProperty, firstAdditionalProperty);
+ Assert.Equal(destination.SecondAdditionalOptionalProperty, secondAdditionalProperty);
+ }
+
+ ///
+ /// Tests map with MapCompositeAll.
+ ///
+ [Fact]
+ public void AutoMapperExtensionsTests_MapCompositeAll()
+ {
+ var mapper = new MapperConfiguration(cfg =>
+ {
+ cfg.CreateMap()
+ .MapProperty(dst => dst.DestinationPropOne, src => src.SourcePropOne)
+ .MapCompositeAll(typeof(MockComposedEntitySource));
+ }).CreateMapper();
+
+ var source = new MockSimpleEntitySource
+ {
+ SourcePropOne = 1,
+ CommonPropOne = Guid.NewGuid().ToString()
+ };
+
+ var composedSource = new MockComposedEntitySource
+ {
+ AdditionalRequiredProperty = DateTime.Now,
+ SecondAdditionalOptionalProperty = Guid.NewGuid().ToString()
+ };
+
+ var destination = source.MapComposeTo(mapper, composedSource);
+
+ Assert.Equal(destination.CommonPropOne, source.CommonPropOne);
+ Assert.Equal(destination.DestinationPropOne, source.SourcePropOne);
+ Assert.Equal(destination.AdditionalRequiredProperty, composedSource.AdditionalRequiredProperty);
+ Assert.Equal(destination.SecondAdditionalOptionalProperty, composedSource.SecondAdditionalOptionalProperty);
}
}
}
diff --git a/Enterwell.AutoMapper.Extensions.Tests/AutoMapperTestProfile.cs b/Enterwell.AutoMapper.Extensions.Tests/AutoMapperTestProfile.cs
deleted file mode 100644
index 4058089..0000000
--- a/Enterwell.AutoMapper.Extensions.Tests/AutoMapperTestProfile.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using AutoMapper;
-
-namespace Enterwell.AutoMapper.Extensions.Tests
-{
- ///
- /// Automapper tests profile.
- ///
- ///
- public class AutoMapperTestProfile : Profile
- {
- ///
- /// Initializes a new instance of the class.
- ///
- public AutoMapperTestProfile()
- {
-
- }
- }
-
- ///
- /// Mock entity for source.
- ///
- public class MockEntitySource
- {
- public string Prop1 { get; set; }
- }
-
- ///
- /// Mock entity for destination.
- ///
- public class MockEntityDestination
- {
- public string Prop1 { get; set; }
- }
-}
\ No newline at end of file
diff --git a/Enterwell.AutoMapper.Extensions.Tests/Enterwell.AutoMapper.Extensions.Tests.csproj b/Enterwell.AutoMapper.Extensions.Tests/Enterwell.AutoMapper.Extensions.Tests.csproj
index b44df53..bfa7f21 100644
--- a/Enterwell.AutoMapper.Extensions.Tests/Enterwell.AutoMapper.Extensions.Tests.csproj
+++ b/Enterwell.AutoMapper.Extensions.Tests/Enterwell.AutoMapper.Extensions.Tests.csproj
@@ -7,7 +7,7 @@
-
+
@@ -20,4 +20,8 @@
+
+
+
+
diff --git a/Enterwell.AutoMapper.Extensions.Tests/MockComposedEntityDestination.cs b/Enterwell.AutoMapper.Extensions.Tests/MockComposedEntityDestination.cs
new file mode 100644
index 0000000..6ed4715
--- /dev/null
+++ b/Enterwell.AutoMapper.Extensions.Tests/MockComposedEntityDestination.cs
@@ -0,0 +1,27 @@
+using System;
+
+namespace Enterwell.AutoMapper.Extensions.Tests
+{
+ ///
+ /// Mock composed entity destination.
+ ///
+ ///
+ public class MockComposedEntityDestination : MockSimpleEntityDestination
+ {
+ ///
+ /// Gets or sets the additional property.
+ ///
+ ///
+ /// The additional property.
+ ///
+ public DateTime AdditionalRequiredProperty { get; set; }
+
+ ///
+ /// Gets or sets the second additional property.
+ ///
+ ///
+ /// The second additional property.
+ ///
+ public string SecondAdditionalOptionalProperty { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Enterwell.AutoMapper.Extensions.Tests/MockComposedEntitySource.cs b/Enterwell.AutoMapper.Extensions.Tests/MockComposedEntitySource.cs
new file mode 100644
index 0000000..f5134c6
--- /dev/null
+++ b/Enterwell.AutoMapper.Extensions.Tests/MockComposedEntitySource.cs
@@ -0,0 +1,26 @@
+using System;
+
+namespace Enterwell.AutoMapper.Extensions.Tests
+{
+ ///
+ /// Mock source composed entity.
+ ///
+ public class MockComposedEntitySource
+ {
+ ///
+ /// Gets or sets the additional property.
+ ///
+ ///
+ /// The additional property.
+ ///
+ public DateTime AdditionalRequiredProperty { get; set; }
+
+ ///
+ /// Gets or sets the second additional property.
+ ///
+ ///
+ /// The second additional property.
+ ///
+ public string SecondAdditionalOptionalProperty { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Enterwell.AutoMapper.Extensions.Tests/MockEntityDestination.cs b/Enterwell.AutoMapper.Extensions.Tests/MockEntityDestination.cs
deleted file mode 100644
index 0b8c66a..0000000
--- a/Enterwell.AutoMapper.Extensions.Tests/MockEntityDestination.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-namespace Enterwell.AutoMapper.Extensions.Tests
-{
- ///
- /// Mock entity for destination.
- ///
- public class MockEntityDestination
- {
- ///
- /// Gets or sets the prop1.
- ///
- ///
- /// The prop1.
- ///
- public string Prop1 { get; set; }
- }
-}
\ No newline at end of file
diff --git a/Enterwell.AutoMapper.Extensions.Tests/MockEntitySource.cs b/Enterwell.AutoMapper.Extensions.Tests/MockEntitySource.cs
deleted file mode 100644
index 677e594..0000000
--- a/Enterwell.AutoMapper.Extensions.Tests/MockEntitySource.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Enterwell.AutoMapper.Extensions.Tests
-{
- ///
- /// Mock entity for source.
- ///
- public class MockEntitySource
- {
- public string Prop1 { get; set; }
- }
-}
\ No newline at end of file
diff --git a/Enterwell.AutoMapper.Extensions.Tests/MockSimpleEntityDestination.cs b/Enterwell.AutoMapper.Extensions.Tests/MockSimpleEntityDestination.cs
new file mode 100644
index 0000000..ffd9013
--- /dev/null
+++ b/Enterwell.AutoMapper.Extensions.Tests/MockSimpleEntityDestination.cs
@@ -0,0 +1,24 @@
+namespace Enterwell.AutoMapper.Extensions.Tests
+{
+ ///
+ /// Mock entity for destination.
+ ///
+ public class MockSimpleEntityDestination
+ {
+ ///
+ /// Gets or sets the prop1.
+ ///
+ ///
+ /// The prop1.
+ ///
+ public string CommonPropOne { get; set; }
+
+ ///
+ /// Gets or sets the destination property one.
+ ///
+ ///
+ /// The destination property one.
+ ///
+ public int DestinationPropOne { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Enterwell.AutoMapper.Extensions.Tests/MockSimpleEntitySource.cs b/Enterwell.AutoMapper.Extensions.Tests/MockSimpleEntitySource.cs
new file mode 100644
index 0000000..7768e8c
--- /dev/null
+++ b/Enterwell.AutoMapper.Extensions.Tests/MockSimpleEntitySource.cs
@@ -0,0 +1,40 @@
+using System;
+
+namespace Enterwell.AutoMapper.Extensions.Tests
+{
+ ///
+ /// Mock simple entity source
+ ///
+ public class MockSimpleEntitySource
+ {
+ ///
+ /// Gets or sets the common property one.
+ ///
+ ///
+ /// The common property one.
+ ///
+ public string CommonPropOne { get; set; }
+
+ ///
+ /// Gets or sets the source property one.
+ ///
+ ///
+ /// The source property one.
+ ///
+ public int SourcePropOne { get; set; }
+ }
+
+ ///
+ /// Mock additional entity source.
+ ///
+ public class MockAdditionalEntitySource
+ {
+ ///
+ /// Gets or sets the additional source property.
+ ///
+ ///
+ /// The additional source property.
+ ///
+ public DateTime AdditionalSourceProperty { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Enterwell.AutoMapper.Extensions/Enterwell.AutoMapper.Extensions.csproj b/Enterwell.AutoMapper.Extensions/Enterwell.AutoMapper.Extensions.csproj
index 69d99c7..d3f72cb 100644
--- a/Enterwell.AutoMapper.Extensions/Enterwell.AutoMapper.Extensions.csproj
+++ b/Enterwell.AutoMapper.Extensions/Enterwell.AutoMapper.Extensions.csproj
@@ -1,11 +1,10 @@
-
-
+
netstandard2.0
+ 1.0.0
+ true
-
-
-
+
\ No newline at end of file
diff --git a/Enterwell.AutoMapper.Extensions/MapCompositeDefinitionExtensions.cs b/Enterwell.AutoMapper.Extensions/MapCompositeDefinitionExtensions.cs
index 659a7b1..ed65b9c 100644
--- a/Enterwell.AutoMapper.Extensions/MapCompositeDefinitionExtensions.cs
+++ b/Enterwell.AutoMapper.Extensions/MapCompositeDefinitionExtensions.cs
@@ -2,6 +2,7 @@
using System.Linq;
using System.Linq.Expressions;
using AutoMapper;
+using AutoMapper.Internal;
namespace Enterwell.AutoMapper.Extensions
{
@@ -82,5 +83,56 @@ public static IMappingExpression MapCompositePropertyRequired> dest,
int additionalPropertyIndex = 0) =>
map.MapPropertyFunc(dest, (source, ctx) => ctx.GetCompositePropertyRequired(additionalPropertyIndex));
+
+ ///
+ /// An IMappingExpression<TSrc,TDest> extension method that map composite object properties to source object.
+ ///
+ /// Type of the source.
+ /// Type of the destination.
+ /// The map to act on.
+ /// Type of the composite.
+ /// (Optional) The index.
+ public static void MapCompositeAll(
+ this IMappingExpression map,
+ Type compositeType,
+ int index = 0) =>
+ Array.ForEach(compositeType.GetProperties(),
+ compositeProperty =>
+ map.ForMember(compositeProperty.Name,
+ opt => opt.MapFrom((src, dest, _, ctx) =>
+ compositeProperty.GetValue(ctx.GetCompositePropertyRequired(compositeType, index)))));
+
+ ///
+ /// Gets the composite property required.
+ ///
+ /// The context.
+ /// The type.
+ /// The index.
+ /// Returns the required composite property at given index.
+ /// Failed to cast composite property at index {index} to type {typeof(TSource)}
+ public static object GetCompositePropertyRequired(this ResolutionContext context, Type type, int index)
+ {
+ if (context == null) throw new ArgumentNullException(nameof(context));
+ if (type == null) throw new ArgumentNullException(nameof(type));
+ if (context.Items.Values.Count <= index)
+ throw new ArgumentOutOfRangeException($"Missing parameter at index {index} of type {type.FullName}");
+
+ return context.Items.Values.ElementAt(index) != type.GetDefaultValue()
+ ? context.Items.Values.ElementAt(index)
+ : throw new InvalidCastException(
+ $"Failed to cast composite property at index {index} to type {type.FullName}");
+ }
+
+ ///
+ /// Gets the default value of given type.
+ ///
+ /// The type.
+ /// Returns null for classes and default value for value types.
+ private static object GetDefaultValue(this Type t)
+ {
+ if (t.IsValueType && Nullable.GetUnderlyingType(t) == null)
+ return Activator.CreateInstance(t);
+ return null;
+ }
}
}
\ No newline at end of file
diff --git a/Enterwell.AutoMapper.Extensions/MapPropertyDefinitionExtensions.cs b/Enterwell.AutoMapper.Extensions/MapPropertyDefinitionExtensions.cs
index ad3af48..bb2b1df 100644
--- a/Enterwell.AutoMapper.Extensions/MapPropertyDefinitionExtensions.cs
+++ b/Enterwell.AutoMapper.Extensions/MapPropertyDefinitionExtensions.cs
@@ -69,7 +69,6 @@ public static IMappingExpression MapPropertyFunc sourceFunc) =>
map.ForMember(dest, opt => opt.MapFrom((source, unused1, unused2, mapper) => sourceFunc(source, mapper)));
-
///
/// An IMappingExpression<TSource,TDest> extension method that maps property to source
/// member.