Search code examples
c#asp.net-corecarter

Carter modules are not registered in ASP.NET Core app


I have defined Carter modules in my app, but none of them trigger breakpoints during startup and no routes are registered. What is the issue?

using Carter;
using MediatR;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using OneOf;
using MyApp.API.Data;
using MyApp.API.Data.Models;
using MyApp.API.Models;

namespace MyApp.API.Features.Sheets;

public static class GetSheet
{
    public record Command(string SheetId) : IRequest<OneOf<Sheet, NotFound>>;

    public class Handler : IRequestHandler<Command, OneOf<Sheet, NotFound>>
    {
        private readonly ApplicationDbContext context;

        public Handler(ApplicationDbContext context)
        {
            this.context = context;
        }

        public async Task<OneOf<Sheet, NotFound>> Handle(Command request, CancellationToken cancellationToken)
        {
            var sheetCells = await context.SheetCells
                .Where(x => x.SheetId == request.SheetId)
                .ToListAsync(cancellationToken: cancellationToken);

            if (sheetCells == null)
            {
                return new NotFound();
            }

            var sheet = new Sheet(sheetCells);

            return sheet;
        }
    }

    public class Endpoint : ICarterModule
    {
        public void AddRoutes(IEndpointRouteBuilder app)
        {
            app.MapGet("/api/v1/{sheetId:string}", 
                async ([FromBody]Command command, IMediator mediator) =>
                {
                    var result = await mediator.Send(command);
                    return result.Match(
                        cell => Results.Ok(cell), 
                        _ => Results.NotFound());
                });
        }
    }
}

Program.cs

using Microsoft.EntityFrameworkCore;
using MyApp.API.Data;
using Carter;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<ApplicationDbContext>(x =>
    x.UseSqlServer(builder.Configuration.GetConnectionString("Default")));
builder.Services.AddMediatR(c => c.RegisterServicesFromAssemblyContaining(typeof(Program)));

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddCarter();

var app = builder.Build();

app.UseSwagger();
app.UseSwaggerUI();

app.MapCarter();

app.Run();

Solution

  • Nested classes have IsPublic property set to false and IsNestedPublic set to true. Carter`s package sources don't take this into account and register modules improperly.

    https://github.com/CarterCommunity/Carter/blob/97393f890e432730547fcc052e8fd2c8581516c3/src/Carter/CarterExtensions.cs#L280C1-L281C1

    User needs either make classes not nested, or register each module individually

    var catalog = new DependencyContextAssemblyCatalog();
    var types = catalog.GetAssemblies().SelectMany(x => x.GetTypes());
    var modules = types
                .Where(t =>
                    !t.IsAbstract &&
                    typeof(ICarterModule).IsAssignableFrom(t)
                    && (t.IsPublic || t.IsNestedPublic)
                ).ToList();
    
    builder.Services.AddCarter(configurator: c =>
    {
        c.WithModules(modules.ToArray());
    });