Introduction to ASP.NET Core part 8: MVC continued with controller actions and our first view

Introduction

In the previous post we started looking into MVC in .NET Core. We added a new library through NuGet and wired up the MVC middleware in Startup.cs. Routing is about mapping parts of the requested URL to a controller and an action. The controller is a normal C# class which most often derives from the Controller base class. The action is a C# function that returns some type of response to the HTTP request. The action can have 0, 1 or more parameters that must match the query parameters of the URL otherwise the routing engine won’t find it. We also wrote our first controller called the HomeController and the first action called Index which corresponds to the /home/index URL. We also discussed the basics of routing and added a very basic convention based route to Startup.cs.

In this post we’ll look into the various return types of a controller action. We’ll also build our first HTML view using Razor.

We’ll be working with the DotNetCoreBookstore demo application as before.

The books controller

Let’s add our first model to the project. Insert a folder called Models and the following class into it:

public class BookViewModel
{
	public int Id { get; set; }
	public string Title { get; set; }
	public string Author { get; set; }
}

Note how the class is called BookViewModel and not just Book. We can on the one hand have a domain object, also called an entity, called Book in another C# class library in the solution. On the other hand it’s also possible that we don’t want to expose the full Book object to the View depending on who’s asking for it. So a single domain object Book can have multiple view models that only contain the information that the requester is supposed to see and use. E.g. the controller may ask a service class to get a list of books. The service can decide which properties of the Book should be visible to the caller. E.g. if the caller has elevated rights then they may see more than normal users. If this is new to you then just keep in mind that a single domain object which often includes its own logic can have one or more distilled view models that are only meant for viewing and/or data binding purposes.

We’ll now add a matching controller to the Controllers folder. This time we’ll leave the IActionResult return type but return null at first since we have no matching view yet:

namespace DotNetCoreBookstore.Controllers
{
    public class BooksController : Controller
    {
        public IActionResult Index()
        {
	      return null;
        }
    }
}

The IActionResult interface is an interface for a wide variety of return types of actions. It’s very rare that an MVC action only returns a plain string like our current home controller. Instead, there are specialised action result objects that help us build proper HTTP responses. The available result objects often map to a standard HTTP status code like 200 OK, 201 Created, 400 Bad Request etc.

If you start typing…

return this.

…in the action body then IntelliSense will provide a lot of these concrete implementations. Example include Accepted, BadRequest, Challenge, File etc. These are helper methods coming from the Controller and ControllerBase base classes. They tend to have multiple overloads to make them flexible so that different types of responses can be built from them. It happens that the action result has no helper method in which case it can be instantiated. Let’s first return a bad request:

return BadRequest("No, you won't get any books from here!!!");

Start the application and navigate to /books. Since our default route in Startup.cs defines “index” as the default action the Index action method will be correctly found by the routing engine. You should see the string returned above in the browser:

No, you won’t get any books from here!!!

Let’s test the File response. I saved a file called Books.txt in my C:\tmp folder. The content doesn’t make any difference, mine looks like this:

blah
blah
blah

The File helper method has a couple of overloads with different options. We’ll go for the streaming variant which requires a stream, a content type and a file download name:

return File(System.IO.File.OpenRead(@"c:\tmp\books.txt"), "application/octet-stream", "books.txt");

If you skip the last parameter, i.e. the file download name, then the downloaded file will get the name of the streamed file with no file extension like “books” which may be confusing for the client.

Navigate to /books again and you should see that the file was downloaded by the browser:

Text file downloaded by .NET Core controller action

We can return a simple 200 response with the Ok function:

return Ok("Great stuff");

There are many more response types like that. We’ll look at two more interesting and important ones. Let’s start with ObjectResult. This object has no dedicated function like the ones we’ve looked at so far. The object result in turn comes in different shapes: object result, bad request object result, not found object result and OK object result. All of them require an object that can be serialised.

Let’s add a private function to BooksController where we return a number of books:

private IEnumerable<BookViewModel> GetBooks()
{
	return new List<BookViewModel>()
	{
		new BookViewModel() {Id = 1, Author = "John Smith", Title = "C# for beginners" },
		new BookViewModel() {Id = 2, Author = "Jane Cook", Title = "Java for beginners" },
		new BookViewModel() {Id = 3, Author = "Mary Stone", Title = "F# for beginners" },
		new BookViewModel() {Id = 4, Author = "Andrew Cooper", Title = "Software architecture" },
		new BookViewModel() {Id = 5, Author = "Susan Williams", Title = "SOLID principles" }
	};
}

If you start typing…

return new Obj

…within the Index function then IntelliSense will list those 4 object result types that I listed above. We’ll try the OkObjectResult:

return new OkObjectResult(GetBooks());

This produces a 200 OK HTTP response with a serialised list of BookViewModel objects. The default serialiser is JSON which fits most purposes. Run the application and navigate to /books. You should see the JSON list of books:

[{“id”:1,”title”:”C# for beginners”,”author”:”John Smith”},{“id”:2,”title”:”Java for beginners”,”author”:”Jane Cook”},{“id”:3,”title”:”F# for beginners”,”author”:”Mary Stone”},{“id”:4,”title”:”Software architecture”,”author”:”Andrew Cooper”},{“id”:5,”title”:”SOLID principles”,”author”:”Susan Williams”}]

This action result type is great for APIs and Javascript clients that process JSON data for client-side HTML manipulation.

A HTML view

It’s time to build our first HTML view and list our books in a table. A controller action can return a ViewResult that’s linked to a view file. If you used ASP.NET MVC before then all of this is familiar to you. The standard HTML generator engine is Razor like before. Razor view files have the extension cshtml which obviously indicates a mix of C# and HTML. In Razor views we can have standard HTML alongside with embedded C# code to build dynamic views. A view can have a model which is injected by MVC. Most views will have some object dependency but this is not mandatory.

Some important conventions to follow here are the following:

  • There must be a folder called Views or Shared in the web project root. We can have both. The Shared folder views will be available to all controllers
  • Within the Views or Shared folder there must be another folder reflecting the name of the controller, e.g. Books and Home in our case so far
  • The view files must be placed within these controller specific sub-folders
  • The view file name must by default reflect the name of the action method, though this can be customised

These conventions are in place so that MVC can find the correct view without explicit coupling.

So add a new folder called Views to the web project root and a sub-folder called Books into it. Then right-click the Books folder and add a new item of type MVC View Page called Index.cshtml:

Insert a new Razor view into .NET Core web application

Here’s the content of the view file:

@model IEnumerable<DotNetCoreBookstore.Models.BookViewModel>

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Our books</title>
</head>
<body>
    <h1>These are our books on offer</h1>
    <table>
        <thead>
            <tr>
                <th>ID</th>
                <th>Author</th>
                <th>Title</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var bookViewModel in Model)
            {
                <tr>
                    <td>@bookViewModel.Id</td>
                    <td>@bookViewModel.Author</td>
                    <td>@bookViewModel.Title</td>
                </tr>
            }
        </tbody>
    </table>
</body>
</html>

We declare the model of the view using the @model declaration. In our case it’s a sequence of book view models. Then we have some easy HTML including a table. The Model property refers to the model declared by the @model directive. C# code in Razor is separated from HTML by the at sign ‘@’. In the for-each loop we loop through the list of book view models and print their available properties.

Back in the Index function of BooksController we can use the View helper method and pass in the list of books as follows:

return View(GetBooks());

The View helper method has an overload where we can declare a different view file name if needed:

return View("someViewName", GetBooks());

In this case MVC will be looking for a view file called someViewName.cshtml in the Views/Books/ or Shared/Books folder. However, since we followed the default naming conventions this is not necessary.

Run the application and you’ll see a table with all the book view models in the browser:

Listing all book view models in ASP.NET Core MVC

Great, we’ve just closed the full MVC cycle in ASP.NET Core. We get a URL which is matched against a controller/action method combination. The controller builds a view model which is passed into a matching view. The view is then sent back to the user and rendered in the browser. We’re making progress!

We’ll continue in the next part soon.

View the list of MVC and Web API related posts here.

Advertisement

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Elliot Balynn's Blog

A directory of wonderful thoughts

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Once Upon a Camayoc

Bite-size insight on Cyber Security for the not too technical.

%d bloggers like this: