Skip to content

Adding support for DI-enabled destination factories.#4603

Merged
jbogard merged 5 commits intomainfrom
4602-enable-di-for-constructusing
Feb 21, 2026
Merged

Adding support for DI-enabled destination factories.#4603
jbogard merged 5 commits intomainfrom
4602-enable-di-for-constructusing

Conversation

@jbogard
Copy link
Contributor

@jbogard jbogard commented Feb 20, 2026

Fixes #4602

@jbogard jbogard linked an issue Feb 20, 2026 that may be closed by this pull request
@jbogard jbogard requested a review from Copilot February 20, 2026 23:07
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ConstructUsing method overloads that accept factory types
  • Updates ServiceCollectionExtensions to automatically register IDestinationFactory implementations 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.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

jbogard and others added 2 commits February 20, 2026 18:28
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +5 to +6
using static Expression;
using static ExpressionBuilder;
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
using static Expression;
using static ExpressionBuilder;

Copilot uses AI. Check for mistakes.
Comment on lines +268 to +271
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.");
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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.");

Copilot uses AI. Check for mistakes.
Comment on lines +160 to +172
/// 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&lt;TSource, TDestination&gt;</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);
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines 37 to 63
## 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
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
This was referenced Mar 4, 2026
PhilipWoulfe pushed a commit to PhilipWoulfe/F1Competition that referenced this pull request Mar 10, 2026
Updated [AutoMapper](https://github.com/LuckyPennySoftware/AutoMapper)
from 14.0.0 to 16.1.0.

<details>
<summary>Release notes</summary>

_Sourced from [AutoMapper's
releases](https://github.com/LuckyPennySoftware/AutoMapper/releases)._

## 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

## 16.0.0

## What's Changed
* Fix release pipelines by @​jbogard in
LuckyPennySoftware/AutoMapper#4583
* Adding support for .NET 10 by @​jbogard in
LuckyPennySoftware/AutoMapper#4586


**Full Changelog**:
LuckyPennySoftware/AutoMapper@v15.1.0...v16.0.0

## 16.0.0-beta-1

## What's Changed
* Fix release pipelines by @​jbogard in
LuckyPennySoftware/AutoMapper#4583
* Adding support for .NET 10 by @​jbogard in
LuckyPennySoftware/AutoMapper#4586

**Full Changelog**:
LuckyPennySoftware/AutoMapper@v15.1.0...v16.0.0-beta-1

This release is a beta release that introduces .NET 10 support and
package signing. Signed packages means going forward packages can be
validated against trusted authorities that the package has been
published by Lucky Penny Software and not tampered with.

## 15.1.0

## What's Changed
* remove Microsoft.SourceLink.GitHub by @​SimonCropp in
LuckyPennySoftware/AutoMapper#4555
* Direct .NET 4.x support by @​jbogard in
LuckyPennySoftware/AutoMapper#4569
* Bumping the JsonWebTokens because of GHSA-8g4q-xg66-9fp4; fixes #​4575
by @​jbogard in
LuckyPennySoftware/AutoMapper#4576
* Provide better exceptions for errors when building the mapping plan by
@​jbogard in LuckyPennySoftware/AutoMapper#4577
* Adding docs for license configuration by @​jbogard in
LuckyPennySoftware/AutoMapper#4581
* Updating refs to fix missing method issue by @​jbogard in
LuckyPennySoftware/AutoMapper#4582

## New Contributors
* @​SimonCropp made their first contribution in
LuckyPennySoftware/AutoMapper#4555

**Full Changelog**:
LuckyPennySoftware/AutoMapper@v15.0.1...v15.1.0

## 15.0.1

## What's Changed
* Removing public signing; fixes #​4545 by @​jbogard in
LuckyPennySoftware/AutoMapper#4552
* Adding back missing overloads and reverting registering behavior by
@​jbogard in LuckyPennySoftware/AutoMapper#4554


**Full Changelog**:
LuckyPennySoftware/AutoMapper@v15.0.0...v15.0.1

This release supersedes the 15.0.0 release, reverting behavior and
overloads so that the `AddAutoMapper` overloads separate the "scanning
for maps" from the "scanning for dependencies". Unfortunately it's not
really possible to combine these two together.

This also fixes a critical bug in #​4545 that does not work with .NET
4.x applications (as intended).

Because of this, the 15.0.0 will be delisted because of the breaking
changes there.

## 15.0.0

**Full Changelog**:
LuckyPennySoftware/AutoMapper@v14.0.0...v15.0.0

* Added support for .NET Standard 2.0
* Requiring license key
* Moving from MIT license to dual commercial/OSS license

To set your license key:

```csharp
services.AddAutoMapper(cfg => {
    cfg.LicenseKey = "<License key here>";
});
```

This also introduced a breaking change with `MapperConfiguration`
requiring an `ILoggerFactory` for logging purposes:

```csharp
public MapperConfiguration(MapperConfigurationExpression configurationExpression, ILoggerFactory loggerFactory)
```

Registering AutoMapper with `services.AddAutoMapper` will automatically
supply this parameter. Otherwise you'll need to supply the logger
factory.

You can obtain your license key at
[AutoMapper.io](https://automapper.io)

Commits viewable in [compare
view](LuckyPennySoftware/AutoMapper@v14.0.0...v16.1.0).
</details>

[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=AutoMapper&package-manager=nuget&previous-version=14.0.0&new-version=16.1.0)](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 this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enable DI for ConstructUsing

2 participants