Skip to content
Open
128 changes: 128 additions & 0 deletions docs/articles/packages/DI-Configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
---
uid: Mapster.Packages.DependencyInjection.Configuration
title: "Packages - Configuration Support via DI"
---

## Installation

This package allows you to configure Mapster through `appsettings.json` and HostBuilder integration.

```nuget
PM> Install-Package Mapster.DependencyInjection
```

For basic dependency injection setup, see [Dependency Injection Support](xref:Mapster.Packages.DependencyInjection).

## Usage

### HostBuilder Integration

`UseMapster` simplifies setup by binding `MapsterOptions` from configuration and registering `TypeAdapterConfig`:

```csharp
var host = new HostBuilder()
.UseMapster() // binds from the "Mapster" section by default
.Build();
```

By default, `UseMapster` registers `TypeAdapterConfig.GlobalSettings`. To use an isolated `TypeAdapterConfig` instance instead:

```csharp
var host = new HostBuilder()
.UseMapster(useGlobalConfig: false)
.Build();
```

To bind from a different configuration section:

```csharp
var host = new HostBuilder()
.UseMapster(sectionName: "CustomMapster")
.Build();
```

If you want options/config registration without adding the default `IMapper` (`ServiceMapper`) registration:

```csharp
var host = new HostBuilder()
.UseMapster(registerDefaultMapper: false)
.Build();
```

### Configure Mapster in appsettings

You can configure Mapster's global `TypeAdapterConfig` switches through your `appsettings.json` file:

```json
{
"Mapster": {
"RequireExplicitMapping": true,
"AllowImplicitSourceInheritance": true
}
}
```

These options are bound to `IOptions<MapsterOptions>` and applied to `TypeAdapterConfig` on registration.

> [!NOTE]
> Expect the `MapsterOptions` to match the properties available on `TypeAdapterConfig`.

### Advanced Service Collection Extensions

For finer control, you can use the individual extension methods directly:

#### AddMapsterOptions

Registers `MapsterOptions` and binds them from configuration:

```csharp
services.AddMapsterOptions(context, sectionName: "CustomMapster");
```

Or provide a custom configuration delegate:

```csharp
services.AddMapsterOptions(context, configuration: ctx => ctx.Configuration.GetSection("MySection"));
```

#### AddTypeAdapterConfig

Registers `TypeAdapterConfig` as a singleton with several configuration options:

| Parameter | Default | Description |
| ----------------- | ------- | -------------------------------------------- |
| `useGlobalConfig` | `true` | Use `TypeAdapterConfig.GlobalSettings` |
| `useExisting` | `false` | Use existing registered config as base |
| `readFromOptions` | `true` | Apply bound `MapsterOptions` to the config |
| `configure` | `null` | Additional configuration delegate |

> [!IMPORTANT]
> Configuration options are applied in the order of the parameters. If `useExisting` is `true` and an existing `TypeAdapterConfig` is found, it will replace the config created by `useGlobalConfig`.

```csharp
services.AddTypeAdapterConfig(context, useGlobalConfig: false, configure: config =>
{
config.RequireExplicitMapping = true;
config.NewConfig<Source, Destination>()
.Map(dest => dest.FullName, src => $"{src.FirstName} {src.LastName}");
return config;
});
```

Complete example with `UseMapster`:

```csharp
var host = new HostBuilder()
.UseMapster(useGlobalConfig: false, configureMappers: (ctx, services) =>
{
services.AddTypeAdapterConfig(ctx, configure: config =>
{
config.NewConfig<Source, Destination>()
.Map(dest => dest.FullName, src => $"{src.FirstName} {src.LastName}");
return config;
});
})
.Build();
```

For service injection during mapping, see [Dependency Injection Support](xref:Mapster.Packages.DependencyInjection).
3 changes: 3 additions & 0 deletions docs/articles/packages/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
- name: Dependency Injection
uid: Mapster.Packages.DependencyInjection
href: Dependency-Injection.md
- name: Configuration Support
uid: Mapster.Packages.DependencyInjection.Configuration
href: DI-Configuration.md
- name: "EF 6 and EF Core Support"
uid: Mapster.Packages.EntityFramework
href: EF-6-and-EF-Core.md
Expand Down
10 changes: 10 additions & 0 deletions src/Mapster.DependencyInjection.Tests/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
global using System;
global using System.Collections.Generic;
global using MapsterMapper;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.Hosting;
global using Microsoft.Extensions.Options;
global using Microsoft.VisualStudio.TestTools.UnitTesting;
global using Shouldly;
global using Mapster;
115 changes: 115 additions & 0 deletions src/Mapster.DependencyInjection.Tests/HostBuilderExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
namespace Mapster.DependencyInjection.Tests;

[TestClass]
public class HostBuilderExtensionsTests
{
[TestMethod]
public void UseMapster_WithConfigureMappers_AllowsCustomTypeAdapterConfigConfiguration()
{
// Arrange
var host = new HostBuilder()
.UseMapster(
configureMappers: (ctx, services) =>
{
services.AddTypeAdapterConfig(ctx, useGlobalConfig: false, configure: config =>
{
config.NewConfig<ValueSource, ValueDest>()
.Map(dest => dest.Value, src => src.Value * 2);
return config;
});
})
.Build();

// Act
var mapper = host.Services.GetRequiredService<IMapper>();
var dest = mapper.Map<ValueDest>(new ValueSource { Value = 5 });

// Assert
dest.Value.ShouldBe(10);
}

[TestMethod]
public void UseMapster_Default_RegistersServices()
{
// Arrange
var host = new HostBuilder()
.UseMapster()
.Build();

// Act + Assert
host.Services.GetRequiredService<IOptions<MapsterOptions>>().ShouldNotBeNull();
host.Services.GetRequiredService<TypeAdapterConfig>().ShouldBe(TypeAdapterConfig.GlobalSettings);
host.Services.GetRequiredService<IMapper>().ShouldBeOfType<ServiceMapper>();
}

[TestMethod]
public void UseMapster_WhenCalledTwice_RunsConfigureMappersEachTime_ButRegistersCoreServicesOnce()
{
// Arrange
var configureMappersCalls = 0;
var typeAdapterConfigRegistrations = 0;
var mapperRegistrations = 0;
var mapsterOptionsConfigureRegistrations = 0;

var host = new HostBuilder()
.UseMapster(
configureMappers: (_, _) => configureMappersCalls++)
.UseMapster(
configureMappers: (_, _) => configureMappersCalls++)
.ConfigureServices((_, services) =>
{
typeAdapterConfigRegistrations = services.Count(d => d.ServiceType == typeof(TypeAdapterConfig));
mapperRegistrations = services.Count(d => d.ServiceType == typeof(IMapper));
mapsterOptionsConfigureRegistrations = services.Count(d => d.ServiceType == typeof(IConfigureOptions<MapsterOptions>));
})
.Build();

// Act + Assert
configureMappersCalls.ShouldBe(2);

typeAdapterConfigRegistrations.ShouldBe(1);
mapperRegistrations.ShouldBe(1);
mapsterOptionsConfigureRegistrations.ShouldBe(1);

host.Services.GetRequiredService<TypeAdapterConfig>().ShouldNotBeNull();
}

[TestMethod]
public void UseMapster_WhenUseGlobalConfigFalse_RegistersNewTypeAdapterConfigInstance()
{
// Arrange
var host = new HostBuilder()
.UseMapster(useGlobalConfig: false)
.Build();

// Act
var config = host.Services.GetRequiredService<TypeAdapterConfig>();

// Assert
ReferenceEquals(config, TypeAdapterConfig.GlobalSettings).ShouldBeFalse();
}

[TestMethod]
public void UseMapster_WhenRegisterMapsterSingletonFalse_DoesNotRegisterIMapper()
{
// Arrange
var host = new HostBuilder()
.UseMapster(registerDefaultMapper: false)
.Build();

// Act + Assert
host.Services.GetService<IMapper>().ShouldBeNull();
host.Services.GetRequiredService<TypeAdapterConfig>().ShouldNotBeNull();
host.Services.GetRequiredService<IOptions<MapsterOptions>>().ShouldNotBeNull();
}

private sealed class ValueSource
{
public int Value { get; set; }
}

private sealed class ValueDest
{
public int Value { get; set; }
}
}
Loading
Loading