Skip to content
This repository has been archived by the owner on May 25, 2023. It is now read-only.

No options required #136

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -1,25 +1,24 @@
using Microsoft.Extensions.DependencyInjection.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using AutoMapper.Internal;
using Microsoft.Extensions.DependencyInjection;

namespace AutoMapper
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;

/// <summary>
/// Extensions to scan for AutoMapper classes and register the configuration, mapping, and extensions with the service collection:
/// <list type="bullet">
/// <item> Finds <see cref="Profile"/> classes and initializes a new <see cref="MapperConfiguration" />,</item>
/// <item> Scans for <see cref="ITypeConverter{TSource,TDestination}"/>, <see cref="IValueResolver{TSource,TDestination,TDestMember}"/>, <see cref="IMemberValueResolver{TSource,TDestination,TSourceMember,TDestMember}" /> and <see cref="IMappingAction{TSource,TDestination}"/> implementations and registers them as <see cref="ServiceLifetime.Transient"/>, </item>
/// <item> Registers <see cref="IConfigurationProvider"/> as <see cref="ServiceLifetime.Singleton"/>, and</item>
/// <item> Registers <see cref="IMapper"/> as a configurable <see cref="ServiceLifetime"/> (default is <see cref="ServiceLifetime.Transient"/>)</item>
/// </list>
/// After calling AddAutoMapper you can resolve an <see cref="IMapper" /> instance from a scoped service provider, or as a dependency
/// To use <see cref="QueryableExtensions.Extensions.ProjectTo{TDestination}(IQueryable,IConfigurationProvider, System.Linq.Expressions.Expression{System.Func{TDestination, object}}[])" /> you can resolve the <see cref="IConfigurationProvider"/> instance directly for from an <see cref="IMapper" /> instance.
/// </summary>
public static class ServiceCollectionExtensions
/// <summary>
/// Extensions to scan for AutoMapper classes and register the configuration, mapping, and extensions with the service collection:
/// <list type="bullet">
/// <item> Finds <see cref="Profile"/> classes and initializes a new <see cref="MapperConfiguration" />,</item>
/// <item> Scans for <see cref="ITypeConverter{TSource,TDestination}"/>, <see cref="IValueResolver{TSource,TDestination,TDestMember}"/>, <see cref="IMemberValueResolver{TSource,TDestination,TSourceMember,TDestMember}" /> and <see cref="IMappingAction{TSource,TDestination}"/> implementations and registers them as <see cref="ServiceLifetime.Transient"/>, </item>
/// <item> Registers <see cref="IConfigurationProvider"/> as <see cref="ServiceLifetime.Singleton"/>, and</item>
/// <item> Registers <see cref="IMapper"/> as a configurable <see cref="ServiceLifetime"/> (default is <see cref="ServiceLifetime.Transient"/>)</item>
/// </list>
/// After calling AddAutoMapper you can resolve an <see cref="IMapper" /> instance from a scoped service provider, or as a dependency
/// To use <see cref="QueryableExtensions.Extensions.ProjectTo{TDestination}(IQueryable,IConfigurationProvider, System.Linq.Expressions.Expression{System.Func{TDestination, object}}[])" /> you can resolve the <see cref="IConfigurationProvider"/> instance directly for from an <see cref="IMapper" /> instance.
/// </summary>
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddAutoMapper(this IServiceCollection services, params Assembly[] assemblies)
=> AddAutoMapperClasses(services, null, assemblies);
Expand Down Expand Up @@ -59,25 +58,25 @@ public static IServiceCollection AddAutoMapper(this IServiceCollection services,
private static IServiceCollection AddAutoMapperClasses(IServiceCollection services, Action<IServiceProvider, IMapperConfigurationExpression> configAction,
IEnumerable<Assembly> assembliesToScan, ServiceLifetime serviceLifetime = ServiceLifetime.Transient)
{
// Just return if we've already added AutoMapper to avoid double-registration
if (services.Any(sd => sd.ServiceType == typeof(IMapper)))
return services;

var sd = services.FirstOrDefault(sd => sd.ServiceType == typeof(IConfigurationProvider));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what the Options stuff does, though. Why create our own factory, and leverage something out-of-the-box instead?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not out of the box :) You have to add a package reference for how many lines of code, maybe five? I don't see the point.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't mind a package reference in a library that is meant to support a specific package reference though, and in a way that is more or less the design of what this factory does.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it just makes things more difficult to understand. Abstraction over clarity. My initial impression was that it might enable some client use cases, but I guess not.

ConfigFactory configFactory;
if (sd == null)
{
configFactory = new ConfigFactory();
services.AddSingleton<IConfigurationProvider>(configFactory.Build);
services.Add(new ServiceDescriptor(typeof(IMapper), sp => new Mapper(sp.GetRequiredService<IConfigurationProvider>(), sp.GetService), serviceLifetime));
}
else
{
configFactory = (ConfigFactory) sd.ImplementationFactory.Target;
}
configFactory.Configs += configAction;
assembliesToScan = assembliesToScan as Assembly[] ?? assembliesToScan.ToArray();

var allTypes = assembliesToScan
.Where(a => !a.IsDynamic && a.GetName().Name != nameof(AutoMapper))
.Distinct() // avoid AutoMapper.DuplicateTypeMapConfigurationException
.SelectMany(a => a.DefinedTypes)
.ToArray();

void ConfigAction(IServiceProvider serviceProvider, IMapperConfigurationExpression cfg)
{
configAction?.Invoke(serviceProvider, cfg);

cfg.AddMaps(assembliesToScan);
}

configFactory.Configs += (_, c) => c.AddMaps(assembliesToScan);
var openTypes = new[]
{
typeof(IValueResolver<,,>),
Expand All @@ -93,18 +92,19 @@ void ConfigAction(IServiceProvider serviceProvider, IMapperConfigurationExpressi
{
services.AddTransient(type.AsType());
}

services.AddSingleton<IConfigurationProvider>(sp => new MapperConfiguration(cfg => ConfigAction(sp, cfg)));
services.Add(new ServiceDescriptor(typeof(IMapper),
sp => new Mapper(sp.GetRequiredService<IConfigurationProvider>(), sp.GetService), serviceLifetime));

return services;
}

private static bool ImplementsGenericInterface(this Type type, Type interfaceType)
=> type.IsGenericType(interfaceType) || type.GetTypeInfo().ImplementedInterfaces.Any(@interface => @interface.IsGenericType(interfaceType));

private static bool IsGenericType(this Type type, Type genericType)
=> type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == genericType;
class ConfigFactory
{
public event Action<IServiceProvider, IMapperConfigurationExpression> Configs;
public MapperConfiguration Build(IServiceProvider sp)
{
if (Configs == null)
{
throw new ArgumentException("You need to pass either some assemblies to scan or an explicit configuration!");
}
return new MapperConfiguration(c => Configs.Invoke(sp, c));
}
}
}
}
}
1 change: 1 addition & 0 deletions src/TestApp/Program.cs
Expand Up @@ -11,6 +11,7 @@ public static void Main(string[] args)
{
IServiceCollection services = new ServiceCollection();
services.AddTransient<ISomeService>(sp => new FooService(5));
services.AddAutoMapper(Type.EmptyTypes);
services.AddAutoMapper(typeof(Source));
var provider = services.BuildServiceProvider();
using (var scope = provider.CreateScope())
Expand Down