ardalis / ApiEndpoints

A project for supporting API Endpoints in ASP.NET Core web applications.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Use Route and Body Parameters in an Endpoint

ardalis opened this issue · comments

For a PUT request that is making an update to a resource, how would an API endpoint allow the ID of the resource to be specified on the ROUTE while the new state of the resource is specified in the request BODY?

Some example code that is not working:

public class Put : Endpoint.WithRequest<InvestmentTypePutRequest>.WithResponse<InvestmentTypePutResponse>
{
    [HttpPut("/investment-types/{Id:Guid}")]
    public override async Task<ActionResult<InvestmentTypePutResponse>> HandleAsync(InvestmentTypePutRequest request, CancellationToken cancellationToken = default)
     {
           // request.Id is null here
      }
}

public class InvestmentTypePutRequest
{
  [FromRoute] public Guid Id { get; set; }
  [FromBody] public byte[] Version { get; set; } = null!;
  [FromBody] public string Name { get; set; } = null!;
  [FromBody] public string? Description { get; set; }
}

Try

[HttpPut("/investment-types/{Id}")]
public override async Task<ActionResult<InvestmentTypePutResponse>> HandleAsync(Guid id, InvestmentTypePutRequest request, CancellationToken cancellationToken = default) {}

public class InvestmentTypePutRequest
{
  public byte[] Version { get; set; } = null!;
  public string Name { get; set; } = null!;
  public string? Description { get; set; }
}

Edit: Oh, right, that cannot be done since HandleAsync signature is fixed..

Okay, let's think about it in a different way.
Why does Handle method needs to be predefined at all? We can just do this:

public class Put : BaseEndpointAsync
{
    [HttpPut("/investment-types/{Id:Guid}")]
    public async Task<ActionResult<InvestmentTypePutResponse>> HandleAsync(Guid id, InvestmentTypePutRequest request)
	{

    }
}

public class InvestmentTypePutRequest
{
  public byte[] Version { get; set; } = null!;
  public string Name { get; set; } = null!;
  public string? Description { get; set; }
}

And analyzer will keep us from defining multiple actions.

Side note:

  • I think it's ok to define all actions this way. No WithRequest/WithResponce needed. MediatR needs them to resolve information about the command/query so it can be called from some other code, but in this case Asp.Net will resolve all information, and direct invocations to our action automatically.
  • So essentially, this package can be boiled down to
    • Base class BaseEndpoint
    • Analyzer (that's keeps developers from creating more than one action in a controller)
    • And maybe source generator (that will automatically add [Route] attribute to every controller based on it's folder or some other criteria). Everything else (file nesting and request handling) is already built-in. maxkoshevoi@41e49ee

Original question seemed to indicate that wasn't working for him, but admittedly I don't have a reproduction of the problem, yet.

Original question seemed to indicate that wasn't working for him

Maybe it's because [FromRoute] is missing from parameter's signature?

image

I haven't tried to reproduce it either, but I can say for sure that inheriting directly from BaseEndpointAsync would work.

This issue was created by Steve for me. I've created a VS solution with a repro at https://github.com/msantor/ApiEnpointsIssue124. I've included a Postman collection with the test call I'm using. It's possible I'm missing something very simple. Thank you for your time.

I resolved my problem. It was because I had multiple [FromBody] attributes directly on the request type. I created an additional class to represent the body of the request and model binding does the rest. Sorry to have wasted everyone's time on this. I needed to more thoroughly read through the documentation. Please close this issue.

Ah, yes. That will do it. We've all been there. :)