Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Injected dependency into automapper profile in .net core - IHttpContextAccessor returns null

Goal: To inject IHttpContextAccessor as a dependency into autmapper profile 's constructor

Why: I need to pass in the current user 's identity 's name as part of construction of one of my domain objects

Issue The obtained User identity is always null

What do I have so far?

Startup.cs

mc.AddProfile(new DtoToOtherProfile(new HttpContextAccessor()));

//OR

var mapperConfiguration = new MapperConfiguration(mc =>
        {
            IServiceProvider provider = services.BuildServiceProvider();
            mc.AddProfile(new DtoToOtherProfile(provider.GetService<IHttpContextAccessor>()));
        });

DtoToOtherProfile.cs

 public DtoToOtherProfile(IHttpContextAccessor httpContextAccessor)
    {
     _httpContextAccessor = httpContextAccessor;

     CreateMap<MyDto, MyOther>()
    .ConstructUsing(myDto => new myOther(myDto.prop1, myDto .prop2,  _httpContextAccessor.HttpContext.User.Identity.Name)); // the name is what i intend to pass
      // other mapping
    }

Controller.cs

  public async Task<IActionResult> Create([FromBody]MyDto model)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        // save campaign
        var myOtherModel = _mapper.Map<MyOther>(model);
        // myOtherModel.UserName is always null 
       // rest
      }

Is there something am missing? Pointers please?

like image 422
Jaya Avatar asked May 02 '17 03:05

Jaya


2 Answers

I believe the right way to do it is not to inject IHttpContextAccessor in the Profile but create a separate IValueResolver and inject there the IHttpContextAccessor. So just do the following, where i want to get the user claims for the httpcontext

In your StartUp.cs inside ConfigureServices add

 services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
 services.AddAutoMapper();

Create a Profile with your mappings

public class MappingProfile : Profile
    {
        public MappingProfile()
        {
            CreateMap<QueryUniqueDto, QueryUnique>()
                .ForMember(s=>s.UserClaim,opt=>opt.MapFrom<IdentityResolver>());
        }
    }

Create the IValueResolver

public class IdentityResolver : IValueResolver<QueryUniqueDto, QueryUnique, UserClaim>
  {
    private IHttpContextAccessor _httpContextAccessor;

    public IdentityResolver(IHttpContextAccessor httpContextAccessor) 
    {
      _httpContextAccessor = httpContextAccessor;
    }

    public UserClaim Resolve(QueryUniqueDto source, QueryUnique destination, UserClaim destMember, ResolutionContext context)
    {
        return Helper.GetClaimsFromUser(_httpContextAccessor.HttpContext?.User.Identity as ClaimsIdentity);
    }
  }

That's working amazing!!!

like image 105
George Papadakis Avatar answered Nov 15 '22 01:11

George Papadakis


The problem is that AutoMapper configuration is usually singleton. It should be singleton because constructing it is quite heavy. But the username in the request is not singleton-scope. You will most likely have to find a different solution rather than using AutoMapper.

One option I though of:

services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

IServiceProvider provider = services.BuildServiceProvider();

var mapperConfiguration = new MapperConfiguration(mc =>
{
    mc.AddProfile(new DtoToOtherProfile(provider.GetRequiredService<IHttpContextAccessor>()));
});

This builds the service provider and fetches the context accessor from the container. However, this did not work.

like image 26
juunas Avatar answered Nov 15 '22 00:11

juunas