'How to instantiate a class that implements dependency injection?

I am getting the handle on .NET Core 6 and I am stuck. I am using AutoMapper and I have dependency injection set up.

My implementation:

public class LSif : ISif
{
    private readonly DataContext _db;
    private readonly IMemoryCache _memoryCache;

    public LSif(DataContext db, IMemoryCache memoryCache)
    {
        _db = db;
        _memoryCache = memoryCache;
    }

    public List<DropDown> MjernaJedinicaDD(int selected)
    {
        string key = "MjernaJedinicaDD" + selected;

        List<DropDown> dd = new List<DropDown>();

        if (!_memoryCache.TryGetValue(key, out dd))
        {
            var model = GetAllMjernaJedinica();

            if (model != null)
            {
                foreach (var item in model)
                {
                    dd.Add(
                        new DropDown()
                        {
                            Id = item.Id,
                            Name = item.Name,
                            Selected = selected
                        }
                    );
                }
            }

            var cacheEntryOptions = new MemoryCacheEntryOptions()
                .SetSlidingExpiration(TimeSpan.FromSeconds(30));
            _memoryCache.Set(key, dd, cacheEntryOptions);
        }

        return dd;
    }
}

My goal is to call that implementation method from Automapper resolver:

.ForMember(d => d.MjernaJedinicaDD, o => o.MapFrom<MjernaJedinicaDDArtikal>());

And the resolver looks like this:

public class MjernaJedinicaDDArtikal : IValueResolver<Artikal, ArtikalVM, List<DropDown>>
{
    public List<DropDown> Resolve(Artikal source, ArtikalVM destination, List<DropDown> member, ResolutionContext context)
    {
        var services = new ServiceCollection(); // With this i shoud work
        services.AddScoped<ISif, LSif>();       // but i doesn't

        using ServiceProvider serviceProvider = services.BuildServiceProvider(validateScopes: true);
        using (IServiceScope scope = serviceProvider.CreateScope())
        {
            ISif reff = scope.ServiceProvider.GetRequiredService<ISif>();
            if (reff != null)
            {
                return reff.MjernaJedinicaDD(source.MjernaId);
            }
        }

        return null;

        // This is how I did it in .NET Framework 4.5
        var lSif = new LSif();
        return lSif.MjernaJedinicaDD(source.MjernaId);
    }
}

Question: how to instantiate / access class that has dependency injection components (parameters) form AutoMapper custom resolver?

Aditional info: I initiate AutoMapper using

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

and then

public class AutoMapperProfile : Profile
{

    public AutoMapperProfile()
    {
        ApplyMappingsFromAssembly(Assembly.GetExecutingAssembly());
    }

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

        foreach (var type in types)
        {
            var instance = Activator.CreateInstance(type);

            var methodInfo = type.GetMethod("Mapping") ??
                             type.GetInterface("IMapFrom`1").GetMethod("Mapping");

            methodInfo?.Invoke(instance, new object[] { this });
        }
    }
}

and finaly in program.cs

builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly());


Solution 1:[1]

When you call one of the AddAutoMapper extension methods on IServiceCollection provided by the NuGet package AutoMapper.Extensions.Microsoft.DependencyInjection during startup, this does several things including:

  1. Adding your custom value resolver (e.g. MjernaJedinicaDDArtikal) to the dependency injection (DI) container
  2. Configuring the Mapper to resolve dependencies from the container needed by your custom components that implement AutoMapper interfaces (such as IValueResolver)

(For additional details on what the AddAutoMapper method does, see the README for the NuGet package.)

This allows you to use constructor injection to directly supply the dependencies that your custom value resolver needs. These dependencies will be resolved from the DI container and they can also require other dependencies from the container themselves.

Your custom value resolver becomes:

public class MjernaJedinicaDDArtikal : IValueResolver<Artikal, ArtikalVM, List<DropDown>>
{
    private readonly ISif _isif;

    public MjernaJedinicaDDArtikal(ISif isif)
    {
        _isif = isif ?? throw new ArgumentNullException(nameof(isif));
    }

    public List<DropDown> Resolve(Artikal source, ArtikalVM destination, List<DropDown> member, ResolutionContext context)
    {
        return _isif.MjernaJedinicaDD(source.MjernaId);
    }
}

Here is a simplified version of your AutoMapper profile that uses your custom value resolver:

public class AutoMapperProfile : Profile
{
    public AutoMapperProfile()
    {
        CreateMap<Artikal, ArtikalVM>()
            .ForMember(dest => dest.MjernaJedinicaDD, src => src.MapFrom<MjernaJedinicaDDArtikal>());
    }
}

Finally, add your ISif service to the container using the LSif class as its implementation as well as any dependencies the LSif class needs.

When using the minimal hosting model for an ASP.NET Core app with .NET 6 or later, add the snippets below to Program.cs (some of the lines included with default .NET templates are included to provide context):

// TODO: add 'using' directives for namespace(s) containing ISif and LSif

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();  // or other similar method

// Register DataContext, which is injected into LSif
// TODO: Fill in app-specific implementation

// Register IMemoryCache implementation for injection into LSif
builder.Services.AddMemoryCache(options =>
{
    // TODO: fill in desired cache options
});

// Register ISif service using LSif implementation
builder.Services.AddTransient<ISif, LSif>();

builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly());

// ...

var app = builder.Build();

For other people that may want to use a similar approach but are using an earlier version of ASP.NET Core without the minimal hosting model (e.g. ASP.NET Core 3.1 or 5.0 with the Generic Host), add the custom service registrations to Startup.ConfigureServices(IServiceCollection) instead of adding them in Program.cs using builder.Services.AddXyz. For example:

public void ConfigureServices(IServiceCollection services)
{
    // ...

    // Register DataContext, which is injected into LSif
    // TODO: Fill in app-specific implementation

    // Register IMemoryCache implementation for injection into LSif
    services.AddMemoryCache(options =>
    {
        // TODO: fill in desired cache options
    });

    // Register ISif service using LSif implementation
    services.AddTransient<ISif, LSif>();

    services.AddAutoMapper(Assembly.GetExecutingAssembly());

    // ...
}

The way that AutoMapper can use the application's default DI container to resolve dependencies is that the AddAutoMapper extension method passes the IServiceProvider.GetService(Type) method as the serviceCtor argument to the Mapper constructor (source, v11.0.0).

Side note: You may also want to tweak your LSif.MjernaJedinicaDD method to avoid throwing a NullReferenceException on the line for dd.Add(...). After calling _memoryCache.TryGetValue(key, out dd), dd will be null when TryGetValue returns false because null is the default value for any C# reference type, such as a class, which List<T> is (reference).

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1