refactor: Migrate from AutoMapper to Mapperly#2093
Draft
Conversation
- Replace AutoMapper (runtime reflection) with Mapperly (compile-time source generation) - Add Riok.Mapperly 4.2.1 to Exceptionless.Web - Remove AutoMapper 14.0.0 from Exceptionless.Core Breaking changes: - Controllers now use abstract mapping methods instead of generic MapAsync<T> - Base controllers require derived classes to implement MapToModel, MapToViewModel, MapToViewModels Mapping structure: - Created dedicated mapper files per type in src/Exceptionless.Web/Mapping/ - OrganizationMapper: NewOrganization -> Organization, Organization -> ViewOrganization - ProjectMapper: NewProject -> Project, Project -> ViewProject - TokenMapper: NewToken -> Token, Token -> ViewToken - UserMapper: User -> ViewUser - WebHookMapper: NewWebHook -> WebHook - InvoiceMapper: Stripe.Invoice -> InvoiceGridModel - ApiMapper facade delegates to individual mappers Testing: - Added comprehensive unit tests for all mappers (29 tests) - Tests follow backend-testing skill patterns Benefits: - Compile-time type safety for mappings - Better performance (no runtime reflection) - Cleaner separation of concerns with per-type mappers
8ed51f6 to
1328834
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
This PR replaces the previous AutoMapper-based runtime mapping with Mapperly-based (and some manual) mappings, refactors controller base classes to use explicit mapping hooks, and wires up an ApiMapper facade plus dedicated per-type mappers, along with unit tests to validate the new mappings.
Changes:
- Removed AutoMapper dependencies and registration from
Exceptionless.CoreandExceptionless.Web, addingRiok.Mapperlyand anApiMapperfacade with per-type mappers (OrganizationMapper,ProjectMapper,TokenMapper,UserMapper,WebHookMapper,InvoiceMapper). - Refactored
ReadOnlyRepositoryApiControllerandRepositoryApiControllerto use abstractMapToModel/MapToViewModel/MapToViewModelsmethods implemented by concrete controllers instead of genericMapAsync/MapCollectionAsync. - Updated controllers (
OrganizationController,ProjectController,TokenController,UserController,WebHookController,EventController,StackController) to depend onApiMapperand implement the new mapping hooks, and added focused mapper unit tests undertests/Exceptionless.Tests/Mapping.
Reviewed changes
Copilot reviewed 27 out of 27 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
src/Exceptionless.Core/Exceptionless.Core.csproj |
Removes the AutoMapper package reference, completing the core-layer dependency removal. |
src/Exceptionless.Core/Models/CoreMappings.cs |
Deletes the empty CoreMappings AutoMapper profile, as mappings are no longer configured in core. |
src/Exceptionless.Core/Bootstrapper.cs |
Removes AutoMapper profile registration and IMapper singleton wiring, ensuring DI no longer constructs an AutoMapper instance. |
src/Exceptionless.Web/Exceptionless.Web.csproj |
Adds a reference to Riok.Mapperly so Mapperly source generation can run for the new mapping classes. |
src/Exceptionless.Web/Bootstrapper.cs |
Drops the AutoMapper ApiMappings profile and IMapper registration, and registers ApiMapper as a singleton for controller injection. |
src/Exceptionless.Web/Mapping/ApiMapper.cs |
Introduces an ApiMapper facade that composes OrganizationMapper, ProjectMapper, TokenMapper, UserMapper, WebHookMapper, and InvoiceMapper and exposes strongly-typed mapping methods used by controllers. |
src/Exceptionless.Web/Mapping/OrganizationMapper.cs |
Adds a Mapperly-based mapper from NewOrganization → Organization and Organization → ViewOrganization, with explicit post-processing of IsOverMonthlyLimit using Organization.IsOverMonthlyLimit(TimeProvider), plus list mapping helpers. |
src/Exceptionless.Web/Mapping/ProjectMapper.cs |
Adds a Mapperly-based mapper from NewProject → Project and a two-stage Project → ViewProject mapping (core Mapperly map plus explicit HasSlackIntegration computation), plus list mapping helpers. |
src/Exceptionless.Web/Mapping/TokenMapper.cs |
Adds a Mapperly-based mapper from NewToken → Token that ignores Token.Type (leaving its default) and maps Token → ViewToken, plus list mapping helpers; preserves prior AutoMapper semantics around token type. |
src/Exceptionless.Web/Mapping/UserMapper.cs |
Adds a Mapperly-based mapper from User → ViewUser with list mapping helpers, replacing the AutoMapper user mapping. |
src/Exceptionless.Web/Mapping/WebHookMapper.cs |
Adds a Mapperly-based mapper from NewWebHook → WebHook, relying on domain defaults and WebHookController.AddModelAsync to normalize Version as before. |
src/Exceptionless.Web/Mapping/InvoiceMapper.cs |
Introduces a manual mapper from Stripe.Invoice → InvoiceGridModel that strips the "in_" prefix from Id and copies Created and Paid, plus list mapping helpers, matching the old AutoMapper AfterMap behavior. |
src/Exceptionless.Web/Controllers/Base/ReadOnlyRepositoryApiController.cs |
Replaces generic AutoMapper-based MapAsync/MapCollectionAsync with abstract MapToViewModel / MapToViewModels, injects ApiMapper instead of IMapper, and ensures AfterResultMapAsync still strips sensitive data from IData.Data. |
src/Exceptionless.Web/Controllers/Base/RepositoryApiController.cs |
Updates to depend on ApiMapper, adds an abstract MapToModel(TNewModel) hook, and refactors PostImplAsync / UpdateModelAsync / UpdateModelsAsync to use the new mapping hooks plus AfterResultMapAsync rather than the removed AutoMapper helpers. |
src/Exceptionless.Web/Controllers/OrganizationController.cs |
Switches constructor from IMapper to ApiMapper, implements organization-specific MapToModel/MapToViewModel/MapToViewModels using OrganizationMapper, replaces generic mapping calls with these methods (and explicit AfterResultMapAsync calls), and maps Stripe invoices via ApiMapper.MapToInvoiceGridModels. |
src/Exceptionless.Web/Controllers/ProjectController.cs |
Switches to ApiMapper, implements project-specific mapping methods using ProjectMapper, replaces MapAsync/MapCollectionAsync usage with these methods, and ensures AfterResultMapAsync still enriches ViewProject instances with organization data and usage stats. |
src/Exceptionless.Web/Controllers/TokenController.cs |
Switches to ApiMapper, adds token-specific mapping overrides using TokenMapper, and replaces generic collection mapping with MapToViewModels plus AfterResultMapAsync for organization- and project-based token listing endpoints. |
src/Exceptionless.Web/Controllers/UserController.cs |
Removes AutoMapper usage, injects ApiMapper, implements mapping overrides (using ViewUser as both new and view model and mapping User→ViewUser via UserMapper), and updates organization-based user listing to use the new mapping flow plus AfterResultMapAsync. |
src/Exceptionless.Web/Controllers/WebHookController.cs |
Replaces IMapper with ApiMapper, implements webhook mapping overrides using WebHookMapper and identity mapping for the WebHook view model, while leaving webhook permission and version-normalization logic intact. |
src/Exceptionless.Web/Controllers/StackController.cs |
Switches injection to ApiMapper and implements mapping overrides that treat Stack as both model and view model (identity mapping), ensuring the new base class’ requirements are satisfied without changing behavior. |
src/Exceptionless.Web/Controllers/EventController.cs |
Switches from IMapper to ApiMapper, implements identity mapping overrides for PersistentEvent, and replaces the AutoMapper-based UserDescription→EventUserDescription mapping in SetUserDescriptionAsync with an explicit object initializer that sets all relevant properties. |
tests/Exceptionless.Tests/Mapping/OrganizationMapperTests.cs |
Adds unit tests that validate OrganizationMapper correctly maps NewOrganization→Organization and Organization→ViewOrganization, including suspension state and list mappings. |
tests/Exceptionless.Tests/Mapping/ProjectMapperTests.cs |
Adds unit tests checking ProjectMapper mappings, including Slack integration detection and collection mappings. |
tests/Exceptionless.Tests/Mapping/TokenMapperTests.cs |
Adds unit tests for TokenMapper, ensuring organization/project/notes are mapped, Token.Type is left at its default, and collection mappings work. |
tests/Exceptionless.Tests/Mapping/UserMapperTests.cs |
Adds unit tests verifying UserMapper correctly maps core user properties, roles, organization IDs, and collections. |
tests/Exceptionless.Tests/Mapping/WebHookMapperTests.cs |
Adds unit tests for WebHookMapper, verifying that organization/project IDs, URL, and event types are correctly transferred, including null and empty edge cases for project/event types. |
tests/Exceptionless.Tests/Mapping/InvoiceMapperTests.cs |
Adds unit tests ensuring InvoiceMapper strips the "in_" prefix from Stripe invoice IDs, maps Created and Paid, and supports collection mapping. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Migrates from AutoMapper (runtime reflection-based mapping) to Mapperly (compile-time source generation) for improved performance and type safety.
Changes
Package Updates
Architecture Changes
Mapping Structure
Created dedicated mapper files per type in \src/Exceptionless.Web/Mapping/:
\ApiMapper\ acts as a facade delegating to individual mappers.
Testing
Benefits
Breaking Changes
Controllers that inherit from \RepositoryApiController\ or \ReadOnlyRepositoryApiController\ must now implement the abstract mapping methods.