Mapster icon indicating copy to clipboard operation
Mapster copied to clipboard

Mapster.DI IRegister class with autofac Injected interfaces

Open anotowski opened this issue 2 years ago • 1 comments

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();
    }

anotowski avatar Jun 01 '23 08:06 anotowski

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.

DocSvartz avatar Oct 29 '23 01:10 DocSvartz