diff --git a/.github/prompts/csharp-docs.prompt.md b/.github/prompts/csharp-docs.prompt.md new file mode 100644 index 00000000..23687706 --- /dev/null +++ b/.github/prompts/csharp-docs.prompt.md @@ -0,0 +1,63 @@ +--- +agent: 'agent' +tools: ['changes', 'search/codebase', 'edit/editFiles', 'problems'] +description: 'Ensure that C# types are documented with XML comments and follow best practices for documentation.' +--- + +# C# Documentation Best Practices + +- Public members should be documented with XML comments. +- It is encouraged to document internal members as well, especially if they are complex or not self-explanatory. + +## Guidance for all APIs + +- Use `` to provide a brief, one sentence, description of what the type or member does. Start the summary with a present-tense, third-person verb. +- Use `` for additional information, which can include implementation details, usage notes, or any other relevant context. +- Use `` for language-specific keywords like `null`, `true`, `false`, `int`, `bool`, etc. +- Use `` for inline code snippets. +- Use `` for usage examples on how to use the member. + - Use `` for code blocks. `` tags should be placed within an `` tag. Add the language of the code example using the `language` attribute, for example, ``. +- Use `` to reference other types or members inline (in a sentence). +- Use `` for standalone (not in a sentence) references to other types or members in the "See also" section of the online docs. +- Use `` to inherit documentation from base classes or interfaces. + - Unless there is major behavior change, in which case you should document the differences. + +## Methods + +- Use `` to describe method parameters. + - The description should be a noun phrase that doesn't specify the data type. + - Begin with an introductory article. + - If the parameter is a flag enum, start the description with "A bitwise combination of the enumeration values that specifies...". + - If the parameter is a non-flag enum, start the description with "One of the enumeration values that specifies...". + - If the parameter is a Boolean, the wording should be of the form "`` to ...; otherwise, ``.". + - If the parameter is an "out" parameter, the wording should be of the form "When this method returns, contains .... This parameter is treated as uninitialized.". +- Use `` to reference parameter names in documentation. +- Use `` to describe type parameters in generic types or methods. +- Use `` to reference type parameters in documentation. +- Use `` to describe what the method returns. + - The description should be a noun phrase that doesn't specify the data type. + - Begin with an introductory article. + - If the return type is Boolean, the wording should be of the form "`` if ...; otherwise, ``.". + +## Constructors + +- The summary wording should be "Initializes a new instance of the class [or struct].". + +## Properties + +- The `` should start with: + - "Gets or sets..." for a read-write property. + - "Gets..." for a read-only property. + - "Gets [or sets] a value that indicates whether..." for properties that return a Boolean value. +- Use `` to describe the value of the property. + - The description should be a noun phrase that doesn't specify the data type. + - If the property has a default value, add it in a separate sentence, for example, "The default is ``". + - If the value type is Boolean, the wording should be of the form "`` if ...; otherwise, ``. The default is ...". + +## Exceptions + +- Use `` to document exceptions thrown by constructors, properties, indexers, methods, operators, and events. +- Document all exceptions thrown directly by the member. +- For exceptions thrown by nested members, document only the exceptions users are most likely to encounter. +- The description of the exception describes the condition under which it's thrown. + - Omit "Thrown if ..." or "If ..." at the beginning of the sentence. Just state the condition directly, for example "An error occurred when accessing a Message Queuing API." diff --git a/.github/workflows/build-deploy-docs.yml b/.github/workflows/build-deploy-docs.yml new file mode 100644 index 00000000..84bd2a3d --- /dev/null +++ b/.github/workflows/build-deploy-docs.yml @@ -0,0 +1,77 @@ +name: Build and Deploy DocFX + +on: + push: + branches: [ 'master' ] + paths: + - 'docs/**' + - 'README.md' + # Uncomment this if API docs changes should trigger a rebuild of the documentation + # - 'src/**' + - '.github/workflows/build-deploy-docs.yml' + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build-docs: + if: ${{ github.ref == 'refs/heads/master' }} + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 8.x + 9.x + # - name: Show dotnet version + # run: | + # dotnet --list-sdks + # dotnet --list-runtimes + + # - name: Restore dependencies before docs deployment as recommended by docfx + # run: dotnet restore ./src/Mapster.sln + + # - name: Build with dotnet + # run: dotnet build ./src/Mapster.sln + + # - name: Run tests on .NET 9.0 + # run: dotnet test --verbosity normal ./src/Mapster.sln + + - name: Install DocFX as .NET tool + run: | + dotnet tool update -g docfx + + - name: Build docfx site + working-directory: docs + run: docfx docfx.json + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + # Upload entire repository + path: 'docs/_site' + + deploy-docs: + needs: build-docs + if: ${{ needs.build-docs.result == 'success' && github.ref == 'refs/heads/master' }} + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/conventional-commits.yml b/.github/workflows/conventional-commits.yml new file mode 100644 index 00000000..77a0a8c8 --- /dev/null +++ b/.github/workflows/conventional-commits.yml @@ -0,0 +1,21 @@ +name: Conventional Commits + +on: + pull_request: + branches: + - master + - development + - release/* + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + commitsar: + name: Validate for conventional commits + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - name: Run commitsar + uses: aevea/commitsar@v0.20.2 diff --git a/.gitignore b/.gitignore index 9c0a14cd..434416f4 100644 --- a/.gitignore +++ b/.gitignore @@ -131,6 +131,9 @@ packages/ # This line needs to be after the ignore of the build folder (and the packages folder if the line above has been uncommented) !packages/build/ +# Not ignore packages folder in docs folder +!docs/articles/packages/ + # Windows Azure Build Output csx/ *.build.csdef @@ -184,5 +187,4 @@ src/.idea # VS Code settings .vscode/launch.json -.vscode/settings.json .vscode/tasks.json diff --git a/.markdownlint.jsonc b/.markdownlint.jsonc new file mode 100644 index 00000000..17ee9dd2 --- /dev/null +++ b/.markdownlint.jsonc @@ -0,0 +1,16 @@ +{ + "$schema": "https://raw.githubusercontent.com/DavidAnson/markdownlint/v0.38.0/schema/markdownlint-config-schema.json", + "default": true, + "line-length": false, + "commands-show-output": false, + "no-bare-urls": false, + "no-inline-html": false, + "no-duplicate-heading": false, + "no-emphasis-as-heading": false, + // Headers must start at the beginning of the line - false positive in some cases where it makes sense. + "MD023": false, + // First line in a file should be a top-level heading - false positive for include files. + "MD041": false, + // Link fragments should be valid - false positive for docfx tabs + "MD051": false +} \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..a22d4740 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "DavidAnson.vscode-markdownlint", + "streetsidesoftware.code-spell-checker-cspell-bundled-dictionaries", + "ms-vscode.powershell", + "joshbolduc.commitlint" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..781f0a8d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "files.associations": { + "*.agent.md": "chatagent", + "*.instructions.md": "instructions", + "*.prompt.md": "prompt", + "docfx.json": "jsonc" + } +} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..b8db01f2 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,36 @@ +![Mapster Icon](https://raw.githubusercontent.com/MapsterMapper/Mapster/master/docs/images/mapster-logo.svg) + +# Contributing to Mapster + +Thank you for your interest in contributing! We welcome contributions from the community. + +## How to Contribute + +1. **Fork the repository** and create your branch from [`development`](https://github.com/MapsterMapper/Mapster/tree/development) +2. **Make your changes** following the existing code style +3. **Write tests** using [MSTest](https://docs.microsoft.com/en-us/dotnet/core/testing/unit-testing-with-mstest) and [xUnit](https://xunit.net/) to ensure your changes work correctly +4. **Document your code** with XML comments and update [docs/articles/](./docs/articles/) if needed following the [DocFX](https://dotnet.github.io/docfx/) guidelines +5. **Commit with clear messages** following [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) (e.g., `feat:`, `fix:`, `docs:`) +6. **Submit a pull request** with a description of your changes + +## Reporting Issues + +Found a bug or have a feature request? Please [open an issue](https://github.com/MapsterMapper/Mapster/issues) with: + +- Clear description of the problem or request +- Steps to reproduce (for bugs) +- Code samples if applicable +- Environment details (Mapster version, .NET version) + +For questions, use [GitHub Discussions](https://github.com/MapsterMapper/Mapster/discussions). + +## Development Guidelines + +- Follow existing code conventions +- Add XML documentation for public APIs +- Write unit tests for new features and bug fixes +- Keep code clean, well-documented, and tested + +## License + +By contributing, you agree that your contributions will be licensed under the [MIT License](LICENSE). diff --git a/README.md b/README.md index 9be17bb0..208e2074 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,77 @@ -![Mapster Icon](https://cloud.githubusercontent.com/assets/5763993/26522718/d16f3e42-4330-11e7-9b78-f8c7402624e7.png) +![Mapster Icon](https://raw.githubusercontent.com/MapsterMapper/Mapster/master/docs/images/mapster-logo.svg) + +# Mapster - The Mapper of Your Domain -## Mapster - The Mapper of Your Domain Writing mapping methods is a machine job. Do not waste your time, let Mapster do it. -[![NuGet](https://img.shields.io/nuget/v/Mapster.svg)](https://www.nuget.org/packages/Mapster) +## NuGet Sources + +### NuGet Packages + +| Package | Stable | Pre-release | +|---------|--------|-------------| +| **Mapster** | [![Mapster](https://img.shields.io/nuget/v/Mapster.svg?label=Mapster&color=informational)](https://www.nuget.org/packages/Mapster/latest) | [![Mapster](https://img.shields.io/nuget/vpre/Mapster.svg?label=Mapster&color=orange)](https://www.nuget.org/packages/Mapster) | +| **Mapster.Core** | [![Mapster.Core](https://img.shields.io/nuget/v/Mapster.Core.svg?label=Mapster.Core&color=informational)](https://www.nuget.org/packages/Mapster.Core/latest) | [![Mapster.Core](https://img.shields.io/nuget/vpre/Mapster.Core.svg?label=Mapster.Core&color=orange)](https://www.nuget.org/packages/Mapster.Core) | +| **Mapster.DependencyInjection** | [![Mapster.DependencyInjection](https://img.shields.io/nuget/v/Mapster.DependencyInjection.svg?label=Mapster.DependencyInjection&color=informational)](https://www.nuget.org/packages/Mapster.DependencyInjection/latest) | [![Mapster.DependencyInjection](https://img.shields.io/nuget/vpre/Mapster.DependencyInjection.svg?label=Mapster.DependencyInjection&color=orange)](https://www.nuget.org/packages/Mapster.DependencyInjection) | +| **Mapster.EFCore** | [![Mapster.EFCore](https://img.shields.io/nuget/v/Mapster.EFCore.svg?label=Mapster.EFCore&color=informational)](https://www.nuget.org/packages/Mapster.EFCore/latest) | [![Mapster.EFCore](https://img.shields.io/nuget/vpre/Mapster.EFCore.svg?label=Mapster.EFCore&color=orange)](https://www.nuget.org/packages/Mapster.EFCore) | +| **Mapster.EF6** | [![Mapster.EF6](https://img.shields.io/nuget/v/Mapster.EF6.svg?label=Mapster.EF6&color=informational)](https://www.nuget.org/packages/Mapster.EF6/latest) | [![Mapster.EF6](https://img.shields.io/nuget/vpre/Mapster.EF6.svg?label=Mapster.EF6&color=orange)](https://www.nuget.org/packages/Mapster.EF6) | +| **Mapster.JsonNet** | [![Mapster.JsonNet](https://img.shields.io/nuget/v/Mapster.JsonNet.svg?label=Mapster.JsonNet&color=informational)](https://www.nuget.org/packages/Mapster.JsonNet/latest) | [![Mapster.JsonNet](https://img.shields.io/nuget/vpre/Mapster.JsonNet.svg?label=Mapster.JsonNet&color=orange)](https://www.nuget.org/packages/Mapster.JsonNet) | +| **Mapster.Immutable** | [![Mapster.Immutable](https://img.shields.io/nuget/v/Mapster.Immutable.svg?label=Mapster.Immutable&color=informational)](https://www.nuget.org/packages/Mapster.Immutable/latest) | [![Mapster.Immutable](https://img.shields.io/nuget/vpre/Mapster.Immutable.svg?label=Mapster.Immutable&color=orange)](https://www.nuget.org/packages/Mapster.Immutable) | +| **Mapster.Diagnostics** | [![Mapster.Diagnostics](https://img.shields.io/nuget/v/Mapster.Diagnostics.svg?label=Mapster.Diagnostics&color=informational)](https://www.nuget.org/packages/Mapster.Diagnostics/latest) | | +| **ExpressionDebugger** | [![ExpressionDebugger](https://img.shields.io/nuget/v/ExpressionDebugger.svg?label=ExpressionDebugger&color=informational)](https://www.nuget.org/packages/ExpressionDebugger/latest) | | + +### DotNet Tools + +| Tool | Stable | Pre-release | +|------|--------|-------------| +| **Mapster.Tool** | [![Mapster.Tool](https://img.shields.io/nuget/v/Mapster.Tool.svg?label=Mapster.Tool&color=informational)](https://www.nuget.org/packages/Mapster.Tool/latest) | [![Mapster.Tool](https://img.shields.io/nuget/vpre/Mapster.Tool.svg?label=Mapster.Tool&color=orange)](https://www.nuget.org/packages/Mapster.Tool) | + +_Badges zeigen die jeweils aktuellste Stable-Version und die aktuellste Pre-Release-Version._ + +## Installation -### Installation Install Mapster with the NuGet CLI: -``` + +```Powershell Install-Package Mapster ``` Or use the .NET core CLI to install Mapster: + +```bash +dotnet add package Mapster --project ``` -dotnet add package Mapster -``` -### Basic usage -#### Mapping to a new object +## Basic usage + +### Mapping to a new object + Mapster creates the destination object and maps values to it. ```csharp var destObject = sourceObject.Adapt(); ``` -#### Mapping to an existing object +### Mapping to an existing object + You create the object, Mapster maps to the object. ```csharp sourceObject.Adapt(destObject); ``` -#### You can get IMapper instance via dependency injection so you do not have to change code when migrating to mapster from automapper -Add Mapster to service collection +### Use Mapster with Dependency Injection + +You can get your `IMapper` instance via dependency injection, so you do not have to change code, when migrating to mapster from automapper! + +**Just add Mapster to service collection:** + ```csharp services.AddMapster(); ``` -And use it with DI + +**And use it with DI in your Project:** + ```csharp public class Test { @@ -47,7 +82,8 @@ public class Test } ``` -#### Queryable Extensions +### Queryable Extensions + Mapster also provides extensions to map queryables. ```csharp @@ -67,7 +103,8 @@ using (MyDbContext context = new MyDbContext()) } ``` -#### Generating models & mappers +### Generating models & mappers + No need to write your own DTO classes. Mapster provides [Mapster.Tool](https://github.com/MapsterMapper/Mapster/wiki/Mapster.Tool) to help you generating models. And if you would like to have explicit mapping, Mapster also generates mapper class for you. ```csharp @@ -77,7 +114,8 @@ public class Student { } ``` -Then Mapster will generate: +Then Mapster will generate for you: + ```csharp public class StudentDto { ... @@ -89,7 +127,8 @@ public static class StudentMapper { } ``` -### What's new +## What's new + - [Fluent API for code generation](https://github.com/MapsterMapper/Mapster/wiki/Fluent-API-Code-generation) - [Automatically generate mapping code on build](https://github.com/MapsterMapper/Mapster/wiki/Mapster.Tool) - [Define setting to nested mapping](https://github.com/MapsterMapper/Mapster/wiki/Config-for-nested-mapping) @@ -99,10 +138,13 @@ public static class StudentMapper { - New plugins - [Immutable collection support](https://github.com/MapsterMapper/Mapster/wiki/Immutable) -### Why Mapster? -#### Performance & Memory efficient +## Why Mapster? + +### Performance & Memory efficient + Mapster was designed to be efficient on both speed and memory. You could gain a 4x performance improvement whilst using only 1/3 of memory. -And you could gain up to 12x faster performance with +And you could gain up to 12x faster performance with: + - [Roslyn Compiler](https://github.com/MapsterMapper/Mapster/wiki/Debugging) - [FEC](https://github.com/MapsterMapper/Mapster/wiki/FastExpressionCompiler) - Code generation @@ -116,35 +158,38 @@ And you could gain up to 12x faster performance with | 'ExpressMapper 1.9.1' | 205.78 ms | 5.357 ms | 8.098 ms | 59000.0000 | - | - | 236.51 MB | | 'AutoMapper 10.0.0' | 420.97 ms | 23.266 ms | 35.174 ms | 87000.0000 | - | - | 350.95 MB | +### Step into debugging +[Step-into debugging](https://mapstermapper.github.io/docs/packages/Diagnostics.html) lets you debug your mapping and inspect values just like your code. -#### Step into debugging +![inspect-generated-source-code-while-debugging](https://cloud.githubusercontent.com/assets/5763993/26521773/180427b6-431b-11e7-9188-10c01fa5ba5c.png) -[Step-into debugging](https://github.com/MapsterMapper/Mapster/wiki/Debugging) lets you debug your mapping and inspect values just like your code. -![image](https://cloud.githubusercontent.com/assets/5763993/26521773/180427b6-431b-11e7-9188-10c01fa5ba5c.png) +### Code Generation -#### Code Generation Code generation allows you to + - Validate mapping at compile time - Getting raw performance - Seeing your mapping code & debugging - Finding usage of your models' properties There are currently two tools which you can choose based on your preferences. -* [Mapster.Tool](https://github.com/MapsterMapper/Mapster/wiki/Mapster.Tool) NEW! -* [TextTemplate](https://github.com/MapsterMapper/Mapster/wiki/TextTemplate) -### Change logs +- [Mapster.Tool](https://github.com/MapsterMapper/Mapster/wiki/Mapster.Tool) _**NEW!**_ +- [TextTemplate](https://github.com/MapsterMapper/Mapster/wiki/TextTemplate) + +## Change logs + https://github.com/MapsterMapper/Mapster/releases -### Usages -* [English](https://github.com/MapsterMapper/Mapster/wiki) -* [中文文档](https://github.com/rivenfx/Mapster-docs) (sp thx to [@staneee](https://github.com/staneee)) +## Usage Guides with Translations + +- [English](https://github.com/MapsterMapper/Mapster/wiki) +- [中文文档](https://github.com/rivenfx/Mapster-docs) (sp thx to [@staneee](https://github.com/staneee)) -### Acknowledgements +## Acknowledgements [JetBrains](https://www.jetbrains.com/?from=Mapster) kindly provides Mapster with a free open-source licence for their Resharper and Rider. + - **Resharper** makes Visual Studio a much better IDE - **Rider** is fast & powerful cross platform .NET IDE - -![image](https://upload.wikimedia.org/wikipedia/commons/thumb/1/1a/JetBrains_Logo_2016.svg/121px-JetBrains_Logo_2016.svg.png) diff --git a/cSpell.json b/cSpell.json new file mode 100644 index 00000000..23a431b2 --- /dev/null +++ b/cSpell.json @@ -0,0 +1,26 @@ +{ + "version": "0.2", + "language": "en", + "words": [ + "alertblocks", + "appsettings", + "definitionlists", + "docfx", + "dotnet", + "github", + "gridtables", + "markdig", + "medialinks", + "MVVM" + ], + "dictionaries": [ + "csharp", + "companies" + ], + "ignoreWords": [], + "ignorePaths": [ + "src/*", + "**.gitignore", + "LICENSE" + ] +} diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 00000000..c91848d7 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,2 @@ +_site/* +!docs/articles/**.{md,yml} diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/docs/Build-Docs.ps1 b/docs/Build-Docs.ps1 new file mode 100644 index 00000000..e6bc559c --- /dev/null +++ b/docs/Build-Docs.ps1 @@ -0,0 +1,45 @@ +#!/usr/bin/env pwsh +<# +.SYNOPSIS + Builds DocFX documentation with clean API regeneration. + +.DESCRIPTION + 1. Cleans obsolete API documentation files + 2. Regenerates metadata from current source code + 3. Builds the documentation site + +.EXAMPLE + .\Build-Docs.ps1 + .\Build-Docs.ps1 -Verbose + .\Build-Docs.ps1 -serve $true -openBrowser $true +#> + +[CmdletBinding()] +param( + [Parameter()] + [ValidateSet('Quiet', 'Info', 'Warning', 'Error', 'Verbose')] + [string]$LogLevel = 'Warning', + [bool]$serve = $false, + [bool]$openBrowser = $false +) + +Set-Location $PSScriptRoot + +# Step 1: Clean obsolete API docs +Write-Information "Cleaning obsolete API documentation..." -InformationAction Continue +& .\Clean-ApiDocs.ps1 + +# Step 2: Run DocFX (metadata + build + pdf in one command) +Write-Information "Running DocFX..." -InformationAction Continue +docfx docfx.json --logLevel $LogLevel --serve:$serve --open-browser:$openBrowser + +if ($LASTEXITCODE -ne 0) { + Write-Error "DocFX build failed" + exit $LASTEXITCODE +} + +Write-Host "✓ Documentation built successfully!" -ForegroundColor Green +Write-Debug "Output: $PSScriptRoot\_site" + +# Step 3: Reset to original location +Pop-Location \ No newline at end of file diff --git a/docs/Clean-ApiDocs.ps1 b/docs/Clean-ApiDocs.ps1 new file mode 100644 index 00000000..a60065cc --- /dev/null +++ b/docs/Clean-ApiDocs.ps1 @@ -0,0 +1,45 @@ +#!/usr/bin/env pwsh +<# +.SYNOPSIS + Cleans obsolete API documentation files before regenerating with DocFX. + +.DESCRIPTION + Removes all generated YAML files from the api/ directory and the _site/ + output folder to ensure only current types are documented and built. + Preserves any manually-created markdown files like api/index.md. + +.EXAMPLE + .\Clean-ApiDocs.ps1 + .\Clean-ApiDocs.ps1 -Verbose +#> + +[CmdletBinding()] +param() + +$apiPath = Join-Path $PSScriptRoot "api" +$sitePath = Join-Path $PSScriptRoot "_site" +$isVerboseOutput = $PSBoundParameters.ContainsKey('Verbose').Equals($true) + +# Step 1: Clean API folder +if (Test-Path $apiPath) { + Write-Information "Cleaning API documentation folder..." -InformationAction Continue + + # Remove all .yml files (generated API docs) + Get-ChildItem -Path $apiPath -Filter "*.yml" -File | Remove-Item -Force -Verbose:$isVerboseOutput + + Write-Host "✓ Cleaned obsolete API documentation files" -ForegroundColor Green +} else { + Write-Debug "API folder does not exist yet: $apiPath" +} + +# Step 2: Clean _site folder +if (Test-Path $sitePath) { + Write-Information "Cleaning output site folder..." -InformationAction Continue + Remove-Item -Path $sitePath -Recurse -Force -Verbose:$isVerboseOutput + Write-Host "✓ Cleaned output site folder" -ForegroundColor Green +} else { + Write-Debug "Output site folder does not exist yet: $sitePath" +} + +# Step 3: Reset to original location +Pop-Location \ No newline at end of file diff --git a/docs/Clean-and-Build-Docs.ps1 b/docs/Clean-and-Build-Docs.ps1 new file mode 100644 index 00000000..53db82ff --- /dev/null +++ b/docs/Clean-and-Build-Docs.ps1 @@ -0,0 +1,44 @@ +#!/usr/bin/env pwsh +<# +.SYNOPSIS + Full documentation build: Clean, regenerate, and build. + +.DESCRIPTION + 1. Cleans obsolete API documentation files (Clean-ApiDocs.ps1) + 2. Builds documentation using Build-Docs.ps1 + +.EXAMPLE + .\Build-Docs-Full.ps1 + .\Build-Docs-Full.ps1 -Verbose + .\Build-Docs-Full.ps1 -serve $true -openBrowser $true +#> + +[CmdletBinding()] +param( + [Parameter()] + [ValidateSet('Quiet', 'Info', 'Warning', 'Error', 'Verbose')] + [string]$LogLevel = 'Warning' +) + +# Step 1: Set the current location +Set-Location $PSScriptRoot + +# Step 2: Clean obsolete API docs +Write-Information "Cleaning obsolete API documentation..." -InformationAction Continue +& .\Clean-ApiDocs.ps1 + +# Step 3: Build documentation +Write-Information "Building documentation..." -InformationAction Continue + +& .\Build-Docs.ps1 -LogLevel $LogLevel -serve $true -open-browser $true + +if ($LASTEXITCODE -ne 0) { + Write-Error "Documentation build failed" + exit $LASTEXITCODE +} + +Write-Information "✓ Full documentation build completed successfully!" -InformationAction Continue +Write-Debug "Output: $PSScriptRoot\_site" + +# Step 4: Reset to original location +Pop-Location \ No newline at end of file diff --git a/docs/api/.gitignore b/docs/api/.gitignore new file mode 100644 index 00000000..13609968 --- /dev/null +++ b/docs/api/.gitignore @@ -0,0 +1,2 @@ +*.yml +.manifest diff --git a/docs/api/Reference.md b/docs/api/Reference.md new file mode 100644 index 00000000..9626ce24 --- /dev/null +++ b/docs/api/Reference.md @@ -0,0 +1,139 @@ +--- +uid: Mapster.References +--- + +# References + +## Basic + +| Method | Description | Link | +| -------------|-----------------------| ----- | +| `src.Adapt()` | Mapping to new type | [basic](xref:Mapster.Mapping.BasicUsages) | +| `src.Adapt(dest)` | Mapping to existing object | [basic](xref:Mapster.Mapping.BasicUsages) | +| `query.ProjectToType()` | Mapping from queryable | [basic](xref:Mapster.Mapping.BasicUsages) | +| | Convention & Data type support | [data types](xref:Mapster.Mapping.DataTypes) | + +### Mapper instance (for dependency injection) + +| Method | Description | Link | +| -------------|----------------------| ----- | +| `IMapper mapper = new Mapper()`| Create mapper instance | [mappers](xref:Mapster.Mapping.Mappers) | +| `mapper.Map(src)` | Mapping to new type | | +| `mapper.Map(src, dest)` | Mapping to existing object | | + +### Builder (for complex mapping) + +| Method | Description | Link | +| ------------- |-----------------------| ----- | +| `src.BuildAdapter()`
`mapper.From(src)` | Create builder | [mappers](xref:Mapster.Mapping.Mappers) | +| `.ForkConfig(config => ...)` | Inline configuration | [config location](xref:Mapster.Configuration.Location) | +| `.AddParameters(name, value)` | Passing runtime value | [setting values](xref:Mapster.Settings.SettingValues) | +| `.AdaptToType()` | Mapping to new type | | +| `.AdaptTo(dest)` | Mapping to existing object | | +| `.CreateMapExpression()` | Get mapping expression | | +| `.CreateMapToTargetExpression()` | Get mapping to existing object expression | | +| `.CreateProjectionExpression()` | Get mapping from queryable expression | | + +## Config + +| Method | Description | Link | +|----------------------------------------------|-----------------------------------------| ----- | +| `TypeAdapterConfig.GlobalSettings` | Global config | [config](xref:Mapster.Configuration.Overview) | +| `var config = new TypeAdapterConfig()` | Create new config instance | [config instance](xref:Mapster.Configuration.Instance) | +| `src.Adapt(config)` | Passing config to mapping | | +| `new Mapper(config)` | Passing config to mapper instance | | +| `src.BuildAdapter(config)` | Passing config to builder | | +| `config.RequireDestinationMemberSource` | Validate all properties are mapped | [config validation](xref:Mapster.Configuration.ValidationAndCompilation) | +| `config.RequireExplicitMapping` | Validate all type pairs are defined | [config validation](xref:Mapster.Configuration.ValidationAndCompilation) | +| `config.AllowImplicitDestinationInheritance` | Use config from destination based class | [inheritance](xref:Mapster.Configuration.Inheritance) | +| `config.AllowImplicitSourceInheritance` | Use config from source based class | [inheritance](xref:Mapster.Configuration.Inheritance) | +| `config.SelfContainedCodeGeneration` | Generate all nested mapping in 1 method | [TextTemplate](xref:Mapster.Tools.TextTemplate) | +| `config.Compile()` | Validate mapping instruction & cache | [config validation](xref:Mapster.Configuration.ValidationAndCompilation) | +| `config.CompileProjection()` | Validate mapping instruction & cache for queryable | | +| `config.Clone()` | Copy config | [config instance](xref:Mapster.Configuration.Instance) | +| `config.Fork(forked => ...)` | Inline configuration | [config location](xref:Mapster.Configuration.Location) | + +### Config scanning + +| Method | Description | Link | +|------------------------------|-------------------------------| ----- | +| `IRegister` | Interface for config scanning | [config location](xref:Mapster.Configuration.Location) | +| `config.Scan(...assemblies)` | Scan for config in assemblies | [config location](xref:Mapster.Configuration.Location) | +| `config.Apply(...registers)` | Apply registers directly | [config location](xref:Mapster.Configuration.Location) | + + +### Declare settings + +| Method | Description | Link | +| ------------- |-----------------------| ----- | +| `config.Default` | Get setting applied to all type pairs | [config](xref:Mapster.Configuration.Overview) | +| `TypeAdapterConfig.NewConfig()`
`config.NewConfig()` | Create setting applied to specific type pairs | [config](xref:Mapster.Configuration.Overview) | +| `TypeAdapterConfig.ForType()`
`config.ForType()` | Get setting applied to specific type pairs | [config](xref:Mapster.Configuration.Overview) | +| `config.ForType(typeof(GenericPoco<>),typeof(GenericDto<>))` | Get setting applied to generic type pairs | [config](xref:Mapster.Configuration.Overview) | +| `config.When((src, dest, mapType) => ...)` | Get setting that applied conditionally | [config](xref:Mapster.Configuration.Overview) | +| `config.ForDestinationType()` | Get setting that applied to specific destination type | [config](xref:Mapster.Configuration.Overview) | +| | Configuration for nested mapping | [nested mapping](xref:Mapster.Configuration.NestedMapping) | + +## Settings + +| Method | Description | Apply to queryable | Link | +| ------------- |-----------------------| ------------ | ----- | +| `AddDestinationTransform` | Clean up data for a specific type | x | [setting values](xref:Mapster.Settings.SettingValues) | +| `AfterMapping` | Add steps after mapping done | | [before-after](xref:Mapster.Settings.BeforeAfterMapping) | +| `AvoidInlineMapping` | Skip inline process for large type mapping | | [object reference](xref:Mapster.Settings.ObjectReferences) | +| `BeforeMapping` | Add steps before mapping start | | [before-after](xref:Mapster.Settings.BeforeAfterMapping) | +| `ConstructUsing` | Define how to create object | x | [constructor](xref:Mapster.Settings.ConstructorMapping) | +| `EnableNonPublicMembers` | Mapping non-public properties | | [non-public](xref:Mapster.Settings.Custom.NonPublicMembers) | +| `EnumMappingStrategy` | Choose whether mapping enum by value or by name | | [data types](xref:Mapster.Mapping.DataTypes) | +| `Fork` | Add new settings without side effect on main config | x | [nested mapping](xref:Mapster.Configuration.NestedMapping) | +| `GetMemberName` | Define how to resolve property name | x | [custom naming](xref:Mapster.Settings.Custom.NamingConvention) | +| `Ignore` | Ignore specific properties | x | [ignore](xref:Mapster.Settings.Custom.IgnoringMembers) | +| `IgnoreAttribute` | Ignore specific attributes annotated on properties | x | [attribute](xref:Mapster.Settings.Custom.Attributes) | +| `IgnoreIf` | Ignore conditionally | x | [ignore](xref:Mapster.Settings.Custom.IgnoringMembers) | +| `IgnoreMember` | Setup rules to ignore | x | [rule based](xref:Mapster.Settings.Custom.RuleBasedMapping) | +| `IgnoreNonMapped` | Ignore all properties not defined in `Map` | x | [ignore](xref:Mapster.Settings.Custom.IgnoringMembers) | +| `IgnoreNullValues` | Not map if src property is null | | [shallow & merge](xref:Mapster.Settings.ShallowMerge) | +| `Include` | Include derived types on mapping | | [inheritance](xref:Mapster.Configuration.Inheritance) | +| `IncludeAttribute` | Include specific attributes annotated on properties | x | [attribute](xref:Mapster.Settings.Custom.Attributes) | +| `IncludeMember` | Setup rules to include | x | [rule based](xref:Mapster.Settings.Custom.RuleBasedMapping) | +| `Inherits` | Copy setting from based type | x | [inheritance](xref:Mapster.Configuration.Inheritance) | +| `Map` | Define property pairs | x | [custom mapping](xref:Mapster.Settings.Custom.Mapping) | +| `MapToConstructor` | Mapping to constructor | x | [constructor](xref:Mapster.Settings.ConstructorMapping) | +| `MapToTargetWith` | Define how to map to existing object between type pair | | [custom conversion](xref:Mapster.Settings.CustomConversionLogic) | +| `MapWith` | Define how to map between type pair | x | [custom conversion](xref:Mapster.Settings.CustomConversionLogic) | +| `MaxDepth` | Limit depth of nested mapping | x | [object reference](xref:Mapster.Settings.ObjectReferences) | +| `NameMatchingStrategy` | Define how to resolve property's name | x | [custom naming](xref:Mapster.Settings.Custom.NamingConvention) | +| `PreserveReference` | Tracking reference when mapping | | [object reference](xref:Mapster.Settings.ObjectReferences) | +| `ShallowCopyForSameType` | Direct assign rather than deep clone if type pairs are the same | | [shallow & merge](xref:Mapster.Settings.ShallowMerge) | +| `TwoWays` | Define type mapping are 2 ways | x | [2-ways & unflattening](xref:Mapster.Settings.Custom.TwoWaysMapping) | +| `Unflattening` | Allow unflatten mapping | x |[2-ways & unflattening](xref:Mapster.Settings.Custom.TwoWaysMapping) | +| `UseDestinationValue` | Use existing property object to map data | |[readonly-prop](xref:Mapster.Settings.Custom.ReadonlyProperty) | + +## Attributes + +| Annotation | Description | Link | +| ------------- |-----------------------| ----- | +| `[AdaptMember(name)]` | Mapping property to different name | [attribute](xref:Mapster.Settings.Custom.Attributes) | +| `[AdaptIgnore(side)]` | Ignore property from mapping | [attribute](xref:Mapster.Settings.Custom.Attributes) | +| `[UseDestinationValue]` | Use existing property object to map data | [attribute](xref:Mapster.Settings.Custom.Attributes) | +| `[AdaptTo]` `[AdaptFrom]` `[AdaptTwoWays]` | Add setting on POCO class | [location](xref:Mapster.Configuration.Location#attributes) | +| `[Mapper]` `[GenerateMapper]` `[PropertyType]` | Define setting for code generation | [Mapster.Tool](xref:Mapster.Tools.MapsterTool.Overview) | + +## Packages + +| Packages | Method | Description | +| ------ | ------------- |-----------------------| +| [Async](xref:Mapster.Packages.Async) | `setting.AfterMappingAsync`
`builder.AdaptToTypeAsync` | perform async operation on mapping | +| [Debugging](xref:Mapster.Packages.ExpressionDebugging) | `config.Compiler = exp => exp.CompileWithDebugInfo()` | compile to allow step into debugging | +| [Dependency Injection](xref:Mapster.Packages.DependencyInjection) | `MapContext.Current.GetService()` | Inject service into mapping logic | +| [EF 6 & EF Core](xref:Mapster.Packages.EntityFramework) | `builder.EntityFromContext` | Copy data to tracked EF entity | +| [FEC](xref:Mapster.Packages.FastExpressionCompiler) | `config.Compiler = exp => exp.CompileFast()` | compile using FastExpressionCompiler | +| [Immutable](xref:Mapster.Packages.Immutable) | `config.EnableImmutableMapping()` | mapping to immutable collection | +| [Json.net](xref:Mapster.Packages.JsonNet) | `config.EnableJsonMapping()` | map json from/to poco and string | + +## Code Generation Tools + +| Plugin | Tool | Description | +| ------ | ------------- |-----------------------| +| [Mapster.Tool](xref:Mapster.Tools.MapsterTool.Overview) | `dotnet mapster` | generate DTOs and mapping codes on build | +| [TextTemplate](xref:Mapster.Tools.TextTemplate) | `t4` | generate mapping codes using t4 | diff --git a/docs/api/index.md b/docs/api/index.md new file mode 100644 index 00000000..96322154 --- /dev/null +++ b/docs/api/index.md @@ -0,0 +1,5 @@ +# MapsterMapper Mapster API Documentation + +Welcome in the Mapster API documentation! + +Use the table of contents to browse API documentation for the `MapsterMapper.Mapster` Package. diff --git a/docs/articles/.assets/step-into-debugging.png b/docs/articles/.assets/step-into-debugging.png new file mode 100644 index 00000000..5183ce54 Binary files /dev/null and b/docs/articles/.assets/step-into-debugging.png differ diff --git a/docs/articles/_Sidebar.md b/docs/articles/_Sidebar.md new file mode 100644 index 00000000..22c58675 --- /dev/null +++ b/docs/articles/_Sidebar.md @@ -0,0 +1,56 @@ +# Getting Started with Mapster + +## [References](https://github.com/MapsterMapper/Mapster/wiki) + +## Mapping + +* [Basic usages](https://github.com/MapsterMapper/Mapster/wiki/Basic-usages) +* [Mappers](https://github.com/MapsterMapper/Mapster/wiki/Mappers) +* [Data types](https://github.com/MapsterMapper/Mapster/wiki/Data-types) +* [Mapping with interface](https://github.com/MapsterMapper/Mapster/wiki/Mapping-Configuration-With-IMapFrom-Interface) + +## Configuration + +* [Configuration](https://github.com/MapsterMapper/Mapster/wiki/Configuration) +* [Config inheritance](https://github.com/MapsterMapper/Mapster/wiki/Config-inheritance) +* [Config instance](https://github.com/MapsterMapper/Mapster/wiki/Config-instance) +* [Config location](https://github.com/MapsterMapper/Mapster/wiki/Config-location) +* [Config validation & compilation](https://github.com/MapsterMapper/Mapster/wiki/Config-validation-&-compilation) +* [Config for nested mapping](https://github.com/MapsterMapper/Mapster/wiki/Config-for-nested-mapping) + +## Settings + +* [Constructor mapping](https://github.com/MapsterMapper/Mapster/wiki/Constructor-mapping) +* [Before & after mapping](https://github.com/MapsterMapper/Mapster/wiki/Before-after-mapping) +* [Setting values](https://github.com/MapsterMapper/Mapster/wiki/Setting-values) +* [Shallow & merge mapping](https://github.com/MapsterMapper/Mapster/wiki/Shallow-merge) +* [Recursive & object references](https://github.com/MapsterMapper/Mapster/wiki/Object-references) +* [Custom conversion logic](https://github.com/MapsterMapper/Mapster/wiki/Custom-conversion-logic) +* [Inheritance](https://github.com/MapsterMapper/Mapster/wiki/Config-inheritance) +* **Custom member matching logic** + * [Custom mapping](https://github.com/MapsterMapper/Mapster/wiki/Custom-mapping) + * [Custom naming convention](https://github.com/MapsterMapper/Mapster/wiki/Naming-convention) + * [Setting by attributes](https://github.com/MapsterMapper/Mapster/wiki/Setting-by-attributes) + * [Ignoring members](https://github.com/MapsterMapper/Mapster/wiki/Ignoring-members) + * [Rule-based member matching](https://github.com/MapsterMapper/Mapster/wiki/Rule-based-member-mapping) + * [Mapping readonly prop](https://github.com/MapsterMapper/Mapster/wiki/Mapping-readonly-prop) + * [Mapping non-public members](https://github.com/MapsterMapper/Mapster/wiki/Mapping-non-public-members) + * [Two ways & unflattening mapping](https://github.com/MapsterMapper/Mapster/wiki/Two-ways) + +## Plugins + +* [Async Support](https://github.com/MapsterMapper/Mapster/wiki/Async) +* [Debugging](https://github.com/MapsterMapper/Mapster/wiki/Debugging) +* [Dependency Injection](https://github.com/MapsterMapper/Mapster/wiki/Dependency-Injection) +* [EF 6 & EF Core](https://github.com/MapsterMapper/Mapster/wiki/EF-6-&-EF-Core) +* [FastExpressionCompiler](https://github.com/MapsterMapper/Mapster/wiki/FastExpressionCompiler) +* [Immutable](https://github.com/MapsterMapper/Mapster/wiki/Immutable) +* [Json.net](https://github.com/MapsterMapper/Mapster/wiki/Json.net) + +## Tools + +* [TextTemplate](https://github.com/MapsterMapper/Mapster/wiki/TextTemplate) +* [Mapster.Tool](https://github.com/MapsterMapper/Mapster/wiki/Mapster.Tool) + * [Fluent API](https://github.com/MapsterMapper/Mapster/wiki/Fluent-API-Code-generation) + * [Attributes](https://github.com/MapsterMapper/Mapster/wiki/Attribute-base-Code-generation) + * [Interfaces](https://github.com/MapsterMapper/Mapster/wiki/Interface-base-Code-generation) diff --git a/docs/articles/configuration/Config-for-nested-mapping.md b/docs/articles/configuration/Config-for-nested-mapping.md new file mode 100644 index 00000000..9416b503 --- /dev/null +++ b/docs/articles/configuration/Config-for-nested-mapping.md @@ -0,0 +1,72 @@ +--- +uid: Mapster.Configuration.NestedMapping +title: "Configuration - Config for nested mapping" +--- + +For example if you have parent and child classes. + +```csharp +class ParentPoco +{ + public string Id { get; set; } + public List Children { get; set; } + public string Name { get; set; } +} + +class ChildPoco +{ + public string Id { get; set; } + public List GrandChildren { get; set; } +} + +class GrandChildPoco +{ + public string Id { get; set; } +} +``` + +And if you have setting on parent type. + +```csharp +TypeAdapterConfig.NewConfig() + .PreserveReference(true); +``` + +By default, children types will not get effect from `PreserveReference`. + +To do so, you must specify all type pairs inside `ParentPoco`. + +```csharp +TypeAdapterConfig.NewConfig() + .PreserveReference(true); +TypeAdapterConfig.NewConfig() + .PreserveReference(true); +TypeAdapterConfig.NewConfig() + .PreserveReference(true); +``` + +Or you can set `PreserveReference` in global setting. + +```csharp +TypeAdapterConfig.GlobalSettings.Default.PreserveReference(true); +``` + +## Fork + +You can use `Fork` command to define config that applies only specified mapping down to nested mapping without polluting global setting. + +```csharp +TypeAdapterConfig.NewConfig() + .Fork(config => config.Default.PreserveReference(true)); +``` + +**Ignore if string is null or empty** + +Another example, Mapster only can ignore null value ([IgnoreNullValues](xref:Mapster.Settings.ShallowMerge#copy-vs-merge)), however, you use `Fork` to ignore null or empty. + +```csharp +TypeAdapterConfig.NewConfig() + .Fork(config => config.ForType() + .MapToTargetWith((src, dest) => string.IsNullOrEmpty(src) ? dest : src) + ); +``` diff --git a/docs/articles/configuration/Config-inheritance.md b/docs/articles/configuration/Config-inheritance.md new file mode 100644 index 00000000..edcc93f4 --- /dev/null +++ b/docs/articles/configuration/Config-inheritance.md @@ -0,0 +1,56 @@ +--- +uid: Mapster.Configuration.Inheritance +title: "Configuration - Inheritance" +--- + +## Implicit inheritance + +Type mappings will automatically inherit for source types. Ie. if you set up following config. + +```csharp +TypeAdapterConfig.NewConfig() + .Map(dest => dest.Name, src => src.Name + "_Suffix"); +``` + +A derived type of `SimplePoco` will automatically apply the base mapping config. + +```csharp +var dest = TypeAdapter.Adapt(src); +//dest.Name = src.Name + "_Suffix" +``` + +If you don't wish for a derived type to use the base mapping, you can turn it off by using `AllowImplicitSourceInheritance` + +```csharp +TypeAdapterConfig.GlobalSettings.AllowImplicitSourceInheritance = false; +``` + +And by default, Mapster will not inherit destination type mappings. You can turn it on by `AllowImplicitDestinationInheritance`. + +```csharp +TypeAdapterConfig.GlobalSettings.AllowImplicitDestinationInheritance = true; +``` + +## Explicit inheritance + +You can copy setting from based type explicitly. + +```csharp +TypeAdapterConfig.NewConfig() + .Inherits(); +``` + +## Include derived types + +You can also include derived type to the based type declaration. For example: + +```csharp +TypeAdapterConfig.NewConfig() + .Include(); + +Vehicle vehicle = new Car { Id = 1, Name = "Car", Make = "Toyota" }; +var dto = vehicle.Adapt(); + +dto.ShouldBeOfType(); +((CarDto)dto).Make.ShouldBe("Toyota"); //The 'Make' property doesn't exist in Vehicle +``` diff --git a/docs/articles/configuration/Config-instance.md b/docs/articles/configuration/Config-instance.md new file mode 100644 index 00000000..c82a1860 --- /dev/null +++ b/docs/articles/configuration/Config-instance.md @@ -0,0 +1,72 @@ +--- +uid: Mapster.Configuration.Instance +title: "Configuration - Config Instance" +--- + +## Creating `Config` Instance with `TypeAdapterConfig` + +You may wish to have different settings in different scenarios. +If you would not like to apply setting at a static level, Mapster also provides setting instance configurations. + +```csharp +var config = new TypeAdapterConfig(); +config.Default.Ignore("Id"); +``` + +### `ForType` Configuration Methods + +For instance configurations, you can use the same `NewConfig` and `ForType` methods that are used at the global level with +the same behavior: `NewConfig` drops any existing configuration and `ForType` creates or enhances a configuration. + +```csharp +config.NewConfig() + .Map(dest => dest.FullName, + src => string.Format("{0} {1}", src.FirstName, src.LastName)); + +config.ForType() + .Map(dest => dest.FullName, + src => string.Format("{0} {1}", src.FirstName, src.LastName)); +``` + +### `Adapt` Configuration Methods + +You can apply a specific config instance by passing it to the `Adapt` method. (NOTE: please reuse your config instance to prevent recompilation) + +```csharp +var result = src.Adapt(config); +``` + +### `Map` Configuration Methods + +Or to a Mapper instance. + +```csharp +var mapper = new Mapper(config); +var result = mapper.Map(src); +``` + +## Cloning `Config` Instance + +If you would like to create configuration instance from existing configuration, you can use `Clone` method. For example, if you would like to clone from global setting. + +```csharp +var newConfig = TypeAdapterConfig.GlobalSettings.Clone(); +``` + +Or clone from existing configuration instance + +```csharp +var newConfig = oldConfig.Clone(); +``` + +### `Fork` Configuration + +`Fork` is similar to `Clone`, but `Fork` will allow you to keep configuration and mapping in the same location. See [Config Location](xref:Mapster.Configuration.Location) for more info. + +```csharp +var forked = mainConfig.Fork(config => + config.ForType() + .Map(dest => dest.code, src => src.Id)); + +var dto = poco.Adapt(forked); +``` diff --git a/docs/articles/configuration/Config-location.md b/docs/articles/configuration/Config-location.md new file mode 100644 index 00000000..4ac27893 --- /dev/null +++ b/docs/articles/configuration/Config-location.md @@ -0,0 +1,117 @@ +--- +uid: Mapster.Configuration.Location +title: "Configuration - Location" +--- + +## Entry point for configuration + +Configuration should be set only once and reuse for mapping. Therefore, we should not keep configuration and mapping in the same location. For example: + +```csharp +config.ForType().Ignore("Id"); +var dto1 = poco1.Adapt(config); + +config.ForType().Ignore("Id"); //<--- Exception occurred here, because config was already compiled +var dto2 = poco2.Adapt(config); +``` + +Therefore, you should separate configuration and mapping. Configuration should keep in entry point such as `Main` function or `Global.asax.cs` or `Program.cs` / `Startup.cs`. + +```csharp +// Application_Start in Global.asax.cs +config.ForType().Ignore("Id"); +``` + +```csharp +// in Controller class +var dto1 = poco1.Adapt(config); +var dto2 = poco2.Adapt(config); +``` + +### Keep together with mapping + +A potential problem with separating configuration and mapping is that the code will be separated into 2 locations. You might remove or alter mapping, and you can forget to update the configuration. `Fork` method allow you to keep config and mapping inline. + +```csharp +var dto = poco.Adapt(config.Fork(forked => forked.ForType().Ignore("Id")); +``` + +Don't worry about performance, forked config will be compiled only once. When mapping occurs for the second time, `Fork` function will return config from cache. + +#### Using Fork in generic class or method + +`Fork` method uses filename and line number and the key. But if you use `Fork` method inside generic class or method, you must specify your own key (with all type names) to prevent `Fork` to return invalid config from different type arguments. + +```csharp +IQueryable GetItems() +{ + var forked = config.Fork( + f => f.ForType().Ignore("Id"), + $"MyKey|{typeof(TPoco).FullName}|{typeof(TDto).FullName}"); + return db.Set().ProjectToType(forked); +} +``` + +### In separated assemblies + +It's relatively common to have mapping configurations spread across a number of different assemblies. +Perhaps your domain assembly has some rules to map to domain objects and your web api has some specific rules to map to your api contracts. + +#### Scan method + +It can be helpful to allow assemblies to be scanned for these rules so you have some basic method of organizing your rules and not forgetting to have the registration code called. In some cases, it may even be necessary to register the assemblies in a particular order, so that some rules override others. Assembly scanning helps with this. + +Assembly scanning is simple, just create any number of `IRegister` implementations in your assembly, then call `Scan` from your `TypeAdapterConfig` class: + +```csharp +public class MyRegister : IRegister +{ + public void Register(TypeAdapterConfig config) + { + config.NewConfig(); + + //OR to create or enhance an existing configuration + config.ForType(); + } +} +``` + +To scan and register at the Global level: + +```csharp +TypeAdapterConfig.GlobalSettings.Scan(assembly1, assembly2, assemblyN) +``` + +For a specific config instance: + +```csharp +var config = new TypeAdapterConfig(); +config.Scan(assembly1, assembly2, assemblyN); +``` + +#### Apply method + +If you use other assembly scanning library such as MEF, you can easily apply registration with `Apply` method. + +```csharp +var registers = container.GetExports(); +config.Apply(registers); +``` + +`Apply` method also allows you to selectively pick from one or more `IRegister` rather than every `IRegister` in assembly. + +```csharp +var register = new MockingRegister(); +config.Apply(register); +``` + +### Attributes + +You can also set config together with your POCO classes. For example: + +```csharp +[AdaptTo(typeof(StudentDto), PreserveReference = true)] +public class Student { + ... +} +``` diff --git a/docs/articles/configuration/Config-validation-and-compilation.md b/docs/articles/configuration/Config-validation-and-compilation.md new file mode 100644 index 00000000..f3a06210 --- /dev/null +++ b/docs/articles/configuration/Config-validation-and-compilation.md @@ -0,0 +1,64 @@ +--- +uid: Mapster.Configuration.ValidationAndCompilation +title: "Configuration - Validation and Compilation" +--- + +To validate your mapping in unit tests and in order to help with "Fail Fast" situations, the following strict mapping modes have been added. + +## Explicit Mapping + +Forcing all classes to be explicitly mapped: + +```csharp +//Default is "false" +TypeAdapterConfig.GlobalSettings.RequireExplicitMapping = true; +//This means you have to have an explicit configuration for each class, even if it's just: +TypeAdapterConfig.NewConfig(); +``` + +## Checking Destination Member + +Forcing all destination properties to have a corresponding source member or explicit mapping/ignore: + +```csharp +//Default is "false" +TypeAdapterConfig.GlobalSettings.RequireDestinationMemberSource = true; +``` + +## Validating Mappings + +Both a specific TypeAdapterConfig or all current configurations can be validated. In addition, if Explicit Mappings (above) are enabled, it will also include errors for classes that are not registered at all with the mapper. + +```csharp +//Validate a specific config +var config = TypeAdapterConfig.NewConfig(); +config.Compile(); + +//Validate globally +TypeAdapterConfig.NewConfig(); +TypeAdapterConfig.NewConfig(); +TypeAdapterConfig.GlobalSettings.Compile(); +``` + +## Config Compilation + +Mapster will automatically compile mapping for first time usage. + +```csharp +var result = poco.Adapt(); +``` + +However, you can explicitly compile mapping by `Compile` method. + +```csharp +//Global config +TypeAdapterConfig.GlobalSettings.Compile(); + +//Config instance +var config = new TypeAdapterConfig(); +config.Compile(); +``` + +Calling `Compile` method on start up helps you validate mapping and detect problem on start-up time, not on run-time. + +NOTE: After compile, when you change setting in config, it will generate errors. Therefore, make sure you finish configuration before calling `Compile`. diff --git a/docs/articles/configuration/Configuration.md b/docs/articles/configuration/Configuration.md new file mode 100644 index 00000000..34a30549 --- /dev/null +++ b/docs/articles/configuration/Configuration.md @@ -0,0 +1,82 @@ +--- +uid: Mapster.Configuration.Overview +title: "Configuration - Overview" +--- + + +## Setting per type pair + +You can easily create settings for a type mapping by using: `TypeAdapterConfig.NewConfig()`. +When `NewConfig` is called, any previous configuration for this particular TSource => TDestination mapping is dropped. + +```csharp +TypeAdapterConfig + .NewConfig() + .Ignore(dest => dest.Age) + .Map(dest => dest.FullName, + src => string.Format("{0} {1}", src.FirstName, src.LastName)); +``` + +As an alternative to `NewConfig`, you can use `ForType` in the same way: + +```csharp +TypeAdapterConfig + .ForType() + .Ignore(dest => dest.Age) + .Map(dest => dest.FullName, + src => string.Format("{0} {1}", src.FirstName, src.LastName)); +``` + +`ForType` differs in that it will create a new mapping if one doesn't exist, but if the specified TSource => TDestination +mapping does already exist, it will enhance the existing mapping instead of dropping and replacing it. + +## Global setting + +Use global settings to apply policies to all mappings. + +```csharp +TypeAdapterConfig.GlobalSettings.Default.PreserveReference(true); +``` + +Then for individual type mappings, you can easily override the global setting(s). + +```csharp +TypeAdapterConfig.NewConfig().PreserveReference(false); +``` + +## Rule based settings + +To set the setting at a more granular level. You can use the `When` method in global settings. +In the example below, when any source type and destination type are the same, we will not the copy the `Id` property. + +```csharp +TypeAdapterConfig.GlobalSettings.When((srcType, destType, mapType) => srcType == destType) + .Ignore("Id"); +``` + +In this example, the config would only apply to Query Expressions (projections). + +```csharp +TypeAdapterConfig.GlobalSettings.When((srcType, destType, mapType) => mapType == MapType.Projection) + .IgnoreAttribute(typeof(NotMapAttribute)); +``` + +## Destination type only + +A setting can also be created without knowing the source type, by using `ForDestinationType`. For example, you can do `AfterMapping` setting to validate after mapping. + +```csharp +TypeAdapterConfig.GlobalSettings.ForDestinationType() + .AfterMapping(dest => dest.Validate()); +``` + +NOTE: `ForDestinationType` above will always apply to all types assignable to `IValidator`. If destination class implements `IValidator`, it will also apply the `AfterMapping` config. + +## Open generics + +If the mapping type is generic, you can create a setting by passing generic type definition to `ForType`. + +```csharp +TypeAdapterConfig.GlobalSettings.ForType(typeof(GenericPoco<>), typeof(GenericDto<>)) + .Map("value", "Value"); +``` diff --git a/docs/articles/configuration/toc.yml b/docs/articles/configuration/toc.yml new file mode 100644 index 00000000..3979af6c --- /dev/null +++ b/docs/articles/configuration/toc.yml @@ -0,0 +1,20 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/dotnet/docfx/main/schemas/toc.schema.json +# Side navigation for Configuration articles +- name: Configuration Overview + uid: Mapster.Configuration.Overview + href: Configuration.md +- name: Config inheritance + uid: Mapster.Configuration.Inheritance + href: Config-inheritance.md +- name: Config Instance + uid: Mapster.Configuration.Instance + href: Config-instance.md +- name: Config location + uid: Mapster.Configuration.Location + href: Config-location.md +- name: Config validation & compilation + uid: Mapster.Configuration.ValidationAndCompilation + href: Config-validation-and-compilation.md +- name: Config for nested mapping + uid: Mapster.Configuration.NestedMapping + href: Config-for-nested-mapping.md diff --git a/docs/articles/mapping/Basic-usages.md b/docs/articles/mapping/Basic-usages.md new file mode 100644 index 00000000..21d0fd12 --- /dev/null +++ b/docs/articles/mapping/Basic-usages.md @@ -0,0 +1,44 @@ +--- +uid: Mapster.Mapping.BasicUsages +title: "Mapping - Basic Usages" +--- + +## Mapping to a new object + +Mapster creates the destination object and maps values to it. + +```csharp +var destObject = sourceObject.Adapt(); +``` + +### Mapping to an existing object + +You make the object, Mapster maps to the object. + +```csharp +sourceObject.Adapt(destObject); +``` + +## Queryable Extensions + +Mapster also provides extensions to map queryables. + +> [!IMPORTANT] +> Avoid calling ProjectToType() before materializing queries from Entity Framework. This is known to cause issues. Instead, call ToList() or ToListAsync() before calling ProjectToType. + +```csharp +using (MyDbContext context = new MyDbContext()) +{ + // Build a Select Expression from DTO + var destinations = context.Sources.ProjectToType().ToList(); + + // Versus creating by hand: + var destinations = context.Sources.Select(c => new Destination { + Id = p.Id, + Name = p.Name, + Surname = p.Surname, + .... + }) + .ToList(); +} +``` diff --git a/docs/articles/mapping/Data-types.md b/docs/articles/mapping/Data-types.md new file mode 100644 index 00000000..f4951ed4 --- /dev/null +++ b/docs/articles/mapping/Data-types.md @@ -0,0 +1,114 @@ +--- +uid: Mapster.Mapping.DataTypes +title: "Mapping - Data Types" +--- + +## Primitives + +Converting between primitive types (ie. int, bool, double, decimal) is supported, including when those types are nullable. For all other types, if you can cast types in c#, you can also cast in Mapster. + +```csharp +decimal i = 123.Adapt(); //equal to (decimal)123; +``` + +## Enums + +Mapster maps enums to numerics automatically, but it also maps strings to and from enums automatically in a fast manner. +The default Enum.ToString() in .NET is quite slow. The implementation in Mapster is double the speed. Likewise, a fast conversion from strings to enums is also included. If the string is null or empty, the enum will initialize to the first enum value. + +In Mapster, flagged enums are also supported. + +```csharp +var e = "Read, Write, Delete".Adapt(); +//FileShare.Read | FileShare.Write | FileShare.Delete +``` + +For enum to enum with different type, by default, Mapster will map enum by value. You can override to map enum by name by: + +```csharp +TypeAdapterConfig.GlobalSettings.Default + .EnumMappingStrategy(EnumMappingStrategy.ByName); +``` + +## Strings + +When Mapster maps other types to string, Mapster will use `ToString` method. And whenever Mapster maps string to the other types, Mapster will use `Parse` method. + +```csharp +var s = 123.Adapt(); //equal to 123.ToString(); +var i = "123".Adapt(); //equal to int.Parse("123"); +``` + +## Collections + +This includes mapping among lists, arrays, collections, dictionary including various interfaces: `IList`, `ICollection`, `IEnumerable`, `ISet`, `IDictionary` etc... + +```csharp +var list = db.Pocos.ToList(); +var target = list.Adapt>(); +``` + +## Mappable Objects + +Mapster can map two different objects using the following rules: + +- Source and destination property names are the same. Ex: `dest.Name = src.Name` +- Source has get method. Ex: `dest.Name = src.GetName()` +- Source property has child object which can flatten to destination. Ex: `dest.ContactName = src.Contact.Name` or `dest.Contact_Name = src.Contact.Name` + +Example: + +```csharp +class Staff { + public string Name { get; set; } + public int GetAge() { + return (DateTime.Now - this.BirthDate).TotalDays / 365.25; + } + public Staff Supervisor { get; set; } + ... +} + +struct StaffDto { + public string Name { get; set; } + public int Age { get; set; } + public string SupervisorName { get; set; } +} + +var dto = staff.Adapt(); +//dto.Name = staff.Name, dto.Age = staff.GetAge(), dto.SupervisorName = staff.Supervisor.Name +``` + +**Mappable Object types are included:** + +- POCO classes +- POCO structs +- POCO interfaces +- Dictionary type implement `IDictionary` +- Record types (either class, struct, and interface) + +Example for object to dictionary: + +```csharp +var point = new { X = 2, Y = 3 }; +var dict = point.Adapt>(); +dict["Y"].ShouldBe(3); +``` + +Example for record types: + +```csharp +class Person { + public string Name { get; } + public int Age { get; } + + public Person(string name, int age) { + this.Name = name; + this.Age = age; + } +} + +var src = new { Name = "Mapster", Age = 3 }; +var target = src.Adapt(); +``` + +There are limitations to map Record type automatically. Record type must not have a setter and have only one non-empty constructor, and all parameter names must match with properties. Otherwise you need to add [`MapToConstructor` configuration](xref:Mapster.Settings.ConstructorMapping#map-to-constructor). diff --git a/docs/articles/mapping/Mappers.md b/docs/articles/mapping/Mappers.md new file mode 100644 index 00000000..b3ac783a --- /dev/null +++ b/docs/articles/mapping/Mappers.md @@ -0,0 +1,57 @@ +--- +uid: Mapster.Mapping.Mappers +title: "Mapping - Mappers" +--- + +## Extension method + +You can simply call `Adapt` method from anywhere. + +```csharp +var dest = src.Adapt(); +``` + +or just + +```csharp +var dest = src.Adapt(); +``` + +2 extension methods are doing the same thing. `src.Adapt` will cast `src` to object. Therefore, if you map value type, please use `src.Adapt` to avoid boxing and unboxing. + +## Mapper instance + +In some cases, you need an instance of a mapper (or a factory function) to pass into a DI container. Mapster has +the `IMapper` and `Mapper` to fill this need: + +```csharp +IMapper mapper = new Mapper(); +``` + +And usage `Map` method to perform mapping. + +```csharp +var result = mapper.Map(source); +``` + +## Builder + +In most case `Adapt` method is enough, but sometimes we need builder to support fancy scenario. Basic example, is to pass run-time value. + +```csharp +var dto = poco.BuildAdapter() + .AddParameters("user", this.User.Identity.Name) + .AdaptToType(); +``` + +Or if you use mapper instance, you can create builder by method `From`. + +```csharp +var dto = mapper.From(poco) + .AddParameters("user", this.User.Identity.Name) + .AdaptToType(); +``` + +## Code generation + +See [Mapster.Tool](xref:Mapster.Tools.MapsterTool.Overview) for generating your specific mapper class, rather than using the provided mappers. diff --git a/docs/articles/mapping/Mapping-Configuration-With-IMapFrom-Interface.md b/docs/articles/mapping/Mapping-Configuration-With-IMapFrom-Interface.md new file mode 100644 index 00000000..1d8ecf50 --- /dev/null +++ b/docs/articles/mapping/Mapping-Configuration-With-IMapFrom-Interface.md @@ -0,0 +1,39 @@ +--- +uid: Mapster.Mapping.IMapFromInterface +title: "Mapping - IMapFrom Interface" +--- + +Before using this feature you have to add this line: + +```csharp +TypeAdapterConfig.GlobalSettings.ScanInheritedTypes(Assembly.GetExecutingAssembly()); +``` + +With adding above line to your Startup.cs or Program.cs or any other way to run at startup, you can write mapping configs in the destination class that implements IMapFrom interface + +Example: + +```csharp +public class InheritedDestinationModel : IMapFrom +{ + public string Type { get; set; } + public int Value { get; set; } + + public void ConfigureMapping(TypeAdapterConfig config) + { + config.NewConfig() + .Map(dest => dest.Value, source => int.Parse(source.Value)); + } +} +``` + +Even if your destination model doesn't have a specific configuration (you don't want to customize anything), you can just inherit from IMapFrom interface + +Example: + +```csharp +public class DestinationModel : IMapFrom +{ + public string Type { get; set; } +} +``` diff --git a/docs/articles/mapping/toc.yml b/docs/articles/mapping/toc.yml new file mode 100644 index 00000000..ab7e7755 --- /dev/null +++ b/docs/articles/mapping/toc.yml @@ -0,0 +1,13 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/dotnet/docfx/main/schemas/toc.schema.json +- name: Basic usages + uid: Mapster.Mapping.BasicUsages + href: Basic-usages.md +- name: Mappers + uid: Mapster.Mapping.Mappers + href: Mappers.md +- name: Data types + uid: Mapster.Mapping.DataTypes + href: Data-types.md +- name: Mapping with interface + uid: Mapster.Mapping.IMapFromInterface + href: Mapping-Configuration-With-IMapFrom-Interface.md \ No newline at end of file diff --git a/docs/articles/packages/Async.md b/docs/articles/packages/Async.md new file mode 100644 index 00000000..563cb65c --- /dev/null +++ b/docs/articles/packages/Async.md @@ -0,0 +1,40 @@ +--- +uid: Mapster.Packages.Async +title: "Packages - Mapster Async Support" +--- + +This Package allows you to perform async operation for mapping. + +```nuget + PM> Install-Package Mapster.Async +``` + +## Setup + +Use `AfterMappingAsync` to setup async operation: + +```csharp +config.NewConfig() + .AfterMappingAsync(async (poco, dto) => + { + var userManager = MapContext.Current.GetService(); + var user = await userManager.FindByIdAsync(poco.UserId); + dto.UserName = user.Name; + }); +``` + +## Mapping + +Then map asynchronously with `AdaptToTypeAsync`. + +```csharp +var dto = await poco.BuildAdapter() + .AdaptToTypeAsync(); +``` + +Or like this, if you use mapper instance: + +```csharp +var dto = await _mapper.From(poco) + .AdaptToTypeAsync(); +``` diff --git a/docs/articles/packages/Dependency-Injection.md b/docs/articles/packages/Dependency-Injection.md new file mode 100644 index 00000000..358b256d --- /dev/null +++ b/docs/articles/packages/Dependency-Injection.md @@ -0,0 +1,59 @@ +--- +uid: Mapster.Packages.DependencyInjection +title: "Packages - Dependency Injection Support" +--- + +## Installation + +This package allows you to inject service into mapping configuration. + +```nuget + PM> Install-Package Mapster.DependencyInjection +``` + +## Usage + +On startup, register `TypeAdapterConfig`, and `ServiceMapper`. + +```csharp +public void ConfigureServices(IServiceCollection services) +{ + ... + var config = new TypeAdapterConfig(); + // Or + // var config = TypeAdapterConfig.GlobalSettings; + services.AddSingleton(config); + services.AddScoped(); + ... +} +``` + +NOTE: lifetime of `ServiceMapper` is up to services you would like to inject. It can be singleton, if you inject only singleton services. Or it can be transient, if any injected services is transient. + +## Mapping configuration + +You can get service by `MapContext.Current.GetService<>()`, for example + +```csharp +config.NewConfig() + .Map(dest => dest.Name, src => MapContext.Current.GetService().Format(src.Name)); +``` + +## Mapping + +If you setup service injection, you need to use mapper instance to map object. + +```csharp +public class FooService { + private readonly IMapper _mapper; + + public FooService(IMapper mapper) { + _mapper = mapper; + } + + public void DoSomething(Poco poco) { + var dto = _mapper.Map(poco); + ... + } +} +``` diff --git a/docs/articles/packages/EF-6-and-EF-Core.md b/docs/articles/packages/EF-6-and-EF-Core.md new file mode 100644 index 00000000..f255d6e9 --- /dev/null +++ b/docs/articles/packages/EF-6-and-EF-Core.md @@ -0,0 +1,61 @@ +--- +uid: Mapster.Packages.EntityFramework +title: "Packages - EF 6 and EF Core Support" +--- + +## [EntityFramework 6 support](#tab/ef6) + +To install the package for EF 6: + +```nuget + PM> Install-Package Mapster.EF6 +``` + +## [EntityFramework Core support](#tab/efcore) + +To install the package for EF Core: + +```nuget + PM> Install-Package Mapster.EFCore +``` + +In EF, objects are tracked, when you copy data from dto to entity containing navigation properties, this package will help finding entity object in navigation properties automatically. + +--- + +## Compatibility + +- use Mapster.EFCore version 5.x for EFCore 5.x +- use Mapster.EFCore version 3.x for EFCore 3.x +- use Mapster.EFCore version 1.x for EFCore 2.x + +## Usage + +Use `EntityFromContext` method to define data context. + +```csharp +var poco = db.DomainPoco.Include("Children") + .Where(item => item.Id == dto.Id).FirstOrDefault(); + +dto.BuildAdapter() + .EntityFromContext(db) + .AdaptTo(poco); +``` + +Or like this, if you use mapper instance + +```csharp +_mapper.From(dto) + .EntityFromContext(db) + .AdaptTo(poco); +``` + +### EF Core `ProjectToType` + +`Mapster.EFCore` also allows you to perform projection from `IQueryable` source via mapper instance and `ProjectToType` directly to your DTO type. + +```csharp +var query = db.Customers.Where(...); +_mapper.From(query) + .ProjectToType(); +``` diff --git a/docs/articles/packages/ExpressionDebugging.md b/docs/articles/packages/ExpressionDebugging.md new file mode 100644 index 00000000..e32f41dd --- /dev/null +++ b/docs/articles/packages/ExpressionDebugging.md @@ -0,0 +1,61 @@ +--- +uid: Mapster.Packages.ExpressionDebugging +title: "Packages - Expression Debugging" +--- + +This Package allows you to perform step-into debugging using Roslyn! + +```nuget + PM> Install-Package ExpressionDebugger +``` + +## Usage + +Then add following code on start up (or anywhere before mapping is compiled) + +```csharp +TypeAdapterConfig.GlobalSettings.Compiler = exp => exp.CompileWithDebugInfo(); +``` + +Now in your mapping code (only in `DEBUG` mode). + +```csharp +var dto = poco.Adapt(); //<--- you can step-into this function!! +``` + +![step-into-debugging-screenshot](../.assets/step-into-debugging.png) + +## Using internal classes or members + +`private`, `protected` and `internal` aren't allowed in debug mode. + +### Get mapping script + +We can also see how Mapster generates mapping logic with `ToScript` method. + +```csharp +var script = poco.BuildAdapter() + .CreateMapExpression() + .ToScript(); +``` + +## Specifics for Visual Studio on Mac + +To step-into debugging, you might need to emit file + +```csharp +var opt = new ExpressionCompilationOptions { EmitFile = true }; +TypeAdapterConfig.GlobalSettings.Compiler = exp => exp.CompileWithDebugInfo(opt); +... +var dto = poco.Adapt(); //<-- you can step-into this function!! +``` + +### Do not worry about performance + +In `RELEASE` mode, Roslyn compiler is actually faster than default dynamic compilation by 2x. +Here is the result: + +| Method | Mean | StdDev | Error | Gen 0 | Gen 1 | Gen 2 | Allocated | +|-------------------------- |---------------:|-------------:|-------------:|------------:|------:|------:|-----------:| +| 'Mapster 4.1.1' | 115.31 ms | 0.849 ms | 1.426 ms | 31000.0000 | - | - | 124.36 MB | +| 'Mapster 4.1.1 (Roslyn)' | 53.55 ms | 0.342 ms | 0.654 ms | 31100.0000 | - | - | 124.36 MB | diff --git a/docs/articles/packages/FastExpressionCompiler.md b/docs/articles/packages/FastExpressionCompiler.md new file mode 100644 index 00000000..a20df828 --- /dev/null +++ b/docs/articles/packages/FastExpressionCompiler.md @@ -0,0 +1,27 @@ +--- +uid: Mapster.Packages.FastExpressionCompiler +title: "Packages - Fast Expression Compiler Support" +--- + +Need more speed? Let's compile with [FastExpressionCompiler](https://github.com/dadhi/FastExpressionCompiler). + +## Installation + +Getting the package: + +```nuget + PM> Install-Package FastExpressionCompiler +``` + +Then add following code on start up + +```csharp +TypeAdapterConfig.GlobalSettings.Compiler = exp => exp.CompileFast(); +``` + +That's it. Now your code will enjoy performance boost. Here is result. + +| Method | Mean | StdDev | Error | Gen 0 | Gen 1 | Gen 2 | Allocated | +|-------------------------- |---------------:|-------------:|-------------:|------------:|------:|------:|-----------:| +| 'Mapster 4.1.1' | 115.31 ms | 0.849 ms | 1.426 ms | 31000.0000 | - | - | 124.36 MB | +| 'Mapster 4.1.1 (FEC)' | 54.70 ms | 1.023 ms | 1.546 ms | 29600.0000 | - | - | 118.26 MB | diff --git a/docs/articles/packages/Immutable.md b/docs/articles/packages/Immutable.md new file mode 100644 index 00000000..3b8bd5ef --- /dev/null +++ b/docs/articles/packages/Immutable.md @@ -0,0 +1,42 @@ +--- +uid: Mapster.Packages.Immutable +title: "Packages - Immutable Support" +--- + +This Package enables Immutable collection support in Mapster. + +### Installation + +```nuget + PM> Install-Package Mapster.Immutable +``` + +### Usage + +Call `EnableImmutableMapping` from your `TypeAdapterConfig` to enable Immutable collection. + +```csharp +TypeAdapterConfig.GlobalSettings.EnableImmutableMapping(); +``` + +or: + +```csharp +config.EnableImmutableMapping(); +``` + +This will allow mapping to: + +- `IImmutableDictionary<,>` +- `IImmutableList<>` +- `IImmutableQueue<>` +- `IImmutableSet<>` +- `IImmutableStack<>` +- `ImmutableArray<>` +- `ImmutableDictionary<,>` +- `ImmutableHashSet<>` +- `ImmutableList<>` +- `ImmutableQueue<>` +- `ImmutableSortedDictionary<,>` +- `ImmutableSortedSet<>` +- `ImmutableStack<>` diff --git a/docs/articles/packages/Json.net.md b/docs/articles/packages/Json.net.md new file mode 100644 index 00000000..2cc8d3b3 --- /dev/null +++ b/docs/articles/packages/Json.net.md @@ -0,0 +1,31 @@ +--- +uid: Mapster.Packages.JsonNet +title: "Packages - Json.net Support" +--- + +The `Json.net` Package adds conversion supports for Json.Net types. + +## Installation + +```nuget + PM> Install-Package Mapster.JsonNet +``` + +## Usage + +Call `EnableJsonMapping` from your `TypeAdapterConfig` to enable Json.Net mapping. + +```csharp +TypeAdapterConfig.GlobalSettings.EnableJsonMapping(); +``` + +or: + +```csharp +config.EnableJsonMapping(); +``` + +This will allow: + +- Mapping between Json.Net types (`JToken`, `JArray`, `JObject`) from/to POCO types +- Serialize and deserialize Json.Net types from/to string diff --git a/docs/articles/packages/toc.yml b/docs/articles/packages/toc.yml new file mode 100644 index 00000000..7558022c --- /dev/null +++ b/docs/articles/packages/toc.yml @@ -0,0 +1,23 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/dotnet/docfx/main/schemas/toc.schema.json +# NOTE: Use quotes for names with special characters like "&" and don't use them in filenames +- name: Async Support + uid: Mapster.Packages.Async + href: Async.md +- name: "Packages - Expression Debugging" + uid: Mapster.Packages.ExpressionDebugging + href: ExpressionDebugging.md +- name: Dependency Injection + uid: Mapster.Packages.DependencyInjection + href: Dependency-Injection.md +- name: "EF 6 and EF Core Support" + uid: Mapster.Packages.EntityFramework + href: EF-6-and-EF-Core.md +- name: FastExpressionCompiler + uid: Mapster.Packages.FastExpressionCompiler + href: FastExpressionCompiler.md +- name: Immutable + uid: Mapster.Packages.Immutable + href: Immutable.md +- name: "Json.net Support" + uid: Mapster.Packages.JsonNet + href: Json.net.md diff --git a/docs/articles/settings/Before-after-mapping.md b/docs/articles/settings/Before-after-mapping.md new file mode 100644 index 00000000..cff135a2 --- /dev/null +++ b/docs/articles/settings/Before-after-mapping.md @@ -0,0 +1,109 @@ +--- +uid: Mapster.Settings.BeforeAfterMapping +title: "Settings - Before and After mapping" +--- + +## Before mapping action + +You can perform actions before mapping started by using `BeforeMapping` method. + +```csharp +TypeAdapterConfig.ForType() + .BeforeMapping((src, result) => result.Initialize()); +``` + +## After mapping action + +You can perform actions after each mapping by using `AfterMapping` method. For instance, you might would like to validate object after each mapping. + +```csharp +TypeAdapterConfig.ForType() + .AfterMapping((src, result) => result.Validate()); +``` + +Or you can set for all mappings to types which implemented a specific interface by using `ForDestinationType` method. + +```csharp +TypeAdapterConfig.GlobalSettings.ForDestinationType() + .AfterMapping(result => result.Validate()); +``` + +## Code generation + +`BeforeMapping` and `AfterMapping` accept action which allowed you to pass multiple statements. In code generation, you might need to pass expression instead of action using `BeforeMappingInline` and `AfterMappingInline`, expression can be translated into code, but action cannot. + +### Single line statement + +For single line statement, you can directly change from `BeforeMapping` and `AfterMapping` to `BeforeMappingInline` and `AfterMappingInline`. + +```csharp +TypeAdapterConfig.GlobalSettings.ForDestinationType() + .AfterMappingInline(result => result.Validate()); +``` + +### Multiple statements + +For multiple statements, you need to declare a method for actions. + +```csharp +public static void Validate(Dto dto) { + action1(dto); + action2(dto); + ... +} +``` + +Then you can reference the method to `BeforeMappingInline` and `AfterMappingInline`. + +```csharp +TypeAdapterConfig.GlobalSettings.ForDestinationType() + .AfterMappingInline(result => PocoToDtoMapper.Validate(result)); +``` + +## Overloads with `destination` parameter + +You can use `BeforeMapping` with `destination` to construct final (`result`) object. + +```csharp +TypeAdapterConfig, IEnumerable>.NewConfig() + .BeforeMapping((src, result, destination) => + { + if (!ReferenceEquals(result, destination) && destination != null && result is ICollection resultCollection) + { + foreach (var item in destination) + { + resultCollection.Add(item); + } + } +}); + +IEnumerable source = new List { 1, 2, 3, }; +IEnumerable destination = new List { 0, }; + +var result = source.Adapt(destination); + +destination.ShouldBe(new List { 0, }); +source.ShouldBe(new List { 1, 2, 3, }); +result.ShouldBe(new List { 0, 1, 2, 3, }); +``` + +Same with `AfterMapping`. + +```csharp +TypeAdapterConfig.NewConfig() + .ConstructUsing((simplePoco, dto) => new SimpleDto()) + .AfterMapping((src, result, destination) => result.Name += $"{destination.Name}xxx"); + +var poco = new SimplePoco +{ + Id = Guid.NewGuid(), + Name = "test", +}; + +var oldDto = new SimpleDto { Name = "zzz", }; +var result = poco.Adapt(oldDto); + +result.ShouldNotBeSameAs(oldDto); +result.Id.ShouldBe(poco.Id); +result.Name.ShouldBe(poco.Name + "zzzxxx"); +``` diff --git a/docs/articles/settings/Constructor-mapping.md b/docs/articles/settings/Constructor-mapping.md new file mode 100644 index 00000000..7cac272f --- /dev/null +++ b/docs/articles/settings/Constructor-mapping.md @@ -0,0 +1,79 @@ +--- +uid: Mapster.Settings.ConstructorMapping +title: "Settings - Constructor mapping" +--- + +## Custom Destination Object Creation + +You can provide a function call to create your destination objects instead of using the default object creation +(which expects an empty constructor). To do so, use the `ConstructUsing` method when configuring. This method expects +a function that will provide the destination instance. You can call your own constructor, a factory method, +or anything else that provides an object of the expected type. + +```csharp +//Example using a non-default constructor +TypeAdapterConfig.NewConfig() + .ConstructUsing(src => new TDestination(src.Id, src.Name)); + +//Example using an object initializer +TypeAdapterConfig.NewConfig() + .ConstructUsing(src => new TDestination{Unmapped = "unmapped"}); + +//Example using an overload with `destination` parameter +TypeAdapterConfig.NewConfig() + .ConstructUsing((src, destination) => new TDestination(src.Id, destination?.Name ?? src.Name)); +``` + +### Map to constructor + +By default, Mapster will only map to fields and properties. You can configure to map to constructors by `MapToConstructor`. + +```csharp +//global level +TypeAdapterConfig.GlobalSettings.Default.MapToConstructor(true); + +//type pair +TypeAdapterConfig.NewConfig().MapToConstructor(true); +``` + +To define custom mapping, you need to use Pascal case. + +```csharp +class Poco { + public string Id { get; set; } + ... +} +class Dto { + public Dto(string code, ...) { + ... + } +} +``` + +```csharp +TypeAdapterConfig.NewConfig() + .MapToConstructor(true) + .Map('Code', 'Id'); //use Pascal case +``` + +If a class has 2 or more constructors, Mapster will automatically select largest number of parameters that satisfy mapping. + +```csharp +class Poco { + public int Foo { get; set; } + public int Bar { get; set; } +} +class Dto { + public Dto(int foo) { ... } + public Dto(int foo, int bar) { ...} //<-- Mapster will use this constructor + public Dto(int foo, int bar, int baz) { ... } +} +``` + +Or you can also explicitly pass ConstructorInfo to the method. + +```csharp +var ctor = typeof(Dto).GetConstructor(new[] { typeof(int), typeof(int) }); +TypeAdapterConfig.NewConfig() + .MapToConstructor(ctor); +``` diff --git a/docs/articles/settings/Custom-conversion-logic.md b/docs/articles/settings/Custom-conversion-logic.md new file mode 100644 index 00000000..0edaaa23 --- /dev/null +++ b/docs/articles/settings/Custom-conversion-logic.md @@ -0,0 +1,49 @@ +--- +uid: Mapster.Settings.CustomConversionLogic +title: "Settings - Custom conversion logic" +--- + +## Custom type conversion + +In some cases, you may want to have complete control over how an object is mapped. You can register specific transformations using the `MapWith` method. + +```csharp +//Example of transforming string to char[]. +TypeAdapterConfig.NewConfig() + .MapWith(str => str.ToCharArray()); +``` + +`MapWith` also useful if you would like to copy instance rather than deep copy the object, for instance, `JObject` or `DbGeography`, these should treat as primitive types rather than POCO. + + ```csharp +TypeAdapterConfig.NewConfig() + .MapWith(json => json); +``` + +In case you would like to combine `MapWith` with other settings, for example, `PreserveReference`, `Include`, or `AfterMapping`, you can pass `applySettings` to true. + +```csharp +TypeAdapterConfig.NewConfig() + .PreserveReference(true) + .MapWith(poco => poco.ToDto(), applySettings: true); +``` + +## Custom mapping data to existing object + +You can control mapping to existing object logic by `MapToTargetWith`. For example, you can copy data to existing array. + +```csharp +TypeAdapterConfig.NewConfig() + .MapToTargetWith((src, dest) => Array.Copy(src, dest, src.Length)); +``` + +NOTE: if you set `MapWith` setting but no `MapToTargetWith` setting, Mapster will use logic from `MapWith` setting. + +### Custom actions after mapping + +You might not need to specify custom mapping logic completely. You can let Mapster do the mapping, and you do logic where Mapster cannot cover by using `AfterMapping`. + +```csharp +TypeAdapterConfig.NewConfig() + .AfterMapping((src, dest) => SpecialSetFn(src, dest)); +``` diff --git a/docs/articles/settings/Object-references.md b/docs/articles/settings/Object-references.md new file mode 100644 index 00000000..86d2d955 --- /dev/null +++ b/docs/articles/settings/Object-references.md @@ -0,0 +1,55 @@ +--- +uid: Mapster.Settings.ObjectReferences +title: "Settings - Object references" +--- + +## Preserve reference (preventing circular reference stackoverflow) + +When mapping objects with circular references, a stackoverflow exception will result. This is because Mapster will get stuck in a loop trying to recursively map the circular reference. If you would like to map circular references or preserve references (such as 2 properties pointing to the same object), you can do it by setting `PreserveReference` to `true` + +```csharp +TypeAdapterConfig + .NewConfig() + .PreserveReference(true); +``` + +NOTE: in Mapster setting is per type pair, not per hierarchy (see [**Configuration for nested Classes**](xref:Mapster.Configuration.NestedMapping)). Therefore, you need to apply config to all type pairs. + +NOTE: you might need to use `MaxDepth`. `PreserveReference` doesn't support EF Query (`ProjectTo`) + +## MaxDepth + +Rather than `PreserveReference`, you could also try `MaxDepth`. `MaxDepth` will map until it reaches the defined limit. Unlike `PreserveReference`, `MaxDepth` also works with queryable projection. + +```csharp +TypeAdapterConfig + .NewConfig() + .MaxDepth(3); +``` + +NOTE 1: `MaxDepth` starts with 1, means you will copy only primitives. POCO class and collection of POCO each count as a depth of 1. + +NOTE 2: even `MaxDepth` has no maximum value, you shouldn't input large number. Each depth will generate a mapping logic, otherwise it will consume a lot of memory. + +## Shallow copy + +By default, Mapster will recursively map nested objects. You can do shallow copying by setting `ShallowCopyForSameType` to `true`. + +```csharp +TypeAdapterConfig + .NewConfig() + .ShallowCopyForSameType(true); +``` + +## Mapping very large objects + +For performance optimization, Mapster tried to inline class mapping. This process will takes time if your models are complex. + +![inline-class-mapping-diagram](https://cloud.githubusercontent.com/assets/21364231/25666644/ce38c8c0-3029-11e7-8793-8a51c519c2a0.png) + +You can skip inlining process by: + +```csharp +TypeAdapterConfig.GlobalSettings.Default + .AvoidInlineMapping(true); +``` diff --git a/docs/articles/settings/Setting-values.md b/docs/articles/settings/Setting-values.md new file mode 100644 index 00000000..a08dc9df --- /dev/null +++ b/docs/articles/settings/Setting-values.md @@ -0,0 +1,55 @@ +--- +uid: Mapster.Settings.SettingValues +title: "Settings - Setting values" +--- + +## Computed value + +You can use `Map` method to specify logic to compute value. For example, compute full name from first name and last name. + +```csharp +TypeAdapterConfig.NewConfig() + .Map(dest => dest.FullName, src => src.FirstName + " " + src.LastName); +``` + +## Transform value + +While `Map` method specify logic for single property, `AddDestinationTransform` allows transforms for all items of a type, such as trimming all strings. But really any operation can be performed on the destination value before assignment. + +### Trim string + +```csharp +TypeAdapterConfig.NewConfig() + .AddDestinationTransform((string x) => x.Trim()); +``` + +### Null replacement + +```csharp +TypeAdapterConfig.NewConfig() + .AddDestinationTransform((string x) => x ?? ""); +``` + +### Return empty collection if null + +```csharp +config.Default.AddDestinationTransform(DestinationTransform.EmptyCollectionIfNull); +``` + +## Passing run-time value + +In some cases, you might would like to pass runtime values (ie, current user). On configuration, we can receive run-time value by `MapContext.Current.Parameters`. + +```csharp +TypeAdapterConfig.NewConfig() + .Map(dest => dest.CreatedBy, + src => MapContext.Current.Parameters["user"]); +``` + +To pass run-time value, we need to use `BuildAdapter` method, and call `AddParameters` method to add each parameter. + +```csharp +var dto = poco.BuildAdapter() + .AddParameters("user", this.User.Identity.Name) + .AdaptToType(); +``` diff --git a/docs/articles/settings/Shallow-merge.md b/docs/articles/settings/Shallow-merge.md new file mode 100644 index 00000000..ba101264 --- /dev/null +++ b/docs/articles/settings/Shallow-merge.md @@ -0,0 +1,24 @@ +--- +uid: Mapster.Settings.ShallowMerge +title: "Settings - Shallow merge" +--- + +## Deep copy vs. shallow copy + +By default, Mapster will recursively map nested objects (deep copy). You can do shallow copying by setting `ShallowCopyForSameType` to `true`. + +```csharp +TypeAdapterConfig + .NewConfig() + .ShallowCopyForSameType(true); +``` + +## Copy vs. Merge + +By default, Mapster will map all properties, even source properties containing null values. You can copy only properties that have values (merge) by using `IgnoreNullValues` method. + +```csharp +TypeAdapterConfig + .NewConfig() + .IgnoreNullValues(true); +``` diff --git a/docs/articles/settings/custom/Custom-mapping.md b/docs/articles/settings/custom/Custom-mapping.md new file mode 100644 index 00000000..b687c081 --- /dev/null +++ b/docs/articles/settings/custom/Custom-mapping.md @@ -0,0 +1,109 @@ +--- +uid: Mapster.Settings.Custom.Mapping +title: "Settings - Custom Mapping" +--- + +## Custom member mapping + +You can customize how Mapster maps values to a property. + +```csharp +TypeAdapterConfig + .NewConfig() + .Map(dest => dest.FullName, + src => $"{src.FirstName} {src.LastName}"); +``` + +You can even map when source and destination property types are different. + +```csharp +TypeAdapterConfig + .NewConfig() + .Map(dest => dest.Gender, //Genders.Male or Genders.Female + src => src.GenderString); //"Male" or "Female" +``` + +### Mapping with condition + +The Map configuration can accept a third parameter that provides a condition based on the source. +If the condition is not met, Mapster will retry with next conditions. Default condition should be added at the end without specifying condition. If you do not specify default condition, null or default value will be assigned. + +```csharp +TypeAdapterConfig + .NewConfig() + .Map(dest => dest.FullName, src => "Sig. " + src.FullName, srcCond => srcCond.Country == "Italy") + .Map(dest => dest.FullName, src => "Sr. " + src.FullName, srcCond => srcCond.Country == "Spain") + .Map(dest => dest.FullName, src => "Mr. " + src.FullName); +``` + +NOTE: if you would like to skip mapping, when condition is met, you can use `IgnoreIf` (xref:Mapster.Settings.Custom.IgnoringMembers#ignore-conditionally). + +### Mapping to non-public members + +`Map` command can map to private member by specify name of the members. + +```csharp +TypeAdapterConfig + .NewConfig() + .Map("PrivateDestName", "PrivateSrcName"); +``` + +For more information about mapping non-public members, please see [Mapping non-public members](xref:Mapster.Settings.Custom.NonPublicMembers). + +### Deep destination property + +`Map` can be defined to map deep destination property. + +```csharp +TypeAdapterConfig.NewConfig() + .Map(dest => dest.Child.Name, src => src.Name); +``` + +### Null propagation + +If `Map` contains only property path, null propagation will be applied. + +```csharp +TypeAdapterConfig.NewConfig() + .Map(dest => dest.Name, src => src.Child.Name); +``` + +From above example, if `src.Child` is null, mapping will return null instead of throw `NullPointerException`. + +### Multiple sources + +**Example 1**: Include property to Poco + +```csharp +public class SubDto +{ + public string Extra { get; set; } +} +public class Dto +{ + public string Name { get; set; } + public SubDto SubDto { get; set; } +} +public class Poco +{ + public string Name { get; set; } + public string Extra { get; set; } +} +``` + +In this case, you would like to map all properties from `Dto` to `Poco`, and also include all properties from `Dto.SubDto` to `Poco`. You can do this by just mapping `dto.SubDto` to `poco` in configuration. + +```csharp +TypeAdapterConfig.NewConfig() + .Map(poco => poco, dto => dto.SubDto); +``` + +**Example 2**: Mapping 2 objects to poco + +In this example, you have `Dto1` and `Dto2`, and you would like to map both objects to a `Poco`. You can do this by wrapping `Dto1` and `Dto2` into a tuple. And then mapping `tuple.Item1` and `tuple.Item2` to `Poco`. + +```csharp +TypeAdapterConfig<(Dto1, Dto2), Poco>.NewConfig() + .Map(dest => dest, src => src.Item1) + .Map(dest => dest, src => src.Item2); +``` diff --git a/docs/articles/settings/custom/Ignoring-members.md b/docs/articles/settings/custom/Ignoring-members.md new file mode 100644 index 00000000..a958ad89 --- /dev/null +++ b/docs/articles/settings/custom/Ignoring-members.md @@ -0,0 +1,69 @@ +--- +uid: Mapster.Settings.Custom.IgnoringMembers +title: "Settings - Ignoring members" +--- + +## Ignore Extension Method + +Mapster will automatically map properties with the same names. You can ignore members by using the `Ignore` method. + +```csharp +TypeAdapterConfig + .NewConfig() + .Ignore(dest => dest.Id); +``` + +## Rule based ignore with `IgnoreMember` + +You can ignore based on member information by `IgnoreMember` command. Please see [Rule-based-member-mapping](xref:Mapster.Settings.Custom.RuleBasedMapping) for more info. + +```csharp +TypeAdapterConfig.GlobalSettings.Default + .IgnoreMember((member, side) => !validTypes.Contains(member.Type)); +``` + +### IgnoreNonMapped + +You can ignore all non-mapped members by IgnoreNonMapped command. For example, we would like to map only Id and Name. + +```csharp +TypeAdapterConfig + .NewConfig() + .Map(dest => dest.Id, src => src.Id) + .Map(dest => dest.Name, src => src.Name) + .IgnoreNonMapped(true); +``` + +## Using the `AdaptIgnore` attribute + +You can ignore member by decorate with `[AdaptIgnore]`, and you can ignore custom attributes by `IgnoreAttribute` command. Please see [Setting by attributes](xref:Mapster.Settings.Custom.Attributes) for more info. + +```csharp +public class Product { + public string Id { get; set; } + public string Name { get; set; } + + [AdaptIgnore] + public decimal Price { get; set; } +} +``` + +### Ignore conditionally with `IgnoreIf` Extension Method + +You can ignore members conditionally, with condition based on source or target. When the condition is met, mapping of the property will be skipped altogether. This is the difference from custom `Map` with condition, where destination is set to `null` when condition is met. + +```csharp +TypeAdapterConfig + .NewConfig() + .IgnoreIf((src, dest) => !string.IsNullOrEmpty(dest.Name), dest => dest.Name); +``` + +### `IgnoreNullValues` Extension Method + +You might would like to merge from input object, By default, Mapster will map all properties, even source properties containing null values. You can copy only properties that have values by using `IgnoreNullValues` method. + +```csharp +TypeAdapterConfig + .NewConfig() + .IgnoreNullValues(true); +``` diff --git a/docs/articles/settings/custom/Mapping-non-public-members.md b/docs/articles/settings/custom/Mapping-non-public-members.md new file mode 100644 index 00000000..21864fa5 --- /dev/null +++ b/docs/articles/settings/custom/Mapping-non-public-members.md @@ -0,0 +1,58 @@ +--- +uid: Mapster.Settings.Custom.NonPublicMembers +title: "Settings - Mapping non-public members" +--- + +## `EnableNonPublicMembers` Extension Method + +This will allow Mapster to set to all non-public members. + +```csharp +//type pair +TypeAdapterConfig.NewConfig().EnableNonPublicMembers(true); + +//global +TypeAdapterConfig.GlobalSettings.Default.EnableNonPublicMembers(true); +``` + +## `AdaptMember` attribute + +You can also map non-public members with `AdaptMember` attribute. + +```csharp +public class Product +{ + [AdaptMember] + private string HiddenId { get; set; } + public string Name { get; set; } +} +``` + +## `Map` + +The `Map` extension can map to private member by specify name of the members. + +```csharp +TypeAdapterConfig + .NewConfig() + .Map("PrivateDestName", "PrivateSrcName"); +``` + +## `IncludeMember` + +With the `IncludeMember` extension, you can select which access modifier to allow. + +```csharp +TypeAdapterConfig.GlobalSettings.Default + .IncludeMember((member, side) => member.AccessModifier == AccessModifier.Internal + || member.AccessModifier == AccessModifier.ProtectedInternal); +``` + +### Note for non-public member mapping + +If type doesn't contain public properties, Mapster will treat type as primitive, you must also declare type pair to ensure Mapster will apply non-public member mapping. + +```csharp +TypeAdapterConfig.GlobalSettings.Default.EnableNonPublicMembers(true); +TypeAdapterConfig.NewConfig(); +``` diff --git a/docs/articles/settings/custom/Mapping-readonly-prop.md b/docs/articles/settings/custom/Mapping-readonly-prop.md new file mode 100644 index 00000000..c027b911 --- /dev/null +++ b/docs/articles/settings/custom/Mapping-readonly-prop.md @@ -0,0 +1,39 @@ +--- +uid: Mapster.Settings.Custom.ReadonlyProperty +title: "Settings - Mapping readonly properties" +--- + +## Non public setter + +Mapster can map to non public setter automatically. + +```csharp +public class Order { + public string Id { get; set; } + public ICollection Items { get; private set; } +} +``` + +## Using `UseDestinationValue` attribute + +You can make your type pure readonly and annotate with `[UseDestinationValue]`. + +```csharp +public class Order { + public string Id { get; set; } + + [UseDestinationValue] + public ICollection Items { get; } = new List(); +} +``` + +## Convention based setup using `UseDestinationValue` Extension Method + +Or you can apply without annotate each type, for example, if you would like all readonly `ICollection<>` to use destination value. + +```csharp +TypeAdapterConfig.GlobalSettings.Default + .UseDestinationValue(member => member.SetterModifier == AccessModifier.None && + member.Type.IsGenericType && + member.Type.GetGenericTypeDefinition() == typeof(ICollection<>)); +``` diff --git a/docs/articles/settings/custom/Naming-convention.md b/docs/articles/settings/custom/Naming-convention.md new file mode 100644 index 00000000..f84e64e7 --- /dev/null +++ b/docs/articles/settings/custom/Naming-convention.md @@ -0,0 +1,79 @@ +--- +uid: Mapster.Settings.Custom.NamingConvention +title: "Settings - Naming convention" +--- + +## Flexible name + +By default, Mapster will map property with case sensitive name. You can adjust to flexible name mapping by setting `NameMatchingStrategy.Flexible` to `NameMatchingStrategy` method. This setting will allow matching between `PascalCase`, `camelCase`, `lower_case`, and `UPPER_CASE`. + +```csharp +//global +TypeAdapterConfig.GlobalSettings.Default.NameMatchingStrategy(NameMatchingStrategy.Flexible); + +//type pair +TypeAdapterConfig.NewConfig().NameMatchingStrategy(NameMatchingStrategy.Flexible); +``` + +## Ignore cases + +Flexible name could not map between `MiXcAsE` and `MixCase`, because flexible name will break `MiXcAsE` into `Mi-Xc-As-E` rather than just `Mix-Case`. In this case, we need to use `IgnoreCase` to perform case insensitive matching. + +```csharp +TypeAdapterConfig.GlobalSettings.Default.NameMatchingStrategy(NameMatchingStrategy.IgnoreCase); +``` + +## Prefix & Replace + +For custom rules, you can use either `ConvertSourceMemberName` or `ConvertDestinationMemberName` up to which side you would like to convert. For example, you might would like to add `m_` to all properties. + +```csharp +TypeAdapterConfig.NewConfig() + .NameMatchingStrategy(NameMatchingStrategy.ConvertSourceMemberName(name => "m_" + name)); +``` + +This example is to replace foreign letter from name. + +```csharp +TypeAdapterConfig.NewConfig() + .NameMatchingStrategy(NameMatchingStrategy.ConvertSourceMemberName(name => name.Replace("Ä", "A")); +``` + +## Naming Convention with `IDictionary` + +If you would like to change case from POCO to `IDictionary` to camelCase, you can use `ToCamelCase`. Another way around, if you would like to map `IDictionary` back to POCO, you can use `FromCamelCase`. + +```csharp +TypeAdapterConfig>.NewConfig() + .NameMatchingStrategy(NameMatchingStrategy.ToCamelCase); +TypeAdapterConfig, Poco>.NewConfig() + .NameMatchingStrategy(NameMatchingStrategy.FromCamelCase); +``` + +> [!NOTE] +> Mapping from `IDictionary` to POCO, you can also use `Flexible` or `IgnoreCase`, but both will be slower since it will scan through dictionary entries rather than lookup. + +## Rule based Naming using `GetMemberName` Extension Method + +You can change name based on rule by `GetMemberName` method. For example, if we would like to rename property based on `JsonProperty` attribute. + +```csharp +TypeAdapterConfig.GlobalSettings.Default + .GetMemberName(member => member.GetCustomAttributes(true) + .OfType() + .FirstOrDefault()?.PropertyName); //if return null, property will not be renamed +``` + +Then in your class: + +```csharp +public class Poco +{ + [JsonProperty("code")] + public string Id { get; set; } + + ... +} +``` + +With above config, `Id` will be mapped to `code`. diff --git a/docs/articles/settings/custom/Rule-based-member-mapping.md b/docs/articles/settings/custom/Rule-based-member-mapping.md new file mode 100644 index 00000000..7901dd48 --- /dev/null +++ b/docs/articles/settings/custom/Rule-based-member-mapping.md @@ -0,0 +1,82 @@ +--- +uid: Mapster.Settings.Custom.RuleBasedMapping +title: "Settings - Rule-based member mapping" +--- + +By default, Mapster will include public fields and properties, but we can change this behavior by `IncludeMember` and `IgnoreMember` method. The methods require predicate, and input types of predicate are: + +```csharp +public interface IMemberModel +{ + Type Type { get; } + string Name { get; } + object Info { get; } + AccessModifier SetterModifier { get; } + AccessModifier AccessModifier { get; } + IEnumerable GetCustomAttributes(bool inherit); +} + +public enum MemberSide +{ + Source, + Destination, +} +``` + +## Not allow fields + +If you would like to allow only properties not public field to be mapped, you can check from `Info`. Possible values could be `PropertyInfo`, `FieldInfo`, or `ParameterInfo`. In this case, we will reject member of type `FieldInfo`. + +```csharp +TypeAdapterConfig.GlobalSettings.Default + .IgnoreMember((member, side) => member.Info is FieldInfo); +``` + +## Allow only some list of types to be mapped + +Suppose you are working with EF, and you would like to skip all navigation properties. Then we will allow only short list of types. + +### Allow by types + +```csharp +TypeAdapterConfig.GlobalSettings.Default + .IgnoreMember((member, side) => !validTypes.Contains(member.Type)); +``` + +### Allow by Namespaces + +```csharp +TypeAdapterConfig.GlobalSettings.Default + .IgnoreMember((member, side) => !member.Type.Namespace.StartsWith("System")); +``` + +### Allow internal members + +If you would like to map members marked as internal, you can do it by: + +```csharp + TypeAdapterConfig.GlobalSettings.Default + .IncludeMember((member, side) => member.AccessModifier == AccessModifier.Internal + || member.AccessModifier == AccessModifier.ProtectedInternal); +``` + +### Allow only `DataMember` attribute + +If you would like to include all members decorated with `DataMember` attribute, and ignore all members with no `DataMember` attribute, you can set up by: + +```csharp +TypeAdapterConfig.GlobalSettings.Default + .IncludeMember((member, side) => member.GetCustomAttributes(true).OfType().Any()); +TypeAdapterConfig.GlobalSettings.Default + .IgnoreMember((member, side) => !member.GetCustomAttributes(true).OfType().Any()); +``` + +### Turn-off non-public setters using `IgnoreMember` Extension Method + +Mapster always allows non-public setters. But you can override by: + +```csharp +TypeAdapterConfig.GlobalSettings.Default + .IgnoreMember((member, side) => side == MemberSide.Destination + && member.SetterModifier != AccessModifier.Public); +``` diff --git a/docs/articles/settings/custom/Setting-by-attributes.md b/docs/articles/settings/custom/Setting-by-attributes.md new file mode 100644 index 00000000..b81dcd22 --- /dev/null +++ b/docs/articles/settings/custom/Setting-by-attributes.md @@ -0,0 +1,108 @@ +--- +uid: Mapster.Settings.Custom.Attributes +title: "Settings - Setting by attributes" +--- + +## `AdaptIgnore` attribute + +When a property decorated with `[AdaptIgnore]`, that property will be excluded from Mapping. For example, if we would like to exclude price to be mapped. + +```csharp +public class Product { + public string Id { get; set; } + public string Name { get; set; } + + [AdaptIgnore] + public decimal Price { get; set; } +} +``` + +`[AdaptIgnore]` will both ignore when type are used as source or destination. You can ignore only one side by passing `MemberSide`. + +```csharp +public class Product { + public string Id { get; set; } + public string Name { get; set; } + + [AdaptIgnore(MemberSide.Source)] + public decimal Price { get; set; } +} +``` + +Above example, `Price` will be ignored only when `Product` is used as source. + +## `IgnoreAttribute` usage + +You can ignore members annotated with any attributes by using the `IgnoreAttribute` method. + +```csharp +TypeAdapterConfig.GlobalSettings.Default + .IgnoreAttribute(typeof(JsonIgnoreAttribute)); +``` + +However `IgnoreAttribute` will ignore both source and destination. If you would like to ignore only one side, you can use `IgnoreMember`. + +```csharp +config.IgnoreMember((member, side) => member.HasCustomAttribute(typeof(NotMapAttribute)) && side == MemberSide.Source); +``` + +## `AdaptMember` attribute + +`AdaptMember` attribute allows you to customize member mapping by attributes. + +### Map to different name + +With `AdaptMember` attribute, you can specify name of source or target to be mapped. For example, if we would like to map `Id` to `Code`. + +```csharp +public class Product { + [AdaptMember("Code")] + public string Id { get; set; } + public string Name { get; set; } +} +``` + +### Map to non-public members + +By default, Mapster only map public members. You can enable mapping to non-public members by `AdaptMember` attribute: + +```csharp +public class Product { + [AdaptMember] + private string HiddenId { get; set; } + public string Name { get; set; } +} +``` + +### Rename from custom attributes + +You can rename member to be matched by `GetMemberName`. For example, if we would like to rename property based on `JsonProperty` attribute. + +```csharp +TypeAdapterConfig.GlobalSettings.Default + .GetMemberName(member => member.GetCustomAttributes(true) + .OfType() + .FirstOrDefault()?.PropertyName); //if return null, property will not be renamed +``` + +### Using `IncludeAttribute` + +And if we would like to include non-public members decorated with `JsonProperty` attribute, we can do it by `IncludeAttribute`. + +```csharp +TypeAdapterConfig.GlobalSettings.Default + .IncludeAttribute(typeof(JsonPropertyAttribute)); +``` + +### Using the `UseDestinationValue` attribute + +You can tell Mapster to use existing property object to map data rather than create new object. + +```csharp +public class Order { + public string Id { get; set; } + + [UseDestinationValue] + public ICollection Items { get; } = new List(); +} +``` diff --git a/docs/articles/settings/custom/Two-ways.md b/docs/articles/settings/custom/Two-ways.md new file mode 100644 index 00000000..cc0ec0ae --- /dev/null +++ b/docs/articles/settings/custom/Two-ways.md @@ -0,0 +1,60 @@ +--- +uid: Mapster.Settings.Custom.TwoWaysMapping +title: "Settings - Two-ways mapping" +--- + +If you need to map object from POCO to DTO, and map back from DTO to POCO. You can define the setting once by using `TwoWays`. + +```csharp +TypeAdapterConfig + .NewConfig() + .TwoWays() + .Map(dto => dto.Code, poco => poco.Id); //<-- this setting will apply dto.Code = poco.Id & poco.Id = dto.Code for reverse mapping +``` + +NOTE: `TwoWays` command need to call before setting to take effect. + +```csharp +TypeAdapterConfig + .NewConfig() + .Map(dto => dto.Foo, poco => poco.Bar) //<-- this map only apply to Poco->Dto + .TwoWays() + .Map(dto => dto.Foz, poco => poco.Baz); //<-- this map will apply both side +``` + +## Flattening + +By default, Mapster will perform flattening. + +```csharp +class Staff { + public string Name { get; set; } + public Staff Supervisor { get; set; } + ... +} + +struct StaffDto { + public string SupervisorName { get; set; } +} +``` + +Above example, without any setup, you can map from POCO to DTO and you will get `SupervisorName` from `Supervisor.Name`. + +## Using `Unflattening` + +However, unflattening process needed to be defined. You can map to `Supervisor.Name` from `SupervisorName` by `Unflattening` setting. + +```csharp +TypeAdapterConfig.NewConfig() + .Unflattening(true); +``` + +## Using `TwoWays` + +Or you can use `TwoWays` to define both flattening and unflattening in one setting. + +```csharp +TypeAdapterConfig + .NewConfig() + .TwoWays(); //<-- this will also map poco.Supervisor.Name = dto.SupervisorName for reverse mapping +``` diff --git a/docs/articles/settings/custom/toc.yml b/docs/articles/settings/custom/toc.yml new file mode 100644 index 00000000..19010846 --- /dev/null +++ b/docs/articles/settings/custom/toc.yml @@ -0,0 +1,25 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/dotnet/docfx/main/schemas/toc.schema.json +- name: Custom mapping + uid: Mapster.Settings.Custom.Mapping + href: Custom-mapping.md +- name: Custom naming convention + uid: Mapster.Settings.Custom.NamingConvention + href: Naming-convention.md +- name: Setting by attributes + uid: Mapster.Settings.Custom.Attributes + href: Setting-by-attributes.md +- name: Ignoring members + uid: Mapster.Settings.Custom.IgnoringMembers + href: Ignoring-members.md +- name: Rule-based member matching + uid: Mapster.Settings.Custom.RuleBasedMapping + href: Rule-based-member-mapping.md +- name: Mapping readonly prop + uid: Mapster.Settings.Custom.ReadonlyProperty + href: Mapping-readonly-prop.md +- name: Mapping non-public members + uid: Mapster.Settings.Custom.NonPublicMembers + href: Mapping-non-public-members.md +- name: Two ways & unflattening mapping + uid: Mapster.Settings.Custom.TwoWaysMapping + href: Two-ways.md diff --git a/docs/articles/settings/toc.yml b/docs/articles/settings/toc.yml new file mode 100644 index 00000000..2d4a0ea4 --- /dev/null +++ b/docs/articles/settings/toc.yml @@ -0,0 +1,21 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/dotnet/docfx/main/schemas/toc.schema.json +- name: Constructor mapping + uid: Mapster.Settings.ConstructorMapping + href: Constructor-mapping.md +- name: Before & after mapping + uid: Mapster.Settings.BeforeAfterMapping + href: Before-after-mapping.md +- name: Setting values + uid: Mapster.Settings.SettingValues + href: Setting-values.md +- name: Shallow & merge mapping + uid: Mapster.Settings.ShallowMerge + href: Shallow-merge.md +- name: Recursive & object references + uid: Mapster.Settings.ObjectReferences + href: Object-references.md +- name: Custom conversion logic + uid: Mapster.Settings.CustomConversionLogic + href: Custom-conversion-logic.md +- name: Custom member matching logic + href: custom/toc.yml \ No newline at end of file diff --git a/docs/articles/toc.yml b/docs/articles/toc.yml new file mode 100644 index 00000000..245cfb26 --- /dev/null +++ b/docs/articles/toc.yml @@ -0,0 +1,18 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/dotnet/docfx/main/schemas/toc.schema.json +# NOTE: starting from here you can add side navigation items by nesting them under "items" +items: +- name: Mapping + href: mapping/toc.yml + topicHref: xref:Mapster.Mapping.BasicUsages +- name: Configuration + href: configuration/toc.yml + topicHref: xref:Mapster.Configuration.Overview +- name: Settings + href: settings/toc.yml + topicHref: xref:Mapster.Settings.Custom.Mapping +- name: Packages + href: packages/toc.yml + topicHref: xref:Mapster.Packages.Async +- name: Tools + href: tools/toc.yml + topicHref: xref:Mapster.Tools.MapsterTool.Overview \ No newline at end of file diff --git a/docs/articles/tools/TextTemplate.md b/docs/articles/tools/TextTemplate.md new file mode 100644 index 00000000..13d94c30 --- /dev/null +++ b/docs/articles/tools/TextTemplate.md @@ -0,0 +1,107 @@ +--- +uid: Mapster.Tools.TextTemplate +--- + +# Text Template + +```nuget + PM> Install-Package ExpressionTranslator +``` + +Although Mapster allow you to [step-into debugging](xref:Mapster.Packages.ExpressionDebugging), but all mapping are generated at runtime. Therefore, error will be captured at runtime, and we also lose the power of static analysis to find the usage. + +Here are steps to add code generation. + +1. Create text template + + ![add-new-text-item](https://user-images.githubusercontent.com/5763993/56052976-f9377580-5d7c-11e9-841c-0a911fdb3a7f.png) + +2. In template, add references & mapping logic + + ```xml + <#@ template debug="true" language="C#" #> + <#@ output extension=".g.cs" #> + <#@ Assembly Name="netstandard" #> + <#@ Assembly Name="System.Core" #> + <#@ Assembly Name="System.Runtime" #> + <#@ Assembly Name="System.Linq.Expressions" #> + <#@ Assembly Name="$(TargetDir)/$(ProjectName).dll" #> + <#@ Assembly Name="$(TargetDir)/Mapster.dll" #> + <#@ Assembly Name="$(TargetDir)/ExpressionTranslator.dll" #> + <#@ import namespace="ExpressionDebugger" #> + <#@ import namespace="Mapster" #> + <#@ import namespace="YourNamespace" #> + ``` + + ```csharp + <# + //this line is to generate all nested mapping in 1 file + TypeAdapterConfig.GlobalSettings.SelfContainedCodeGeneration = true; + var cust = default(Customer); + var def = new ExpressionDefinitions + { + IsStatic = true, //change to false if you want instance + MethodName = "Map", + Namespace = "YourNamespace", + TypeName = "CustomerMapper" + }; + var code = cust.BuildAdapter() + .CreateMapExpression() + .ToScript(def); + WriteLine(code); + #> + ``` + +3. Generate code by right click on template file, and select `Run Custom Tool`. + +That's it. Done! + +--- + +## Additional Information + +### Links + +- Example: [CustomerMapper]( +https://github.com/MapsterMapper/Mapster/blob/master/src/Benchmark/CustomerMapper.tt) +- [code generation and T4 Documentation](https://docs.microsoft.com/en-us/visualstudio/modeling/code-generation-and-t4-text-templates?view=vs-2019) (Microsoft) + +### Q & A + +Q: How to pass lambda to Before/After mapping? +A: Please use [`BeforeMappingInline` and `AfterMappingInline` instead](xref:Mapster.Settings.BeforeAfterMapping) + +Q: Can I generate multiple outputs from single template? +A: You can: [How to: Create multiple output files from a single t4 template using tangible edi](https://stackoverflow.com/questions/33575419/how-to-create-multiple-output-files-from-a-single-t4-template-using-tangible-edi) + +Q: After running template file, it said library XXX not found. +A: Some unused libraries will be excluded during build. You can direct reference to dll in template file. Or [tell Visual Studio to copy all reference libraries to output](https://stackoverflow.com/questions/43837638/how-to-get-net-core-projects-to-copy-nuget-references-to-build-output/43841481). + +```xml + +true + + +true +``` + +Q: After running template file on Mac, it said `netstandard` is not found. +A: You need direct reference. + +```xml + +<#@ Assembly Name="netstandard" #> + + +<#@ Assembly Name="/usr/local/share/dotnet/sdk/2.2.103/Microsoft/Microsoft.NET.Build.Extensions/net461/lib/netstandard.dll" #> +``` + +Q: After running template file in .NET Core project on Windows, it said, System.Runtime version 4.2.x.x not found. +A: You can build using .NET Framework version. Otherwise, [you need to update assembly binding in Visual Studio config file](https://stackoverflow.com/questions/51550265/t4-template-could-not-load-file-or-assembly-system-runtime-version-4-2-0-0) + +Q: After running template file, it said Compile items are duplicated. +A: You can set property to skip generated items. + +```xml +**/*.g.cs +``` diff --git a/docs/articles/tools/mapster-tool/Attribute-base-Code-generation.md b/docs/articles/tools/mapster-tool/Attribute-base-Code-generation.md new file mode 100644 index 00000000..07dcd00e --- /dev/null +++ b/docs/articles/tools/mapster-tool/Attribute-base-Code-generation.md @@ -0,0 +1,120 @@ +--- +uid: Mapster.Tools.MapsterTool.AttributesDtoGeneration +title: Mapster Tool +--- + +## Attribute-based Dto model generation + +Annotate your class with `[AdaptFrom]`, `[AdaptTo]`, or `[AdaptTwoWays]`. + +Example: + +```csharp +[AdaptTo("[name]Dto")] +public class Student { + ... +} +``` + +Then Mapster will generate: + +```csharp +public class StudentDto { + ... +} +``` + +### Ignore some properties on generation + +By default, code generation will ignore properties that annotated `[AdaptIgnore]` attribute. But you can add more settings which include `IgnoreAttributes`, `IgnoreNoAttributes`, `IgnoreNamespaces`. + +Example: + +```csharp +[AdaptTo("[name]Dto", IgnoreNoAttributes = new[] { typeof(DataMemberAttribute) })] +public class Student { + + [DataMember] + public string Name { get; set; } //this property will be generated + + public string LastName { get; set; } //this will not be generated +} +``` + +### Change property types + +By default, if property type annotated with the same adapt attribute, code generation will forward to that type. (For example, `Student` has `ICollection`, after code generation `StudentDto` will has `ICollection`). + +You can override this by `[PropertyType(typeof(Target))]` attribute. This annotation can be annotated to either on property or on class. + +For example: + +```csharp +[AdaptTo("[name]Dto")] +public class Student { + public ICollection Enrollments { get; set; } +} + +[AdaptTo("[name]Dto"), PropertyType(typeof(DataItem))] +public class Enrollment { + [PropertyType(typeof(string))] + public Grade? Grade { get; set; } +} +``` + +This will generate: + +```csharp +public class StudentDto { + public ICollection Enrollments { get; set; } +} +public class EnrollmentDto { + public string Grade { get; set; } +} +``` + +#### Generate readonly properties + +For `[AdaptTo]` and `[AdaptTwoWays]`, you can generate readonly properties with `MapToConstructor` setting. + +For example: + +```csharp +[AdaptTo("[name]Dto", MapToConstructor = true)] +public class Student { + public string Name { get; set; } +} +``` + +This will generate: + +```csharp +public class StudentDto { + public string Name { get; } + + public StudentDto(string name) { + this.Name = name; + } +} +``` + +### Generate nullable properties + +For `[AdaptFrom]`, you can generate nullable properties with `IgnoreNullValues` setting. + +For example: + +```csharp +[AdaptFrom("[name]Merge", IgnoreNullValues = true)] +public class Student { + public int Age { get; set; } +} +``` + +This will generate: + +```csharp +public class StudentMerge { + public int? Age { get; set; } +} +``` diff --git a/docs/articles/tools/mapster-tool/Attribute-based-Extension-generation.md b/docs/articles/tools/mapster-tool/Attribute-based-Extension-generation.md new file mode 100644 index 00000000..ad4d7a81 --- /dev/null +++ b/docs/articles/tools/mapster-tool/Attribute-based-Extension-generation.md @@ -0,0 +1,32 @@ +--- +uid: Mapster.Tools.MapsterTool.AttributeBasedExtensionGeneration +title: Mapster Tool +--- + +## Generate extension methods via attributes + +### Generate using `[GenerateMapper]` attribute + +For any POCOs annotate with `[AdaptFrom]`, `[AdaptTo]`, or `[AdaptTwoWays]`, you can add `[GenerateMapper]` in order to generate extension methods. + +Example: + +```csharp +[AdaptTo("[name]Dto"), GenerateMapper] +public class Student { + ... +} +``` + +Then Mapster will generate: + +```csharp +public class StudentDto { + ... +} +public static class StudentMapper { + public static StudentDto AdaptToDto(this Student poco) { ... } + public static StudentDto AdaptTo(this Student poco, StudentDto dto) { ... } + public static Expression> ProjectToDto => ... +} +``` diff --git a/docs/articles/tools/mapster-tool/Configuration-based-Code-generation.md b/docs/articles/tools/mapster-tool/Configuration-based-Code-generation.md new file mode 100644 index 00000000..82b92741 --- /dev/null +++ b/docs/articles/tools/mapster-tool/Configuration-based-Code-generation.md @@ -0,0 +1,33 @@ +--- +uid: Mapster.Tools.MapsterTool.ConfigurationBasedCodeGeneration +title: Mapster Tool +--- + +## Configuration-based code generation + +If you have configuration, it must be in `IRegister` + +```csharp +public class MyRegister : IRegister +{ + public void Register(TypeAdapterConfig config) + { + config.NewConfig(); + } +} +``` + +### Advanced configuration + +You can also generate extension methods and add extra settings from configuration. + +```csharp +public class MyRegister : IRegister +{ + public void Register(TypeAdapterConfig config) + { + config.NewConfig() + .GenerateMapper(MapType.Map | MapType.MapToTarget); + } +} +``` diff --git a/docs/articles/tools/mapster-tool/Fluent-API-Code-generation.md b/docs/articles/tools/mapster-tool/Fluent-API-Code-generation.md new file mode 100644 index 00000000..1d4076ef --- /dev/null +++ b/docs/articles/tools/mapster-tool/Fluent-API-Code-generation.md @@ -0,0 +1,175 @@ +--- +uid: Mapster.Tools.MapsterTool.FluentAPI +title: Mapster Tool +--- + +## Configuration class + +Create a configuration class implement `ICodeGenerationRegister`. + +```csharp +public class MyRegister : ICodeGenerationRegister +{ + public void Register(CodeGenerationConfig config) + { + config.AdaptTo("[name]Dto") + .ForAllTypesInNamespace(Assembly.GetExecutingAssembly(), "Sample.CodeGen.Domains"); + + config.GenerateMapper("[name]Mapper") + .ForType() + .ForType(); + } +} +``` + +## Generate models + +Declare `AdaptFrom`, `AdaptTo`, or `AdaptTwoWays`. + +Example: + +```csharp +config.AdaptTo("[name]Dto") + .ForType(); +``` + +Then Mapster will generate: + +```csharp +public class StudentDto { + ... +} +``` + +### Add types to generate + +You can add types by `ForTypes`, `ForAllTypesInNamespace`, `ForType<>`, and you can remove added types using `ExcludeTypes`: + +```csharp +config.AdaptTo("[name]Dto") + .ForAllTypesInNamespace(Assembly.GetExecutingAssembly(), "Sample.CodeGen.Domains") + .ExcludeTypes(typeof(SchoolContext)) + .ExcludeTypes(type => type.IsEnum) +``` + +### Ignore some properties on generation + +By default, code generation will ignore properties that annotated `[AdaptIgnore]` attribute. But you can add more settings which include `IgnoreAttributes`, `IgnoreNoAttributes`, `IgnoreNamespaces`. + +Example: + +```csharp +config.AdaptTo("[name]Dto") + .ForType() + .IgnoreNoAttributes (typeof(DataMemberAttribute)); + +public class Student { + [DataMember] + public string Name { get; set; } //this property will be generated + public string LastName { get; set; } //this will not be generated +} +``` + +### Ignore a property + +```csharp +config.AdaptTo("[name]Dto") + .ForType(cfg => { + cfg.Ignore(poco => poco.LastName); + }); +``` + +### Change a property name, type + +```csharp +config.AdaptTo("[name]Dto") + .ForType(cfg => { + cfg.Map(poco => poco.LastName, "Surname"); //change property name + cfg.Map(poco => poco.Grade, typeof(string)); //change property type + }); +``` + +### Forward property types + +By default, code generation will forward type on the same declaration. (For example, `Student` has `ICollection`, after code generation `StudentDto` will has `ICollection`). + +You can override this by `AlterType`. + +```csharp +config.AdaptTo("[name]Dto") + .ForAllTypesInNamespace(Assembly.GetExecutingAssembly(), "Sample.CodeGen.Domains") + .AlterType(); //forward all Student to Person +``` + +### Generate readonly properties + +For `AdaptTo` and `AdaptTwoWays`, you can generate readonly properties with `MapToConstructor` setting. + +For example: + +```csharp +config.AdaptTo("[name]Dto") + .ForType() + .MapToConstructor(true); +``` + +This will generate: + +```csharp +public class StudentDto { + public string Name { get; } + + public StudentDto(string name) { + this.Name = name; + } +} +``` + +### Generate nullable properties + +For `AdaptFrom`, you can generate nullable properties with `IgnoreNullValues` setting. + +For example: + +```csharp +config.AdaptFrom("[name]Merge") + .ForType() + .IgnoreNullValues(true); +``` + +This will generate: + +```csharp +public class StudentMerge { + public int? Age { get; set; } +} +``` + +## Generate extension methods + +### Generate using `GenerateMapper` + +For any POCOs declared with `AdaptFrom`, `AdaptTo`, or `AdaptTwoWays`, you can declare `GenerateMapper` in order to generate extension methods. + +Example: + +```csharp +config.AdaptTo("[name]Dto") + .ForType(); + +config.GenerateMapper("[name]Mapper") + .ForType(); +``` + +Then Mapster will generate: + +```csharp +public class StudentDto { + ... +} +public static class StudentMapper { + public static StudentDto AdaptToDto(this Student poco) { ... } + public static StudentDto AdaptTo(this Student poco, StudentDto dto) { ... } + public static Expression> ProjectToDto => ... +} +``` diff --git a/docs/articles/tools/mapster-tool/Interface-base-Code-generation.md b/docs/articles/tools/mapster-tool/Interface-base-Code-generation.md new file mode 100644 index 00000000..6fca9b8d --- /dev/null +++ b/docs/articles/tools/mapster-tool/Interface-base-Code-generation.md @@ -0,0 +1,47 @@ +--- +uid: Mapster.Tools.MapsterTool.Interfaces +title: Mapster Tool +--- + +## Generate mapper from interface + +Annotate your interface with `[Mapper]` in order for tool to pickup for generation. + +This is example interface. + +```csharp +[Mapper] +public interface IProductMapper +{ + ProductDTO Map(Product customer); +} +``` + +You can add multiple members as you want. All member names are flexible, but signature must be in following patterns: + +```csharp +[Mapper] +public interface ICustomerMapper +{ + //for queryable + Expression> ProjectToDto { get; } + + //map from POCO to DTO + CustomerDTO MapToDto(Customer customer); + + //map to existing object + Customer MapToExisting(CustomerDTO dto, Customer customer); +} +``` + +If you have configuration, it must be in `IRegister` + +```csharp +public class MyRegister : IRegister +{ + public void Register(TypeAdapterConfig config) + { + config.NewConfig(); + } +} +``` diff --git a/docs/articles/tools/mapster-tool/Mapster-Tool-Overview.md b/docs/articles/tools/mapster-tool/Mapster-Tool-Overview.md new file mode 100644 index 00000000..00d551bb --- /dev/null +++ b/docs/articles/tools/mapster-tool/Mapster-Tool-Overview.md @@ -0,0 +1,197 @@ +--- +uid: Mapster.Tools.MapsterTool.Overview +--- + +# Mapster Tool Overview + +The Mapster.Tool is a command-line tool that helps you generate DTOs (Data Transfer Objects) and mapping code based on your domain models or interfaces. It supports various generation strategies, including Fluent API configuration, attribute-based annotations, and interface-based definitions. + +## Install Mapster.Tool + +```bash +#skip this step if you already have dotnet-tools.json +dotnet new tool-manifest + +dotnet tool install Mapster.Tool +``` + +## Install Mapster + +For lightweight dependency, you can just install `Mapster.Core`. + +```nuget +PM> Install-Package Mapster.Core +``` + +However, if you need `TypeAdapterConfig` for advance configuration, you still need `Mapster`. + +```nuget +PM> Install-Package Mapster +``` + +## Commands + +Mapster.Tool provides 3 commands: + +- **model**: generate models from entities +- **extension**: generate extension methods from entities +- **mapper**: generate mappers from interfaces + +And Mapster.Tool provides following options + +- `-a`: Define input assembly +- `-b`: Specify base namespace for generating dynamic outputs & namespaces +- `-n`: Define namespace of generated classes +- `-o`: Define output directory +- `-p`: Print full type name (if your DTOs/POCOs having the same name) +- `-r`: Generate record types instead of POCO types +- -s: Skip generating existing files + +### csproj integration + +#### Generate manually + +add following code to your `csproj` file: + +```xml + + + + + + + +``` + +to generate run following command on `csproj` file directory: + +```bash +dotnet msbuild -t:Mapster +``` + +#### Generate automatically on build + +add following code to your `csproj` file: + +```xml + + + + + + +``` + +#### Clean up + +add following code to your `csproj` file: + +```xml + + + + + + +``` + +to clean up run following command: + +```bash +dotnet msbuild -t:CleanGenerated +``` + +#### Generate full type name + +If your POCOs and DTOs have the same name, you might need to generate using full type name, by adding `-p` flag: + +```xml + + + + + + + +``` + +#### Dynamic outputs & namespaces + +For example you have following structure. + +```text +Sample.CodeGen +- Domains + - Sub1 + - Domain1 + - Sub2 + - Domain2 +``` + +And if you can specify base namespace as `Sample.CodeGen.Domains`: + +```xml + +``` + +Code will be generated to: + +```text +Sample.CodeGen +- Generated + - Sub1 + - Dto1 + - Sub2 + - Dto2 +``` + +### Generate DTOs and mapping codes + +There are 3 flavors, to generate DTOs and mapping codes: + +- [Fluent API](xref:Mapster.Tools.MapsterTool.FluentAPI): if you don't want to touch your domain classes, or generate DTOs from domain types in different assembly. +- [Attributes](xref:Mapster.Tools.MapsterTool.AttributesDtoGeneration): if you would like to keep mapping declaration close to your domain classes. +- [Interfaces](xref:Mapster.Tools.MapsterTool.Interfaces): if you already have DTOs, and you would like to define mapping through interfaces. + +### Sample + +- https://github.com/MapsterMapper/Mapster/tree/master/src/Sample.CodeGen + +### Troubleshooting + +#### System.IO.FileNotFoundException + +If you get an error similar to `Unhandled exception. System.IO.FileNotFoundException: Could not load file or assembly '...'. The system cannot find the file specified.` and you are using Mapster.Tool 8.4.1 or newer, then you can try one of the following workarounds: + +**Workaround 1** + +Ensure that you are using Mapster.Tool version 8.4.2-pre01 or newer. The latest pre-release version is: [![NuGet](https://img.shields.io/nuget/vpre/Mapster.Tool.svg)](https://www.nuget.org/packages/Mapster.Tool) + +**Workaround 2** + +Add `true` to your csproj file as follows: + +```xml + + + net8.0 + true + + [...] +``` + +**Workaround 3** + +Change your `dotnet build` command to `dotnet build -p:CopyLocalLockFileAssemblies=true` as follows: + +```xml + + [...] + + + [...] + + [...] + +``` diff --git a/docs/articles/tools/mapster-tool/toc.yml b/docs/articles/tools/mapster-tool/toc.yml new file mode 100644 index 00000000..627f1be7 --- /dev/null +++ b/docs/articles/tools/mapster-tool/toc.yml @@ -0,0 +1,19 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/dotnet/docfx/main/schemas/toc.schema.json +- name: Overview and Installation + uid: Mapster.Tools.MapsterTool.Overview + href: Mapster-Tool-Overview.md +- name: Fluent API + uid: Mapster.Tools.MapsterTool.FluentAPI + href: Fluent-API-Code-generation.md +- name: Attribute-based Dto model generation + uid: Mapster.Tools.MapsterTool.AttributesDtoGeneration + href: Attribute-base-Code-generation.md +- name: Configuration based Code generation + uid: Mapster.Tools.MapsterTool.ConfigurationBasedCodeGeneration + href: Configuration-based-Code-generation.md +- name: Attribute based Extensions generation + uid: Mapster.Tools.MapsterTool.AttributeBasedExtensionGeneration + href: Attribute-based-Extension-generation.md +- name: Interfaces + uid: Mapster.Tools.MapsterTool.Interfaces + href: Interface-base-Code-generation.md diff --git a/docs/articles/tools/toc.yml b/docs/articles/tools/toc.yml new file mode 100644 index 00000000..88d5ca38 --- /dev/null +++ b/docs/articles/tools/toc.yml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/dotnet/docfx/main/schemas/toc.schema.json +items: +- name: Mapster Tool + topicHref: xref:Mapster.Tools.MapsterTool.Overview + href: mapster-tool/toc.yml +- name: Text Template + uid: Mapster.Tools.TextTemplate + href: TextTemplate.md diff --git a/docs/config/filterConfig.yml b/docs/config/filterConfig.yml new file mode 100644 index 00000000..7d1cde95 --- /dev/null +++ b/docs/config/filterConfig.yml @@ -0,0 +1,14 @@ +# YAML-Language Server: $schema=https://raw.githubusercontent.com/dotnet/docfx/main/schemas/filterconfig.schema.json +# Filter configuration file +# More information about filter configuration can be found at https://dotnet.github.io/docfx/docs/dotnet-api-docs.html#custom-filter-rules +# To exclude specific namespaces, types, or members from the generated API documentation, uncomment and modify the rules below, using appropriate regular expressions. +# apiRules: +# - exclude: +# uidRegex: ^PartialNamespace\.NextNamespace$ +# type: Namespace +# - exclude: +# uidRegex: SomeTypeName$ +# type: Type +# - exclude: +# uidRegex: __SomeMemberName(\(.*\))?$ +# type: Member \ No newline at end of file diff --git a/docs/docfx.json b/docs/docfx.json new file mode 100644 index 00000000..4220df2e --- /dev/null +++ b/docs/docfx.json @@ -0,0 +1,111 @@ +{ + "$schema": "https://raw.githubusercontent.com/dotnet/docfx/main/schemas/docfx.schema.json", + // "metadata": [ + // { + // "src": [ + // { + // "files": [ + // "**/Mapster.sln" + // ], + // "src": "../src" + // } + // ], + // "includeExplicitInterfaceImplementations": false, + // "disableDefaultFilter": false, + // // "filter": "config/filterConfig.yml", + // "properties": { + // "TargetFramework" : "net9.0" + // }, + // "noRestore": false, + // "output": "api", + // "memberLayout": "samePage", + // "outputFormat": "apiPage" + // } + // ], + "build": { + "globalMetadata": { + "_appTitle": "Mapster - The Mapper of Your Domain", + "_appName": "Mapster", + "_appFaviconPath": "images/mapster-logo.svg", + "_appLogoPath": "images/mapster-logo.svg", + "_copyrightFooter": "© 2025 Here comes your Name, All rights reserved.", + "_ownerName": "Awesome Developer", + "_githubProfileLink": "https://github.com/MapsterMapper", + "_enableSearch": true, + "_disableAffix": false, + "_disableBreadcrumb": false, + "_disableNextArticle": false, + "_disableTocFilter": false, + "_disableToc": false, + "_lang": "en", + "_gitConribute": { + "branch": "development" + }, + "pdf": false + }, + "content": [ + { + "files": [ + "articles/**.md", + "articles/toc.yml", + "articles/**/toc.yml", + "toc.yml", + "index.md" + ], + "exclude": [ + "**/.include/**", + "articles/_Sidebar.md" + ] + }, + { + "src": "../", + "files": [ + "README.md" + ] + }, + { + "files": [ + + "api/Reference.md" + ], + // Include the following lines, if we are generating metadata API Docs + "exclude": [ + "api/**.yml", + "index.md" + ] + } + ], + "resource": [ + { + "files": [ + "images/*.{png,ico,svg}", + "articles/.assets/*.{png,gif,svg}" + ], + "dot": true + } + ], + "template": [ + "default", + "modern" + ], + "xref": [ "https://learn.microsoft.com/en-us/dotnet/.xrefmap.json" ], + "markdownEngineProperties": { + "markdigExtensions": [ + "yaml", + "definitionlists", + "diagrams", + "advanced", + "alerts", + "footers", + "footnotes", + "medialinks" + ] + }, + "output": "_site", + "sitemap": { + "baseUrl": "https://mapstermapper.github.io/Mapster", + "priority": 0.1, + "changefreq": "monthly" + } + } +} diff --git a/docs/images/mapster-logo.png b/docs/images/mapster-logo.png new file mode 100644 index 00000000..72ea8e10 Binary files /dev/null and b/docs/images/mapster-logo.png differ diff --git a/docs/images/mapster-logo.svg b/docs/images/mapster-logo.svg new file mode 100644 index 00000000..ae2543fd --- /dev/null +++ b/docs/images/mapster-logo.svg @@ -0,0 +1,88 @@ + + + + + + Layer 1 + + + + + + + + + + + + + diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..3e38bb07 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,6 @@ +--- +uid: Mapster.Overview +_layout: landing +--- + +[!INCLUDE [landing-page](../README.md)] diff --git a/docs/toc.yml b/docs/toc.yml new file mode 100644 index 00000000..b42c2d45 --- /dev/null +++ b/docs/toc.yml @@ -0,0 +1,12 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/dotnet/docfx/main/schemas/toc.schema.json +# NOTE: Those top level items will be shown as the Top Navigation Menu Entries +# IMPORTANT: Do not add the explicit toc.yml files under the appropriate folders here, as this would otherwhise attempt to show them in the top navigation instead of the side navigation TOCs. +# See: https://dotnet.github.io/docfx/docs/table-of-contents.html#navigation-bar +- name: Home + uid: Mapster.Overview + href: index.md +- name: Documentation + href: articles/ +- name: Reference + uid: Mapster.References + href: api/Reference.md