Search code examples
c#asp.net-coreinheritanceserializationautomapper

Issue with AutoMapper inheritance mapping in ASP.NET Core API response serialization


I'm currently working on an ASP.NET Core API project where I'm using AutoMapper to map my domain entities to display models. I have a scenario where I use inheritance on my domain entities and I set up the mappings to handle inheritance using the Include method.

I have a custom Response<T> class to handle API responses, and I'm noticing an issue with the serialization of derived view models in the response. Even though I'm returning a list of derived view models (e.g., GetAllMultipleChoiceQuestionViewModel), the serialized response only includes the properties from the base view model (BaseGetAllQuestionViewModel).

Here's a simplified version of my code:

Models:

  • Question (base class)
  • MultipleChoiceQuestion (derived from Question)
  • Choice

View Models:

  • BaseGetAllQuestionViewModel
  • GetAllMultipleChoiceQuestionViewModel

Automapper Profile:

CreateMap<Question, BaseGetAllQuestionViewModel>()
    .Include<MultipleChoiceQuestion, GetAllMultipleChoiceQuestionViewModel>();

CreateMap<MultipleChoiceQuestion, GetAllMultipleChoiceQuestionViewModel>()
    .ForMember(dest => dest.Choices, opt => opt.MapFrom(src => src.Choices));

Query Handler:

public async Task<Response<IEnumerable<BaseGetAllQuestionViewModel>>> Handle(GetAllQuestionsBySurveyContentIdQuery request, CancellationToken cancellationToken)
{
    var questions = await _questionRepositoryAsync.GetAllQuestionsBySurveyContentIdAsync(request.SurveyContentId);
    var questionViewModels = questions.ToList(); // Ensure it's a concrete list

    var response = new Response<IEnumerable<BaseGetAllQuestionViewModel>>(questionViewModels);
    return response;
}

Controller:

 [HttpGet("GetAllQuestionsBySurveyContentId")]
// [Authorize(Roles = "SuperAdmin")]
 public async Task<IActionResult> GetAllQuestionsBySurveyContentId([FromQuery] GetAllQuestionsBySurveyContentIdQuery query)
 {
     var response = await Mediator.Send(query);
     return Ok(response);
 }

I've confirmed that the _questionRepositoryAsync.GetAllQuestionsBySurveyContentIdAsync method and also response in controller is returning the correct derived view models. But return Ok(response); While the line is running, it redirects to my Response class and only the response is returned as the base view model.

Here is the response in controller before executing return line:

enter image description here


Solution

  • Polymorphic type hierarchy serialization is opt-in with System.Text.Json. For example

    [JsonDerivedType(typeof(WeatherForecastWithCity))]
    public class WeatherForecastBase
    {
        public DateTimeOffset Date { get; set; }
        public int TemperatureCelsius { get; set; }
        public string? Summary { get; set; }
    }
    public class WeatherForecastWithCity : WeatherForecastBase
    {
        public string? City { get; set; }
    }
    

    https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/polymorphism?pivots=dotnet-7-0