MapsterMapper / Mapster

A fast, fun and stimulating object to object Mapper
MIT License
4.29k stars 327 forks source link

Mapster.DI IRegister class with autofac Injected interfaces #592

Open anotowski opened 1 year ago

anotowski commented 1 year ago

Hello,

I'm struggling to register a mapster configuration using .Scan function from Mapster DI. I have a specific date time parser that I want to assure that all strings are parsed from a specific format:

public class DateTimeParser : IDateTimeParser
{
    public static readonly string DateTimeFormat = "yyyy-MM-dd";

    public DateTime Parse(string date)
    {
        if (!DateTime.TryParseExact(date, DateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None,
                out var parsedDate))
        {
            throw new ArgumentException(
                $"Incorrect format for: '{nameof(date)}': '{date}'.",
                nameof(date));
        }

        return parsedDate;
    }
}

As you can see I have interface so that I register Interface using autofac via assembly scan:

    private static void RegisterWithAssemblyScan(ContainerBuilder builder)
    {
        var assembly = Assembly.GetExecutingAssembly();
        builder.RegisterAssemblyTypes(assembly)
            .Where(t => t.Name.EndsWith("Parser"))
            .AsImplementedInterfaces();
    }

And that works fine, I can use this IDateTimeParser anywhere when I want, except the place that I have my map:

public class SomeClassMaps : IRegister
{
    private readonly IDateTimeParser _dateTimeParser;

    // TODO: Figure out how to get rid of this tight coupling when registering mapster maps.
    public SomeClassMaps()
    {
        _dateTimeParser = new DateTimeParser();
    }

    public SomeClassMaps(IDateTimeParser dateTimeParser)
    {
        _dateTimeParser = dateTimeParser;
    }

    public void Register(TypeAdapterConfig config)
    {
        config.ForType<SomeClassToMapFrom, SomeClassToMapTo>()
            .Map(dest => dest.TargetDateTime, src => _dateTimeParser.Parse(src.TargetDateString));
    }
}

As you can see for now I have to expose default constructor and use tight coupling to be able to use my parser. How can I get rid of tight coupling in such case?

If I will comment out that TODO ctor I get an error that stops starting application:

[10:48:43 FTL] x backend Application terminated unexpectedly
System.MissingMethodException: Cannot dynamically create an instance of type 'x.Application.Maps.UpdateCaseMaps'. Reason: No parameterless constructor defined.
   at System.RuntimeType.ActivatorCache..ctor(RuntimeType rt)
   at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean wrapExceptions)
   at System.Activator.CreateInstance(Type type, Boolean nonPublic, Boolean wrapExceptions)
   at System.Activator.CreateInstance(Type type)
   at Mapster.TypeAdapterConfig.<>c.<Scan>b__87_3(Type registerType)
   at System.Linq.Enumerable.WhereSelectArrayIterator`2.MoveNext()
   at System.Collections.Generic.List`1.InsertRange(Int32 index, IEnumerable`1 collection)
   at System.Collections.Generic.List`1.AddRange(IEnumerable`1 collection)
   at System.Linq.Enumerable.SelectManySingleSelectorIterator`2.ToList()
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at Mapster.TypeAdapterConfig.Scan(Assembly[] assemblies)
   at x.Application.Container.ApplicationContainer.RegisterMapster(ContainerBuilder containerBuilder) in D:\work\x\src\x\x.Application\Container\Ap
plicationContainer.cs:line 28

Registration of maps looks like this:

private static void RegisterMapster(ContainerBuilder containerBuilder)
    {
        var typeAdapterConfig = TypeAdapterConfig.GlobalSettings;
        var assembly = Assembly.GetExecutingAssembly();

        typeAdapterConfig.Scan(assembly);
        containerBuilder.RegisterInstance(typeAdapterConfig);
        containerBuilder.RegisterType<ServiceMapper>()
            .As<IMapper>()
            .InstancePerLifetimeScope();
    }
DocSvartz commented 10 months ago

Hello @anotowski As far as I understand while working on open Issue. The Mapster is a Type Orientet mapper, not a dependency injector. Therefore, in order to successfully build a matching function, all dependencies must be resolved before transferring to Mapster. Otherwise there is simply nothing to analyze) If you need validation, then make a decorator or a special class that does two steps: 1) Performs your verification of the source. If successful step 2 2) Calls the mapper for the Source and Destination Target types

Or You must explicitly register SomeClassMaps implementation to the standard interface in the builder container.