Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minimal API query & route parameter mapping to an object #35304

Closed
mumby0168 opened this issue Aug 12, 2021 · 7 comments
Closed

Minimal API query & route parameter mapping to an object #35304

mumby0168 opened this issue Aug 12, 2021 · 7 comments
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc design-proposal This issue represents a design proposal for a different issue, linked in the description feature-minimal-actions Controller-like actions for endpoint routing old-area-web-frameworks-do-not-use *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels

Comments

@mumby0168
Copy link

Summary

The minimal API's currently look for a public static bool TryParse(...) method to map parameters from query or route parameters to an object. It would be great to have this look at the properties on an object to match route and/or route parameters.

Motivation and goals

A get request on a minimal API route that takes a set of parameters where it may be nicer to map these to an object rather than a set of arguments on a delegate.

In scope

  1. Map query parameters to properties on an object for get & delete requests.
  2. Map route parameters to properties on an object for get & delete requests.

Out of scope

  • Supporting post & put requests.

Risks / unknowns

  • It may add more complexity to the by design set of minimal apis.

Examples

Query String Match

app.MapGet("api/todo", (FetchTodosQuery query) => {
    return Results.Ok($"Page: {query.Page} and size {query.PageSize}");
});

public class FetchTodosQuery
{
    public int Page { get; set; }

    public int PageSize { get; set; }
}

The above route would map from a query string such as this http://localhost:5001/api/todo?page=1&pageSize=10

Route Match

app.MapGet("api/todo/{page}/{pageSize}", (FetchTodosQuery query) => {
    return Results.Ok($"Page: {query.Page} and size {query.PageSize}");
});

public class FetchTodosQuery
{
    public int Page { get; set; }

    public int PageSize { get; set; }
}

The above route would map from a query string such as this http://localhost:5001/api/todo/1/10

Implicit vs Explicit

I would expect the choice of which method to use could always be overriden using the attributes [FromRoute] & [FromQuery].

Detailed design

I am pretty sure this is already supported in controllers for aspnet core when using the [FromQuery] attribute it will build up a dictionary of values from the query string and map these where possible to properties on an object.

I would expect this to work the same.

@mumby0168 mumby0168 added the design-proposal This issue represents a design proposal for a different issue, linked in the description label Aug 12, 2021
@davidfowl
Copy link
Member

This isn't something we want to support natively. We're going to look allow you to customize input binding but we don't want to end up writing a de-serializer the way that MVC does today as it is infinitely complex and fragile.

@davidfowl davidfowl added feature-minimal-actions Controller-like actions for endpoint routing old-area-web-frameworks-do-not-use *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels labels Aug 12, 2021
@mumby0168
Copy link
Author

This isn't something we want to support natively. We're going to look allow you to customize input binding but we don't want to end up writing a de-serializer the way that MVC does today as it is infinitely complex and fragile.

How would that look being able to write your own input binding would it be in the form of a custom attribute and an implementation of some interface to perform the request mapping to a complex type?

Okay I thought that may be the case, so currently there is no way to get more than one parameter mapped into a complex type via minimal API's? I stumbled upon this when trying to write some extensions for MediatR I ended up with something like below problem being that it takes object as an implementation of IRequest it does not support simple types, I did add a static TryParse method to one of the objects which worked but was only limited to one parameter on a object this is a snippet of the extension methods:

public static MinimalActionEndpointConventionBuilder MapMedaitRGetFromQuery<TRequest, TResponse>(this IEndpointRouteBuilder builder, string pattern) where TRequest : IRequest<TResponse>
        => builder.MapGet(pattern, async ([FromQuery] TRequest request, IMediator mediator) =>
        {

            try
            {
                var got = await mediator.Send(request);
                return Results.Ok(got);
            }
            catch (MediatRException e)
            {
                return Results.StatusCode((int)e.Code);
            }
        });

public static MinimalActionEndpointConventionBuilder MapMedaitRGetFromRoute<TRequest, TResponse>(this IEndpointRouteBuilder builder, string pattern) where TRequest : IRequest<TResponse>
    => builder.MapGet(pattern, async ([FromRoute] TRequest request, IMediator mediator) =>
    {

        try
        {
            var got = await mediator.Send(request);
            return Results.Ok(got);
        }
        catch (MediatRException e)
        {
            return Results.StatusCode((int)e.Code);
        }
    });

@davidfowl
Copy link
Member

How would that look being able to write your own input binding would it be in the form of a custom attribute and an implementation of some interface to perform the request mapping to a complex type?

The current API we're thinking about would allow global registration based on type and an attribute on the parameter to opt in per parameter.

Okay I thought that may be the case, so currently there is no way to get more than one parameter mapped into a complex type via minimal API's?

That's correct.

The difficulty with your extension method is that you somehow need to bind any arbitrary TRequest and I'm not sure how that would work.

@mumby0168
Copy link
Author

mumby0168 commented Aug 13, 2021

The current API we're thinking about would allow global registration based on type and an attribute on the parameter to opt in per parameter.

Okay that makes sense how would that look then would it be an attribute where the parameter is the type it should attempt to bind to?

Something like [Bind(typeof(Foo)] string bar where record Foo(string Bar)?

The difficulty with your extension method is that you somehow need to bind any arbitrary TRequest and I'm not sure how that would work.

This is what I was going for, however I made an assumption that it would work like MVC, it may not be possible just liked the idea of handlers being registered for a CQRS api without the need for controller methods.

app.MapMedaitRPost<CreateTodo>("api/todo");
app.MapMedaitRGetFromQuery<FetchAllTodos, IEnumerable<TodoDto>>("api/todo");
app.MapMedaitRGetFromRoute<FetchTodo, TodoDto>("api/todo/{id}");
app.MapMedaitRPut<UpdateTodo>("api/todo");
app.MapMedaitRDelete<DeleteTodo>("api/todo");

The post and put methods work a treat as it default to deserialise the request body so those are not an issue.

@davidfowl
Copy link
Member

we've added minimal support for a TryParse overload that can work on custom types that takes the HttpContext (#35433). This should be enough of a stop gap to experiment with binding for custom types. We plan to build something more complete in .NET 7 based on feedback.

@mumby0168
Copy link
Author

we've added minimal support for a TryParse overload that can work on custom types that takes the HttpContext (#35433). This should be enough of a stop gap to experiment with binding for custom types. We plan to build something more complete in .NET 7 based on feedback.

Okay that is great thank you, certainly will let me move forward with my idea. Will this issue continue to track the changes that will come as part of .NET 7 or will I need to keep my 👁️ out?

@davidfowl
Copy link
Member

Closing in favor of #35489

@ghost ghost locked as resolved and limited conversation to collaborators Sep 19, 2021
@amcasey amcasey added the area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc label Jun 2, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc design-proposal This issue represents a design proposal for a different issue, linked in the description feature-minimal-actions Controller-like actions for endpoint routing old-area-web-frameworks-do-not-use *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels
Projects
None yet
Development

No branches or pull requests

3 participants