Approach to Replacing AutoMapper

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

  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.

1 Like