17

I have a controller with one action. In this action method, I have an async method that I call and that is it. This is the code that I am using:

[HttpGet]
public Task<MyObject> Get()
{
    return _task.GetMyObject()
}

This serializes correctly into the JSON I expect from it. Now my manager insists that the signature should be changed to the following:

[HttpGet]
public async Task<IActionResult> Get()
{
    var data = await _task.GetMyObject();
    return Ok(data);
}

I'm of the belief that there is no reason for the code to await in the controller and can just return the Task because nothing afterwards depends on the result. Apart from the extra code generation (creation of state machine etc.) done for the await, are there any implications from a WebApi point of view of these approaches? To clarify, I want to know if returning an IActionResult is better than to just return Task<MyObject> even tho it seems like the results are the same.

Protector one
  • 6,926
  • 5
  • 62
  • 86
JohanP
  • 5,252
  • 2
  • 24
  • 34
  • Possible duplicate of [What is the purpose of "return await" in C#?](https://stackoverflow.com/questions/19098143/what-is-the-purpose-of-return-await-in-c) – SᴇM Jan 24 '18 at 05:10
  • 3
    @SeM No its not duplicate, just little bit of `await` keyword doesn't simply mean it is duplicate of another question with `await` keyword. – Akash Kava Jan 24 '18 at 05:12
  • @SeM It's not a dupe. I have seen that post. I want to know about IActionResult response – JohanP Jan 24 '18 at 05:12
  • @JohanP: It's been asked before, see [here](https://stackoverflow.com/questions/37998291/advantage-of-using-iactionresult-as-result-type-in-actions) – Tseng Jan 24 '18 at 08:18
  • @tseng That is almost it, IActionResult is one part of it, the other is do I lose/gain anything by waiting for async call and then return Ok() or do I lose/gain anything by just returning my task, and what are those things Im losing/gaining – JohanP Jan 24 '18 at 08:41
  • async/await creates an state-machine, which is minimally more costly then just returning the task itself. But it changes exception behavior. the exception will be thrown where the task is awaited not where its returned among a few other things noted in post from first comment. So if you want catch exceptions from `await_task.GetMyObject();` you have to use `await` – Tseng Jan 24 '18 at 09:15
  • @Tseng I dont wrap the call in try catch because I want it to 500 if unhandled exception occurs, so exception behaviour doesnt come into play. Common convention in library code is to just return task and not await it, would you say that convention is not suited for async controllers? – JohanP Jan 24 '18 at 09:52

4 Answers4

12

Task< T>

Pro

Unit tests do not require any casting,

 Product product = await controller.Get();

Big advantage is, your unit tests become truly independent of underlying HTTP Stack.

Swagger does not need any extra attribute to generate response schema as swagger can easily detect result type.

Another big advantage is, you can reuse your controller in some other controller when the logic remains same.

Also avoiding await before return gives slight improvement in performance as that part of code does not need Task state machine. I think future C# version will omit single await as compiler optimization.

Con

Returning error status code requires throwing exception..

    throw new HttpStatusException(404, "File not found");
    throw new HttpStatusException(409, "Unauthorized");

Task< IAsyncResult>

Pro

You can return HTTP Status code such as

 return NotFound(); // (Status Code = 404)
 return Unauthorized(); // (Status Code = 409)

Con

Unit testing requires extra casting..

 Product productResult = ((await controller.Get()) as OkResult).Result as Product;

Due to such casting, it becomes difficult to reuse your controllers in some other controller, leading to duplication of logic.

Swagger generator requires extra attribute to generate response schema

 [ProducesResponseType(typeof(Product), 200)]

This approach is only recommended when you are dealing with logic that is not part of unit tests, and not part of your business logic such as OAuth integration with third party services where you want to focus more on IActionResult based results such as Challenge, Redirect etc.

Akash Kava
  • 39,066
  • 20
  • 121
  • 167
  • It's more then just returning status code, which is also possible with `Task` (by setting the property on the http **response**. It allows you to choose the way the return type will be serialized, i.e. `FileResult` to return/steam a file or `JsonResult` where json will always be returned even if the requested media type was xml and xml serializere were requested or even to return a full or partially rendered html file (using `ViewResult` or `PartialViewResult` – Tseng Jan 24 '18 at 08:30
  • @Tseng Question is about `Task` and `Task`, not about `FileResult`, `ViewResult` etc, if I am expecting a model with Xml or Json in Api, why would I want `FileResult` or `ViewResult` ? – Akash Kava Jan 24 '18 at 08:41
  • *rolls eyes* `FileResult` **is** an implementation of `IActionResult`. When you have an action and enable the xml formatters, then **EVERY** Controller action which returns a model or `IActionResult` will return either Xml or Json depending on what the browser requests, though you can override it by explicitly returning `JsonResult` (or using the `Json` helper method which essentially does the same). Just saying the only advantage of `IActionResult` is to return status code is just **plain wrong**. – Tseng Jan 24 '18 at 08:55
  • 1
    @Tseng do you really think I don't know that `FileResult` is an implementation of `IActionResult`? My point is simple, if I am expecting a product with set of fields, what will I do with `FileResult` ever? You are right, `IActionResult` is best, keep using it, no one is forcing you not to use it. – Akash Kava Jan 24 '18 at 09:00
  • Your comment on `IActionResult` only **pro** being _You can return HTTP Status code such as_ does exactly **imply** that – Tseng Jan 24 '18 at 09:12
  • So is that 90% of cases where the api involves business logic should require only Task instead Task? – Naga Nov 17 '19 at 08:26
5

Actions can return anything, mostly they return an instance of IActionResult (or Task<IActionResult> for async methods) that produces a response. The action method is responsible for choosing what kind of response it return and the action result does the responding.

If an action returns an IActionResult implementor and the controller inherits from Controller, developers have many helper methods corresponding to many of the choices. Results from actions that return objects that are not IActionResult types will be serialized using the appropriate IOutputFormatter implementation.

For non-trivial actions with multiple return types or options (for example, different HTTP status codes based on the result of operations performed), prefer IActionResult as the return type.

Feiyu Zhou
  • 4,344
  • 32
  • 36
3

ASP.NET MVC is a conventions over configuration framework. This means any future maintainer of your code, including your future self, will expect code to be written a certain way in order to reduce the number of class files you have to inspect to make changes or additions.

While the result may be the same from your two technically different options, the conventional approach is to async/await your results. Anything other than that convention will potentially cause confusion for future maintainers. Additionally, future releases of MVC may break your code in unknown ways as you did not follow the convention.

Good leadership of software development teams includes instilling a desire to reduce overall manpower needs for the organization by simplifying potential future maintenance of the code. Your manager may be trying to promote this concept.

Jim Yarbro
  • 2,063
  • 15
  • 21
1

ASP.NET Core team, while unifying MVC and WEB API (Controller and ApiController), abstracted IActionResult for robust exception handling mechanism.

Throwing exceptions for control flow is an anti-pattern on action methods.

[HttpGet]
public async Task<MyObject> Get()
{
    var data = await _task.GetMyObject()
    if(data == null)
    {
        return NotFound(); //Or, return Request.CreateResponse(HttpStatusCode.NotFound)
        // Versus, throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
    }

    return Ok(data);
}

Note: NotFound(), Ok() etc. are IHttpActionResult things in WEBAPI2 era not new to asp.net core.

Bishnu Rawal
  • 1,387
  • 16
  • 33