The best option to use for a return type in ASP.NET Core WebAPI controller

Choosing the proper return type for WebApi controller actions

Starting from .NET Core version 2.1, there are so far three types of WebApi responses that controller actions can return. All three types had it's own pros and cons but all are lacking in options to satisfy both REST and the high level of testability. 

The available response types in ASP.NET Core including recently released 2.2 version are

  • Specific type result
  • IActionResult 
  • ActionResult<T>

Tle last reult option ActionResult<T> was introduced in .NET Core 2.1. I will use a simple example to compare the pros and cons of using any o these three controller action response type options.

Returning additional HTTP statuses

This is one of the requirements you will definitely have during the WebAPI application development process. Although REST is there to be followed, the functionality of any application is essentially driven by the business requirements. One of the things you might stumble upon if you are returning specific type as a result of your controller action will definitely be returning custom HTTP status code.

Let's see how will the following simple action with specific type return behave in different scenarios and compare it with the other two options of return type

Specific type

        [HttpGet("{id}")]
        public IEnumerable<string> GetById(int id)
        {
            if (id > 0)
            {
                return new List<String>()
                {
                    "Value1",
                    "Value2",
                    "Value3",
                };
            }
            return null;
        }
    

We have the simple code above which will return list of strings if ID parameter is greater than 0, otherwise will return null. Translated to HTTP, for any returned data we'll have 200 OK status code response.

Response 200

Response 204

In case of no data we will have 204 NoContent response. This is quite satisfying behavior for most of the clients, but let's assume we need another response code to be returned. For example, for any value less than 0 we want to tell the user that the data sent is not valid. Ideally we would return 400 BadRequest status code.

Now this is the problem for specific type return option. We cannot return 400 BadRequest status code straight away as a result of our method. If we decide to throw an exception which will cause response of 500 ServerError, which is wrong because invalid data is basically the client error and falls under 4xx response code list.

The way to do this is by explicitly setting the response status code and returning null value, which leaves the gap for potentialy not synced two operations as you have to take care of the status and returned data.

        [HttpGet("{id}")]
        public IEnumerable<string> GetById(int id)
        {
            if (id > 0)
            {
                return new List<String>()
                {
                    "Value1",
                    "Value2",
                    "Value3",
                };
            }
            else if (id < 0)
            {
                Response.StatusCode = 400;
            }
            
            return null;
        }
    

Response 400

There are even more problems in handling HTTP POST and HTTP PATCH/PUT requests as instead of 200 OK, you might have to respond with 201 Created of 202 Accepted HTTP status code in addition to previous 400 BadRequest which will be the case in model validation. It is not so straight forward to respond with the different status code and you have more than one line responsible for the proper response, unlike the other two return types.

Let's see how do we resolve this by using IActionResult as a return type of a controller action

IActionResult

        [HttpGet("{id}")]
        public IActionResult GetById(int id)
        {
            if (id > 0)
            {
                return Ok(new List<String>()
                {
                    "Value1",
                    "Value2",
                    "Value3",
                });
            }
            else if (id < 0)
            {
                return BadRequest();
            }
            return NoContent();
        }
    

Now we have freedom to use which ever status code we find suitable to inform the client how did we or we did not process his request without any limitations. Using IAction result is obviously more advanced than returning specific type instance and let .NET decide what will be he status code.

ActionResult<T>

Regarding the status codes, both IActionResult and ActionResult<T> return types are equally suitable ad does not make much differences which one you will use in terms of setting status code directly when returning the result from the method

        [HttpGet("{id}")]
        public ActionResult<IEnumerable<string>> GetById(int id)
        {
            if (id > 0)
            {
                return Ok(new List<String>()
                {
                    "Value1",
                    "Value2",
                    "Value3",
                });
            }
            else if (id < 0)
            {
                return BadRequest();
            }
            return NoContent();
        }
    

However, there are other aspects where ActionResult<T> is more advanced and more suitable than IActionResult for returning response from the controller action method.

More testable controller action methods

When we are looking at the response from the unit test perspective, things are a little bit different. Although it is possible to write unit tests for all three types we have to check for both returned type and the status code to have accurate test results and this is where things get messy

Specific type

Since we are returning the object itself as a result we do not have access to the HttpContext and we can only determine type and value of the data returned but not the status code or other parts of the actual response sent back to the client. Your unit test might pass because you are returning the proper value in your controller action method, but your response code may be wrong since it is not controlled in the same place where you are generating and returning the result data as we saw from the previous example. 

    public class SpecificType_Tests
    {
        [Fact]
        public void Get_Test_ShouldHaveItems()
        {
            var controller = new Values1Controller();
            var result = controller.GetById(3);

            Assert.NotNull(result);
            Assert.IsAssignableFrom<IEnumerable<String>>(result);
        }

        [Fact]
        public void Get_Test_ShouldNotHaveItems()
        {
            var controller = new Values1Controller();
            var result = controller.GetById(0);

            Assert.Null(result);
        }
    }
    

This makes unit tests not as accurate as they should be and you might have your unit tests passing, while in fact you are responding with the wrong status code. The consumers of your WebAPI application will almost certainly rely on the status code as it is the core part of REST and while you are actually returning the proper data, client might consider your response as an invalid one just because you are responding with the wrong status code and this is not detected by your unit tests for the controller. 

IActionResult

By using IAction result, we are returning the status code along with the data in controller action method. 

    public class ActionResult_Tests
    {
        [Fact]
        public void Get_Test_ShouldBeOk()
        {
            var controller = new Values3Controller();
            var result = controller.GetById(3);

            Assert.NotNull(result);
            Assert.IsType<ActionResult<IEnumerable<String>>>(result);
            Assert.IsType<OkObjectResult>(result.Result);
			Assert.IsAssignableFrom<IEnumerable<String>>((result as OkObjectResult).Value as IEnumerable<String>);
        }

        [Fact]
        public void Get_Test_ShouldBeNoContent()
        {
            var controller = new Values3Controller();
            var result = controller.GetById(0);

            Assert.NotNull(result);
            Assert.IsType<ActionResult<IEnumerable<String>>>(result);
            Assert.IsType<NoContentResult>(result.Result);
        }

        [Fact]
        public void Get_Test_ShouldBeBadRequest()
        {
            var controller = new Values3Controller();
            var result = controller.GetById(-1);

            Assert.NotNull(result);
            Assert.IsType<ActionResult<IEnumerable<String>>>(result);
            Assert.IsType<BadRequestResult>(result.Result);
        }
    }
    
    

This way we are not only checking the result data, but status code as well. We do not need access to the HttpContext as status code and the data is returned as an object representation. In our specific case that is OkObjectResult, NoContentResult and BadRequestResult.

If we check the method response for all scenarios, major thing we need to do is to check for these three object types in a response and we already have pretty accurate tests. 

ActionResult<T>

Similar to IActiionResult, ActionResult<T> response returns status code along with the data, so you can perform both status code and data check in controller unit tests. 

    public class IActionResult_Tests
    {
        [Fact]
        public void Get_Test_ShouldBeOk()
        {
            var controller = new Values2Controller();
            var result = controller.GetById(3);

            Assert.NotNull(result);
            Assert.IsType<OkObjectResult>(result);
            Assert.IsAssignableFrom<IEnumerable<String>>((result as OkObjectResult).Value);
        }

        [Fact]
        public void Get_Test_ShouldBeNoContent()
        {
            var controller = new Values2Controller();
            var result = controller.GetById(0);

            Assert.NotNull(result);
            Assert.IsType<NoContentResult>(result);
        }

        [Fact]
        public void Get_Test_ShouldBeBadRequest()
        {
            var controller = new Values2Controller();
            var result = controller.GetById(-1);

            Assert.NotNull(result);
            Assert.IsType<BadRequestResult>(result);
        }
    }
    

The only difference to IActionResult is that value property of the result is not boxed and returned as Object type. Instead you have it returned as a generic type T used for the ActionResult<T> response declared on the controller action method. This makes return type a bit easier to check as you do not need to unbox the resut property which in case of IActionResult you need to by casting it to the expected return type.

Conclusion

You have seen some of the pros and cons of the different response types for the controller actions in ASP.NET MVC Core WebAPI projects. It is obvious that IActionResult and ActionResult<T>are better options than returning the specific type, if not for less code from the first example, than definitely for more testable code in the second example.

Although you might find it more comfortable to return specific type in the controller action you may have your unit tests not covering your code properly and therefore openninig potential bus in the future.

References

Disclaimer

Purpose of the code contained in snippets or available for download in this article is solely for learning and demo purposes. Author will not be held responsible for any failure or damages caused due to any other usage.


About the author

DEJAN STOJANOVIC

Dejan is a passionate Software Architect/Developer. He is highly experienced in .NET programming platform including ASP.NET MVC and WebApi. He likes working on new technologies and exciting challenging projects

CONNECT WITH DEJAN  Loginlinkedin Logintwitter Logingoogleplus Logingoogleplus

JavaScript

read more

SQL/T-SQL

read more

Umbraco CMS

read more

PowerShell

read more

Comments for this article