Following up on our security advisory on the AutoMapper vulnerability (GHSA-rvv3-g6hj-g44x): 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
- Create a unit test for current mapping scenarios.
- Replace AutoMapper using the steps below.
- All Tests should be green, so you have the same capabilities.
Rules
-
One facade class/interface per module (
I<Module>Mapper/<Module>Mapper), not one interface per mapping. -
Methods are
public virtual, registered via DI (AddSingleton, since mappers are stateless). -
Naming:
To<Dest>(source), neverMap(source). -
Explicit typed parameters instead of
ResolutionContext.Items. -
In-place mapping (previously
Map(source, target)) →MapTo(target)(extension method on the source, target as parameter), same argument order AutoMapper used. -
Null semantics preserved:
if (source == null) return null;at the start of every method. -
Destination objects are created via
AbstractTypeFactory<TDest>.TryCreateInstance(), notnew TDest(). -
No generic
IMapper<TSource, TDest>— no real consumers exist for it.
Migration steps
-
Check sibling modules for direct
.Map<TDest>(...)calls against the same types (the host-wideIMapperis shared across modules) before deleting a profile. -
Insert the new DI dependency in the same position
IMapperused to occupy (check git history) — don’t append it at the end. -
State explicitly in the PR description: breaking change (public constructors changed) + security-ticket status (only closes once all dependent modules are updated).
Tests
-
Unit tests for each mapper method — exact expected values.
-
A test for a
nullsource. -
Explicit assertion on intentionally unmapped fields (
Assert.Null(...)). -
A test for DI registration — via
ServiceDescriptor(not by resolving from a builtServiceProvider), includingLifetime. -
Parity check against old AutoMapper: temporarily keep AutoMapper in the test project only, move the old
Profilethere, run the same input through both the old and new mapper, and compare results — instead of manually hand-picking expected values.