File: README.md
Mapperly
Mapperly is a .NET source generator for generating object mappings.
Because Mapperly creates the mapping code at build time, there is minimal overhead at runtime.
Even better, the generated code is perfectly readable, allowing you to verify the generated mapping code easily.
Documentation
The documentation is available here.
Quickstart
Installation
Add the NuGet Package to your project:
dotnet add package Riok.MapperlyCreate your first mapper
Create a mapper declaration as a partial class
and apply the Riok.Mapperly.Abstractions.MapperAttribute attribute.
Mapperly generates mapping method implementations for the defined mapping methods in the mapper.
// Mapper declaration
[Mapper]
public partial class CarMapper
{
public partial CarDto CarToCarDto(Car car);
}
// Mapper usage
var mapper = new CarMapper();
var car = new Car { NumberOfSeats = 10, ... };
var dto = mapper.CarToCarDto(car);
dto.NumberOfSeats.ShouldBe(10);Read the docs for any further information.
Release Channels
Mapperly is distributed through two release channels:
- Stable Channel: Production-ready releases with full support for the latest release. Install the latest stable version for production use. Subject to semantic versioning (breaking changes only in major version bumps).
- Next Channel: Preview releases with upcoming features and improvements. These versions may contain breaking changes and are not subject to semantic versioning. Use for testing and early access to new features.
Support Policy
Only the latest version released on the stable channel is fully supported.
We strive to support all .NET versions that are currently supported by Microsoft.
Upgrading
Find a list of breaking changes for each major version and upgrade guides here.
Support
Need assistance with Mapperly or looking for consulting services? The riok team is available to help with:
- Enterprise support and training
- Architecture consultation
- Custom feature development and integration support
- Performance optimization and code reviews
Reach out through GitHub Discussions for questions, or contact the riok team directly for professional consulting services at [email protected].
You can also support the project and get priority support through GitHub Sponsors.
How To Contribute
We would love for you to contribute to Mapperly and help make it even better than it is today!
Find information on how to contribute in the docs.
License
Mapperly is Apache 2.0 licensed.
File: docs/docs/getting-started/faq.md
sidebar_position: 4 title: FAQ
Frequently asked questions and answers {#faq}
Here you can find answers to frequently asked questions and common problems about Mapperly.
Mapperly does not work when I use source generator X.
Chaining source generators is not supported by Roslyn.
I updated the Mapperly version, but the generated code still looks the same.
Restart the IDE to make it load the new version of Mapperly. This is a bug of the IDE.
Everything is configured correctly and dotnet build works, but the IDE shows the error "[Mapper method] must have an implementation part because it has accessibility modifiers"
Make sure your project meets the requirements.
Try rebuilding the solution or restarting the IDE. This is a bug in the IDE.
My advanced use case isn't supported by Mapperly or needs lots of configuration. What should I do?
Write the mapping for that class manually. You can mix automatically generated mappings and user implemented mappings without problems.
My code throws `FileNotFoundException` with `Riok.Mapperly.Abstractions`. What should I do?
Are you using reference handling
or have you enabled the preservation of Mapperly attributes at runtime?
Make sure ExcludeAssets on the PackageReference does not include runtime as these features require runtime assets.
Can I get professional help or consulting for Mapperly?
Yes! The riok team offers professional consulting and support services for Mapperly.
Whether you need help with architecture consultation, implementation support, custom feature development the team is here to assist. Reach out through [email protected] for professional services. You can also support the project through GitHub Sponsors to get priority support.
File: docs/docs/getting-started/first-mapper.md
sidebar_position: 1
Create your first mapper
Create a mapper declaration as a partial class
and apply the Riok.Mapperly.Abstractions.MapperAttribute attribute.
Mapperly generates mapping method implementations for the defined mapping methods in the mapper.
[Mapper]
public partial class CarMapper
{
public partial CarDto CarToCarDto(Car car);
}var mapper = new CarMapper();
var car = new Car { NumberOfSeats = 10, ... };
var dto = mapper.CarToCarDto(car);
dto.NumberOfSeats.ShouldBe(10);File: docs/docs/getting-started/generated-mapper-example.mdx
sidebar_position: 2
import CodeBlock from '@theme/CodeBlock';
import CarSource from '!!raw-loader!../../src/data/generated/samples/Car.cs';
import CarDtoSource from '!!raw-loader!../../src/data/generated/samples/CarDto.cs';
import CarMapperSource from '!!raw-loader!../../src/data/generated/samples/CarMapper.cs';
import GeneratedCarMapperSource from '!!raw-loader!../../src/data/generated/samples/CarMapper.g.cs';
Generated mapper example
This example will show you what kind of code Mapperly generates.
It is based on the Mapperly sample.
To view the generated code of your own mapper, refer to the generated source configuration.
The source classes
In this example, we have a car class with some general information.
The target classes
Our sample target classes are mostly the same as the source classes.
For demonstration purposes, we named the manufacturer property differently to show how Mapperly can handle this use case.
The mapper
The actual mapper is pretty simple. We use a static mapper class in this example.
As usual, we need to mark the mapper with the [Mapper] attribute, so that the source generator is able to recognize it.
Because the manufacturer/producer properties are named differently, we also need to configure that via an attribute.
In addition, because the CarColor and CarColorDto entries have different numeric values, we need to configure Mapperly to map them by their name.
The generated code
File: docs/docs/getting-started/installation.mdx
sidebar_position: 0
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
import Conditional from '@site/src/components/Conditional';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
Installation
Requirements
- .NET 8+ or .NET Framework 4.x.
- C# language version 9 or later
- Roslyn 4.8 or later
Add the NuGet Package to your project
All you need to do, to install Mapperly is to add a NuGet reference pointing to the package Riok.Mapperly.
dotnet add package Riok.Mapperly:::info
To remove the runtime dependency of Mapperly addExcludeAssets="runtime" and PrivateAssets="all"
to the resulting PackageReference.
:::
Install-Package Riok.Mapperly:::info
To remove the runtime dependency of Mapperly addExcludeAssets="runtime" and PrivateAssets="all"
to the resulting PackageReference.
:::
:::warning
This is a prerelease version of Mapperly and is not subject to semantic release.
The API may break in any prerelease.
Checkout the latest stable version here.
:::
Release Channels
Mapperly is distributed through two release channels:
- Stable Channel: Production-ready releases with full support for latest release. Install the latest stable version for production use. Subject to semantic versioning (breaking changes only in major version bumps).
- Next Channel: Preview releases with upcoming features and improvements. These versions may contain breaking changes and are not subject to semantic versioning. Use for testing and early access to new features.
Support policy
Only the latest version released on the stable channel is fully supported.
We strive to support all .NET versions that are currently supported by Microsoft.
Preserving the attributes at runtime
Mapperly removes the attribute references at compile time by default (they have the ConditionalAttribute).
If you want to preserve the attribute references at runtime
you can set the MSBuild variable MapperlyAbstractionsScope to runtime.
<MapperlyAbstractionsScope>runtime</MapperlyAbstractionsScope>:::info
When preserving the attribute usages, the Mapperly package reference needs to include the runtime assets.
Make sure ExcludeAssets on the PackageReference does not include runtime.
:::
:::infoMapperlyAbstractionsScope only works in projects referencing Riok.Mapperly directly.
Alternatively you can set a constant MAPPERLY_ABSTRACTIONS_SCOPE_RUNTIME:
<DefineConstants>$(DefineConstants);MAPPERLY_ABSTRACTIONS_SCOPE_RUNTIME</DefineConstants>:::
File: docs/docs/contributing/architecture.md
sidebar_position: 1
Architecture
Mapperly is an incremental .NET source generator implementation.
Source generators are explained here
and here.
Solution
benchmarksBenchmarks to analyze the performance of the generated code and the source generator itselfbuildBuild scriptsdocsDocumentation of MapperlysamplesSample implementations of Mappers using MapperlysrcSource code of MapperlyRiok.MapperlyThe source generator implementationRiok.Mapperly.AbstractionsAbstractions and attributes to be used by the application code to configure Mapperly.
This is referenced by the source generator but is not needed at runtime
(except for some features, e.g., reference handling).
testUnit- and integration tests of Mapperly
Flow
This describes the process implemented by Mapperly on a higher level.
For each discovered MapperAttribute a new DescriptorBuilder is created.
The DescriptorBuilder is responsible to build a MapperDescriptor which holds all the mappings.
The DescriptorBuilder does this by following this process:
- Extracting the configuration from the attributes
- Extracting user implemented and user defined mapping methods.
It instantiates aUser*Mapping(eg.UserDefinedNewInstanceMethodMapping) for each discovered mapping method and adds it to the queue of mappings to work on. - Extracting user implemented object factories
- Extracting user implemented format providers
- Extracting external mappings
- For each mapping in the queue the
DescriptorBuildertries to build its implementation bodies.
This is done by a so called*MappingBodyBuilder.
A mapping body builder tries to map each property from the source to the target.
To do this, it asks theDescriptorBuilderto create mappings for the according types.
To create a mapping from one type to another, theDescriptorBuilderloops through a set of*MappingBuilders.
Each of the mapping builders try to create a mapping (anITypeMappingimplementation) for the asked type mapping by using
one approach on how to map types (eg. an explicit cast is implemented by theExplicitCastMappingBuilder).
These mappings are queued in the queue of mappings which need the body to be built. - The
SourceEmitteremits the code described by theMapperDescriptorand all its mappings.
The syntax objects are created by usingSyntaxFactoryandSyntaxFactoryHelper.
TheSyntaxFactoryHelpertries to simplify creating formatted syntax trees.
If indentation is needed,
theSyntaxFactoryHelperinstance of theSourceEmitterContext/TypeMappingBuildContextcan be used.
Roslyn multi targeting
Mapperly targets multiple Roslyn versions by building multiple NuGet packages
and merging them together into a single one.
Multi-targeting is needed to support new language features,
such as required members introduced in C# 11,
while still supporting older compiler versions.
See build/package.sh for details.
To introduce support for a new roslyn version see common tasks.
C# language features
The Mapperly source generator targets .netstandard2.0 but uses the latest C# language version
and polyfills generated by Meziantou.Polyfill.
Some newer C# language features require new runtime features and therefore cannot be used in Mapperly.
A good C# language features requirements overview is available here.
File: docs/docs/contributing/common-tasks.md
sidebar_position: 5
Common tasks
This page provides todo lists for some common tasks one may encounter while contributing to Mapperly.
New diagnostic
To introduce a new diagnostic follow these steps:
- Ensure no existing diagnostic in
Riok.Mapperly.Diagnostics.DiagnosticDescriptors.csmatches the new use case. - Create a new public static readonly entry following the existing naming and formatting style,
as ID useRMG<NUMBER>with<NUMBER>being the highest already used number plus one.
The highest used number can be found inAnalyzerReleases.Shipped.mdas there may be removed diagnostics which are not present inDiagnosticDescriptorsanymore
but which should still not be used for new diagnostics. - Add the new diagnostic to
AnalyzerReleases.Shipped.md(Mapperly does not use theUnshippedfile). - Add a new documentation file at
docs/docs/configuration/analyzer-diagnostics/{id}.mdx(as needed) - Add a unit test generating and asserting the added diagnostic (use
TestHelperOptions.AllowDiagnosticsandShould().HaveDiagnostic(...).HaveAssertedAllDiagnostics().
It is not necessary to update the analyzer-diagnostics/index.mdx documentation file manually,
as it is generated automatically on the basis of the AnalyzerReleases.Shipped.md file.
New public API
If new public API surface is introduced in Riok.Mapperly.Abstractions or existing API surface is modified,
the PublicApiTest snapshot test needs to be updated.
See also VerifyTests.
Add support for a new roslyn version
To support a new roslyn version via multi targeting follow these steps (see also architecture/roslyn multi targeting):
- Include the new version in
roslyn_versionsinbuild/package.sh. - Create a new file
Riok.Mapperly.Roslyn$(Version).propsinsrc/Riok.Mapperlysimilar to the existing ones
and define constants and include dependencies as needed. - Update the default
ROSLYN_VERSIONinsrc/Riok.Mapperly/Riok.Mapperly.csproj. - Update the
Microsoft.CodeAnalysis.CSharpdependency version. - Adjust the .NET version matrix of the
integration-testGitHub Actions job (defined in.github/workflows/test.yml)
to include a dotnet version which is based on the added Roslyn version. - Adjust the .NET version in the
global.jsonfile as needed. - Add the new version in
Riok.Mapperly.IntegrationTests.Helpers.VersionsandRiok.Mapperly.IntegrationTests.BaseMapperTest.GetCurrentVersion. - If generated code changes based on the new Roslyn version,
adjust theVersionedSnapshotAttributes as needed. - Adjust the documentation as needed.
- Add new preprocessor constants to
.csharpierrc.yaml. - Add new GitHub required checks as needed (needs to be done by a maintainer).
Mapping syntax
Mapperly Mappings use Roslyn syntax trees.
RoslynQuoter and SharpLab
are fantastic tools to understand and work with Roslyn syntax trees.
The Riok.Mapperly.Emit.Syntax.SyntaxFactoryHelper and Microsoft.CodeAnalysis.CSharp.SyntaxFactory classes help building these syntax trees.
File: docs/docs/contributing/debugging.md
sidebar_position: 3
Debugging
Generated code
To debug the code generated by Mapperly just set a breakpoint in the generated code and you are ready to go.
Check here on how to inspect the generated code.
Unit tests
To debug the Mapperly in unit tests, set a breakpoint
in the code of Mapperly which you want to debug and run the tests in debug mode.
Integration tests
If your IDE supports the DebugRoslynComponent launch configuration command,
you can set your breakpoints and debug the preconfigured IntegrationTests profile which will debug Mapperly
in the context of the integration tests.
JetBrains Rider and Visual Studio both support DebugRoslynComponent.
Visual Studio requires the Roslyn SDK to be installed.
If your IDE does not support DebugRoslynComponent,
debugging the integration tests requires some more effort.
Mapperly uses a compile constant DEBUG_SOURCE_GENERATOR to determine whether to attach a debugger.
If it is set, it tries to attach a debugger
(it uses Debugger.Launch() on windows, on other operating systems it tries to launch JetBrains Rider)
and waits for the connection of the debugger (for up to 30 seconds).
If the automatic debugger attachment fails you can use these 30 seconds to attach the debugger manually
(for Visual Studio see attach to running processes).
You can use the DefineConstants dotnet build or csproj parameter to define the DEBUG_SOURCE_GENERATOR constant.
For the debugger attachment with JetBrains Rider to work correctly,Generate Shell Scripts needs to be enabled in the JetBrains ToolBox
and the generated shell scripts need to be on the path (Mapperly calls rider attach-to-process {pid} {Mapperly-Sln-File}).
CSC warning CS8785 stack trace
If you see the compiler warning CS8785: Generator 'MapperlyGenerator' failed to generate source,
you can get a stack trace from the C# compiler by emitting a compiler error log. The stack trace is included in the SARIF output.
Add the following MSBuild property when you build:
/p:ErrorLog=compiler-diagnostics.sarif
Then open the SARIF file to inspect the generator exception and stack trace.
File: docs/docs/contributing/docs.md
sidebar_position: 4
Documentation
The Mapperly documentation is built using Docusaurus
and is located in the docs directory.
Dependencies
Docusaurus is based on the node ecosystem.
A .node-version file provides the node version number to be used.
This file can be interpreted by several node version managers (eg. fnm or nvm).
Make sure you use a node version matching the version in .node-version for all following commands.
To install documentation dependencies run:
npm iUpdate generated documentation
To build the generated parts of the documentation
(eg. the API documentation (located in docs/99-api), the table of analyzer rules, the samples, ...)
ensure the dotnet solution is built or run dotnet build in the solutions root directory.
Then run the prebuild script:
npm run prebuildTo reflect changes, this command needs to be re-run each time the source of the generated output changes.
Local Development
npm run startThis command starts a local development server and opens up a browser window.
Most changes are reflected live without having to restart the server.
The documentation search does not work when using the local development server,
as it depends on the statically built content (see build).
Linting
To run the linters locally run:
npm run lintTo fix detected issues automatically run:
npm run fixBuild
npm run buildThis command generates static content into the build directory
and can be served using any static contents hosting service or locally using npm run serve.
File: docs/docs/contributing/index.md
sidebar_position: 0
Contributing
We would love for you to contribute to Mapperly and help make it even better than it is today!
As a contributor, here are the guidelines we would like you to follow.
Code of Conduct
Help us keep Mapperly open and inclusive. Please read and follow our code of conduct.
Got a question or problem?
If you have a question or a problem create a new GitHub Discussion.
Found a Bug?
If you find a bug in the source code, you can help us by submitting a GitHub Issue.
Even better, you can submit a Pull Request with a fix.
Missing a Feature?
You can request a new feature by submitting an issue to our GitHub repository.
If you would like to implement a new feature, please consider the size of the change in order to determine the right steps to proceed:
For a major feature, first open an issue and outline your proposal so that it can be discussed.
Getting early feedback will help ensure your implementation work is accepted by the maintainers.
This will also allow us to better coordinate our efforts and minimize duplicated effort.Note: Adding a new topic to the documentation, or significantly re-writing a topic, counts as a major feature.
Small Features can be crafted and directly submitted as a Pull Request.
Using AI tools
You are welcome to use AI tools to help you contribute to Mapperly.
However, you are responsible for every line of code you submit.
Before opening a pull request, please make sure that you did not use AI tools to generate the PR,
or that you have manually verified that you understand the code, that it is correct, optimal, and follows the project guidelines and architecture.
Low-quality, AI-generated pull requests that have not been reviewed and understood by their author
will be closed immediately without further explanation.
Submission Guidelines
Submitting an issue
Before you submit an issue, please search the issue tracker and discussions.
An issue for your problem might already exist and the discussion might inform you of workarounds readily available.
You can file new issues by filling out the new issue template.
Submitting a pull request (PR)
Before you submit your Pull Request (PR) consider the following guidelines:
Search GitHub for an open or closed PR that relates to your submission.
You don't want to duplicate existing efforts.Be sure that an issue describes the problem you're fixing, or documents the design for the feature you'd like to add.
Discussing the design upfront helps to ensure that we're ready to accept your work.Get an overview on how Mapperly works by reading this contributing documentation, the architectural overview and related documentation.
Fork the riok/mapperly repository.
In your forked repository, make your changes in a new Git branch:
shellgit checkout -b my-fix-branch mainCreate your patch, including appropriate test cases and documentation updates.
Commit your changes using a descriptive commit message.
Adherence to these conventions is necessary because release notes are automatically generated from these messages.
Husky and csharpier automatically format changed files when commited.shellgit commit --allNote: the optional commit
-acommand-line option will automatically "add" and "rm" edited files.If any commits have been made to the upstream main branch,
you should rebase your development branch so that merging it will be a simple fast-forward that won't require any conflict resolution work.- shell
git checkout main git pull upstream main git checkout my-fix-branch git rebase main Now, it may be desirable to squash some of your smaller commits down into a small number of larger more cohesive commits. You can do this with an interactive rebase:
bash# Rebase all commits on your development branch git checkout git rebase -i mainPush your branch to GitHub:
shellgit push origin my-fix-branchIn GitHub, send a pull request to
riok:mainand request a review from a maintainer.
Checks failure
Once you've submitted a pull request, all continuous-integration checks are triggered.
If some of these checks fail, it could be either problems with the pull request or a failure of some test cases.
For more information on the failure, check the output logs of the jobs.
Reviewing a pull request
The reviewers will provide you feedback and approve your changes as soon as they are satisfied.
If we ask you for changes in the code, you can follow the GitHub Guide to incorporate feedback in your pull request.