In this chapter, you will learn about MVC, which is a popular design pattern for the user interface layer in applications, where M stands for Model, V stands for View, and C stands for Controller. In larger applications, MVC is typically combined with other design patterns, like data access and messaging patterns, to create a full application stack. This book will focus on the MVC fundamentals.
The controller is responsible for handling any HTTP requests that come to the application. It could be a user browsing to the /videos URL of the application. The controller’s responsibility is then to gather and combine all the necessary data and package it in model objects, which act as data carriers to the views.
The model is sent to the view, which uses the data when it’s rendered into HTML. The HTML is then sent back to the client browser as an HTML response.
The MVC pattern creates a separation of concerns between the model, view, and controller. The sole responsibility of the controller is to handle the request and to build a model. The model’s responsibility is to transport data and logic between the controller and the view, and the view is responsible for transforming that data into HTML.
For this to work, there must be a way to send HTTP requests to the correct controller. That is the purpose of ASP.NET MVC routing.
The ASP.NET middleware you implemented in the previous chapter must be able to route incoming HTTP requests to a controller, since you are building an ASP.NET Core MVC application. The decision to send the request to a controller action is determined by the URL, and the configuration information you provide.
It is possible to define multiple routes. ASP.NET will evaluate them in the order they have been added. You can also combine convention-based routing with attribute routing if you need. Attribute routing is especially useful in edge cases where convention-based routing is hard to use.
One way to provide the routing configuration is to use convention-based routing in the Startup class. With this type of configuration, you tell ASP.NET how to find the controller’s name, action’s name, and possibly parameter values in the URL. The controller is a C# class, and an action is a public method in a controller class. A parameter can be any value that can be represented as a string, such as an integer or a GUID.
The configuration can be done with a Lambda expression, as an inline method:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
ASP.NET looks at the route template to determine how to pull apart the URL. If the URL contains /Home, it will locate the HomeController class by convention, because the name begins with Home. If the URL contains /Home/Index, ASP.NET will look for a public action method called Index inside the HomeController class. If the URL contains /Home/Index/
123, ASP.NET will look for a public action method called Index with an Id parameter inside the HomeController class. The Id is optional when defined with a question mark after its name. The controller and action names can also be omitted, because they have default values in the Route template.
Another way to implement routing is to use attribute routing, where you assign attributes to the controller class and its action methods. The metadata in those attributes tell ASP.NET when to call a specific controller and action.
Attribute routing requires a using statement to the Microsoft.AspNetCore.Mvc namespace.
[Route("[controller]/[action]")]
public class
HomeController : Controller
{
}
In the previous chapter, you created a C# controller class named HomeController. A controller doesn’t have to inherit from any other class when returning basic data such as strings. You also implemented routing using the UseMvcWithDefaultRoute method, which comes with built-in support for default routing for the HomeController. When building an application with multiple controllers, you want to use convention-based routing, or attribute routing to let ASP.NET know how to handle the incoming HTTP requests.
Let’s implement the default route explicitly, first with a method and then with a Lambda expression. To set this up you replace the UseMvcWithDefaultRoute method with the UseMvc method in the Startup class. In the UseMvc method, you then either call a method or add a Lambda expression for an inline method.
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
Now that you have implemented default routing, it’s time to add another controller and see how you can reach that controller.
public class EmployeeController
{
}
public string Name()
{
return "Jonas";
}
The complete code for the EmployeeController class:
public class EmployeeController
{
public string Name()
{
return "Jonas";
}
public string Country()
{
return "Sweden";
}
public string Index()
{
return "Hello from Employee";
}
}
Let’s implement an example of attribute routing, using the EmployeeController and its actions.
[Route("employee")]
public class EmployeeController
[Route("")]
public string Index()
{
return "Hello from Employee";
}
[Route("name")]
public string Name()
{
return "Jonas";
}
[Route("country")]
public string Country()
{
return "Sweden";
}
[Route("[controller]")]
public class EmployeeController
[Route("")]
[Route("[action]")]
public string Index()
{
return "Hello from Employee";
}
[Route("[action]")]
public string Name()
{
return "Jonas";
}
[Route("company/[controller]")]
public class EmployeeController
[Route("company/[controller]/[action]")]
public class EmployeeController
The complete code in the EmployeeController class:
[Route("company/[controller]/[action]")]
public class EmployeeController
{
public string Name() { return "Jonas"; }
public string Country() { return "Sweden"; }
public string Index() { return "Hello from Employee"; }
}
The controller actions that you have seen so far have all returned strings. When working with actions, you rarely return strings. Most of the time you use the IActionResult return type, which can return many types of data, such as objects and views. To gain access to IActionResult or derivations thereof, the controller class must inherit the Controller class.
There are more specific implementations of that interface, for instance the ContentResult class, which can be used to return simple content such as strings. Using a more specific return type can be beneficial when unit testing, because you get a specific data type to test against.
Another return type is ObjectType, which often is used in Web API applications because it turns the result into an object that can be sent over HTTP. JSON is the default return type, making the result easy to use from JavaScript on the client. The data carrier can be configured to deliver the data in other formats, such as XML.
A specific data type helps the controller decide what to do with the data returned from an action. The controller itself does not do anything with the data, and does not write anything into the response. It is the framework that acts on that decision, and transforms the data into something that can be sent over HTTP. That separation of letting the controller decide what should be returned, and the framework doing the actual transformation, gives you flexibility and makes the controller easier to test.
Let’s change the Name action to return a ContentResult.
public class EmployeeController : Controller
public ContentResult Name()
public ContentResult Name()
{
return Content("Jonas");
}
Using a model class, you can send objects with data and logic to the browser. By convention, model classes should be stored in a folder called Models, but in larger applications it’s not uncommon to store models in a separate project, which is referenced from the application. A model is a POCO (Plain Old CLR Object or Plain Old C# Object) class that can have attributes specifying how the browser should behave when using it, such as checking the length of a string or displaying data with a certain control.
Let’s add a Video model class that holds data about a video, such as a unique id and a title. Typically you don’t hardcode a model into a controller action; the objects are usually fetched from a data source such as a database (which you will do in another chapter).
public class Video
{
public int Id { get; set; }
public string Title { get; set; }
}
public ObjectResult Index()
using AspNetCore22Intro.Models;
var model = new Video { Id = 1, Title = "Shreck" };
return new ObjectResult(model);
The complete code for the HomeController class:
public class
HomeController : Controller
{
public ObjectResult Index()
{
var model = new Video { Id = 1, Title = "Shreck" };
return new ObjectResult(model);
}
}
The most popular way to render a view from an ASP.NET Core MVC application is to use the Razor view engine. To render the view, a ViewResult is returned from the controller action using the View method. It carries with it the name of the view in the filesystem, and a model object if needed.
The framework receives that information and produces the HTML that is sent to the browser.
Let’s implement a view for the Index action and pass in a Video object as its model.
public ViewResult Index()
return View(model);
<title>Video</title>
@model AspNetCore22Intro.Models.Video
<body>@Model.Title</body>
Now that you know how to display one video, it’s time to display a collection of videos. To achieve this, you’ll first have to create the video collection and then pass it to the view displaying the data. In the view, you’ll use a Razor foreach loop to display the data as HTML.
var model = new List<Video>
{
new Video { Id = 1, Title = "Shreck" },
new Video { Id = 2, Title = "Despicable Me" },
new Video { Id = 3, Title = "Megamind" }
};
@model IEnumerable<AspNetCore22Intro.Models.Video>
<table>
<tr>
<td></td>
</tr>
</table>
@foreach (var video in Model)
{
<tr>
<td>@video.Id</td>
<td>@video.Title</td>
</tr>
}
The full code for the Index action:
public ViewResult Index()
{
var model = new List<Video>
{
new Video { Id = 1, Title = "Shreck" },
new Video { Id = 2, Title = "Despicable Me" },
new Video { Id = 3, Title = "Megamind" }
};
return View(model);
}
The full markup for the Index view:
@model IEnumerable<AspNetCore22Intro.Models.Video>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Video</title>
</head>
<body>
<table>
@foreach (var video in Model)
{
<tr>
<td>@video.Id</td>
<td>@video.Title</td>
</tr>
}
</table>
</body>
</html>
Hardcoding data in a controller is not good practice. Instead you want to take advantage of dependency injection to make data available in a constructor, using a service component, like the Message service you added earlier.
One big benefit of implementing a service is that its interface can be used to implement different components. In this book you will implement one for Mock data and one for a SQL Server database.
In this section, you will implement a MockVideoData component that implements an interface called IVideoData.
The data will be implemented as a List<Video>. Note that a List collection isn’t thread safe, and should be used with caution in web applications; but this code is for experimental purposes, and the component will only ever be accessed by one user at a time.
To begin with, the interface will only define one method, called GetAll, which will return an IEnumerable<Video> collection.
public interface IVideoData
{
}
using AspNetCore22Intro.Models;
IEnumerable<Video> GetAll();
using AspNetCore22Intro.Models;
public class MockVideoData : IVideoData
{
public IEnumerable<Video> GetAll()
{
throw new NotImplementedException();
}
}
private
readonly IEnumerable<Video> _videos;
public MockVideoData()
{
}
_videos = new List<Video>
{
new Video { Id = 1, Title = "Shreck" },
new Video { Id = 2, Title = "Despicable Me" },
new Video { Id = 3, Title = "Megamind" }
};
public IEnumerable<Video> GetAll()
{
return _videos;
}
services.AddScoped<IVideoData, MockVideoData>();
using AspNetCore22Intro.Services;
private
readonly IVideoData _videos;
public HomeController(
IVideoData videos)
{
_videos = videos;
}
var model = _videos.GetAll();
The complete code for the IVideoData interface:
public interface IVideoData
{
IEnumerable<Video> GetAll();
}
The complete code for the MockVideoData class:
public class MockVideoData : IVideoData
{
Private
readonly List<Video> _videos;
public MockVideoData()
{
_videos = new List<Video>
{
new Video { Id = 1, Genre = Models.Genres.Romance,
Title = "Shreck" },
new Video { Id = 2, Genre = Models.Genres.Comedy,
Title = "Despicable Me" },
new Video { Id = 3, Genre = Models.Genres.Action,
Title = "Megamind" }
};
}
public IEnumerable<Video> GetAll() { return _videos; }
}
The complete code for the ConfigureServices method in the Startup class:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<IMessageService, ConfigurationMessageService>();
services.AddScoped<IVideoData, MockVideoData>();
}
The complete code for the HomeController class:
public class
HomeController : Controller
{
private readonly IVideoData _videos;
public HomeController(IVideoData videos)
{
_videos = videos;
}
public ViewResult Index()
{
var model = _videos.GetAll();
return View(model);
}
}
In this chapter, you learned about the MVC (Model-View-Controller) design pattern, and how the controller receives an HTTP request, gathers data from various sources, and creates a model, which is then processed into HTML by the view, along with its own markup.
You will continue to use MVC throughout the book and create Razor Views and more sophisticated views and models that can be used to view and edit data.
Join our mailing list to receive the latest news and updates from our team.
Don't worry, your information will not be shared.
50% Complete
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.