I created a .Net Core 5 API having 2 types of models:
- Entities (used by Entity Framework Core)
- DTOs (Data Transfer Objects for requests and responses, replacing "{Property}Id" properties from Entity with "{Property}Code" in DTO)
I have a service responsible of mapping Entities types to Dtos types added as singleton in ConfigureServices:
services.AddSingleton(typeof(IEntityDtoMappingProvider), typeof(EntityDtoMappingProvider));
The service EntityDtoMappingProvider has a method which returns the mapping between Entities and Dtos for an assembly through reflection described by this interface:
public interface IEntityDtoMappingProvider
{
Dictionary<Type,Type> GetEntityDtoMapping(Assembly assembly);
}
I have an AutoMapper Profile requiring Entities and DTOs mapped, returned by the first service IEntityDtoMappingProvider:
public class EntitiesToDtosProfile : Profile
{
public EntitiesToDtosProfile(Dictionary<Type,Type> mapping)
{
if (mapping == null || mapping.Count == 0)
{
throw new ArgumentException( $"Empty mapping argument passed to {nameof(EntitiesToDtosProfile)} profile", nameof(mapping));
}
foreach(var item in mapping)
{
// Create AutoMapper mapping both ways based on those types
CreateMap(item.Key, item.Value); // Entity-DTO
CreateMap(item.Value, item.Key); // DTO-Entity
}
}
}
I need to create the AutoMapper profile in Startup.cs in ConfigureServices method:
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddSingleton(typeof(IEntityDtoMappingProvider), typeof(EntityDtoMappingProvider));
// Note: Using services.BuildServiceProvider() is a bad practice because an additional copy of singleton services being created
using (var serviceProvider = services.BuildServiceProvider())
{
var mappingService = serviceProvider.GetRequiredService<IEntityDtoMappingProvider>();
var mappings = mappingService.GetEntityDtoMapping(typeof(Workflow).Assembly);
// Add AutoMapper IMapper to services
var mappingConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new EntitiesToDtosProfile(mappings));
});
var mapper = mappingConfig.CreateMapper();
services.AddSingleton(mapper);
// Here I should call other IServiceCollection extensions like:
// Database-related services: GenericRepository<TEntity, TDbContext> : IGenericRepository<TEntity>
services.AddDatabaseGenericRepositories<ApplicationDbContext>(mappings, Log.Logger);
// Mapping-related services: MappingHelper<TEntity, TDto> : IMappingHelper<TEntity, TDto>
services.AddMappingHelpers(mappings, Log.Logger);
// ...
}
// ...
}
As I was saying in the code, using services.BuildServiceProvider() is a bad practice because an additional copy of singleton services being created, creates a second container, which can create torn singletons and cause references to object graphs across multiple containers. Microsoft .Net Core 5 documentation backing those statements.
Please give an answer about how I should create the Entity-DTO mapping of type Dictionary<Type,Type> in CreateServices using IEntityDtoMappingProvider in order to build the AutoMapper profile EntitiesToDtosProfile and create other services through reflection without calling services.BuildServiceProvider taking into considerations the following:
- I have many services created through reflection using extension methods for IServiceCollection requiring Entity-DTO mapping in ConfigureServices
- I cannot use IOptions having a property of type Dictionary<Type,Type> because IOptions shouldn't be used in ConfigureServices: "An inconsistent options state may exist due to the ordering of service registrations." Source: IOptions Microsoft Documentation.
- I looked through a lot of questions (some might be a little unrelated), but all solved their issues using services.BuildServiceProvider() or IOptions which is not ok.