diff --git a/.github/lock.yml b/.github/lock.yml deleted file mode 100644 index f030e53..0000000 --- a/.github/lock.yml +++ /dev/null @@ -1,38 +0,0 @@ -# Configuration for Lock Threads - https://github.com/dessant/lock-threads - -# Number of days of inactivity before a closed issue or pull request is locked -daysUntilLock: 31 - -# Skip issues and pull requests created before a given timestamp. Timestamp must -# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable -skipCreatedBefore: false - -# Issues and pull requests with these labels will be ignored. Set to `[]` to disable -exemptLabels: [] - -# Label to add before locking, such as `outdated`. Set to `false` to disable -lockLabel: false - -# Comment to post before locking. Set to `false` to disable -lockComment: > - This thread has been automatically locked since there has not been - any recent activity after it was closed. Please open a new issue for - related bugs. - -# Assign `resolved` as the reason for locking. Set to `false` to disable -setLockReason: true - -# Limit to only `issues` or `pulls` -# only: issues - -# Optionally, specify configuration settings just for `issues` or `pulls` -# issues: -# exemptLabels: -# - help-wanted -# lockLabel: outdated - -# pulls: -# daysUntilLock: 30 - -# Repository to extend settings from -# _extends: repo diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d17934b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,33 @@ +name: CI + +on: + push: + branches: + - master + pull_request: + branches: + - master +jobs: + build: + strategy: + fail-fast: false + runs-on: windows-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Build and Test + run: ./Build.ps1 + shell: pwsh + - name: Push to MyGet + env: + NUGET_URL: https://www.myget.org/F/automapperdev/api/v3/index.json + NUGET_API_KEY: ${{ secrets.MYGET_CI_API_KEY }} + run: ./Push.ps1 + shell: pwsh + - name: Artifacts + uses: actions/upload-artifact@v2 + with: + name: artifacts + path: artifacts/**/* \ No newline at end of file diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml new file mode 100644 index 0000000..a4c3452 --- /dev/null +++ b/.github/workflows/lock.yml @@ -0,0 +1,23 @@ +name: 'Lock threads' + +on: + schedule: + - cron: '0 0 * * 0' + +jobs: + lock: + runs-on: ubuntu-latest + steps: + - uses: dessant/lock-threads@v2 + with: + github-token: ${{ github.token }} + issue-lock-inactive-days: 31 + pr-lock-inactive-days: 31 + issue-lock-comment: > + This issue has been automatically locked since there + has not been any recent activity after it was closed. + Please open a new issue for related bugs. + pr-lock-comment: > + This pull request has been automatically locked since there + has not been any recent activity after it was closed. + Please open a new issue for related bugs. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..779e835 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,36 @@ +name: Release + +on: + push: + tags: + - '*.*.*' +jobs: + build: + strategy: + fail-fast: false + runs-on: windows-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Build and Test + run: ./Build.ps1 + shell: pwsh + - name: Push to MyGet + env: + NUGET_URL: https://www.myget.org/F/automapperdev/api/v3/index.json + NUGET_API_KEY: ${{ secrets.MYGET_CI_API_KEY }} + run: ./Push.ps1 + shell: pwsh + - name: Push to NuGet + env: + NUGET_URL: https://api.nuget.org/v3/index.json + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + run: ./Push.ps1 + shell: pwsh + - name: Artifacts + uses: actions/upload-artifact@v2 + with: + name: artifacts + path: artifacts/**/* \ No newline at end of file diff --git a/AutoMapper.DI.sln b/AutoMapper.DI.sln index f01553e..5d8760e 100644 --- a/AutoMapper.DI.sln +++ b/AutoMapper.DI.sln @@ -1,14 +1,17 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26730.3 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30204.135 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{1082ED20-13D1-444F-BAFD-17EFB9152981}" ProjectSection(SolutionItems) = preProject - appveyor.yml = appveyor.yml AutoMapper.Extensions.Microsoft.DependencyInjection.snk = AutoMapper.Extensions.Microsoft.DependencyInjection.snk Build.ps1 = Build.ps1 + .github\workflows\ci.yml = .github\workflows\ci.yml + Directory.Build.props = Directory.Build.props + Push.ps1 = Push.ps1 README.md = README.md + .github\workflows\release.yml = .github\workflows\release.yml EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoMapper.Extensions.Microsoft.DependencyInjection", "src\AutoMapper.Extensions.Microsoft.DependencyInjection\AutoMapper.Extensions.Microsoft.DependencyInjection.csproj", "{CD3BCC44-44C0-4A5A-9041-34B558A9FBF2}" diff --git a/Build.ps1 b/Build.ps1 index 9e34eee..68d58f1 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -1,15 +1,9 @@ -function Test-Project -{ - param([string] $DirectoryName) - & dotnet test -c Release ("""" + $DirectoryName + """") -} - # Taken from psake https://github.com/psake/psake <# .SYNOPSIS This is a helper function that runs a scriptblock and checks the PS variable $lastexitcode - to see if an error occured. If an error is detected then an exception is thrown. + to see if an error occcured. If an error is detected then an exception is thrown. This function allows you to run command-line programs without having to explicitly check the $lastexitcode variable. .EXAMPLE @@ -27,39 +21,16 @@ function Exec throw ("Exec: " + $errorMessage) } } -######################## -# THE BUILD! -######################## -Push-Location $PSScriptRoot +$artifacts = ".\artifacts" -if(Test-Path .\artifacts) { Remove-Item .\artifacts -Force -Recurse } +if(Test-Path $artifacts) { Remove-Item $artifacts -Force -Recurse } -$branch = @{ $true = $env:APPVEYOR_REPO_BRANCH; $false = $(git symbolic-ref --short -q HEAD) }[$env:APPVEYOR_REPO_BRANCH -ne $NULL]; -$revision = @{ $true = "{0:00000}" -f [convert]::ToInt32("0" + $env:APPVEYOR_BUILD_NUMBER, 10); $false = "local" }[$env:APPVEYOR_BUILD_NUMBER -ne $NULL]; -$suffix = @{ $true = ""; $false = "$($branch.Substring(0, [math]::Min(10,$branch.Length)))-$revision"}[$branch -eq "master" -and $revision -ne "local"] -$commitHash = $(git rev-parse --short HEAD) -$buildSuffix = @{ $true = "$($suffix)-$($commitHash)"; $false = "$($branch)-$($commitHash)" }[$suffix -ne ""] -$versionSuffix = @{ $true = "--version-suffix=$($suffix)"; $false = ""}[$suffix -ne ""] +exec { & dotnet clean -c Release } -echo "build: Package version suffix is $suffix" -echo "build: Build version suffix is $buildSuffix" - -exec { dotnet build -c Release --version-suffix=$buildSuffix -v q /nologo } +exec { & dotnet build -c Release } -foreach ($test in ls test/*.Tests) { - Push-Location $test - - echo "build: Testing project in $test" - - try { - & dotnet test -c Release - if($LASTEXITCODE -ne 0) { exit 3 } - } finally { - Pop-Location - } -} +exec { & dotnet test -c Release -r $artifacts --no-build -l trx --verbosity=normal } -exec { dotnet pack .\src\AutoMapper.Extensions.Microsoft.DependencyInjection -c Release -o ..\..\artifacts --include-symbols --no-build --no-restore $versionSuffix } +exec { & dotnet pack .\src\AutoMapper.Extensions.Microsoft.DependencyInjection\AutoMapper.Extensions.Microsoft.DependencyInjection.csproj -c Release -o $artifacts --no-build } -Pop-Location \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..a585cc8 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,11 @@ + + + + Jimmy Bogard + latest + $(NoWarn);CS1701;CS1702;CS1591 + true + strict + + + diff --git a/Push.ps1 b/Push.ps1 new file mode 100644 index 0000000..9663563 --- /dev/null +++ b/Push.ps1 @@ -0,0 +1,14 @@ +$scriptName = $MyInvocation.MyCommand.Name +$artifacts = "./artifacts" + +if ([string]::IsNullOrEmpty($Env:NUGET_API_KEY)) { + Write-Host "${scriptName}: NUGET_API_KEY is empty or not set. Skipped pushing package(s)." +} else { + Get-ChildItem $artifacts -Filter "*.nupkg" | ForEach-Object { + Write-Host "$($scriptName): Pushing $($_.Name)" + dotnet nuget push $_ --source $Env:NUGET_URL --api-key $Env:NUGET_API_KEY + if ($lastexitcode -ne 0) { + throw ("Exec: " + $errorMessage) + } + } +} diff --git a/README.md b/README.md index 21d518d..2f12340 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # AutoMapper extensions for Microsoft.Extensions.DependencyInjection +[![CI](https://github.com/automapper/automapper.extensions.microsoft.dependencyinjection/workflows/CI/badge.svg)](https://github.com/automapper/automapper.extensions.microsoft.dependencyinjection/workflows/CI) +[![NuGet](http://img.shields.io/nuget/v/automapper.extensions.microsoft.dependencyinjection.svg)](https://www.nuget.org/packages/automapper.extensions.microsoft.dependencyinjection/) +[![MyGet (dev)](https://img.shields.io/myget/automapperdev/v/automapper.extensions.microsoft.dependencyinjection.svg)](http://myget.org/gallery/automapperdev) + Scans assemblies and: 1. adds profiles to mapping configuration @@ -29,11 +33,11 @@ This registers AutoMapper: Mapping configuration is static as it is the root object that can create an `IMapper`. -Mapper instances are registered as transient as they are intented to be used within the lifetime of a request. You can configure this with the `serviceLifetime` parameter. Be careful changing this as `Mapper` takes a dependency on a factory method to instantiate the other extensions. +Mapper instances are registered as transient. You can configure this with the `serviceLifetime` parameter. Be careful changing this, as `Mapper` takes a dependency on a factory method to instantiate the other extensions. ### Mapper.Map usage -To map at runtime, you'll first need a scope. In an ASP.NET Core application, each request already has a scope, so you can depend on `IMapper` directly: +To map at runtime, add a dependency on `IMapper`: ```c# public class EmployeesController { diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index ae01522..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,30 +0,0 @@ -version: '{build}' -pull_requests: - do_not_increment_build_number: true -branches: - only: - - master -image: Visual Studio 2017 -nuget: - disable_publish_on_pr: true -build_script: -- ps: .\Build.ps1 -test: off -artifacts: -- path: .\artifacts\**\*.nupkg - name: NuGet -deploy: -- provider: NuGet - server: https://www.myget.org/F/automapperdev/api/v2/package - api_key: - secure: zKeQZmIv9PaozHQRJmrlRHN+jMCI64Uvzmb/vwePdXFR5CUNEHalnZdOCg0vrh8t - skip_symbols: true - on: - branch: master -- provider: NuGet - name: production - api_key: - secure: t3blEIQiDIYjjWhOSTTtrcAnzJkmSi+0zYPxC1v4RDzm6oI/gIpD6ZtrOGsYu2jE - on: - branch: master - appveyor_repo_tag: true \ No newline at end of file diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..56b96ca Binary files /dev/null and b/icon.png differ diff --git a/src/AutoMapper.Extensions.Microsoft.DependencyInjection/AutoMapper.Extensions.Microsoft.DependencyInjection.csproj b/src/AutoMapper.Extensions.Microsoft.DependencyInjection/AutoMapper.Extensions.Microsoft.DependencyInjection.csproj index e0bb114..f47a314 100644 --- a/src/AutoMapper.Extensions.Microsoft.DependencyInjection/AutoMapper.Extensions.Microsoft.DependencyInjection.csproj +++ b/src/AutoMapper.Extensions.Microsoft.DependencyInjection/AutoMapper.Extensions.Microsoft.DependencyInjection.csproj @@ -3,25 +3,33 @@ AutoMapper extensions for ASP.NET Core Copyright Jimmy Bogard - 7.0.0 - Jimmy Bogard netstandard2.0 AutoMapper.Extensions.Microsoft.DependencyInjection AutoMapper.Extensions.Microsoft.DependencyInjection - https://s3.amazonaws.com/automapper/icon.png - http://automapper.org - https://github.com/AutoMapper/AutoMapper/blob/master/LICENSE.txt - false - true - $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb true ..\..\AutoMapper.Extensions.Microsoft.DependencyInjection.snk + icon.png + https://automapper.org + v + MIT + true + true + snupkg + true + true + true - - - + + + + + + + + + diff --git a/src/AutoMapper.Extensions.Microsoft.DependencyInjection/ServiceCollectionExtensions.cs b/src/AutoMapper.Extensions.Microsoft.DependencyInjection/ServiceCollectionExtensions.cs index 91a1d44..f495b39 100644 --- a/src/AutoMapper.Extensions.Microsoft.DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/AutoMapper.Extensions.Microsoft.DependencyInjection/ServiceCollectionExtensions.cs @@ -1,30 +1,34 @@ -using Microsoft.Extensions.DependencyInjection.Extensions; - -namespace AutoMapper +namespace AutoMapper { using System; using System.Collections.Generic; using System.Linq; using System.Reflection; + using AutoMapper.Configuration; using Microsoft.Extensions.DependencyInjection; - - /// - /// Extensions to scan for AutoMapper classes and register the configuration, mapping, and extensions with the service collection: - /// - /// Finds classes and initializes a new , - /// Scans for , , and implementations and registers them as , - /// Registers as , and - /// Registers as a configurable (default is ) - /// - /// After calling AddAutoMapper you can resolve an instance from a scoped service provider, or as a dependency - /// To use you can resolve the instance directly for from an instance. - /// - public static class ServiceCollectionExtensions + using Microsoft.Extensions.DependencyInjection.Extensions; + using Microsoft.Extensions.Options; + + /// + /// Extensions to scan for AutoMapper classes and register the configuration, mapping, and extensions with the service collection: + /// + /// Finds classes and initializes a new , + /// Scans for , , and implementations and registers them as , + /// Registers as , and + /// Registers as a configurable (default is ) + /// + /// After calling AddAutoMapper you can resolve an instance from a scoped service provider, or as a dependency + /// To use you can resolve the instance directly for from an instance. + /// + public static class ServiceCollectionExtensions { + public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action configAction) + => AddAutoMapperClasses(services, (sp, cfg) => configAction?.Invoke(cfg), null); + public static IServiceCollection AddAutoMapper(this IServiceCollection services, params Assembly[] assemblies) => AddAutoMapperClasses(services, null, assemblies); - public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action configAction, params Assembly[] assemblies) + public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action configAction, params Assembly[] assemblies) => AddAutoMapperClasses(services, (sp, cfg) => configAction?.Invoke(cfg), assemblies); public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action configAction, params Assembly[] assemblies) @@ -33,7 +37,7 @@ public static IServiceCollection AddAutoMapper(this IServiceCollection services, public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action configAction, IEnumerable assemblies, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) => AddAutoMapperClasses(services, (sp, cfg) => configAction?.Invoke(cfg), assemblies, serviceLifetime); - public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action configAction, IEnumerable assemblies, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action configAction, IEnumerable assemblies, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) => AddAutoMapperClasses(services, configAction, assemblies, serviceLifetime); public static IServiceCollection AddAutoMapper(this IServiceCollection services, IEnumerable assemblies, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) @@ -48,55 +52,66 @@ public static IServiceCollection AddAutoMapper(this IServiceCollection services, public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action configAction, params Type[] profileAssemblyMarkerTypes) => AddAutoMapperClasses(services, configAction, profileAssemblyMarkerTypes.Select(t => t.GetTypeInfo().Assembly)); - public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action configAction, + public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action configAction, IEnumerable profileAssemblyMarkerTypes, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) => AddAutoMapperClasses(services, (sp, cfg) => configAction?.Invoke(cfg), profileAssemblyMarkerTypes.Select(t => t.GetTypeInfo().Assembly), serviceLifetime); - public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action configAction, + public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action configAction, IEnumerable profileAssemblyMarkerTypes, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) => AddAutoMapperClasses(services, configAction, profileAssemblyMarkerTypes.Select(t => t.GetTypeInfo().Assembly), serviceLifetime); - private static IServiceCollection AddAutoMapperClasses(IServiceCollection services, Action configAction, + private static IServiceCollection AddAutoMapperClasses(IServiceCollection services, Action configAction, IEnumerable assembliesToScan, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) { - // Just return if we've already added AutoMapper to avoid double-registration - if (services.Any(sd => sd.ServiceType == typeof(IMapper))) - return services; - - assembliesToScan = assembliesToScan as Assembly[] ?? assembliesToScan.ToArray(); + if (configAction != null) + { + services.AddOptions() + .Configure((options, sp) => configAction(sp, options)); + } - var allTypes = assembliesToScan - .Where(a => !a.IsDynamic && a.GetName().Name != nameof(AutoMapper)) - .Distinct() // avoid AutoMapper.DuplicateTypeMapConfigurationException - .SelectMany(a => a.DefinedTypes) - .ToArray(); + var assembliesToScanArray = assembliesToScan as Assembly[] ?? assembliesToScan?.ToArray(); - void ConfigAction(IServiceProvider serviceProvider, IMapperConfigurationExpression cfg) + if (assembliesToScanArray != null && assembliesToScanArray.Length > 0) { - configAction?.Invoke(serviceProvider, cfg); - - cfg.AddMaps(assembliesToScan); + var allTypes = assembliesToScanArray + .Where(a => !a.IsDynamic && a.GetName().Name != nameof(AutoMapper)) + .Distinct() // avoid AutoMapper.DuplicateTypeMapConfigurationException + .SelectMany(a => a.DefinedTypes) + .ToArray(); + + services.Configure(options => options.AddMaps(assembliesToScanArray)); + + var openTypes = new[] + { + typeof(IValueResolver<,,>), + typeof(IMemberValueResolver<,,,>), + typeof(ITypeConverter<,>), + typeof(IValueConverter<,>), + typeof(IMappingAction<,>) + }; + foreach (var type in openTypes.SelectMany(openType => allTypes + .Where(t => t.IsClass + && !t.IsAbstract + && t.AsType().ImplementsGenericInterface(openType)))) + { + // use try add to avoid double-registration + services.TryAddTransient(type.AsType()); + } } - var openTypes = new[] - { - typeof(IValueResolver<,,>), - typeof(IMemberValueResolver<,,,>), - typeof(ITypeConverter<,>), - typeof(IValueConverter<,>), - typeof(IMappingAction<,>) - }; - foreach (var type in openTypes.SelectMany(openType => allTypes - .Where(t => t.IsClass - && !t.IsAbstract - && t.AsType().ImplementsGenericInterface(openType)))) + // Just return if we've already added AutoMapper to avoid double-registration + if (services.Any(sd => sd.ServiceType == typeof(IMapper))) + return services; + + services.AddSingleton(sp => { - services.AddTransient(type.AsType()); - } + // A mapper configuration is required + var options = sp.GetRequiredService>(); + return new MapperConfiguration(options.Value); + }); - services.AddSingleton(sp => new MapperConfiguration(cfg => ConfigAction(sp, cfg))); services.Add(new ServiceDescriptor(typeof(IMapper), - sp => new Mapper(sp.GetRequiredService(), sp.GetService), serviceLifetime)); + sp => new Mapper(sp.GetRequiredService(), sp.GetService), serviceLifetime)); return services; } @@ -107,4 +122,4 @@ private static bool ImplementsGenericInterface(this Type type, Type interfaceTyp private static bool IsGenericType(this Type type, Type genericType) => type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == genericType; } -} +} \ No newline at end of file diff --git a/src/TestApp/TestApp.csproj b/src/TestApp/TestApp.csproj index 70423bd..f36073d 100644 --- a/src/TestApp/TestApp.csproj +++ b/src/TestApp/TestApp.csproj @@ -1,7 +1,7 @@  - netcoreapp2.0 + netcoreapp3.1 TestApp Exe TestApp @@ -15,7 +15,7 @@ - + diff --git a/test/AutoMapper.Extensions.Microsoft.DependencyInjection.Tests/AutoMapper.Extensions.Microsoft.DependencyInjection.Tests.csproj b/test/AutoMapper.Extensions.Microsoft.DependencyInjection.Tests/AutoMapper.Extensions.Microsoft.DependencyInjection.Tests.csproj index 66598cb..7f32a57 100644 --- a/test/AutoMapper.Extensions.Microsoft.DependencyInjection.Tests/AutoMapper.Extensions.Microsoft.DependencyInjection.Tests.csproj +++ b/test/AutoMapper.Extensions.Microsoft.DependencyInjection.Tests/AutoMapper.Extensions.Microsoft.DependencyInjection.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp2.0 + netcoreapp3.1 true AutoMapper.Extensions.Microsoft.DependencyInjection.Tests AutoMapper.Extensions.Microsoft.DependencyInjection.Tests @@ -19,7 +19,7 @@ - + diff --git a/test/AutoMapper.Extensions.Microsoft.DependencyInjection.Tests/MultipleRegistrationTests.cs b/test/AutoMapper.Extensions.Microsoft.DependencyInjection.Tests/MultipleRegistrationTests.cs new file mode 100644 index 0000000..5c14981 --- /dev/null +++ b/test/AutoMapper.Extensions.Microsoft.DependencyInjection.Tests/MultipleRegistrationTests.cs @@ -0,0 +1,41 @@ +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Xunit; + +namespace AutoMapper.Extensions.Microsoft.DependencyInjection.Tests +{ + public class MultipleRegistrationTests + { + [Fact] + public void Can_register_multiple_times() + { + var services = new ServiceCollection(); + + services.AddAutoMapper(cfg => { }); + services.AddAutoMapper(cfg => { }); + services.AddAutoMapper(cfg => { }); + + var serviceProvider = services.BuildServiceProvider(); + + serviceProvider.GetService().ShouldNotBeNull(); + } + + [Fact] + public void Can_register_assembly_multiple_times() + { + var services = new ServiceCollection(); + + services.AddAutoMapper(typeof(MultipleRegistrationTests)); + services.AddAutoMapper(typeof(MultipleRegistrationTests)); + services.AddAutoMapper(typeof(MultipleRegistrationTests)); + services.AddTransient(); + + var serviceProvider = services.BuildServiceProvider(); + + serviceProvider.GetService().ShouldNotBeNull(); + serviceProvider.GetService().ShouldNotBeNull(); + serviceProvider.GetServices().Count().ShouldBe(1); + } + } +} \ No newline at end of file