Search code examples
restasp.net-corepostgetconventions

GET for complex payload scenarios?


Our team is designing a RESTful API that strictly follows REST conventions. We are currently facing a challenging problem for which we have not yet found a solution. As you can see, the GeneratePermissionUrls method should be a GET request instead of a POST because it retrieves data. It may create some temporary URLs for uploading files, but I don’t think that would make this method a POST, right? The reason we had to change it to a POST is that the parameters contain an ICollection , which is only supported in the HTTP Body, something that GET does not have. Do you have any suggestions?

    [HttpPost]
    [ProducesResponseType(201)]
    [ProducesResponseType(400)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<IActionResult> GeneratePermissionUrls(AnimalGeneratePermissionUrlsParameters parameters, CancellationToken cancellationToken = default)
    {
        var result = await animalService.GenerateSASUrls(parameters.FilesInfo, parameters.AnimalId, parameters.ClientUploadSpeed, parameters.ClientInternetLatency, nameof(AnimalsController));
        return result.Match<IActionResult>(
                       success => Ok(success.AttachedData),
                       error => NotFound(error));
    }
using System.ComponentModel.DataAnnotations;

namespace petaverseapi;

public class AnimalGeneratePermissionUrlsParameters
{
    [Required]
    public int AnimalId { get; set; }

    [Required]
    public double ClientUploadSpeed { get; set; }

    [Required]
    public int ClientInternetLatency { get; set; }

    public ICollection<AnimalGeneratePermissionUrlsFileInfoDTO> FilesInfo { get; set; } = null!;
}

public class AnimalGeneratePermissionUrlsFileInfoDTO
{
    public string FileName { get; set; } = string.Empty;
    public float FileSize { get; set; }
}

I was trying to make my action controller is a GET request.


Solution

  • the GeneratePermissionUrls method should be a GET request instead of a POST because it retrieves data. It may create some temporary URLs for uploading files, but I don’t think that would make this method a POST, right?

    GET is appropriate if the semantics of the request are safe (aka: essentially read only). It's analogous to "get me this document out of your document store".

    But that assumes that you can encode into the target resource identifier all of the information you need.

    In those cases where you are looking at the request payload as well -- the registered solution is POST (which, sadly, hides from general purpose application components the information that the request is essentially read only).

    A candidate alternative to POST is to use QUERY, which is intended for those circumstances in which you want a safe request with a body. You can find the most recent draft here; the issue tracker at the git repository will give you additional details, so that you can judge whether the semantics of your request are (or can be adapted to be) suitable for this method token.

    Me? If a body were necessary, I would use POST -- I'm not a test pilot, and an unregistered method token with an expired draft specification has little appeal.


    But I might push back on the assumption that a body is necessary here.

    The reason we had to change it to a POST is that the parameters contain an ICollection , which is only supported in the HTTP Body, something that GET does not have.

    It sounds like you are using implementation limitations to dictate your API, which... well, it's a choice, and it could be even be the right choice, depending on the value of tradeoffs in your local context.

    I don't see anything in the data classes here that couldn't be described by a URI template (assuming that de facto limits on URI length don't interfere with the result), which would allow you to copy the information you want from the resource identifier to your in-memory representation.

    You might have to write the parsing code yourself, including handling the various errors that could occur in the presence of bad/malicious data.


    In some cases, it could even make sense to implement two handlers - one for common cases (assuming that in the common cases the URL length is reasonably bounded) and other to handle the outliers.