Skip to content

Klausmd5/AutoController

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AutoController

Auto-generated controllers & CRUD scaffolding for ASP.NET Core – generic CRUD controllers, dynamic service controllers from interfaces (via IApplicationFeatureProvider), optional repository layer, Gridify paging/filter/sort, and mapping profiles via Klausmd5.AutoMapperLegacy — all with file-scoped namespaces and a modern C# style.

Namespaces:

  • AutoController.Abstractions
  • AutoController.AspNetCore
  • AutoController.EfCore
  • AutoController.Mapping

Table of contents


Installation

dotnet add package Klausmd5.AutoController
# optional/recommended
dotnet add package Swashbuckle.AspNetCore.Annotations
dotnet add package Gridify
dotnet add package Gridify.EntityFramework

Klausmd5.AutoController and Klausmd5.AutoMapperLegacy are publicly installable via nuget.org.

Note: Klausmd5.AutoController brings Klausmd5.AutoMapperLegacy, Asp.Versioning.Abstractions, EF Core etc. as dependencies. You do not need AutoMapper.Extensions.Microsoft.DependencyInjection.

If you want to use the mapper package directly, install:

dotnet add package Klausmd5.AutoMapperLegacy

Klausmd5.AutoMapperLegacy intentionally stays on the AutoMapper v14 API surface due to the licensing changes in newer upstream releases.


Quickstart

1) Program.cs (working minimal sample)

// file-scoped
namespace AutoController_Demo;

using Asp.Versioning;
using Asp.Versioning.Conventions;
using AutoController.Abstractions;
using AutoController.AspNetCore;
using AutoController.Bootstrap;
using AutoController.EfCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder(args);

// DbContext
builder.Services.AddDbContext<TestDbContext>(opt =>
{
    opt.UseSqlServer("Data source=(localdb)\\MSSQLLocalDB;initial catalog=AutoControllerDemo;persist security info=True;");
});

// Bridge: GenericCrudService<,,,> expects DbContext base type
builder.Services.AddScoped<DbContext>(sp => sp.GetRequiredService<TestDbContext>());

// Registrations (CRUD for TestEntity)
var regs = new[]
{
    new GenericRegistration(typeof(TestEntity), typeof(TestDto), typeof(TestUpdateDto), typeof(TestUpdateDto))
};

// AutoController bootstrap (controllers/feature provider/conventions + mapping + EF defaults)
builder.Services.AddControllers()
    .AddAutoControllerSetup(regs, opts =>
    {
        opts.RouteFormat = ApiRouteFormat.Versioned;                  // v{version}/[controller]/[action]
        opts.OperationIdGenerator = CustomOperationIdGenerator.Generate;
    },
    soft =>
    {
        soft.FlagPropertyName = "IsDeleted";                          // soft-delete flag
        soft.IdPropertyName = "Id";
    });

// API versioning (URL segment) + ApiExplorer
builder.Services.AddApiVersioning(opt =>
{
    opt.DefaultApiVersion = new ApiVersion(1);
    opt.AssumeDefaultVersionWhenUnspecified = true;
    opt.ReportApiVersions = true;
    opt.ApiVersionReader = new UrlSegmentApiVersionReader();
})
.AddMvc(opt =>
{
    // Optional: namespace-based versioning; can be omitted for dynamic controllers
    opt.Conventions.Add(new VersionByNamespaceConvention());
})
.AddApiExplorer(opt =>
{
    opt.GroupNameFormat = "'v'VVV";        // v1.0
    opt.SubstituteApiVersionInUrl = true;
});

// Generic CRUD service (open generic)
builder.Services.AddScoped(typeof(IGenericCrudService<,,,>), typeof(GenericCrudService<,,,>));

// Swagger
builder.Services.AddSwaggerGen(opt =>
{
    opt.SwaggerDoc("v1", new OpenApiInfo
    {
        Title = "AutoController Demo API",
        Version = "v1",
        Description = "Demo for AutoController with EF Core"
    });
    opt.EnableAnnotations();
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI(c =>
    {
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
        c.RoutePrefix = string.Empty;
    });
}

app.UseHttpsRedirection();
app.MapControllers();

// (Optional) dump endpoints – handy for debugging
var eds = app.Services.GetRequiredService<EndpointDataSource>();
foreach (var e in eds.Endpoints)
    Console.WriteLine(e.DisplayName);

using (var scope = app.Services.CreateScope())
{
    var db = scope.ServiceProvider.GetRequiredService<TestDbContext>();
    await db.Database.EnsureDeletedAsync();
    await db.Database.EnsureCreatedAsync();
}

app.Run();

2) DTOs & entity (file-scoped)

namespace AutoController_Demo;

public sealed class TestEntity
{
    public Guid Id { get; set; }
    public string Name { get; set; } = "";
    public bool IsDeleted { get; set; }
}

public sealed class TestDto
{
    public Guid Id { get; set; }
    public string Name { get; set; } = "";
}

public sealed class TestUpdateDto
{
    public string Name { get; set; } = "";
}

Done. You immediately get (with Versioned):

  • GET v1/TestEntity/GetById/{id}
  • GET v1/TestEntity/GetAll
  • GET v1/TestEntity/GetAllNotDeleted
  • GET v1/TestEntity/GetAllDeleted
  • POST v1/TestEntity/Create
  • PUT v1/TestEntity/Update/{id}
  • DELETE v1/TestEntity/Delete/{id}

Pluralization depends on your generator configuration (e.g., TestEntities). Defaults use the type name.


Registrations (Entities & Services)

GenericRegistration controls what is generated:

new GenericRegistration(
    Entity: typeof(TestEntity),
    Dto: typeof(TestDto),
    CreateDto: typeof(TestUpdateDto), // Create=Update for demo
    UpdateDto: typeof(TestUpdateDto),
    ServiceInterface: null,                     // custom interface? -> service controller
    ServiceImplementation: null,                // custom implementation
    ControllerNameOverride: "TestEntity",       // optional
    CreateController: true,
    CreateMappingProfile: true,
    CreateCrudService: true
);
  • CRUD controller is generated when ServiceInterface == null.
  • Service controller is generated when ServiceInterface != null (see next section).

Routes & API Versioning

  • ApiRouteFormat.Versionedv{version:apiVersion}/[controller]/[action]
  • ApiRouteFormat.Plain[controller]/[action]

API Versioning:

  • URL segment (/v1/...) via UrlSegmentApiVersionReader()
  • Alternatively query (?api-version=1.0) or header (x-api-version), if desired (can be combined)

Enabling v2: Add a second API version in your versioning config (and ensure your dynamic controllers are marked for it if you use a custom convention). Example:

builder.Services.AddApiVersioning(opt =>
{
    opt.DefaultApiVersion = new ApiVersion(1, 0);
    opt.AssumeDefaultVersionWhenUnspecified = true;
    opt.ApiVersionReader = new UrlSegmentApiVersionReader();
    opt.ReportApiVersions = true;
});

If your AutoController options expose supported versions, add both 1.0 and 2.0 there; otherwise annotate per controller via attribute/convention.


Swagger / OperationIds

  • For dynamic service methods (with annotations enabled) meaningful OperationIds are set automatically.
  • Global generator (optional):
opts.OperationIdGenerator = CustomOperationIdGenerator.Generate;

Service controllers via interface ([RouteAs])

Annotate interface methods with [RouteAs("template", HttpMethodKind.POST|GET|...)]. Parameter-binding attributes ([FromQuery], [FromBody], [FromRoute]) on the interface are mirrored.

namespace AutoController_Demo;

using AutoController.Abstractions;

public interface ITimeMachineService
{
    [RouteAs("travel-to", HttpMethodKind.POST)]
    Task<DateTime> TravelTo(DateTime date);

    [RouteAs("reset", HttpMethodKind.POST)]
    Task<DateTime> Reset();

    [RouteAs("get-current-time", HttpMethodKind.GET)]
    Task<DateTime> GetCurrentTime();
}

With Versioned:

  • POST v1/TimeMachine/travel-to
  • POST v1/TimeMachine/reset
  • GET v1/TimeMachine/get-current-time

CRUD service: optional repository

Without a custom repository, GenericCrudService<,,,> falls back to EF Core (DbContext).

Plug in your repo:

namespace AutoController_Demo;

using AutoController.Abstractions;

public sealed class TestRepository(TestDbContext db) : ICrudRepository<TestEntity>
{
    // Implement QueryAll/Insert/Update/SoftDelete/HardDelete
}
builder.Services.AddScoped<ICrudRepository<TestEntity>, TestRepository>();

AutoMapper profiles (generic)

The bootstrap extension (AddAutoControllerSetup) automatically creates generic profiles (GenericMappingProfile<,,,>) for your regs. You therefore don’t need a separate Klausmd5.AutoMapperLegacy / AutoMapper registration.

The generic profiles map entity ↔ DTOs and ignore non-collection members marked with [IncludeObject] during updates.


Gridify paging/filter/sort

Example:

GET v1/TestEntity/GetAll?filter=Name==~"ann"&orderBy=Name desc&page=1&pageSize=20

Utility extensions include:

  • PaginateAsync<TEntity, TDto>(query, gridifyQuery, mapper[, gridifyMapper])
  • GridifyEntityAsync<T>(query, gridifyQuery)

AOT / NativeAOT note

  • CRUD controllers: regular usage.
  • Dynamic service controllers rely on Reflection/Emit → not NativeAOT-friendly. For NativeAOT, use static controllers or Minimal APIs.

Configuration / Extensibility

  • Route format: opts.RouteFormat = ApiRouteFormat.Versioned | Plain
  • OperationIds: opts.OperationIdGenerator = …
  • Soft-delete / Id properties: via AddAutoControllerSetup(..., soft => { ... })
  • Custom repos: services.AddScoped<ICrudRepository<TEntity>, MyRepo>();
  • Custom services: services.AddScoped(ServiceInterface, ServiceImplementation);

Troubleshooting

404 (Not Found) for generated endpoints → Feature provider wasn’t active. Ensure you use AddControllers().AddAutoControllerSetup(...) (it wires the ApplicationPartManager correctly). Optionally dump EndpointDataSource (as in Quickstart) to see registered routes.

400 (Bad Request) at /v1/... → Usually API versioning/model state (missing/incompatible version). Try:

  • /v1.0/...
  • ...?api-version=1.0

Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContext' … → Add the bridge:

builder.Services.AddScoped<DbContext>(sp => sp.GetRequiredService<TestDbContext>());

Ambiguous AddAutoMapper(...) → Not needed — the bootstrap extension registers Klausmd5.AutoMapperLegacy manually. Remove your own AddAutoMapper calls.


License

MIT

About

AutoController with GenericCRUD Services and FastAPI Like Controllers with Interfaces

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages