5

I have a couple interfaces (IMapFrom and IMapTo) that allow me to simplify my AutoMapper configurations. Each of the interfaces has a default implementation for the MapTo and MapFrom methods. I have a separate MappingProfile class that uses reflection to find all of the implementing classes, and invokes their map creation.

The aforementioned classes look like so:

public interface IMapFrom<T>
{
    void MapFrom(Profile profile) => profile.CreateMap(typeof(T), GetType());
}

public interface IMapTo<T>
{
    void MapTo(Profile profile) => profile.CreateMap(GetType(), typeof(T));
}

public class MappingProfile : Profile
{
    public MappingProfile()
    {
        ApplyMappingsFromAssembly(Assembly.GetExecutingAssembly());
    }

    private void ApplyMappingsFromAssembly(Assembly assembly)
    {
        var types = assembly.GetExportedTypes()
            .Where(t => t.GetInterfaces().Any(i =>
                i.IsGenericType && (i.GetGenericTypeDefinition() == typeof(IMapFrom<>) || 
                                    i.GetGenericTypeDefinition() == typeof(IMapTo<>))))
            .ToList();

        foreach (var type in types)
        {
            var instance = Activator.CreateInstance(type);
            var mapTo = type.GetMethod("MapTo");
            var mapFrom = type.GetMethod("MapFrom");
            mapTo?.Invoke(instance, new object[] {this});
            mapFrom?.Invoke(instance, new object[] {this});
        }
    }
}

If the class implementing the interfaces overrides the default interface implementations, the MappingProfile class works as desired. However, if the classes simply rely on the default implementations, mapTo and mapFrom in the ApplyMappingsFromAssembly method are both null.

For example, this class would not have its mappings applied successfully:

public class CreateJobCommand : 
        UpdateJobInputModel, 
        IMapFrom<UpdateJobInputModel>,
        IMapTo<Job>,
        IRequest<int>
{

}

How can I get the default implementations if they're not reimplemented in the inheriting class?

Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
JD Davis
  • 3,517
  • 4
  • 28
  • 61
  • why are you calling `.GetInterfaces()`? you need to create instances of classes. – Daniel A. White Oct 06 '20 at 20:33
  • @DanielA.White I'll be honest, I've had this code buried in the codebase for quite some time. I believe it was taken from a CLEAN architecture demo by `Jason Taylor`. Previously, I was using the code without the default implementations, but with the migration to C# 8, I added the default implementations back. – JD Davis Oct 06 '20 at 20:35
  • @DanielA.White you can see the original source [here](https://github.com/jasontaylordev/NorthwindTraders/blob/master/Src/Application/Common/Mappings/MappingProfile.cs). – JD Davis Oct 06 '20 at 20:36
  • Actually, reviewing the code once more. The call to `.GetInterfaces()` is to find all exported classes that implement the listed interfaces. The code below that then instantiates the classes implementing the interface, and calls the requisite methods. – JD Davis Oct 06 '20 at 20:39
  • 1
    For what it's worth, you can fetch the method from the interface: ```instance.GetType().GetInterface("YourNamespace.IMapFrom`1").GetMethod("MapFrom");```. So you can use that when `type.GetMethod("MapFrom")` returns null. I don't know if there's a way to retrieve that method directly from `type.GetMethods` – Kevin Gosse Oct 06 '20 at 20:43
  • @JDDavis: The code was always wrong. It breaks down on explicit implementation. – Joshua Oct 07 '20 at 21:05
  • @Joshua can you expand? I've been using explicit implementations following the above paradigm for quite some time, and it's always seemed to work as desired. It's just the interface defaults that appear not to work. – JD Davis Oct 08 '20 at 18:26
  • @JDDavis: Try writing your target module in VB and implement an interface with a private method of a completely different name. `Implments` keyword in VB can have any method of the same signature implement a method from the interface. – Joshua Oct 08 '20 at 19:22

1 Answers1

3

Per Kevin Gosse's comment to my question, I looked into using GetInterface().GetMethod() as seen in the Microsoft documentation.

If I take that approach, the now functional, resulting code looks like:

public class MappingProfile : Profile
{
    public MappingProfile()
    {
        ApplyMappingsFromAssembly(Assembly.GetExecutingAssembly());
    }

    private void ApplyMappingsFromAssembly(Assembly assembly)
    {
        var types = assembly.GetExportedTypes()
            .Where(t => t.GetInterfaces().Any(i =>
                i.IsGenericType && (i.GetGenericTypeDefinition() == typeof(IMapFrom<>) || 
                                    i.GetGenericTypeDefinition() == typeof(IMapTo<>))))
            .ToList();

        foreach (var type in types)
        {
            var instance = Activator.CreateInstance(type);
            var mapTo = type.GetMethod("MapTo") ?? 
                        instance!.GetType()
                            .GetInterface("IMapTo`1")?
                            .GetMethod("MapTo");
            var mapFrom = type.GetMethod("MapFrom") ??
                            instance!.GetType()
                                .GetInterface("IMapFrom`1")?
                                .GetMethod("MapFrom");

            mapTo?.Invoke(instance, new object[] {this});
            mapFrom?.Invoke(instance, new object[] {this});
        }
    }
}
JD Davis
  • 3,517
  • 4
  • 28
  • 61
  • https://docs.automapper.org/en/latest/Attribute-mapping.html – Lucian Bargaoanu Oct 07 '20 at 03:31
  • If you have to write all that, if you end up having to simplify AutoMapper, you're probablu abusing it. From [AutoMapper's Design Philosophy](https://jimmybogard.com/automappers-design-philosophy): `AutoMapper works because it enforces a convention. It assumes that your destination types are a subset of the source type. It assumes that everything on your destination type is meant to be mapped. It assumes that the destination member names follow the exact name of the source type. It assumes that you want to flatten complex models into simple ones.` – Panagiotis Kanavos Oct 07 '20 at 21:05
  • I'm not saying that using DIMs as traits is wrong. That's one of the reasons they were created. But this definitely doesn't look like a good fit for AutoMapper – Panagiotis Kanavos Oct 07 '20 at 21:08
  • @PanagiotisKanavos care to expand a bit? I primarily use these `IMapTo` and `IMapFrom` implementations to map between entities and their requisite DTOs or viewmodels. For that purpose, this approach seems to be rather nice. – JD Davis Oct 08 '20 at 18:29
  • @JDDavis - Looks like the default method implementation will only exist on the interface definition itself as opposed to the type (as far as reflection is concerned). For what it's worth, I was able to make this a little more type safe by referencing the method and interface name: `var methodName = nameof(IMapFrom.Mapping);` `var interfaceName = typeof(IMapFrom<>).GetGenericTypeDefinition().Name;` – pnavk Jan 02 '21 at 00:35