Intro to AspNetCore MVC Controllers

In MVC every http request is handled by a Controller or rather a action method. These controllers are implemented as simple C# classes contained in your application. The Controller that is utilized to handle the incoming request is choosen by the routing system as I explain in my post about AspNetCore routing. Controllers then produce a response to the request and can either create their own responses or use action results that are handled by the MVC framework itself.

A Controller can handle the request in any way, as long as it does not interfere with the responsibility of the model or view. Each request instantiates a new instance of the controller that was chosen by your routing implementation. This means you do not need to synchronize on each controller, only on shared databases or singleton services.

What this post will cover

  • Understanding Convention for controllers
  • Using context data
  • Different ActionResults like ViewResult and Redirects

What this post will not cover

  • How a controller is choosen by the routing system
  • API controllers
  • Organization and design of controllers (e.g. Areas)
  • View rendering
  • Context data via ModelBinding

Caution

There are two things to consider when designing controllers for your web application:

  1. It is important that the controller does not implement logic that is better suited in the domain model. Firstly it couples the web app to the domain, secondly it is harder to test and grasp.
  2. All MVC classes you create whose name ends with Controller can handle Http requests, even though they are not intended to be controllers.

Understanding controllers

As mentioned in the introduction, Controllers in MVC applications are simple C# classes whose methods (called actions or action methods) are invoked by the framework to handle incoming http requests. The selection is done by the routing system, which invokes a RouteHandler Delegate and in turn creates a controller and invokes the selected method. This handling includes the invocation of domain logic and the preparation of an applicable response to the requesting client. During this process, MVC provides some context data from the incoming http request to the controller.

The response from a controller in an MVC application mostly amounts to a razor view. If you use a special version of a controller, called an API controller, that returns data without embedding it in HTML (think restful interfaces), then it will mostly be JSON/XML data objects. Also action methods can be used to advise MVC to perform a redirect.

So with this overview:

There are three important things to understand with controllers in MVC:

  1. How to define controllers for handling requests (plain C# classes but there are several options to create a controller)
  2. How does MVC provide actions with context data, important to get the data you need for effective web app dev
  3. Last but not least it is important to understand how action methods produce a response and how to fine tune this for maximum control

Creating Controllers

MVC favors convention over configuration, this means that every class which name  ends with Controller, is a controller and all public methods on this class define an invokable action for the incoming requests.
The convention is to put the controller classes in a folder below root that is named Controllers, yet you can create a controller wherever you want in your application.

You can create two types of controllers, firstly you can create a POCO controller, that has no dependencies to MVC:

namespace ControllerDemo.Controllers
{
   public class MyPOCOController
   {
       public string Index()
           => "this is my POCO controller";

   }
}

As you can see I used the conventions that I explained above. This controller simply returns a plain string on the Index method.

Controllerbase class

The other way to create a controller is to derive from the ControllerBase class that comes with ASP.net core. This gives you the benefit of some methods that are defined on the base, which make it easier to get hold of the context data of the incoming request, that the controller action was picked to handle. You can find the ControllerBase Class in Microsoft.AspNetCore.Mvc namespace.

public class MyDerivedController : Controller
{
    public ViewResult Index()
        => View("Result", "my derived controller");
}

If you would start an application with this controller in the root/Controllers folder, and with the URL of http://localhost:8034/MyDerived/Index will now be Displayed:
Model Data: my derived controller

Both controllers work the same (except for the different route, from their name). The key change here is, that the POCO Controller returns a string and the derived controller returns a ViewResult, which can be rendered from the ViewEngine of MVC.
To do this from a POCO controller you would need to import several namespaces and recreate all the steps that are built into the Controller base class. I will omit it, because I recommend to always derive from the ControllerBase class.

NonController and Controller Attributes

There are two attributes to tell MVC specifically to ingnore or include a class as a Controller. These two attributes are useful, when you can for some reason or another not follow the convention of MVC to postfix all controllers with Controller. A simple example might be the use of a class like MyMockController for unit testing purposes, then you would apply the NonController attribute to this class.

If for any other reason you could not hold up with the convention for a controller that should be used (maybe for style guide maxim class name length restrictions or whatever) you could apply the ControllerAttribute to this class.

Both Attributes can be found in the Microsoft.AspNetCore.Mvc namespace.

Receiving context data

Controllers will obviously not exist in isolation. They need to access data from the incoming requests, that they should handle. This might include query string values, form values or parsed parameters from the URL or request Body, that stem from the routing system. Those data is known as context data and it can be accessed in three different ways with MVC controllers:

  1. Extract from context objects
  2. As parameter in action method
  3. Explicitly invoke the MVC Model binding feature

1.) Context objects

This is one of the best reasons to use the ControllerBase class: convenient access to this context objects. These objects describe the current request, the response that is in preparation, and the state of the overall application.

  • Request: HttpRequest object, describes the request.
  • Response: HttpRespnse object, describes the response
  • HttpContext: HttpContext object, Source for other objects like request and Response
  • RouteData: RouteData object, produced by the routing system (see post on Routing)
  • ModelState: ModelStateDictionary object, validates client side data
  • User: ClaimsPrincipal object, describes the User of the request

Most controllers probably do not need all or any of those properties, because there are higher abstractions for those properties, that are accessible by the controller. For example you will probably not interact with HttpRequest object, because this is more elegant to do with the model binding feature.

Yet often those objects might not be needed for production, they can be useful for aid of debugging or to test assertions.

Just as side note in a POCO controller you can get access to the controller context by applying the ControllerContext Attribute, this will be injected for each incoming request via Dependency Injection:

[ControllerContext]
public ControllerContext ControllerContext {get; set;}

public string AnyAction()
{
     HttpRequest request = ControllerContext.HttpContext.Request;
}

The HttpContext can also be accessed from a ControllerBase class derived controller and offers the following properties:

  • Connection: Describes the low level connection
  • Request: HttpRequest (same as  above)
  • Response: HttpResponse (same as above)
  • Session: ISession object, describes the request associated session
  • User: ClaimsPrincipal, (same as above)

You can see, that there is no magic going on in the ControllerBase class, yet it leads to more concise and readable controller classes for your application.

2.) Action method parameters

The second way to gain access to context data is via action method parameters. This can lead to more elegant and natural code, which is why they should be preferred to context objects.
The first example that comes to mind is that of form data values submitted by the user. We will look into this specifically by a comparison of form data from context objects and from parameter values.

First by context object:

public ViewResult Receive()
{
    var firstname = Request.Form["firstname"];
    var lastname = Request.Form["lastname"];

    return View("Result", $"{lastname}, {firstname}");
}

Then by parameter values:

public ViewResult Receive(string firstname, string lastname)
    => View("Result", $"{lastname}, {firstname}");

for this to work you need to name the parameters exactly like the form data values (yet you can ignore case).

IMHO the second approach is much more readable and concise.

3.) Model binding

Because this is a pretty big topic in of itself, I will write some dedicated posts and omit it at this place.

Producing a Response

The eventual goal of a controller action in an MVC application, is to produce an adequate response to an incoming request. In most cases (except you use a API controller) this will be HTML. So the controller, or rather its action methods need a way to produce the response.

To produce a response you can again utilize Context objects with the HttpResponse object. This is the most basic way, like in the Request case. You can access the properties of the HttpResponse, like StatusCode, ContentType, Headers, Cookies and Body.

But it is not advisable, because it couples the action method to hard coded HTML and such.

Therefore you should always try to utilize the ActionResult feature of MVC. Those ActionResults are used to separate stating intentions from executing intentions. This is done by indirection of the framework.

Instead of working directly with the HttpResponse object, the Result implements the IActionResult from Microsoft.AspNetCore.Mvc namespace. The IActionResult object or rather the action result, describes what the response of the controller should be. This can amount to rendering a view, redirection to a different URL, or simply returning a JSON data format object. MVC takes care of how this action result will be transformed to the result that is desired.

The action result, is somewhat an implementation of the command pattern.

The IActionResult interface is defined as follows:

using System.Threading.Tasks;

namespace Microsoft.AspNetCore.Mvc
{
    public interface IActionResult
    {
        Task ExecuteResultAsync(ActionContext context);
    }
}

MVC does not imply any restrictions how the response of an action result is produced. So the action calls the IActionResult that it employs and then delegates the creation of the desired response to this IActionResult object. The ActionContext then provides the data I described above that is available to the controllers via the ControllerContext object.

So in essence you yourself could write a class that implements IActionResult and create your own action result and Http response as you see fit.

This is not only extendable but also very good for unit testing controllers. Because the way the controllers are built with the controllercontexts you can easily create your mocked client requests by invoking the action method directly and test for the expected ActionResult.

HTML Responses

To generate HTML Responses, MVC offers the ViewResult class that is an Action result. This action result provides the access to the Razor view engine, which can process .cshtml files to incorporate model data and much more. Also it sends the result to the client with the aid of the HttpResponse context engine. The ViewEngine in itself is a huge topic and should be of no concern for this post. We will now only look at how an action result of type ViewResult can be used in your controllers.

the best way to go about this is to use one of the overloads of the Controller base class View() methods.

  1. View() –> ViewResult with default view, as instructed by the name. (e.g. MyAction leads to MyAction.cshtml) being rendered.
  2. View(view) –> name of the view file that should be used to render the content.
  3. View(model) –> like 1. but with model data
  4. View(view, model) –> like 2. but with model data.

Search for a view

With calling the View method and a specified name, MVC needs to figure out where to find this .cshtml file to render the view. This is done by convention. By default MVC will start with the following locations:

/Views/<ControllerName w/o Controller>/<ViewName>.cshtml
/Views/Shared/<ViewName>.cshtml

This would result for a Controller named MyController and a view named MyView to

/Views/My/MyView.cshtml

If you use the parameterless View() method, then MVC looks for a view name that is equal to the Action method from which the View() method was called.

E.g. if the action method is named MyAction() in the Controller MyController then the resulting View would be looked for in:

/Views/My/MyAction.cshtml

Note that the first View that is found will be used for rendering.

As always with Asp.Net Core, you can favor convention, but could also use configuration to determine which view to be rendered. This can be done by specifying the path to the view file you ‘d like to be rendered like so:

View(/Views/MyPath/MyView.cshtml");

But be careful with this approach, as it might indicate a design problem of some sorts. Maybe you could better use a redirect (which we will look into in the next sections)

Redirect  & the Post/redirect/Get pattern

As we saw earlier common results of action methods are a resulting HTML or JSON format as a response to an incoming request.
Another action result can be used to redirect the client to another URL, which most often will be another action method, that then will be used to generate the output that you want it to be for the given request.

A redirect has one of two http status codes. The first is 302 which is a temporary redirection, and is the most frequently used type of redirection. When you use the Post/Redirect/Get pattern (as we will look shortly into) this is the status code you will see.
The other status code is 301, which in turn marks a permanent redirection. Which is quite dangerous to do, because it instructs the client not to request the original URL ever again. So if there might be any doubt, use 302 temporary redirection that is.

To perform a redirect you have four different methods to do so:

  1. RedirectResult
    1. MethodName: Redirect or RedirectPermanent
    2. Description: 302/301 redirect to the specified URL
  2. LocalRedirectResult
    1. MethodName: LocalRedirect
    2. Description: Redirects to local URL
  3. RedirectToActionResult
    1. MethodName: RedirectToAction / -Permanent
    2. Description: redirects to specific action and controller
  4. RedirectToRouteResult
    1. MethodName: RedirectToRoute/-Permanent
    2. Description: redirects client to URL of a specific route

Redirect to a literal URL

Most basic way is to redirect a client is with the Redirect method that can be called via the Controller base class. This method returns a RedirectResult. The parameter is given as a string literal that describes the URL where the client should be redirected to.

Redirect to a Routing System URL

Use Redirect via Routing System URL to redirect the user of your application to a different part of your application. Make sure, that your URL fits your defined URL routing schema.

Using hard coded URLs should ring the maintenance hell bell with you. That is why Asp.Net Core offers a way to generate valid URLs with the RedirectToRoute method. This creates an instance of RedirectToRouteResult. It issues a temporary redirect and takes an anonymous type whose properties are then passed to the routing system to generate a URL.

Also to avoid hardcoding urls, you can use the RedirectToAction method that uses the name of the Action method as a string parameter. With only the Action method name, the redirect happens in the same controller. If you specify the name like (“myController.MyAction”) than this controller and action will be used.

Post/Redirect/Get

The last thing about Redirecting URLs is the Post/Redirect/Get Pattern. This is also probably one of the most common usages of redirecting in a web application.

It is so important, because you wouldn’t want to execute a POST method twice on a form. This is because from the http verbs, POST is not idempotent, i.e. it is not safe for the state of the applications data to execute it twice. Or in other words, you could execute the other verbs as often as you wish and the state of the application is the same after the second request, as after the nth. And this is not true for POST operations.

For a more telling example, think of an application that creates a new todo item with a form and a POST operation on submit. If you submit it a second time this will lead to another, potentially equal item be posted to the persistent storage (if there is no validation or such). This might leave the application in an inconsistent state. To avoid this behavior you can follow the pattern called POST/Redirect/GET.

With MVC you would receive a POST request on your Action method, change the state of the application and then use the RedirectToAction or RedirectToRoute method to an action method that produces a GET response like so:

public class MyController : Controller
{
    [HttpPost]
    public RedirectActionResult ReceiveForm(string firstname, string lastname)
    {
        // change state in some way
       return RedirectToAction(nameof(ShowData));
    }

    public ViewResult ShowData() => View();
}

With the HttpPost attribute only POST requests are allowed for this method.

Temp data

With a redirect the client sends an entirely new HTTP request, which means the context data of the original request is lost. To avoid this loss of context data you could use the MVC TempData, which works similar to session data. The good thing about TempData in MVC is that it is automatically removed from the data store if the request has been processed. This is quite ideal for short lived data as in the case of a redirect.
So to demonstrate this behavior look at the above Receive

[HttpPost]
public RedirectActionResult ReceiveForm(string firstname, string lastname)
{     
     TempData["firstname"] = firstname;
     TempData["lastname"] = lastname;

     return RedirectToAction(nameof(ShowData));
}

public ViewResult ShowData()
{
    string firstname = TempData["firstname"] as string;
    string lastname = TempData["lastname"] as string;

    return View((object)$"{lastname}, {firstname}");
}

(note the cast to object on the string data so MVC does not mistake the string for a viewName)
TempData can also be used without deletion by using its  Peek method. If you need to have longer lived data, you need to use session data.

Passing data from action method to view/ with Viewbag

Besides Redirect, the normal use of a controller is to render HTML. This is done as mentioned above by View files and ViewResults. These View files are often strongly typed for a specific model or data in general. From calling a ViewResult by its View() method, you can transfer data to this View that should be rendered. So that the controller can glue together the Domain Model and the corresponding View like it is supposed to do with MVC pattern.

This can be done in various ways. As we saw in the section before you can use the View method overload with the parameter. This results in the ViewData.Model property of the ViewResult having the value that you specified in the parameter. If this is a string you need to cast it into object first, or it will be mistaken for the view name.

If you use a weakly typed view, you’ll need to cast the model parameter to the expected type. If you use a strongly typed view this is done for you by MVC. In both cases if you supply data of a type that is not expected, MVC will throw an exception.

The next approach to supplying data to the View is via the ViewBag. The ViewBag is a dynamic object, which can be created in the acton method and which properties can be accessed in the same way in the view. It utilizes the DLR and is a true dynamic Type in .Net.

public ViewResult MyAction()
{
    ViewBag.Data = "my data";
    ViewBag.Origin = "my viewBag";
}

In a view you would access it like this:

...
<body>
   <p>This is: @ViewBag.Data</p>
   <p>And the origin is: @ViewBag.Origin</p>
</body>
...

Return different types of Content

With a controller you can return several content types as a Result. Those are the following:

  • JsonResult
    • formats the result object as JSON
  • ContentResult
    • Content returns a specified object to the client (e.g. binary file like gif)
  • ObjectResult
    • Is not available because it is used by content negotiation
  • OkObjectResult
    • uses content negotiation and sends an object with http status code 200
  • NotFoundObjectResult
    • NotFoundMethod same as with Ok but status code 400

Besides these methods there are several Status Code Action Results. One way is to use the StatusCode method directly and supply it with a parameter of StatusCodes enum.

The other way is to use one of the predefined methods like OkResult, NotFoundResult, BadRequestResult etc. that are associated with a specific HTTP status code.

Summary

In this post we looked at how a controller works in general with MVC. First we explored what a controller is supposed to do in an MVC application. As the part we looked at how a controller is created and how it can access data during a request.

As a next part we saw that a controller can delegate the creation of the http Response via an IActionResult. We then took a closer look at what action results are there and how they can be utilized to create specific web application behaviors.


0 Comments

Leave a Reply

Avatar placeholder