Automapper error when trying to send POST request

62 Views Asked by At

I'm new to .NET 6 and im trying to set up a basic API that stores trading cards in a database as practice.

However, when trying to test a POST request I'm getting an automapper error.

Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
      An unhandled exception has occurred while executing the request.
      AutoMapper.AutoMapperMappingException: Missing type map configuration or unsupported mapping.
      
      Mapping types:
      CardCreateDto -> Card
      Api.Dtos.CardCreateDto -> Api.Models.Card
         at lambda_method5(Closure , Object , Card , ResolutionContext )
         at Program.<>c.<<<Main>$>b__0_3>d.MoveNext() in /Users/mark/Documents/Web Projects/api/Program.cs:line 53
      --- End of stack trace from previous location ---
         at Microsoft.AspNetCore.Http.RequestDelegateFactory.ExecuteTaskResult[T](Task`1 task, HttpContext httpContext)
         at Microsoft.AspNetCore.Http.RequestDelegateFactory.<>c__DisplayClass46_3.<<HandleRequestBodyAndCompileRequestDelegate>b__2>d.MoveNext()
      --- End of stack trace from previous location ---
         at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
         at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
         at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
         at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

This is my program.cs. The error is on the following line:

    var cardModel = mapper.Map<Card>(cardCreateDto);
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using AutoMapper;

using Api.Data;
using Api.Dtos;
using Api.Models;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();


// Connect to server using connection string from appSettings.Development.json and user-secrets
var sqlConBuilder = new SqlConnectionStringBuilder();

sqlConBuilder.ConnectionString = builder.Configuration.GetConnectionString("SQLDbConnection");
sqlConBuilder.UserID = builder.Configuration["UserId"];
sqlConBuilder.Password = builder.Configuration["Password"];

builder.Services.AddDbContext<AppDbContext>(opt => opt.UseSqlServer(sqlConBuilder.ConnectionString));
builder.Services.AddScoped<ICardRepo, CardRepo>();
builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.MapGet("api/v1/cards", async (ICardRepo repo, IMapper mapper) => {
    var cards = await repo.GetAllCards();
    return Results.Ok(mapper.Map<IEnumerable<CardReadDto>>(cards));
});

app.MapGet("api/v1/cards/{id}", async (ICardRepo repo, IMapper mapper, int id) => {
    var card = await repo.GetCardById(id);
    if (card != null)
        return Results.Ok(mapper.Map<CardReadDto>(card));

    return Results.NotFound();
});

app.MapPost("api/v1/cards", async (ICardRepo repo, IMapper mapper, CardCreateDto cardCreateDto) => {
    var cardModel = mapper.Map<Card>(cardCreateDto);

    await repo.CreateCard(cardModel);
    await repo.SaveChanges();

    var cardReadDto = mapper.Map<CardReadDto>(cardModel);

    return Results.Created($"api/v1/cards/{cardReadDto.Id}", cardReadDto);
});

app.Run();

and here is the DTO in question

using System.ComponentModel.DataAnnotations;

namespace Api.Dtos
{
    public class CardCreateDto
    {
        [Required]
        [MaxLength(5)]
        public string? SetNo { get; set; }

        [Required]
        [MaxLength(5)]
        public string? SetTotal { get; set; }

        [Required]
        public string? ImgSrc { get; set; }

        [Required]
        public string? Title { get; set; }

        [Required]
        public string? SubTitle { get; set; }
        
        [Required]
        public string? Suit { get; set; }

        [Required]
        public string? Set { get; set; }

        [Required]
        public string? Rarity { get; set; }
    }
}

and its corresponding model

using System.ComponentModel.DataAnnotations;

namespace Api.Models
{
    public class Card
    {
        [Key]
        public int Id { get; set; }

        [Required]
        [MaxLength(5)]
        public string? SetNo { get; set; }

        [Required]
        [MaxLength(5)]
        public string? SetTotal { get; set; }

        [Required]
        public string? ImgSrc { get; set; }

        [Required]
        public string? Title { get; set; }

        [Required]
        public string? SubTitle { get; set; }
        
        [Required]
        public string? Suit { get; set; }

        [Required]
        public string? Set { get; set; }

        [Required]
        public string? Rarity { get; set; }
}

CardProfile.cs

using AutoMapper;

using Api.Dtos;
using Api.Models;

namespace Api.Profiles
{
    public class CardProfile : Profile
    {
        public void CardsProfile()
        {
            // Source -> Target
            CreateMap<Card, CardReadDto>();
            CreateMap<CardCreateDto, Card>();
            CreateMap<CardUpdateDto, Card>();
        }
    }
}

CardRepo.cs

using Microsoft.EntityFrameworkCore;

using Api.Models;

namespace Api.Data
{
    public class CardRepo : ICardRepo
    {
        private readonly AppDbContext _context;

        public CardRepo(AppDbContext context)
        {
            _context = context;
        }

        public async Task CreateCard(Card card)
        {
            if(card == null)
            {
                throw new ArgumentNullException(nameof(card));
            }

            await _context.AddAsync(card);
        }

        public void DeleteCard(Card card)
        {
            if(card == null)
            {
                throw new ArgumentNullException(nameof(card));
            }

            _context.Cards.Remove(card);
        }

        public async Task<IEnumerable<Card>> GetAllCards()
        {
            return await _context.Cards.ToListAsync();
        }

        public async Task<Card?> GetCardById(int id)
        {
            return await _context.Cards.FirstOrDefaultAsync(c => c.Id == id);
        }

        public async Task SaveChanges()
        {
            await _context.SaveChangesAsync();
        }
    }
}

ICardRepo.cs

using Api.Models;

namespace Api.Data
{
    public interface ICardRepo
    {
        Task SaveChanges();
        Task<Card?> GetCardById(int id); // Get individual resource
        Task<IEnumerable<Card>> GetAllCards(); // Get all resources
        Task CreateCard(Card card);

        void DeleteCard(Card card);
    }
}

also here is my api.csiproj file so you can see what versions of things i am using

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <UserSecretsId>###</UserSecretsId>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.5" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.5">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.5" />
    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
  </ItemGroup>

</Project>
1

There are 1 best solutions below

0
Yong Shun On
  1. You have to put the mapping configuration in the constructor as mentioned in the Profile Instance docs.
public class CardProfile : Profile
{
    public CardProfile()
    {
        // Source -> Target
        CreateMap<Card, CardReadDto>();
        CreateMap<CardCreateDto, Card>();
        CreateMap<CardUpdateDto, Card>();
    }
}
  1. Move the cardCreateDto before the injected service.

Dependency Injection and Services

You can get a service injected because ASP.NET Core will resolve types from the IServiceProvider after it has exhausted the likelihood that value resolution should occur from route values, query string, HTTP Headers, and the HTTP body.

app.MapPost("api/v1/cards", async (CardCreateDto cardCreateDto, ICardRepo repo, IMapper mapper) => 
{ 
    ...
});