Search code examples
c#asp.net-coreasp.net-core-webapiasp.net-core-2.2

Proper way to return calculation result from Web Api


I'm creating an API that will take parameters (type double) and return the result. ex. http://localhost:54897/api/Power/Nominal/6/-2

In the browser, I'll see: 4.0

Here's the code I have so far:

Model

public class PowerModel
{
    [Required]
    [Range(0,50)]
    public  double PowerFront { get; set; }

    [Required]
    [Range(-50,-1)]
    public  double PowerBack { get; set; }

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

Controller

[Produces("application/json")]
[Route("api/Power")]
public class PowerController : Controller
{
    [HttpGet("Nominal/{powerFront}/{powerBack}")]
    public double NominalPower(PowerModel powerModel)
    {
        if (ModelState.IsValid)
        {   
            powerModel.Result = Power.NominalPower(powerModel.PowerFront, powerModel.PowerBack);
            return powerModel.Result;
        }
        else
        {
            return 0;           
        }
    }
}

With the above code, I can take in parameters, validate them and return a numeric result.

Something doesn't seem right though. I don't want to return 0 on an invalid model state, I want to return a message in the event of invalid input.

What would be the best way to take in parameters (preferably named), validate those parameters with attributes, perform a calculation, return a value on successful input or show error message on incorrect input?


Solution

  • This is something that HTTP Status Codes are used for. In your successful case, 200 is returned to indicate that the request was successful. For an invalid ModelState, it's common to return 400 (which indicates a bad request was made).

    To achieve this in ASP.NET Core, you can take advantage of ActionResult<T>. Here's a complete example of how this would affect NominalPower:

    [HttpGet("Nominal/{powerFront}/{powerBack}")]
    public ActionResult<double> NominalPower(PowerModel powerModel)
    {
        if (!ModelState.IsValid)
            return BadRequest(ModelState);
    
        powerModel.Result = Power.NominalPower(powerModel.PowerFront, powerModel.PowerBack);
        return powerModel.Result;
    }
    

    In the example above, we pass ModelState into the BadRequest method, which will be serialised as JSON to show a list of errors that occurred when validing the model. If you'd prefer not to include this, you can just omit the ModelState argument when calling BadRequest.


    Alternatively, you could simply decorate your PowerController class with the ApiController attribute, which will cause any requests that result in an invalid ModelState to automatically return a 400 with JSON-serialised errors. Here's an example of that approach:

    [Produces("application/json")]
    [Route("api/Power")]
    [ApiController]
    public class PowerController : Controller
    {
        [HttpGet("Nominal/{powerFront}/{powerBack}")]
        public double NominalPower(PowerModel powerModel)
        {
            powerModel.Result = Power.NominalPower(powerModel.PowerFront, powerModel.PowerBack);
            return powerModel.Result;
        }
    }
    

    In this version, there's no need to check ModelState as that's already been checked thanks to the presence of the ApiController attribute. You can even customise the automatic response that gets returned, if needed, as I've detailed in another answer, here.