# Approach to Replacing AutoMapper

**URL:** <https://www.virtocommerce.org/t/approach-to-replacing-automapper/860>\
**Category:** dev\
**Created:** [August 5, 2026, 8:28am UTC](https://www.virtocommerce.org/t/approach-to-replacing-automapper/860 "2026-08-05T08:28:43Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![DmitryGrishin](https://yyz2.discourse-cdn.com/flex032/user_avatar/www.virtocommerce.org/dmitrygrishin/32/2716_2.png) [@DmitryGrishin](https://www.virtocommerce.org/u/DmitryGrishin)\
**Post date:** [August 5, 2026, 8:28am UTC](https://www.virtocommerce.org/t/approach-to-replacing-automapper/860/1 "2026-08-05T08:28:43Z")

</div>

Following up on our [security advisory on the AutoMapper vulnerability (GHSA-rvv3-g6hj-g44x)](https://www.virtocommerce.org/t/security-advisory-automapper-vulnerability-ghsa-rvv3-g6hj-g44x/843): we’ve now confirmed the decision to remove AutoMapper from the Virto Commerce Platform, and we’ll be following the approach described above — [NAME IT, e.g. source-generated mapping with Mapperly / explicit hand-written mapping extensions].

The benefits are twofold: better performance, and a cleaner, more explicit data-transformation layer. If you’re looking for an AutoMapper replacement in your own modules, you can apply the same process.

### Process

1. Create a unit test for current mapping scenarios.
2. Replace AutoMapper using the steps below.
3. All Tests should be green, so you have the same capabilities.

### Rules

1. One facade class/interface per module (`I<Module>Mapper`/`<Module>Mapper`), not one interface per mapping.

2. Methods are `public virtual`, registered via DI (`AddSingleton`, since mappers are stateless).

3. Naming: `To<Dest>(source)`, never `Map(source)`.

4. Explicit typed parameters instead of `ResolutionContext.Items`.

5. In-place mapping (previously `Map(source, target)`) → `MapTo(target)` (extension method on the source, target as parameter), same argument order AutoMapper used.

6. Null semantics preserved: `if (source == null) return null;` at the start of every method.

7. Destination objects are created via `AbstractTypeFactory<TDest>.TryCreateInstance()`, not `new TDest()`.

8. No generic `IMapper<TSource, TDest>` — no real consumers exist for it.

### Migration steps

1. Check sibling modules for direct `.Map<TDest>(...)` calls against the same types (the host-wide `IMapper` is shared across modules) before deleting a profile.

2. Insert the new DI dependency in the same position `IMapper` used to occupy (check git history) — don’t append it at the end.

3. State explicitly in the PR description: breaking change (public constructors changed) + security-ticket status (only closes once all dependent modules are updated).

### Tests

1. Unit tests for each mapper method — exact expected values.

2. A test for a `null` source.

3. Explicit assertion on intentionally unmapped fields (`Assert.Null(...)`).

4. A test for DI registration — via `ServiceDescriptor` (not by resolving from a built `ServiceProvider`), including `Lifetime`.

5. Parity check against old AutoMapper: temporarily keep AutoMapper in the test project only, move the old `Profile` there, run the same input through both the old and new mapper, and compare results — instead of manually hand-picking expected values.
