Asp.Net Core Action Results Explained

Asp.Net Core has a set of action results which are intended to facilitate the creation and formatting of response data. Without a well formed correct response, our application cannot work correctly and efficiently. Therefore action results and as a whole mechanisms that are responsible for generating the response are an important part of an Asp.Net Core application. Knowing and using them correctly not only contribute to a more readable controller that states its intention clearly, but also it can reduce a lot of codes that are superfluous and are not needed to be written.

In this post I’m going to explain how Asp.Net Core action results works and what kind of response they return to the client. Also I’m going to discuss when and why to use them and how you can create you own custom action results. Also I’m going to introduce some ideas and opinions about correct usage and best practices that might be of benefit.

Table Of Contents

Different categories of action result

Quick note on returning action results

Miscellaneous action results

Security related action results

Redirect related action results

Web API related action results

File related action results

Action results form previous version of Asp.Net MVC that are either reamed or deleted

Building and returning a custom result

Best practices regarding the use of action results

Summary

 

Different categories of action result

I categorize the action results to five sections, these sections are mostly based on usage:

Miscellaneous: These are action results that are stand on their own or are too general

Security: These are action results that are related to security

Redirect: These are action results that are related to different kinds of redirection

Web API: These are action results that are most likely used in API controllers, but some of them can be used everywhere

Files: These are action results that are related to files

Here is a diagram describing the actions results’ inheritance hierarchy:

Asp.Net Core Action Results Inheritance Hierarchy

Asp.Net Core Action Results Inheritance Hierarchy

I could go with explaining the action results in accordance with this picture, but I thought categorizing it based on usability helps with remembering and explanation. Also because some of their characteristics can be the same.


 

Quick note on returning action results

When we want to render a view, we simply use return View("ViewName", Model). But what the framework actually does for us behind the scene is that it news up an instance of ViewResult, fill its property with the values we provided, or the values that should be set on the controller level. It makes our job simpler by doing some plumbing work for us, lets see what the framework does for us behind the scene:

As you can see if the framework didn’t do this, we needed to do a lot of plumbing work and our controller would have become harder to read. By the way what the framework does here is actually called Command Pattern.

So this basically means whenever we return Json(data), we could also return new JsonResult(data), and it’s true for all types of return result, some of them have less setup work to do, some of them have more. other thing to note is that some of these convenience methods are in abstract class Controller which we inherit from, and some of them are in ControllerBase. I think it’s very useful to know what the framework does for you under the hood, because in some circumstances it can make things more flexible or simpler.


 

Miscellaneous action results

IActionResult and ActionResult

IActionResult and ActionResult work as a container for other action results, in that IActionResult is an interface and ActionResult is an abstract class that other action results inherit from. So they can’t be newed up and returned like other action results. IActionResult and ActionResult have not much of a different from usability perspective, but since IActionResult is the intended contract for action results, it’s better to use it as opposed to ActionResult. IActionResult/ActionResult should be used to give us more flexibility, like when we need to return different type of response based on user interaction.

For example if something not found we return NotFoundResult, but if it was found we return it as part of a ViewResult. We can also use it to implement graceful degradation, for example if JavaScript was enabled we return a JsonResult but if it wasn’t we return ViewResult. We find this out by setting a flag of some kind to true form JavaScript if it was enabled, like I’ve explained in this post.

ViewResult

ViewResult is intended to render a view to response, we use it when we want to render a simple .cshtml view for example.

JsonResult

JsonResult is intended to return JSON-formatted data, it returns JSON regardless of what format is requested through Accept header. There is no content negotiation happen when we use JsonResult. Content negotiation is the process of figuring out what type of data browser requested through its Http request Accept header. For example this is an accept header that request content of type HTML: Accept: application/xml, */*; q=0.01, with action results of type JsonResult no content negotiation takes place. Which means server ignores the user requested type and return JSON, I explain content negotiation in more detail in subsequent section.

PartialViewResult

PartialView are essential when it comes to loading a part of page through AJAX, they return raw rendered HTML. Here I try to explain a scenario that I might want to use PartialViews:

I have a page that submit a Product, I have a main page for it. Now I want to add different brand of the same product with some info about it, I have a button to add more brand of products. I can either submit the product and add brand to it using a normal view, or I can add a button and a modal containing the fields needed for submitting new brands.

But how should I do it? place the needed HTML on the main page? What if there was a different model for the data involved with it? Or some kind of calculation was involved? Wther way is to do all the calculation from JavaScript side but that would be too verbose. Best way is to use an action result of type PartialViewResult, do the stuff I need to do there, return the HTML and attach the HTML to the main page through JavaScript.

ViewComponentResult

Usually we use view component by calling Component.InvokeAsync in the view, but can we use the returned HTML form a view component directly? Maybe we want to reuse our business logic or refresh our the HTML part of the page that are loaded with view component, can we do that? YES! we can do that with ViewComponentResult, as you can see with the code excerpt above, the HomeSliderComponent is a view component action that we can directly call and get HTML, and do something like what has asked in this question.

ContentResult

The default return type of a ContentResult is string, but it’s not limited to string. We can return any type of response by specifying a MIME type, in the code excerpt above I’ve returned a content of type application/json.

EmptyResult

I use EmptyResult when I have some kind of command, like delete, update, create and I don’t want to return anything. According to CQS principle commands shouldn’t return anything. EmptyResult execute our command and return 200 status code. There is one other kind of action result that return null but it doesn’t return 200 HTTP status code, but 204. It’s called NoContentResult, but we might want to use that when we have a web api. I explain that in detail in subsequent section.

Result of type POCO!

If we want to return a POCO class for an action, we can. As you can see in the code above the PocoResult action returns an object of type Person and when accessed, we get a nicely formatted JSON.

That’s because the framework automatically creates an ObjectResult wrapper for you, and the default format of serialization in MVC is JSON. You can also have an action of type generic list of Person, like with the GetAllPersons action and the framework takes care of serialization for you.

Primitive Types Result

You can also return string or int or any other kind of primitive types and the framework tries its best to convert it to a response that is pertinent to the current type. Here what happens when you return a string in StringResult:

In this case we get a response with content type of text/plain, but that’s not true for other types, for example here is what you get when you return int in action IntResult:

Here we see that result is converted to JSON, hmm.

NonAction Attribute

If you want an action to not be accessed from outside, and be public too, you can use [NonAction] attribute. By decorating an action by [NonAction], you’ll get a 404.


 

Security related action results

SignInResult

SignInResult will sign in the user based on provided mechanism. As you can see in the code above, the SignInActionResult creates a ClaimsPrincipal along with an identity called passport and the claims needed for that identity. Then it passes the claim principal to the SignIn method of the controller. Currently we use cookie to sign the user in.

Also note that returning SignInResult is the same as calling HttpContext.Authentication.SignInAsync, on AuthenticationManager class as you can see happened in the method SignInResultAsync. SignInResult internally calls the SignInAsync for you in its ExecuteResultAsync method. The effect of returning a SignInResult or calling the SignInAsync is the same but the SignInResult is more readable in the context of a controller in my opinion. I use SignInAsync outside controllers if I wanted to sign the user in. By the way if you want to know more about the authentication process in asp.net core Andrew Lock has a fantastic introductory article on it.

SignOutResult

This one is the same as SignInResult with the difference that it sign the user out. As you can see in the SignOutActionResult method, SignOut method takes an authentication scheme which determine from what kind of authentication the user should get signed out. You can also call the HttpContext.Authentication.SignOutAsync if you like as I did in the SignOutResultAsync method.

ForbidResult

We use ForbidResult when we want to refuse request to a particular resource. it returns 403 status code to response and redirect us to the path specified in cookie authentication setup through the AccessDeniedPath property. From what I understood from its HTTP specification, it should be used to allow access if the user had the correct authorization credentials, and completely refuse it if user hadn’t.

By this I mean we don’t redirect the user to a login page. We might even show a 404 page for more security and don’t let the unauthorized user even know that such a resource exist and needs the correct credentials. It’s like saying what are you doing here with this credential dude? You shouldn’t event be here! ForbidResult calls HttpContext.Authentication.ForbidAsync internally, so we can basically call the FordbidAsync method of AuthenticationManager directly like in ForbidAsyncResult method as you can see in the code excerpt above.

ChallengeResult

We use ChallengeResult when we need to tell the user that his authentication credential wasn’t valid or not even present. Then redirect the user to a login page which is our way of challenging the user so to speak. With doing so we get 401 Unauthorized status code in our response and get redirected to the path specified in cookie authentication setup through the AccessDeniedPath property. Like other security related results you can also call HttpContext.Authentication.ChallengeAsync as in ChallengeAsyncResult directly.

UnauthorizedResult

UnauthorizedResult returns 401 status code, its difference with ChallengeResult is that it just returns an status code and doesn’t do anything else. In contrast with its counterpart that has many options for redirecting the user and options related to asp.net core identity.


 

Redirect related action results

There are four types of action results that are related to redirect.  With each one you can either return normal redirect, or permanent. The the return method related to permanent ones are suffixed with Permanent keyword. You can also return these results with their Permanent property set to true. These action results are:

  • RedirectResult
  • RedirectToActionResult
  • RedirectToRouteResult
  • LocalRedirectResult

In subsequent section I’m going to explain each one of them and when to use them.

RedirectResult

RedirectResult will redirect us to the provided URL, it doesn’t matter if the URL is relative or absolute, it just redirect, very simple. Other thing to note is that it can redirect us temporarily which we’ll get 302 status code or redirect us permanently which we’ll get 301 status code. If we call the Redirect method, it redirect us temporarily.

if we call the RedirectPermanent method, it redirect us permanently. Also as I explained in previous section we don’t need to use these methods to redirect permanently or temporarily, we can just new up an instance of RedirectResult with its Permanent property set to true or false and return that instead, like this:

return new RedirectResult("/") {Permanent = true};

RedirectToActionResult

RedirectToActionResult can redirect us to an action. It takes in action name, controller name, and route value, like the previous one. It can redirect us temporarily(RedirectToAction method) or permanently(RedirectToActionPermanent method). By using it and not using a pure string to specify URL, we have the advantage of inspecting the addresses easily as opposed to parsing string.

RedirectToRouteResult

RedirectToRouteResult should be used when we want to redirect to a route, it takes a route name, route value and redirect us to that route with the route values provided. It can also redirect us permanently or temporarily by setting the Permanent property to true or false or by using the controller base methods RedirectToRoute/RedirectToRoutePermanent. Like previous method it is also a better option than RedirectResult because we don’t have to parse route values which are string or assume anything if we wanted to unit test the action for example.

LocalRedirectResult

We should use LocalRedirectResult if we want to make sure that the redirects that happens in some context are local to our site. By doing that we make ourselves immune to open redirect attacks. This action result type takes a string for URL needed for redirect, and a bool flag to tell it if it’s permanent. Under the hood it checks the URL with Url.IsLocalUrl("URL") method to see if it’s local. If it was it redirect us to the address, but if it wasn’t it’ll throws an InvalidOperationException. One other caveat is that if you pass a local URL with an absolute address like this, http://localhost:12059/Home/Index, you’ll get an exception. That’s because the IsLocalUrl method consider URL like this to not be local, so you must always pass a relative URL in.


 

Web API related action results

In this section I’m going to explain services that might be used in an API contoller, I know some of them might be used everywhere, I just did it to categorize them.

BadRequestResult

We use this action result to indicate a bad request, it doesn’t take any argument, it just return a 400 status code.

BadRequestObjectResult

It is the same as BadRequestResult, with the difference that it can pass an object or a ModelStateDictionary containing the details regarding the error, as you see in the picture below:

NotFoundResult

This one is simple, it returns a 404 status code to response.

NotFoundObjectResult

The same as NotFoundResult, with the different that you can pass an object with the 404 response.

ObjectResult

ObjectResult is the super type of: CreatedAtActionResult, CreatedAtRouteResult, CreatedResult, BadRequestObjectResult, NotFoundObjectResult, OkObjectResult, AcceptedResult, AcceptedAtActionResult, AcceptedAtRouteResult. ObjectResult primary role is content negotiation, if you dig deep, it has some variation of method called SelectFormatter on its ObjectResultExecutor. You can return an object with it, and it formats the response based on what user is requested in the Accept header, if the header didn’t exist, it returns the default format configured for the app. It’s important to note that if the request is issued through a browser, the Accept header will be ignored, unless we set the RespectBrowserAcceptHeader to true when we configure the MVC options in Startup.cs. Also it doesn’t set the status code, which cause the status code to be null.

OkObjectResult

OkObjectResult is like ObjectResult, it does the formatting and content negotiation, the only difference is that it returns 200 status code, as opposed to ObjectResult that returns null status code.

OkResult

OkResult return 200 status code, without any related object.

NoContentResult

The action result returns 204 status code. It’s different from EmptyResult in that EmptyResult returns an empty 200 status code, but NoContentResult returns 204. Use EmptyResult in normal controllers and NoContentResult in API controllers.

StatusCodeResult

StatusCodeResult accept an status code number and set that status code for the current request. One thing to point is that you can return an ObjectResult with and status code and object. There is a method on ControllerBase called StatusCode(404, new { Name = "TomDickHarry" }), which can take a status code and an object and return an ObjectResult.

CreatedResult

CreatedResult returns 201 status code along with a URI to the created resource. You should use it when you creating a resource, and after creation you can pass the URI of the created resource and that in turn set the Location header field of the response.

CreatedAtActionResult

Almost the same as CreatedResult, it returns a 201 status code. With the difference that it takes a controller, action, route value, and the object that is created, as opposed to CreatedResult that only takes a URI and an object.

CreatedAtRouteResult

Almost the same as CreatedResult, with the difference that it takes a route name and route value, instead of URI.

AcceptedResult

AcceptedResult returns a 202 status code, indicating that the request is successfully accepted for processing, but it might or might not acted upon. Which in this case we should redirect the user to a location that provide some kind of monitor on the current state of the process, for this purpose we pass a URI.

AcceptedAtActionResult

Almost the same as AcceptedResult with the difference that it takes a controller, action, route value, and an object instead of URI.

AcceptedAtRouteResult

Almost the same as AcceptedResult with the difference that it takes a route name and route value instead of URI.

UnsupportedMediaTypeResult

This action result returns 415 status code, which means server cannot continue to process the request with the given payload. It doing this by inspecting the  Content-Type or Content-Encoding of the current request or inspecting the incoming data directly.


 

File related action results


FileResult

FileResult is the parent of all file related action results. These are: FileContentResult, FileStreamResult, VirtualFileResult, PhysicalFileResult. Since we can use it to return any kind of file, we can use it when we need flexibility for example if we need to return files from different places in the system based on the parameters we receive, kind of like IActionResult. There is a method on ControllerBase class called File. This method accept a set of parameters based on the type of file and its location, which maps directly to the more specific return types mentioned above, I’ll discuss how to use it in the following section.

FileContentResult

Use FileContentResult if you want to return the file as an array of bytes as you see in FileContentActionResult.

FileStreamResult

We use FileStreamResult when we want to return the file as a FileStream as you can see in FileStreamActionResult.

VirtualFileResult

You can use VirtualFileResult if you want to read a file form a virtual address and return it, as shown in the VirtualFileActionResult .

PhysicalFileResult

You can use PhysicalFileResult to read a file from a physical address and return it, as shown in PhysicalFileActionResult method.


 

Action results form previous version of Asp.Net MVC that are either reamed or deleted

JavaScriptResult (doesn’t exist anymore, you can use ContentResult instead)
FilePathResult (Use VirtualFileResult or PhysicalFileResult insead)
HttpNotFoundResult (Use NotFoundResult instead)
HttpStatusCodeResult (Use StatusCodeResult instead)
HttpUnauthorizedResult (Use UnauthorizedResult instead)

If you know of any other changed or deleted action results, please let me know in the comments section.


 

Building and returning a custom result


If the current preexisting action results doesn’t meet your requirement, you can create your own. First let me tell you that if you need an action result that returns XML, you don’t need a custom action result. You can use input and output formatter explained near the bottom of this page. The reason for explaining this is to see how asp.net core produce response and the fact that we can customize it however we want.

In order to build a custom action result, we need to inherit form IActionresult or ActionResult. I have two constructor function, one only get the value to be serialized and other one get the value and the status code. Next I override the method ExecuteResultAsync and assigned the HttpResponse object to a variable, then I set the ContentType and  StatusCode value. Finally I’ve serialized the value using Serialize private method, converted that serialized value to an array of byte, and wrote that to response body using context.HttpContext.Response.Body.WriteAsync. Here is what we get when we use it:

As I’ve said you don’t need to do this if you need to format a value to another type, there are Input formatters that are used with model binding, and output formatters that are responsible for formatting responses.


 

Best practices regarding the use of action results

I’m a proponent of being as specific as possible and not using IActionResult and ActionResult unless you really need flexibility, here is my reasons for doing so:

  • programmers can mistakenly return an action result type that are not pertinent and usable by the caller, take a look at this image:

As you can see, when we are specific we immediately get a build error, but with IActionResult we don’t get anything. Let’s try to use the result with IActionResult to see what happens:

As you can see we can’t use the NotFoundResult that is returned. What I mean is that we cannot react to this kind of result, any code I put here isn’t going to run. You might say who will do such a thing? But I see this a lot, often an action result type are returned that are incompatible with the way this action is going to be used.

  • Another reason is that by returning an specific kind of action result our controller becomes more clear. If we have 20 action in our controller, and four of them are JsonResults for example, the return type singles them out
  •  Another minor reason is when we unit test the controller’s action, we don’t need to cast the results all the time, this is a small reason, but it’s still  a reason 😉

I’m not saying we shouldn’t use IActionResult, I say we use it when we need it, not because we don’t know what type of result we should return or using IActionResult make our life simpler.


 

Summary

In this post I described all the action results available in Asp.Net Core and categorized them based on usability. I also described what happens under the hood when we return an action result and introduced some ideas about when and how to use them. You can find the code files used in this post here.

Share...
 

Hamid Mosalla

Hi, I'm Hamid ("Arman"). I'm a software developer with 8+ years of experience in C#, .NET Core, Software Architecture and Web Development. I enjoy creating dev tools, contributing to open-source projects, and sharing insights on my blog. Outside of tech, I’m into indie cinema, classical music and abstract art.

 

8 thoughts on “Asp.Net Core Action Results Explained

Leave a Reply

Your email address will not be published. Required fields are marked *