2

I'm trying to automate the implementation of the CRUD for my application.

I'm using the Minimal API design, and I wrote a ICrudService<T> which can Get, Single, Add, Update and Delete the T Entity.

Now I'm trying to automate the endpoints part. I'm able to have the Get and the Single working with this code :

Type[] types = 
{ 
    typeof(Place), 
    typeof(PlaceLink),
};

foreach (var type in types)
{
    var scope = app.Services.CreateScope();
    var serviceType = typeof(ICrudService<>).MakeGenericType(type);
    dynamic service = scope.ServiceProvider.GetService(serviceType);

    app
    .MapGet($"/{type.Name.ToLower()}s", async () =>
    {
        var result = await service.Get();
        return Results.Ok(result);
    })
    .WithTags($"{type.Name}s");

    app
    .MapGet($"/{type.Name.ToLower()}s/" + "{id}", async (int id) =>
    {
        var result = await service.Single(id);
        return Results.Ok(result);
    })
    .WithTags($"{type.Name}s");
}

Which gives me the swagger I want :

enter image description here

But I'm struggling with the Add (and probably with the Update one).

Here is the actual code for the Add part :

app
.MapPost($"/{type.Name.ToLower()}s/" + "{id}", async(TYPE_OF_ENTITY entity) =>
{
    var result = await service.Add(entity);
    return Results.Ok(result);
})
.WithTags($"{type.Name}s");

I'm able to get the service by using the .MakeGenericType(type) function. But here, the async(TYPE_OF_ENTITY entity) is a signature for an anonymous function, and I don't know if there is any trick I can use to specify the Type of the entity var by using the type var.

The dynamic keyword works, but Swagger isn't able to know the Type of my entity, so the GUI appears raw without any default schema for the request :

how can I dynamically create the signature of my anonymous function ?

enter image description here

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
M. Ozn
  • 1,018
  • 1
  • 19
  • 42

1 Answers1

1

To perform dynamic Minimal API registration you can move the setup into a generic method and then call it via reflection. For example:

class MapExts
{
    public static void MapType<T>(WebApplication app)
    {
        Type type = typeof(T);
        app
            .MapGet($"/{type.Name.ToLower()}s", async (ICrudService<T> service) =>
            {
                var result = await service.Get();
                return Results.Ok(result);
            })
            .WithTags($"{type.Name}s");

        app
            .MapGet($"/{type.Name.ToLower()}s/" + "{id}", async (int id, ICrudService<T> service) =>
            {
                var result = await service.Single(id);
                return Results.Ok(result);
            })
            .WithTags($"{type.Name}s");

        app
            .MapPost($"/{type.Name.ToLower()}s/" + "{id}", async (T entity, ICrudService<T> service) =>
            {
                var result = await service.Add(entity);
                return Results.Ok(result);
            })
            .WithTags($"{type.Name}s");
    } 
}

And usage:

var mapMethod = typeof(MapExts).GetMethod(nameof(MapExts.MapType));
foreach (var type in types)
{
    mapMethod.MakeGenericMethod(type).Invoke(null, new object?[] { app });
}

P.S.

This:

var scope = app.Services.CreateScope();
var serviceType = typeof(ICrudService<>).MakeGenericType(type);
dynamic service = scope.ServiceProvider.GetService(serviceType);

is bad. Not only due to using dynamic type (which can be opinionated claim) but this will result in the same instance of service handling all requests for the app lifetime which can lead to a lot of different issues depending on the actual service implementation.

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • Thanks ! It works like a charm ! I was aware of how bad the `dynamic` keyword usage was but I didn't know for the instance of the service. Anyway, the solution you provided get ride of this issue. Thank you very much ! – M. Ozn Apr 04 '23 at 18:05
  • 1
    @M.Ozn was glad to help! _"but I didn't know for the instance of the service."_ - yes, you are creating a single instance which is captured via [closure](https://stackoverflow.com/a/428624/2501279) mechanism by the handler method, so yeah, it will be a single instance per lifetime. – Guru Stron Apr 04 '23 at 18:09