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
- Base class
Answer 2:
Doesn't this section of readme addresses this exact question?
https://github.com/ardalis/ApiEndpoints#how-can-i-bind-parameters-from-multiple-locations-to-my-model
Original question seemed to indicate that wasn't working for him, but admittedly I don't have a reproduction of the problem, yet.
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. :)