Search code examples
asp.netentity-frameworkautomapperdto

Practical usage of AutoMapper with POST request


In a GET request, I can create a mapping from my back-end model to a customized DTO with AutoMapper with ease. However, I have some concerns when using AutoMapper with POST requests.

Suppose a user orders a product online, he sends the server a POST request with some required data. The fact is, not every piece of data in the back-end model is sent by the user. Let's say the ID of the Order is a GUID which is generated automatically when the entry is inserted into the database; or maybe there are other properties which are auto-incremented. All of these cannot-be-mapped properties lead to a lot of .ForMember(dest => dest.myProperty, opt => opt.Ignore()) chains, and extra handling on the mapped instance after var mappedInstance = Mapper.Map<PostDTO, BackEndModel>(postDTO).

Is AutoMapper not designed for the aforementioned scenario? What is the practice for handling the model-mapping process if the back-end model is much more complex than the DTO?


Update

public class MultipleChoiceQuestion
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid MultipleChoiceQuestionId { get; set; }
    [Required]
    public string Question { get; set; }
    [Required]
    public ICollection<PossibleChoice> PossibleChoices { get; set; }
}

public class PossibleChoice
{
    [Key, Column(Order = 1), ForeignKey("MultipleChoiceQuestion")]
    public Guid MultipleChoiceQuestionId { get; set; }
    [Key, Column(Order = 2)]
    public int ChoiceIndex { get; set; }
    [Required]
    public string AnswerText { get; set; }

    public MultipleChoiceQuestion MultipleChoiceQuestion { get; set; }
}

The user sends a request to create a new question. Only 2 fields are sent.

{
    "Question": "How are you?",
    "Answers": [
        { "Text": "I am fine." },
        { "Text": "Feeling bad." }
    ]
}

Properties that are missing at this stage:

  • MultipleChoiceQuestionId
    • Generated after the insertion
  • ChoiceIndex
    • Auto-incremented from 1 up to the number of answers available

Without manual mapping, how to handle this situation with AutoMapper?


Solution

  • 1- Define your DTOs to be something like this:

    public class MultipleChoiceQuestionDto
    {
        // This property could stay here, because you may need to use the same DTO for update (PUT), 
        // which means you need the Id to distinguish and validate the DTO data against the URL id
        //public Guid MultipleChoiceQuestionId { get; set; } 
    
        public string Question { get; set; }
    
        public ICollection<PossibleChoiceDto> PossibleChoices { get; set; }
    }
    
    public class PossibleChoiceDto
    {
        // This can go from this dto, because this DTO is a child dto for its parent.
        //public Guid MultipleChoiceQuestionId { get; set; }
    
        // This property could stay here, because you may need to use the same DTO for update (PUT), 
        // which means you need the Id to know which Choice was updated.
        //public int ChoiceIndex { get; set; }
    
        public string AnswerText { get; set; }
    }
    

    2- You create a mapping between the entity and the corresponding Dto like this, make sure you call this code from the global.asax file.

    Mapper.CreateMap<MultipleChoiceQuestion, MultipleChoiceQuestionDto>();
    Mapper.CreateMap<MultipleChoiceQuestionDto, MultipleChoiceQuestion>()
        .ForMember(m => m.MultipleChoiceQuestionId, e => e.Ignore()); // you force automapper to ignore this property
    
    Mapper.CreateMap<PossibleChoice, PossibleChoiceDto>();
    Mapper.CreateMap<PossibleChoiceDto, PossibleChoice>()
        .ForMember(m => m.MultipleChoiceQuestion, e => e.Ignore()) //
        .ForMember(m => m.MultipleChoiceQuestionId, e => e.Ignore())
        .ForMember(m => m.ChoiceIndex, e => e.Ignore());
    

    3- In your controller.Post you need to map from the DTO to the entity and save the mapped entity to the database.

    Now, the above solution will work for you for POST, however, you need to think about the PUT scenario and soon you will realize that you need the Ids to be included in the DTOs, and if you decided to do that then you need to revisit the mapping in point 2 and remove the Ignore code for the properties that you decided to include in the DTO.

    Hope that helps.