2

I ma using;

AutoMapper.Extensions.Microsoft.DependencyInjection 6.0.0

in a web api project trunning net core 2.2

when mapping my DTO object I use Automapper to map a few fields;

public class AutoMapperProfile : AutoMapper.Profile
{
    public AutoMapperProfile()
    {     
        CreateMap<ReviewPostInputModel, Review>()
            .ForMember(x => x.ReceiveThirdPartyUpdates, opt => opt.MapFrom(src => src.ReceiveThirdPartyUpdates ? (DateTime?)DateTime.UtcNow : null))
            .ForMember(x => x.ReceiveUpdates, opt => opt.MapFrom(src => src.ReceiveUpdates ? (DateTime?)DateTime.UtcNow : null))
            .ForMember(x => x.AverageScore, opt => opt.MapFrom(src => (decimal)Math.Round((src.Courtsey + src.Reliability + src.Tidiness + src.Workmanship) / 4, 2)));
        // ...
    }
}

Where;

using System;
using System.Collections.Generic;
using System.Text;

public class Review 
{
    // ...

    public decimal Reliability { get; set; }
    public decimal Tidiness { get; set; }
    public decimal Courtsey { get; set; }
    public decimal Workmanship { get; set; }

    public decimal AverageScore { get; set; }
    public DateTime? ReceiveUpdates { get; set; }
    public DateTime? ReceiveThirdPartyUpdates { get; set; }
} 

However, when I try to map using;

var review = _mapper.Map<Review>(model);

All standard members are mapped bar my ForMember listed above, where the DateTimes are set to a new instance of DateTime and Averagescore is set to 0.

For completness I DI the mapper into my controller as follows;

private readonly IMapper _mapper;

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

I configure Automapper in my StartUp.cs as follows;

services.AddAutoMapper();

I have also tried adding a test to the controller to confirm that the values from the input are not the issue (completed after the map and can confirm that this value is correctly updated);

review.AverageScore = (decimal)Math.Round((model.Courtsey + model.Reliability + model.Tidiness + model.Workmanship) / 4, 2);

Does anyone have any ideas why this is occurring?

Matthew Flynn
  • 3,661
  • 7
  • 40
  • 98
  • 1
    sorry .. are you calling the AutoMapperProfile() in startUp ? – federico scamuzzi Apr 11 '19 at 13:31
  • In my startup `ConfigureServices` method I have `services.AddAutoMapper();` all standard mapping are otherwise working. It is just these edge cases that are not. – Matthew Flynn Apr 11 '19 at 13:32
  • So I've just tried to reproduce your problem with no avail. I followed this blog: http://www.projectcodify.com/using-automapper-in-aspnet-core to setup a .NET Core 2.2 project to use AutoMapper.Extensions.Microsoft.DependencyInjection, and copied your model and mapper profile, but all properties are being mapped. Are you sure your input model has non zero values for Courtsey, Reliability? Also, what do you mean by a "new DateTime"? Do you mean a non-null value? You're using `DateTime.UtcNow` to populate it which won't necessarily be your local time. – Adrian Sanguineti Apr 11 '19 at 14:26
  • Thanks for taking the time to reproduce, I am sure I have passed the values across as I have manually added a test line calculating the average value so I could debug that calculation. By new DateTime I mean I am getting {01/01/0001 00:00:00} for the value, instead of DateTime.UTCNow or Null – Matthew Flynn Apr 11 '19 at 14:54

2 Answers2

0

You need to use "ResolveUsing" and not "MapFrom"

public class AutoMapperProfile : AutoMapper.Profile
{
    public AutoMapperProfile()
    {     
        CreateMap<ReviewPostInputModel, Review>()
            .ForMember(x => x.ReceiveThirdPartyUpdates, opt => opt.ResolveUsing(src => src.ReceiveThirdPartyUpdates ? (DateTime?)DateTime.UtcNow : null))
            .ForMember(x => x.ReceiveUpdates, opt => opt.ResolveUsing(src => src.ReceiveUpdates ? (DateTime?)DateTime.UtcNow : null))
            .ForMember(x => x.AverageScore, opt => opt.ResolveUsing(src => (decimal)Math.Round((src.Courtsey + src.Reliability + src.Tidiness + src.Workmanship) / 4, 2)));
        // ...
    }
}

You can look on this answer: AutoMapper: What is the difference between MapFrom and ResolveUsing?

We had a problem in the past when adding auto mapper from "ConfigureServices" and may be it is the same for you. You can try to put this:

AutoMapperConfiguration.Init();

in Startup function and add this class:

    using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AutoMapper;

namespace yournamespace.ViewModels.Mappings
{
    public static class AutoMapperConfiguration
    {
        public static void Init()
        {
            Mapper.Initialize(cfg =>
            {
                cfg.CreateMap<ReviewPostInputModel, Review>()
                .ForMember(x => x.ReceiveThirdPartyUpdates, opt => opt.MapFrom(src => src.ReceiveThirdPartyUpdates ? (DateTime?)DateTime.UtcNow : null))
                .ForMember(x => x.ReceiveUpdates, opt => opt.MapFrom(src => src.ReceiveUpdates ? (DateTime?)DateTime.UtcNow : null))
                .ForMember(x => x.AverageScore, opt => opt.MapFrom(src => (decimal)Math.Round((src.Courtsey + src.Reliability + src.Tidiness + src.Workmanship) / 4, 2)));
            });
        }
    }
}
Yair I
  • 1,133
  • 1
  • 6
  • 9
0

After some investigation, I think I have reproduced your problem. I created a basic ASP.NET Core 2.2 website, installed AutoMapper.Extensions.Microsoft.DependencyInjection 6.0.0, created a Review class that matches yours and a best guess of what ReviewPostInputModel class looks like based on your mapping definition. I then added your mapping profile class AutoMapperProfile into the project, and configured start up like so:

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddAutoMapper();
    services.AddMvc...
}

I then "hacked" the default generated HomeController like so to test the mapping:


 public class HomeController : Controller
    {
        private IMapper _mapper;

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

        public IActionResult Index()
        {
            var input = new ReviewPostInputModel();
            input.ReceiveThirdPartyUpdates = true;
            input.Tidiness = 3;
            input.Reliability = 2;
            input.NotDefinedOnProfile = "sss";

            var output = _mapper.Map<Review>(input);

            // Lazy test to avoid changing model.
            throw new Exception($"{output.ReceiveThirdPartyUpdates} - {output.AverageScore} - {output.NotDefinedOnProfile}");

            return View();
        }
...

Now this worked for me, as in the Exception message I received was 11/04/2019 2:56:31 PM - 1.25 - sss.

I then created another assembly and moved the AutoMapperProfile class into it. I then re-run the test, but got the following error:

AutoMapper.AutoMapperConfigurationException: Unmapped members were found. Review the types and members below. Add a custom mapping expression, ignore, add a custom resolver, or modify the >source/destination type

For no matching constructor, add a no-arg ctor, add optional arguments, or map all of ?the constructor parameters

AutoMapper created this type map for you, but your types cannot be mapped using the >current configuration. ReviewPostInputModel -> Review (Destination member list) ReviewPostInputModel -> Review (Destination member list)

Unmapped properties: AverageScore ReceiveUpdates NotDefinedOnProfile

This makes sense, since the services.AddAutoMapper(); method only searches the currently assembly for profiles.

So I then changed to configuration to: services.AddAutoMapper(cfg => cfg.ValidateInlineMaps = false); to turn off the error, and re-run the test.

New Output: 1/01/0001 12:00:00 AM - 0 - sss

So this leads me to believe that AutoMapper can't find your Profile class, in which case, you can manually configure it using:

 services.AddAutoMapper(cfg =>
 {
    cfg.AddProfile<AutoMapperProfile>();
 });

or manually define the assemblies to search by using one of the other overloads e.g.:

services.AddAutoMapper(param System.Reflection.Assembly[] assemblies);

If this is not your problem, no matter because my time spent reproducing this question has cured my insomnia.

Adrian Sanguineti
  • 2,455
  • 1
  • 27
  • 29