diff --git a/BSN.Commons.sln b/BSN.Commons.sln index dffa831..1ceccb8 100644 --- a/BSN.Commons.sln +++ b/BSN.Commons.sln @@ -43,9 +43,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BSN.Commons.Orm.EntityFrame EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BSN.Commons.Users", "Source\BSN.Commons.Users\BSN.Commons.Users.csproj", "{213ABCEF-7E9A-4CE5-A3EF-289C9781344D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BSN.Commons.Orm.Redis", "Source\BSN.Commons.Orm.Redis\BSN.Commons.Orm.Redis.csproj", "{1A1586E8-46EB-43AC-91EC-F6EDCA5689A9}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BSN.Commons.Orm.Redis", "Source\BSN.Commons.Orm.Redis\BSN.Commons.Orm.Redis.csproj", "{1A1586E8-46EB-43AC-91EC-F6EDCA5689A9}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BSN.Commons.Orm.Redis.Tests", "Test\BSN.Commons.Orm.Redis.Tests\BSN.Commons.Orm.Redis.Tests.csproj", "{2D1DB295-5181-48D7-8EC0-1147ED2DAD4A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BSN.Commons.Orm.Redis.Tests", "Test\BSN.Commons.Orm.Redis.Tests\BSN.Commons.Orm.Redis.Tests.csproj", "{2D1DB295-5181-48D7-8EC0-1147ED2DAD4A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BSN.Commons.AutoMapper", "Source\BSN.Commons.AutoMapper\BSN.Commons.AutoMapper.csproj", "{279E7016-E2E9-430E-82A3-C9037FE0E08E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BSN.Commons.AutoMapper.Tests", "Test\BSN.Commons.AutoMapper.Tests\BSN.Commons.AutoMapper.Tests.csproj", "{E8077467-8559-4669-B9FA-22530F1411E1}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -93,6 +97,14 @@ Global {2D1DB295-5181-48D7-8EC0-1147ED2DAD4A}.Debug|Any CPU.Build.0 = Debug|Any CPU {2D1DB295-5181-48D7-8EC0-1147ED2DAD4A}.Release|Any CPU.ActiveCfg = Release|Any CPU {2D1DB295-5181-48D7-8EC0-1147ED2DAD4A}.Release|Any CPU.Build.0 = Release|Any CPU + {279E7016-E2E9-430E-82A3-C9037FE0E08E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {279E7016-E2E9-430E-82A3-C9037FE0E08E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {279E7016-E2E9-430E-82A3-C9037FE0E08E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {279E7016-E2E9-430E-82A3-C9037FE0E08E}.Release|Any CPU.Build.0 = Release|Any CPU + {E8077467-8559-4669-B9FA-22530F1411E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E8077467-8559-4669-B9FA-22530F1411E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E8077467-8559-4669-B9FA-22530F1411E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E8077467-8559-4669-B9FA-22530F1411E1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -108,6 +120,8 @@ Global {213ABCEF-7E9A-4CE5-A3EF-289C9781344D} = {DC377ADC-CC9D-4785-81BE-726DBF5F3096} {1A1586E8-46EB-43AC-91EC-F6EDCA5689A9} = {DC377ADC-CC9D-4785-81BE-726DBF5F3096} {2D1DB295-5181-48D7-8EC0-1147ED2DAD4A} = {5C6BA7B5-832A-495A-AF5E-C2A74F6A1EF9} + {279E7016-E2E9-430E-82A3-C9037FE0E08E} = {DC377ADC-CC9D-4785-81BE-726DBF5F3096} + {E8077467-8559-4669-B9FA-22530F1411E1} = {5C6BA7B5-832A-495A-AF5E-C2A74F6A1EF9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BCAF76D3-AA3C-4D0F-8D10-34065F8FED09} diff --git a/Build/build.cake b/Build/build.cake index 149665a..2737a44 100644 --- a/Build/build.cake +++ b/Build/build.cake @@ -21,10 +21,11 @@ var projects = new List<(string path, string name, string version)> { ("BSN.Commons/", "BSN.Commons.csproj", solutionVersion), ("BSN.Commons.Users/", "BSN.Commons.Users.csproj", solutionVersion), - ("BSN.Commons.PresentationInfrastructure/", "BSN.Commons.PresentationInfrastructure.csproj", solutionVersion), + ("BSN.Commons.Orm.Redis/", "BSN.Commons.Orm.Redis.csproj", solutionVersion), + ("BSN.Commons.AutoMapper/", "BSN.Commons.AutoMapper.csproj", solutionVersion), ("BSN.Commons.Orm.EntityFramework/", "BSN.Commons.Orm.EntityFramework.csproj", solutionVersion), ("BSN.Commons.Orm.EntityFrameworkCore/", "BSN.Commons.Orm.EntityFrameworkCore.csproj", solutionVersion), - ("BSN.Commons.Orm.Redis/", "BSN.Commons.Orm.Redis.csproj", solutionVersion) + ("BSN.Commons.PresentationInfrastructure/", "BSN.Commons.PresentationInfrastructure.csproj", solutionVersion) }; var mainProject = "../Source/BSN.Commons/BSN.Commons.csproj"; @@ -35,7 +36,8 @@ var testProjects = new List<(string path, string name, string dll)> ("BSN.Commons.Tests/", "BSN.Commons.Tests.csproj", "bin/Release/net472/BSN.Commons.Tests.dll"), ("BSN.Commons.Orm.EntityFramework.Tests/", "BSN.Commons.Orm.EntityFramework.Tests.csproj", "bin/Release/net48/BSN.Commons.Orm.EntityFramework.Tests.dll"), ("BSN.Commons.Orm.EntityFrameworkCore.Tests/", "BSN.Commons.Orm.EntityFrameworkCore.Tests.csproj", "bin/Release/netcoreapp3.1/BSN.Commons.Orm.EntityFrameworkCore.Tests.dll"), - ("BSN.Commons.Orm.Redis.Tests/", "BSN.Commons.Orm.Redis.Tests.csproj", "bin/Release/net8.0/BSN.Commons.Orm.Redis.Tests.dll") + ("BSN.Commons.Orm.Redis.Tests/", "BSN.Commons.Orm.Redis.Tests.csproj", "bin/Release/net8.0/BSN.Commons.Orm.Redis.Tests.dll"), + ("BSN.Commons.AutoMapper.Tests/", "BSN.Commons.AutoMapper.Tests.csproj", "bin/Release/net8.0/BSN.Commons.AutoMapper.Tests.dll") }; var coverageResultsFileName = "coverage.xml"; var testResultsFileName = "nunitResults.xml"; diff --git a/Source/BSN.Commons.AutoMapper/BSN.Commons.AutoMapper.csproj b/Source/BSN.Commons.AutoMapper/BSN.Commons.AutoMapper.csproj new file mode 100644 index 0000000..caf4d84 --- /dev/null +++ b/Source/BSN.Commons.AutoMapper/BSN.Commons.AutoMapper.csproj @@ -0,0 +1,47 @@ + + + + net8.0 + 1.15.0 + 1.15.0 + BSN Developers + BSN Company + AutoMapper Helpers for using AutoMapper in enterprise application. + BSN Co 2019-2024 + MIT + https://github.com/BSVN/Commons + https://github.com/BSVN/Commons.git + git + Please see CHANGELOG.md + 1.15.0 + True + True + BSN.Commons.AutoMapper + README.md + True + snupkg + BSN.jpg + BSN;Commons;AutoMapper + + true + + true + + + + net8.0 + enable + enable + + + + + + + + + + + + + diff --git a/Source/BSN.Commons.AutoMapper/CHANGELOG.md b/Source/BSN.Commons.AutoMapper/CHANGELOG.md new file mode 100644 index 0000000..05d22aa --- /dev/null +++ b/Source/BSN.Commons.AutoMapper/CHANGELOG.md @@ -0,0 +1,20 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.15.0] - 2024-06-29 +Fixed issue with mapping between domain and view models. + +### Added +- README.md and CHANGELOG.md +- Added support for AutoMapper 13.0.1 +- Added predefined conversions for mapping `BSN.Commons.PresentationInfrastructure` Models. + +### Fixed +- Resolve some missing elements in nupkg. + +### Changed +- Update documentation. \ No newline at end of file diff --git a/Source/BSN.Commons.AutoMapper/CommonsMappingProfile.cs b/Source/BSN.Commons.AutoMapper/CommonsMappingProfile.cs new file mode 100644 index 0000000..09209bd --- /dev/null +++ b/Source/BSN.Commons.AutoMapper/CommonsMappingProfile.cs @@ -0,0 +1,40 @@ +using AutoMapper; +using BSN.Commons.Responses; + +namespace BSN.Commons.AutoMapper +{ + public class CommonMapperProfile : Profile + { + public CommonMapperProfile() + { + CreateMap(typeof(PagedEntityCollection<>), typeof(PaginationMetadata)).ConvertUsing(typeof(PagedEntityCollectionToMetaDataConverter<>)); + + CreateMap(typeof(IEnumerable<>), typeof(CollectionViewModel<>)).ConvertUsing(typeof(GenericIEnumerableToCollectionViewModelConverter<,>)); + } + + private class PagedEntityCollectionToMetaDataConverter : ITypeConverter, PaginationMetadata> + { + public PaginationMetadata Convert(PagedEntityCollection source, PaginationMetadata destination, ResolutionContext context) + { + return new PaginationMetadata() + { + Page = source.CurrentPage, + PageCount = source.PageSize, + PageSize = source.PageSize, + RecordCount = source.RecordCount + }; + } + } + + private class GenericIEnumerableToCollectionViewModelConverter : ITypeConverter, CollectionViewModel> + { + public CollectionViewModel Convert(IEnumerable source, CollectionViewModel destination, ResolutionContext context) + { + return new CollectionViewModel + { + Items = context.Mapper.Map>(source) + }; + } + } + } +} diff --git a/Source/BSN.Commons.AutoMapper/Extensions/IServiceCollectionExtensions.cs b/Source/BSN.Commons.AutoMapper/Extensions/IServiceCollectionExtensions.cs new file mode 100644 index 0000000..b70d44c --- /dev/null +++ b/Source/BSN.Commons.AutoMapper/Extensions/IServiceCollectionExtensions.cs @@ -0,0 +1,27 @@ +using AutoMapper; +using Microsoft.Extensions.DependencyInjection; + +namespace BSN.Commons.AutoMapper.Extensions +{ + public static class IServiceCollectionExtensions + { + public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action configure) + { + var mappingConfig = new MapperConfiguration(config => + { + MapperConfigurationExpression mapperConfigurationExpression = new MapperConfigurationExpression(); + configure(mapperConfigurationExpression); + + configure(config); + + config.AddProfile(new CommonMapperProfile()); + }); + + IMapper mapper = mappingConfig.CreateMapper(); + + services.AddSingleton(mapper); + + return services; + } + } +} diff --git a/Source/BSN.Commons.AutoMapper/README.md b/Source/BSN.Commons.AutoMapper/README.md new file mode 100644 index 0000000..7310d6f --- /dev/null +++ b/Source/BSN.Commons.AutoMapper/README.md @@ -0,0 +1,59 @@ +# BSN.Commons.AutoMapper + +This package contains some facilities for using AutoMapper in Enterprise Applications. + +AutoMapper is a popular library for mapping objects from one Model to Another. It simplifies the process of mapping complex objects and reduces the amount of code needed to perform these mappings stuffs. + +**BSN.Commons.Automapper** is a package that provides some predefined mappings for **BSN.Commons.PresentationInfrastructure** Models. +This helps **BSN.Commons** users to skip writing required mapping to dealing with these Models. + +### 1. Installation +To use this package, you need to first install it on your web api or presentation layer. You can do this by running the following command (in package manager console): +``` +Install-Package BSN.Commons.AutoMapper +``` + +### 2. Add Your required mapping profiles +To use these predefined mapping profiles and injecting your preferred profiles you just need to add following line in the `ServiceCollection`: +``` +services.AddAutoMapper(config => config.AddProfile()); +``` +or +``` +services.AddAutoMapper(config => +{ + config.AddProfile(); + config.AddProfile(); +}); +``` + +### 3. Predefined mapping profile: + +Provided built in mapping profile contains following mappings: + +#### PagedEntityCollectionToMetaDataConverter: +A default converter which converts `PagedEntityCollection` to `PaginationMetadata`. +#### GenericIEnumerableToCollectionViewModelConverter: +A generic default converter which converts `IEnumerable` to `CollectionViewModel` + +### 4. Example Usage + +#### PagedEntityCollectionToMetaDataConverter +``` +var pagedEntities = new PagedEntityCollection(products, 1, 10, 100); +var paginationMetadata = mapper.Map(pagedEntities); +``` + +#### GenericIEnumerableToCollectionViewModelConverter +``` +var products = new List +{ + new Product { Id = 1, Name = "Product 1" }, + new Product { Id = 2, Name = "Product 2" }, + new Product { Id = 3, Name = "Product 3" } +}; + +var collectionViewModel = mapper.Map>(products); +``` + +`BSN.Commons.AutoMapper` is Copyright © 2024 BSN and other contributors under the BSN license. \ No newline at end of file diff --git a/Test/BSN.Commons.AutoMapper.Tests/AutoMapperTestBase.cs b/Test/BSN.Commons.AutoMapper.Tests/AutoMapperTestBase.cs new file mode 100644 index 0000000..6291ca6 --- /dev/null +++ b/Test/BSN.Commons.AutoMapper.Tests/AutoMapperTestBase.cs @@ -0,0 +1,19 @@ +using AutoMapper; + +namespace BSN.Commons.AutoMapper.Tests +{ + public abstract class AutoMapperTestBase + { + protected readonly IMapper _mapper; + + protected AutoMapperTestBase() + { + var configuration = new MapperConfiguration(cfg => + { + cfg.AddProfile(); + }); + + _mapper = configuration.CreateMapper(); + } + } +} diff --git a/Test/BSN.Commons.AutoMapper.Tests/BSN.Commons.AutoMapper.Tests.csproj b/Test/BSN.Commons.AutoMapper.Tests/BSN.Commons.AutoMapper.Tests.csproj new file mode 100644 index 0000000..42189c4 --- /dev/null +++ b/Test/BSN.Commons.AutoMapper.Tests/BSN.Commons.AutoMapper.Tests.csproj @@ -0,0 +1,33 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Test/BSN.Commons.AutoMapper.Tests/CommonMapperProfileTests.cs b/Test/BSN.Commons.AutoMapper.Tests/CommonMapperProfileTests.cs new file mode 100644 index 0000000..02ccf91 --- /dev/null +++ b/Test/BSN.Commons.AutoMapper.Tests/CommonMapperProfileTests.cs @@ -0,0 +1,94 @@ +using AutoMapper; +using BSN.Commons.Responses; + +namespace BSN.Commons.AutoMapper.Tests +{ + [TestFixture] + public class CommonMapperProfileTests + { + [Test] + public void PagedEntityCollectionToMetaDataConverter_ConvertsCorrectly() + { + // Arrange + var profile = new CommonMapperProfile(); + var configuration = new MapperConfiguration(cfg => cfg.AddProfile(profile)); + var mapper = new Mapper(configuration); + var pagedEntityCollection = new PagedEntityCollection + { + CurrentPage = 1, + PageSize = 10, + RecordCount = 100 + }; + + // Act + var result = mapper.Map(pagedEntityCollection); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(pagedEntityCollection.CurrentPage, result.Page); + Assert.AreEqual(pagedEntityCollection.PageSize, result.PageSize); + Assert.AreEqual(pagedEntityCollection.RecordCount, result.RecordCount); + } + + [Test] + public void GenericIEnumerableToCollectionViewModelConverter_ConvertsCorrectly() + { + // Arrange + var profile = new CommonMapperProfile(); + var configuration = new MapperConfiguration(cfg => cfg.AddProfile(profile)); + var mapper = new Mapper(configuration); + var items = new List { 1, 2, 3 }; + + // Act + var result = mapper.Map>(items); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(items.Count, result.Items.Count()); + } + + [Test] + public void CustomProfileConverter_ConvertsCorrectly() + { + // Arrange + var customProfile = new CustomMapperProfile(); + var profile = new CommonMapperProfile(); + var configuration = new MapperConfiguration(cfg => + { + cfg.AddProfile(profile); + cfg.AddProfile(customProfile); + }); + + var mapper = new Mapper(configuration); + var customEntity = new CustomEntity { Id = 1, Name = "Custom Entity" }; + + // Act + var result = mapper.Map(customEntity); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(customEntity.Id, result.Id); + Assert.AreEqual(customEntity.Name, result.Name); + } + + public class CustomMapperProfile : Profile + { + public CustomMapperProfile() + { + CreateMap(); + } + } + + public class CustomEntity + { + public int Id { get; set; } + public string Name { get; set; } + } + + public class CustomViewModel + { + public int Id { get; set; } + public string Name { get; set; } + } + } +} \ No newline at end of file diff --git a/Test/BSN.Commons.AutoMapper.Tests/IServiceCollectionExtensionsTests.cs b/Test/BSN.Commons.AutoMapper.Tests/IServiceCollectionExtensionsTests.cs new file mode 100644 index 0000000..9f30ca4 --- /dev/null +++ b/Test/BSN.Commons.AutoMapper.Tests/IServiceCollectionExtensionsTests.cs @@ -0,0 +1,24 @@ +using AutoMapper; +using Microsoft.Extensions.DependencyInjection; + +namespace BSN.Commons.AutoMapper.Tests +{ + public class IServiceCollectionExtensionsTests : AutoMapperTestBase + { + [Test] + public void AddAutoMapper_AddsMapperToServices() + { + // Arrange + var services = new ServiceCollection(); + var configure = new Action(config => { }); + + // Act + services.AddAutoMapper(configure); + var serviceProvider = services.BuildServiceProvider(); + + // Assert + var mapper = serviceProvider.GetService(); + Assert.NotNull(mapper); + } + } +}