If you've been working in ASP.NET MVC at all, you've seen code like this before:
public ActionResult Index()
{
return View();
}
Any action in an MVC controller, generally speaking, returns an ActionResult
instance. Obviously that class represents an action of some kind, but exactly what does it do? Let's peel back the covers on ActionResult
and discover why it and its derived classes are critical to understanding ASP.NET MVC.
What is an ActionResult?
ActionResult is an abstract class that represents the result of an action method. The class itself inherits from System.Object, and only adds one additional abstract method: ExecuteResult, which is an abstract method that the derived classes of ActionResult
will implement themselves.
Many of the derived classes we're going to discuss have associated helpers. These helpers provide shortcuts to the constructor methods of their related Results. It's what allows us to return View()
rather than return new ViewResult()
.
Why is ActionResult
an abstract class? So that different controller actions can return different types of results and still have MVC handle them properly.
public ActionResult Multiple()
{
if(DateTime.Now.Day % 2 == 0)
{
return View("Other");
}
else
{
return RedirectToAction("Index");
}
}
You may have guessed that this implementation is done because ActionResult has a lot of derived classes, and you'd be exactly right. But what exactly are these different kinds of results? I've categorized them into three sections: content-returning, redirection, and status results, and we'll take a look at each kind in turn.
The Sample Project
There's a sample project over on GitHub that demos each of the ActionResult types. Check it out!
Now then, let's start this post properly by taking a look at the wide variety of content-returning results.
Content-Returning Results
These classes which derive from ActionResult
are responsible for returning content to the browser or calling script. In basic scenarios, the most common of these is the ViewResult
.
ViewResult
ViewResult
returns a view.
public class HomeController : Controller
{
public ViewResult Index()
{
return View();
}
}
Since MVC follows convention-over-configuration, MVC will look for a View named "Index" in the Views/Home subfolder, and then look in Views/Shared if it doesn't find it (and will throw an InvalidOperationException
if it can't find it at all).
What if I wanted to return a view other than the one that matches the action name?
public ActionResult Alternate()
{
return View("Other");
}
Now it will attempt to find a view named "Other" in the Views/Home folder.
PartialViewResult
We can also specify an action to return a partial view instead of a regular view:
public PartialViewResult Partial()
{
return PartialView("CustomPartial");
}
If we were to redirect to this action, we would see the content of that partial view, but without the layout page applied. This isn't very useful by itself, so a more useful application might be to call this action in an AJAX scenario and display the returned view.
FileResult
If we want to return a file, this is the class that we use. Depending on which overload we pick, we can specify what action the browser is to take with the downloaded file. For example, if we just specify a URL and a MIME type, the browser attempts to display the file specified (at least it does in Firefox, Chrome, and IE10, where I tested it):
public FileResult ViewFile()
{
return File(Url.Content("~/Files/testfile.txt"), "text/plain");
}
We can also just return the byte array for the file content, which (in Chrome at least) will display the file in the browser:
public FileResult FileBytes()
{
byte[] fileBytes =
System.IO.File.ReadAllBytes(Server.MapPath("~/Files/testfile.txt"));
return File(fileBytes, "text/plain");
}
Another overload will specify a download name, and using this overload causes the browser to download the file, rather than just display it:
public FileResult DownloadFile()
{
return File(Url.Content("~/Files/testfile.txt"),
"text/plain",
"testFile.txt");
}
FileResult
is one of the most open-ended of the classes that inherit from ActionResult
, and can handle a lot of different scenarios. It can accept byte arrays, FileStreams, and URLs of files, and in all scenarios return or download the file specified.
ContentResult
ContentResult
is used when we want to allow the action to specify what should be returned. It's a sort of catchall for scenarios where we need to allow the action full control over the returned content. All we need to do is specify the content and MIME type:
public ContentResult Content()
{
return Content("<h3>Here's a custom content header</h3>", "text/html");
}
Calling this action will display the <h3>
tag in the browser.
The most interesting thing about ContentResult
is that if you do this:
public string Content()
{
return "<h3>Here's a custom content header</h3>";
}
MVC actually creates a ContentResult
instances and wraps it around the returned value (and it doesn't have to be a string). Unless you return null
, in which case MVC returns an instance of EmptyResult
.
EmptyResult
MVC wants you to use EmptyResult
when the action is specifically intended to return nothing. Unlike all of the previous ActionResults though, EmptyResult doesn't have a helper. Additionally, if an action returns null
, MVC will detect that and make it return an EmptyResult.
public EmptyResult Empty()
{
return new EmptyResult();
}
public ActionResult NothingReturned()
{
return null; //Returns an EmptyResult
}
JsonResult
In my opinion, this is one of the coolest of the classes that inherit from ActionResult
. JsonResult
is used to represent JSON-encoded data, which is most commonly used to return structured data to a calling script, especially in AJAX scenarios.
But that's not why I like it so much. In ASP.NET MVC, you can JSONify anything. For example:
public JsonResult Json()
{
return Json(new { Name = "John Smith",
ID = 4,
DateOfBirth = new DateTime(1999, 12, 31) });
}
That returns a JSON structure that looks like this:
{"Name":"John Smith","ID":4,"DateOfBirth":"\/Date(946623600000)\/"}
Problems with JSON dates aside, the implications of this are staggering. Since that just encoded an anonymous object, it follows that JsonResult
can handle any object, including user-defined ones, and encode them into the JSON format.
However, there's one trick you should be aware of. If you attempt to redirect to the action above, MVC will throw an exception saying that "This request has been blocked because sensitive information could be disclosed to third party web sites when this is used in a GET request." MVC is trying to protect you here; it doesn't want you to share JSON information over a GET request because it could potentially contain sensitive information. This is attempting to protect you from an exploit known as JSON Hijacking, and Phil Haack has a great blog post on it.
You can turn off this error by doing this:
public JsonResult Json()
{
return Json(new { Name = "John Smith",
ID = 4,
DateOfBirth = new DateTime(1999, 12, 31) },
JsonRequestBehavior.AllowGet);
}
JsonRequestBehavior.AllowGet
does exactly what it sounds like; it allows browsers to access this JSON information in a GET request. I'd only recommend turning this on if you know what you are doing, since it could potentially expose you to JSON Hijacking.
JavaScriptResult
Another potentially dangerous kind of ActionResult
is the JavaScriptResult
, which returns script that is to be executed by the browser.
public JavaScriptResult LoadScript()
{
return JavaScript("alert('Hello! This popup shows that we can return JavaScript from controller actions.')");
}
On a view, we could do this:
<script type="text/javascript" src="@Url.Action("LoadScript")"></script>
This can be really useful for MVVM scenarios and other dynamic script scenarios, but be cautious about using it, as it potentially violates separation of concerns by making the Controller in charge of View functionality.
Redirection Results
So far, we've been dealing with kinds of ActionResult
which return or display content. Now, let's see what kind of ActionResult
we need for redirecting to other URLs or actions.
RedirectResult
If I wanted to redirect to a URL, I can use the RedirectResult
class, like this:
public RedirectResult RedirectToOtherSite()
{
return Redirect("http://www.exceptionnotfound.net");
}
That works great for redirecting to outside sites from the current app, but not so much for redirecting to other pages within the same app. For that, we can use RedirectToRouteResult
.
RedirectToRouteResult
RedirectToRouteResult
is used whenever we need to go from one action to another. There are two different ways of using it. One is to create your own Route values using RedirectToRoute
:
public RedirectToRouteResult RedirectToRoute()
{
return RedirectToRoute(new { controller = "Home", action = "Route" });
}
That's not very friendly though. There's a better way, an overload of this helper called RedirectToAction()
:
public RedirectToRouteResult RedirectToAction()
{
return RedirectToAction("Action");
}
The method RedirectToAction()
follows the same convention-over-configuration idea from the ViewResult
class; namely, that it looks for a corresponding action in the current controller if you don't specify which controller to look in.
RedirectToAction()
has many overloads that allow you to specify controllers, actions, route values, etc. It's probably the second most common ActionResult
derived class, because it's so readable.
Status Results
The final set of classes that inherit from ActionResult
are the Status Results, which return status codes to the browser for it to use.
HttpStatusCodeResult
HttpStatusCodeResult
returns an HTTP status code to the browser, along with a custom message to be displayed:
public HttpStatusCodeResult UnauthorizedStatusCode()
{
return new HttpStatusCodeResult(HttpStatusCode.Unauthorized, "You are not authorized to access this controller action.");
}
public HttpStatusCodeResult BadGateway()
{
return new HttpStatusCodeResult(HttpStatusCode.BadGateway, "I have no idea what this error means.");
}
Notice that there is no helper method for this class.
The HttpStatusCode enumeration contains all HTTP status codes (so that you don't have to remember what 402 or 307 mean). These are useful in exception-driven scenarios where you have custom error pages defined. However, as any experienced web developer knows, some kinds of status are MUCH more common than others, and MVC has ActionResult
-derived classes for the two most common.
HttpUnauthorizedResult
Returning an instance of HttpUnauthorizedResult
is the same as returning HttpStatusCodeResult
with HttpStatusCode.Unauthorized
, it's just more readable:
public HttpStatusCodeResult UnauthorizedResult()
{
return new HttpUnauthorizedResult("You are not authorized to access this controller action.");
}
HttpNotFoundResult
This is also an overload of HttpStatusCodeResult
, but unlike HttpUnauthorizedResult
, it actually does have a helper method:
public HttpNotFoundResult NotFound()
{
return HttpNotFound("We didn't find that action, sorry!");
}
Summary
There you have it, all of the classes that inherti from ActionResult
fit to demo. There's a few more of them, such as FileContentResult, but their functionality is pretty easy to figure out, so I leave that to you, my readers.
Don't forget about the sample project on GitHub with simple demos of all of these ActionResult
classes.
Also, if this project helped your better understand the ActionResult
classes, would you consider buying me a coffee? Your support goes straight to my projects and helps keep traditional ads off my site. Thanks!
Happy Coding!