Adding support for DI-enabled destination factories.#4603
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds dependency injection support for ConstructUsing by introducing the IDestinationFactory<TSource, TDestination> interface, enabling class-based destination object construction with DI capabilities. This addresses issue #4602 by providing a similar pattern to the existing class-based conditions, resolvers, and converters.
Changes:
- Introduces
IDestinationFactory<TSource, TDestination>interface for class-based destination construction - Adds generic and non-generic
ConstructUsingmethod overloads that accept factory types - Updates
ServiceCollectionExtensionsto automatically registerIDestinationFactoryimplementations as transient services
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/AutoMapper/Configuration/IMappingExpressionBase.cs | Defines the new IDestinationFactory<TSource, TDestination> interface and adds ConstructUsing method overloads to support class-based factories |
| src/AutoMapper/TypeMap.cs | Implements ConstructUsingObjectConstructor method to build expression trees that resolve and invoke destination factories at mapping time |
| src/AutoMapper/Configuration/TypeMapConfiguration.cs | Adds implementation for generic and non-generic ConstructUsing overloads that register factory types with the type map |
| src/AutoMapper/ServiceCollectionExtensions.cs | Adds IDestinationFactory<,> to the list of automatically registered AutoMapper types for DI scanning |
| src/UnitTests/Construction/ClassBasedObjectConstructorTests.cs | Adds comprehensive unit tests covering basic factories, source-aware construction, non-generic usage, and member mapping combinations |
| src/AutoMapper.DI.Tests/Profiles.cs | Adds test profile and factory implementation demonstrating DI integration |
| src/AutoMapper.DI.Tests/DependencyTests.cs | Adds integration tests verifying that destination factories work correctly with dependency injection |
| docs/source/Construction.md | Documents the new class-based destination factory feature with usage examples and DI integration guidance |
| docs/source/Dependency-injection.md | Updates the list of automatically registered types to include IDestinationFactory and removes redundant condition example |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| using static Expression; | ||
| using static ExpressionBuilder; |
There was a problem hiding this comment.
using static Expression; and using static ExpressionBuilder; look redundant here because the AutoMapper project already adds these as global static usings (see src/AutoMapper/AutoMapper.csproj <Using ... Static="true"/>). Consider removing the file-level static usings to reduce noise and avoid inconsistencies with the rest of the codebase.
| using static Expression; | |
| using static ExpressionBuilder; |
| throw new InvalidOperationException($"Type '{objectConstructorType.Name}' does not implement IDestinationFactory<{SourceType.Name}, {DestinationType.Name}>"); | ||
| } | ||
| var constructMethod = expectedInterface.GetMethod("Construct") ?? | ||
| throw new InvalidOperationException($"IDestinationFactory<{SourceType.Name}, {DestinationType.Name}> does not define a 'Construct' method."); |
There was a problem hiding this comment.
The exception messages in ConstructUsingObjectConstructor use .Name for types, which can be ambiguous (same type name in different namespaces) and less actionable when debugging. Consider using FullName (or the Type itself) for objectConstructorType, SourceType, and DestinationType in these error messages.
| throw new InvalidOperationException($"Type '{objectConstructorType.Name}' does not implement IDestinationFactory<{SourceType.Name}, {DestinationType.Name}>"); | |
| } | |
| var constructMethod = expectedInterface.GetMethod("Construct") ?? | |
| throw new InvalidOperationException($"IDestinationFactory<{SourceType.Name}, {DestinationType.Name}> does not define a 'Construct' method."); | |
| throw new InvalidOperationException( | |
| $"Type '{objectConstructorType.FullName ?? objectConstructorType.Name}' does not implement IDestinationFactory<{SourceType.FullName ?? SourceType.Name}, {DestinationType.FullName ?? DestinationType.Name}>"); | |
| } | |
| var constructMethod = expectedInterface.GetMethod("Construct") ?? | |
| throw new InvalidOperationException( | |
| $"IDestinationFactory<{SourceType.FullName ?? SourceType.Name}, {DestinationType.FullName ?? DestinationType.Name}> does not define a 'Construct' method."); |
| /// Supply a custom object constructor type for instantiating the destination type with dependency injection support | ||
| /// </summary> | ||
| /// <remarks>Not used for LINQ projection (ProjectTo).</remarks> | ||
| /// <typeparam name="TConstructor">Constructor type implementing IDestinationFactory<TSource, TDestination></typeparam> | ||
| /// <returns>Itself</returns> | ||
| TMappingExpression ConstructUsing<TConstructor>() where TConstructor : IDestinationFactory<TSource, TDestination>; | ||
| /// <summary> | ||
| /// Supply a custom object constructor type for instantiating the destination type with dependency injection support. | ||
| /// Used when the constructor type is not known at compile-time. | ||
| /// </summary> | ||
| /// <remarks>Not used for LINQ projection (ProjectTo).</remarks> | ||
| /// <param name="objectConstructorType">Constructor type implementing IDestinationFactory</param> | ||
| void ConstructUsing(Type objectConstructorType); |
There was a problem hiding this comment.
Adding members to the public IMappingExpressionBase<...> interface is a binary/source breaking change for any external implementers. If compatibility is a concern, consider implementing this as extension method(s) (backed by the existing ConstructUsing(Func<...>) overload) or introducing a new interface rather than expanding the existing one.
| ## Low level API-s | ||
|
|
||
| AutoMapper supports the ability to construct [Custom Value Resolvers](Custom-value-resolvers.html), [Custom Type Converters](Custom-type-converters.html), [Value Converters](Value-converters.html), and [Class-based Conditions](Conditional-mapping.html#class-based-conditions) using static service location: | ||
|
|
||
| ```c# | ||
| var configuration = new MapperConfiguration(cfg => | ||
| { | ||
| cfg.ConstructServicesUsing(ObjectFactory.GetInstance); | ||
|
|
||
| cfg.CreateMap<Source, Destination>(); | ||
| }, loggerFactory); | ||
| ``` | ||
|
|
||
| ### Automatic Class Registration | ||
|
|
||
| When using `AddAutoMapper`, AutoMapper will automatically register implementations of the following types as `ServiceLifetime.Transient` from the specified assemblies: | ||
|
|
||
| - `IValueResolver<TSource, TDestination, TDestMember>` | ||
| - `IMemberValueResolver<TSource, TDestination, TSourceMember, TDestMember>` | ||
| - `ITypeConverter<TSource, TDestination>` | ||
| - `IValueConverter<TSourceMember, TDestinationMember>` | ||
| - `IDestinationFactory<TSource, TDestination>` | ||
| - `ICondition<TSource, TDestination, TMember>` | ||
| - `IPreCondition<TSource, TDestination>` | ||
| - `IMappingAction<TSource, TDestination>` | ||
|
|
||
| This allows you to use class-based conditions with dependency injection: | ||
|
|
||
| ```c# | ||
| public class MyCondition : ICondition<Source, Destination, int> | ||
| { | ||
| private readonly IMyService _myService; | ||
|
|
||
| public MyCondition(IMyService myService) | ||
| { | ||
| _myService = myService; | ||
| } | ||
|
|
||
| public bool Evaluate(Source source, Destination destination, int sourceMember, | ||
| int destMember, ResolutionContext context) | ||
| { | ||
| return _myService.ShouldMap(sourceMember); | ||
| } | ||
| } | ||
|
|
||
| public class ConditionProfile : Profile | ||
| { | ||
| public ConditionProfile() | ||
| { | ||
| CreateMap<Source, Destination>() | ||
| .ForMember(d => d.Value, o => | ||
| { | ||
| o.Condition<MyCondition>(); | ||
| o.MapFrom(s => s.Value); | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| // In Startup.cs / Program.cs: | ||
| services.AddTransient<IMyService, MyService>(); | ||
| services.AddAutoMapper(cfg => { }, typeof(ConditionProfile).Assembly); | ||
| ``` | ||
|
|
||
| Or dynamic service location, to be used in the case of instance-based containers (including child/nested containers): | ||
|
|
||
| ```c# | ||
| var mapper = new Mapper(configuration, childContainer.GetInstance); | ||
|
|
||
| var dest = mapper.Map<Source, Destination>(new Source { Value = 15 }); | ||
| ``` | ||
|
|
||
| ## Queryable Extensions |
There was a problem hiding this comment.
This change removes the detailed examples for class-based conditions and dynamic service location from the DI docs, but nothing in the PR description indicates they should be deleted. Unless this was intentional, consider restoring that removed content (or moving it elsewhere) so the DI documentation doesn’t regress.
Updated [AutoMapper](https://github.com/LuckyPennySoftware/AutoMapper) from 16.0.0 to 16.1.1. <details> <summary>Release notes</summary> _Sourced from [AutoMapper's releases](https://github.com/LuckyPennySoftware/AutoMapper/releases)._ ## 16.1.1 ## What's Changed * Better async handling of license validation; fixes #4612 by @jbogard in LuckyPennySoftware/AutoMapper#4613 * More artifacts for builds (test results and SBOM) by @jbogard in LuckyPennySoftware/AutoMapper#4615 * Update Microsoft.Sbom.DotNetTool to 4.1.5 by @jbogard in LuckyPennySoftware/AutoMapper#4616 ## Security Fixed an issue where certain cyclic or self-referential object graphs could trigger uncontrolled recursion during mapping, potentially resulting in stack exhaustion and denial of service. Applications that process untrusted or attacker-controlled object graphs through affected mapping paths may be impacted. Users should upgrade to this release. Security advisory: GHSA-rvv3-g6hj-g44x Thanks to @bluefossa for responsibly disclosing this issue. **Full Changelog**: LuckyPennySoftware/AutoMapper@v16.1.0...v16.1.1 ## 16.1.0 ## What's Changed * Add Debug and Release build configurations to slnx by @Copilot in LuckyPennySoftware/AutoMapper#4590 * Migrating to slnx by @jbogard in LuckyPennySoftware/AutoMapper#4589 * Allow disabling of polymorphic LINQ mapping by @jbogard in LuckyPennySoftware/AutoMapper#4596 * Fix duplicate BOM in ServiceCollectionExtensions.cs by @Copilot in LuckyPennySoftware/AutoMapper#4600 * Fix review feedback: double semicolon, DI condition integration test, docs example by @Copilot in LuckyPennySoftware/AutoMapper#4601 * Adding DI-enabled conditions and pre-conditions; updated docs accordi… by @jbogard in LuckyPennySoftware/AutoMapper#4599 * Adding support for DI-enabled destination factories. by @jbogard in LuckyPennySoftware/AutoMapper#4603 * Correctly converting nullables for MapAtRuntime; fixes #4597 by @jbogard in LuckyPennySoftware/AutoMapper#4604 * Correctly handling consecutive uppercase characters; fixes #4593 by @jbogard in LuckyPennySoftware/AutoMapper#4605 * Wrapping the exception to provide better feedback to the user; fixes … by @jbogard in LuckyPennySoftware/AutoMapper#4606 * Fixing bug around order of open generic registration by @jbogard in LuckyPennySoftware/AutoMapper#4607 * Adding perpetual licensing by @jbogard in LuckyPennySoftware/AutoMapper#4608 ## New Contributors * @Copilot made their first contribution in LuckyPennySoftware/AutoMapper#4590 **Full Changelog**: LuckyPennySoftware/AutoMapper@v16.0.0...v16.1.0 Commits viewable in [compare view](LuckyPennySoftware/AutoMapper@v16.0.0...v16.1.1). </details> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore <dependency name> major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore <dependency name> minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore <dependency name>` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore <dependency name>` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore <dependency name> <ignore condition>` will remove the ignore condition of the specified dependency and ignore conditions You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/mlapaglia/OpenAlprWebhookProcessor/network/alerts). </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
|
This pull request has been automatically locked since there has not been any recent activity after it was closed. |
Fixes #4602