Initializing AutoMapper in an Azure Function
Asked Answered
R

2

8

I am trying to create an Azure function in which I am using some code with AutoMapper. I am quite new to C#, Azure and AutoMapper and I'm having some trouble finding the correct way of initializing my AutoMapper configuration.

MapInitializer.cs:

public static class MapInitializer
    {
        public static void Activate()
        {
            Mapper.Initialize(cfg =>
            {
                // initialize mappings here
            });
        }
    }

Then in my function, I am trying to do the following:

Function.cs:

public static class ProcessQueueForIntercom
    {

        [FunctionName("ProcessQueue")]
        public static void Run([QueueTrigger("messages")]string myQueueItem, TraceWriter log)
        {
            MapInitializer.Activate(); 

            // rest of the code
        }
    }

Now the problem is, the first time I processed a message with this function, everything went smoothly and the code ran as I expected. However, from the second time on, I get an error saying my configuration is already initialized. But I don't really have an idea on how to do this properly with an Azure Function, since normally you would initialize this in the App Startup, but I don't think there is such a thing for Azure Functions (CMIW), and I'm not finding much information about how to exactly do this. I was thinking about just surrounding the Activate() call with a try catch and just log a warning that the configuration is already loaded, but that doesn't seem very clean...

Rida answered 29/11, 2017 at 10:56 Comment(0)
P
9

You only need to call Activate once. You can do it from a static constructor:

public static class ProcessQueueForIntercom
{
    static ProcessQueueForIntercom()
    {
        MapInitializer.Activate();
    }

    [FunctionName("ProcessQueue")]
    public static void Run([QueueTrigger("messages")]string myQueueItem, TraceWriter log)
    {             
        // rest of the code
    }
}

Or just make a static constructor on MapInitializer itself.

See also this answer.

Phalarope answered 29/11, 2017 at 11:1 Comment(3)
This works great, thank you! I hadn't seen/used a static constructor before, interesting!Rida
@Mikhail Thanks for introducing me to static constructor :)Redintegration
How do you use the mapper that you have initialized in the static consturctor ProcessQueueForIntercom() in your function "ProcessQueue"? How do you get to the "_mapper" instance from your function?Phototransistor
K
1

As the static Mapper.Initialize API is now officially dead, I think the best solution is to inject a mapper in the function class constructor. So, assuming you want to map User model to UserResponse DTO, you need to first configure an automapper in your Startup.cs like this

using AutoMapper;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using YourNamespace.Models;

[assembly: FunctionsStartup(typeof(YourNamespace.Startup))]

namespace YourNamespace
{
    class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            var mappperConfig = new MapperConfiguration(cfg =>
                {
                cfg.CreateMap<User, UserResponse>(); 
                // you can have the maps in a dedicated Profile subclass if you like
            });
            builder.Services.AddSingleton(mappperConfig.CreateMapper());
        }
    }
}

Then, you inject the automapper to your functions class like this:

public class UsersApi
{
    private readonly IMapper _mapper;

    public UsersApi(IMapper mapper)
    {
        _mapper = mapper; 
    }

    [FunctionName(nameof(UsersApi.GetUser))]
    public async Task<IActionResult> GetUser(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "users/{id}")] HttpRequest req,
        ILogger log,
        string id)
    {
        User user = await UserRepository.GetUser(id);
        UserResponse res = _mapper.Map<UserResponse>(user);
        return new OkObjectResult(res);
    }
}
Krems answered 10/11, 2022 at 11:35 Comment(5)
I'm guessing that constructor is called for every invocation. If that's the case, it's an anti pattern, the config should be singleton.Vinson
@LucianBargaoanu Thank you for reviewing my answer. Why exactly is it an anti-pattern? Does the automapper documentation say something about it? Also, what should be implemented as singleton? The config or the mapper itself? Or both?Volvulus
The config, docs.automapper.org/en/latest/…Vinson
@LucianBargaoanu thanks again. So apparently there's no better way than using DI... I'll rewrite my answer.Volvulus
docs.automapper.org/en/latest/…Vinson

© 2022 - 2024 — McMap. All rights reserved.