Skip to content

Commit

Permalink
Importing documentation from the Wiki (#106)
Browse files Browse the repository at this point in the history
  • Loading branch information
SteveWilkes committed Nov 26, 2018
1 parent 6c32f81 commit c2ff88a
Show file tree
Hide file tree
Showing 46 changed files with 2,773 additions and 0 deletions.
7 changes: 7 additions & 0 deletions docs/Assembly-Scanning.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
By default, the assembly in which a base type is declared is searched for derived types. If there are additional assemblies which should be searched, use:

```C#
Mapper.WhenMapping.LookForDerivedTypesIn(
typeof(DerivedType1).Assembly,
typeof(DerivedType2).Assembly);
```
33 changes: 33 additions & 0 deletions docs/Cloning-Mappers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
Mappers can be cloned, to enable 'derived' mappers to inherit, add to and override a 'root' configuration:

``` C#
var baseMapper = Mapper.CreateNew();

// Setup the base configuration:
baseMapper.WhenMapping
.From<Order>()
.To<OrderDto>()
.Map((o, dto) => o.ProdId)
.To(dto => dto.ProductId)
.And
.Map((o, dto) => o.Id)
.To(dto => dto.OrderNumber);

// Clone a new mapper for mapping UK orders:
var ukOrderMapper = baseMapper.CloneSelf();

// Setup UK-specific configuration:
ukOrderMapper.WhenMapping
.To<OrderDto>()
.Map(ctx => DateTime.UtcNow.AddHours(1))
.To(dto => dto.DateCreated);

// Clone a new mapper for mapping US orders:
var usOrderMapper = baseMapper.CloneSelf();

// Setup US-specific configuration:
usOrderMapper.WhenMapping
.To<OrderDto>()
.Map(ctx => DateTime.UtcNow.AddHours(-4))
.To(dto => dto.DateCreated);
```
37 changes: 37 additions & 0 deletions docs/Collections.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
The following collection types are supported by default:

- Arrays
- `IEnumerable`
- `IEnumerable<T>`
- `ICollection`
- `ICollection<T>`
- `List<T>`
- `IList<T>`
- `ReadOnlyCollection<T>`
- `IReadOnlyCollection<T>`
- `Collection<T>`
- `HashSet<T>`
- `ISet<T>`
- [`Dictionary<string, T>`](Dictionary-Mapping)
- [`IDictionary<string, T>`](Dictionary-Mapping)

Generic `List<T>` instances are created for interface-type members except `ISet<T>`, which uses a `HashSet<T>`. If a member is already populated with a non-readonly collection (e.g. an array), the existing object will be reused.

[Updates](Performing-Updates) and [merges](Performing-Merges) of collections of identifiable objects (i.e. Entities) are performed in an id-aware manner.

## Null Source Collections

By default, if the source collection matching a target collection is null, the target collection is populated with an empty collection. You can configure setting the target collection to null instead like this:

```C#
// Map null-source collections to null for all source
// and target types:
Mapper.WhenMapping.MapNullCollectionsToNull();

// Map null collections to null only when mapping from
// Order to OrderDto:
Mapper.WhenMapping
.From<Order>()
.To<OrderDto>()
.MapNullCollectionsToNull();
```
162 changes: 162 additions & 0 deletions docs/Configuration-Classes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
Multiple, dedicated configuration classes can be created by deriving from the abstract `MapperConfiguration` base class. This enables configuration to be split up and placed nearer where mapping is performed (although [inline](Inline-Configuration) is nearer).

`MapperConfiguration` provides the same configuration API, and exposes services you inject.

### Configuration and Caching

Mapper configuration is set up by implementing the abstract `Configure` method:

```C#
public class ProductMappingConfiguration : MapperConfiguration
{
protected override void Configure()
{
// Configure default Mapper ProductDto -> Productmapping:
WhenMapping
.From<ProductDto>()
.To<Product>()
.Map((dto, p) => dto.Spec)
.To(p => p.Specification)
.And
.Ignore(p => p.Price, p => p.CatNum);

// Cache all Product -> ProductDto mapping plans:
GetPlansFor<Product>().To<ProductDto>();
}
}
```

In this example the default mapper is configured - the one used via the [static](Static-vs-Instance-Mappers) Mapper API.

### Applying Configurations

`MapperConfiguration` classes can be discovered and applied in various ways.

To apply a particular `MapperConfiguration` Type, supply it explicitly:

```C#
Mapper.WhenMapping
.UseConfigurations.From<ProductMappingConfiguration>();
```

To apply all `MapperConfiguration` Types from an Assembly, supply a Type from that Assembly:

```C#
Mapper.WhenMapping
.UseConfigurations.FromAssemblyOf<Product>();
```

To apply all `MapperConfiguration` Types from multiple Assemblies, supply the Assemblies:

```C#
// Scan all Assemblies from the AppDomain:
Mapper.WhenMapping
.UseConfigurations.From(assembly1, assembly2, assembly3);

// Scan all the given Assemblies which match the filter -
// Assembly scanning can be expensive, so this can be useful!
Mapper.WhenMapping
.UseConfigurations.From(
GetLotsOfAssemblies(),
assembly => assembly.FullName.Contains(nameof(MyNamespace)));
```

To apply all `MapperConfiguration` Types from the Assemblies current loaded into the `AppDomain`, use:

```C#
// Scan all Assemblies from the AppDomain:
Mapper.WhenMapping
.UseConfigurations.FromCurrentAppDomain();

// Scan all Assemblies from the AppDomain which match the filter -
// Assembly scanning can be expensive, so this is advisable!
Mapper.WhenMapping
.UseConfigurations.FromCurrentAppDomain(
assembly => assembly.FullName.Contains("MyCompanyName"));
```

### Ordering Configurations

Calling `GetPlansFor<Source>().To<Target>()` caches the mapping function at the point you call it. If Types configured in the object graph are configured in more than one `MapperConfiguration`, you might need to define an order in which configuration classes are applied. Use:

```C#
// Configure aspects of Parent -> Parent mapping, which includes
// mapping Child -> Child. Automatically apply ChildMapperConfiguration,
// then apply this configuration afterwards.
[ApplyAfter(typeof(ChildMapperConfiguration))]
public class ParentMapperConfiguration : MapperConfiguration
{
}

// Configure aspects of Child -> Child mapping:
public class ChildMapperConfiguration : MapperConfiguration
{
}
```

Chains of `ApplyAfter` attributes will be followed, with all configurations automatically applied in the defined order. Defining circular references between configuration types will throw a `MappingConfigurationException`.

### Accessing Services

[Configured Service Providers](Dependency-Injection) are available to `MapperConfiguration` classes. For example:

```C#
// Get a Dependency Injection container:
var diContainer = GetDiContainer();

// Register the container:
Mapper.WhenMapping.UseServiceProvider(diContainer);

// Scan for MapperConfigurations:
Mapper.WhenMapping
.UseConfigurations.FromAssemblyOf<Product>();
```

...the DI container and its registered services are now available to the `MapperConfiguration` class via the `GetService<TService>()` and `GetServiceProvider<TContainer>()` methods:

```C#
public class MyMappingConfiguration : MapperConfiguration
{
protected override void Configure()
{
// Get a reference to the configured container:
var diContainer = GetServiceProvider<IUnityContainer>();

// Get a reference to a configured ILogger - this just passes
// the request to the container and returns the result:
var logger = GetService<ILogger>();

// Create a new mapper for Product mapping:
var productMapper = CreateNewMapper();

// Configure productMapper Product -> ProductDto mapping:
productMapper.WhenMapping
.From<ProductDto>()
.To<Product>()
.Map((dto, p) => dto.Spec)
.To(p => p.Specification);

// Cache all Product -> ProductDto mapping plans:
productMapper.GetPlansFor<Product>().To<ProductDto>();

// Create a new mapper for Order mapping:
var orderMapper = CreateNewMapper();

// Configure orderMapper Order -> OrderDto create new mapping:
orderMapper.WhenMapping
.From<Order>()
.ToANew<OrderDto>()
.Map((o, dto) => o.Items.Sum(i => i.Cost))
.To(dto => dto.TotalCost);

// Cache the Order -> OrderDto create new mapping plan:
orderMapper.GetPlanFor<Order>().ToANew<OrderDto>();

// Register named IMapper instances with the container:
diContainer.RegisterInstance("ProductMapper", productMapper);
diContainer.RegisterInstance("OrderMapper", orderMapper);

logger.LogDebug("Product and Order mapping configured and registered");
}
}
```
14 changes: 14 additions & 0 deletions docs/Configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Many aspects of mapping can be configured, but no up-front type registration is required - a mapping function is created and cached the first time two types are mapped.

Various configuration options are available:

- [Static](Static-vs-Instance-Mappers) configuration (`Mapper.WhenMapping`) configures a default, instance-scoped mapper behind the scenes. This configures the mapper used when you call `Mapper.Map(source)`

- [Instance](Static-vs-Instance-Mappers) configuration (`mapper.WhenMapping`) configures that particular instance

- [Inline](Inline-configuration) configuration combines the configuration of the mapper performing the mapping with the inline configuration you supply

- [Class](Configuration-Classes) configuration splits configuration up into dedicated configuration classes.

The same API is available in all four contexts.

50 changes: 50 additions & 0 deletions docs/Configuring-Constructor-Arguments.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
By default, types are created using the 'greediest' public constructor - the one with the most parameters that have [matching](Member-Matching) source members. If there are no available constructors whose parameters can all be matched - and no parameterless constructor - the member for which the type would be created is ignored.

Constructor arguments can be configured by type or name, and constant values or expressions can be specified.

For example, to configure mapping these types:

```C#
public class CustomerDto
{
public string CustomerNum { get; set; }
public string Name { get; set; }
}

public class Customer
{
public Customer(Guid customerId, string customerName)
{
}
}
```

...use:

```C#
Mapper.WhenMapping
.From<CustomerDto>() // Apply to CustomerDto mappings
.ToANew<Customer>() // Apply to Customer creations
.Map((dto, c) => dto.CustomerNum) // Map CustomerDto.CustomerNum
.ToCtor<Guid>() // To Customer's Guid constructor param
.And // Not done configuring yet...
.Map((dto, c) => dto.Name) // Map CustomerDto.Name
.ToCtor("customerName"); // To Customer's 'customerName' param
```

...or, if inline configuration is preferred:

```C#
// Source, target and mapping types are implicit from the mapping:
Mapper
.Map(customerDto).ToANew<Customer>(cfg => cfg
.Map((dto, c) => dto.CustomerNum) // Map CustomerDto.CustomerNum
.ToCtor<Guid>() // To Customer's Guid constructor param
.And // Not done configuring yet...
.Map((dto, c) => dto.Name) // Map CustomerDto.Name
.ToCtor("customerName")); // To Customer's 'customerName' param
```

In these examples the `string` `CustomerNum` is parsed and [converted](Type-Conversion) to the `Guid` `customerId` out of the box.

If configuring constructor parameters is awkward (perhaps because there's a lot of them), you can also [configure an object factory](Configuring-Object-Creation) for a particular object type.
40 changes: 40 additions & 0 deletions docs/Configuring-Exception-Handling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
By default, an `Exception` thrown during a mapping is wrapped in a [`MappingException`](/agileobjects/AgileMapper/blob/master/AgileMapper/MappingException.cs) and rethrown. To configure a mapper to swallow exceptions and return null instead, use:

```C#
Mapper.WhenMapping
.SwallowAllExceptions();
```

Alternatively, to have a mapper call a callback in the event of an exception use:

```C#
Mapper.WhenMapping
.PassExceptionsTo(ctx =>
{
Debug.Print(string.Format(
"Error mapping from {0} to {1}: {2}",
ctx.Source,
ctx.Target,
ctx.Exception));

throw ctx.Exception;
});
```

To only swallow exceptions thrown when mapping particular types, use:

```C#
Mapper.WhenMapping
.From<PersonViewModel>() // Apply to PersonViewModel mappings (optional)
.To<Person>() // Apply to Person creation, updates and merges
.SwallowAllExceptions();
```

...and to have a callback called for a particular type, use:

```C#
Mapper.WhenMapping
.To<Person>()
.PassExceptionsTo(ctx =>
Debug.Log(new PersonException(ctx.Source.Id, ctx.Exception)));
```

0 comments on commit c2ff88a

Please sign in to comment.