Model-View-Controller in .NET Core

Today’s header image was created by Patryk Grądys, the original source for the image is available here
This article is going to be a high level introductory overview of what an MVC Application is, what it can provide and how to build a very basic one in .NET Core.
If you already know what an MVC Application is (with regards to .NET Framework), then I would recommend skipping straight to the section on Middleware as this is entirely new in .NET Core (but the stuff before that isn’t).
I’m preparing some tutorials where we’ll build an MVC Application together. During those tutorials I’ll go into what each component does in much greater detail.
Hang tight for them
MVC – What Is It?
MVC stands for Model-View-Controller, and is a design pattern that’s used mostly with Web Applications.
It allows you to separate the business logic and response generation (Controller) from the mark up (View) and data models (Model).
Because we love to separate our concerns, as it makes things easier to maintain.
Also, describing an acronym backwards is cool. Right?
In .NET Core (as with the .NET Framework), Controllers are types of classes which deal with generating responses to requests that a user might send to your web server.
Views are written in HTML, Razor syntax (Razor is a C# preprocessor which allows you to use C# code via special helper methods in the markup, which will be processed and parsed to HTML), and Javascript. They’re usually found within a Views directory, with sub-directories named after each of the Controllers.
More about Controllers in a moment.
Models are POCOs and are usually found in a Models directory.
I’ve mentioned them in other posts, but POCO stands for Plain Old CLR Object
Controllers are classes which deal with generating responses to requests sent to you MVC application. They are usually found in a Controllers directory.
Controllers don’t deal with Routing (which takes the request URL and figures out which Controllers and methods to call). This is handled by Kestrel’s routing middleware.
More on that in a moment.
Models are instantiated in the Controllers and consumed by the Views.
Middleware
Middleware are types of classes, the concept of Middleware is new for .NET Core’s web server (called Kestrel), and there are lots of different Middleware classes. There’s logging middleware, routing middleware, authorisation middleware, exception handling middleware.
And that’s just naming a few.
Middleware needs to be registered in the startup class before it can be consumed in any of your Controllers. The middleware that you do register can be conditionally added, too.
Let’s say that you only want logging enabled in development. Just wrap it in an if – if we’re in development, then register the logging middleware.
Here’s an example:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) | |
{ | |
if (env.IsDevelopment()) | |
{ | |
loggerFactory.AddConsole(Configuration.GetSection("Logging")); | |
loggerFactory.AddDebug(); | |
} | |
app.UseMvc(routes => | |
{ | |
routes.MapRoute( | |
name: "default", | |
template: "{controller=Home}/{action=Index}/{id?}"); | |
}); | |
} |
I’ve highlighted the relevant lines
The Configuration class can be found in the startup.cs file.
You wouldn’t necessarily only register logging in development, but I’ve included it as an example.
Routing
The routing middleware is what deals with routing incoming requests to your controllers. You can set up as many routing options are you want – to fit the ideal flow of your application – but the default will usually suffice for most cases.
Here is an example of default routing and how you would set it up in .NET Core:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) | |
{ | |
if (env.IsDevelopment()) | |
{ | |
loggerFactory.AddConsole(Configuration.GetSection("Logging")); | |
loggerFactory.AddDebug(); | |
} | |
app.UseMvc(routes => | |
{ | |
routes.MapRoute( | |
name: "default", | |
template: "{controller=Home}/{action=Index}/{id?}"); | |
}); | |
} |
Again, I’ve highlighted the relevant lines
The above adds a route called default which takes the format
/Controller/Action/Parameter
Where the parameter is nullable, and can take the form of either a value or a query string.
Dealing with Requests
Let’s say that our have a Home controller
Which we will, in a few moments
And within that controller, we have an Index method. Let’s also say that we don’t want to accept any user supplied parameters. The Home controller might look like this:
using Microsoft.AspNetCore.Mvc; | |
using WebMvcApplication.ViewModels; | |
using System; | |
namespace WebMvcApplication.Controllers | |
{ | |
public class HomeController : Controller | |
{ | |
public IActionResult Index() | |
{ | |
return View(); | |
} | |
} | |
} |
A request to the Index method on the Home controller would look like this:
https://myawesomesite.com/home/index
Actually the index method is special, in you don’t need to explicitly call it in your request.
Using default routing, the request would be sent to the Index method in your Home controller. From there a response would be generated and sent back to the user.
Now lets say that you had a Products controller with an Information method which took a product id as its parameter. The whole thing might look like this:
using Microsoft.AspNetCore.Mvc; | |
using WebMvcApplication.ViewModels; | |
using WebMvcApplication.Services; | |
using System; | |
namespace WebMvcApplication.Controllers | |
{ | |
public class ProductController : Controller | |
{ | |
private readonly IProductService _productService { get; set; } | |
public class ProductController(IProductService productService) | |
{ | |
_productService = productService | |
} | |
public IActionResult Information(int id) | |
{ | |
// _productService queries the Products database | |
var productViewModel = _productService.getOrCreate(id); | |
return View(productViewModel); | |
} | |
} | |
} |
There’s a lot going on here, but I’ve highlighted the controller method.
The request might look like this:
https://my awesomesite.com/products/information/22
The default routing options would parse the id (in this case 22) to an integer and pass it to the Information method located on the Product controller. The Information method would then generate a response and send it back to the user.
Why Would I Use an MVC Application?
Because MVC is an implementation of the idea of Separation of Concerns, we can say that each of the main steps in MVC deals with one very specific thing.
- Model – our data is represented here
- View – our response markup is dealt with here
- Controller – our logic is created and used here
Its entirely possible to create web applications and websites without using MVC, but if you want to tailor the responses to specific data (perhaps entities in a database), then it could become difficult to manage if you didn’t use MVC.
I’m talking here of Server side stuff, by the way. I know that MVVM is a thing, but that’s mostly client side.
Not to mention the amount of Javascript that’s required to swap HTML out on the fly. This is entirely doable, but let’s let .NET Core take care of the harder stuff for us.
Unless you’re doing CGI, in which case I’d say come into the 21st century.
Plus we can partially generate HTML ahead of time, inserting values or other blocks of markup using C#, when we generate our response.
Making it easy peasy to edit your view and add properties from a model.
Again, this is doable without using MVC. For instance WebForms (a precursor to MVC) did this, but didn’t follow the separation of concerns rule fully – controller-like code was included via “code behind” files, which sat behind the HTML.
Whereas in MVC the Controllers (which are anaglous to code-behind files) are completely separate to the Views. Or anything else for that matter.
The key here is separation of concerns.
We get it, Jamie, you like separation for concerns.
By keeping components separate and providing interfaces to them (what parameters they accept, etc.) we make the system easier to maintain and we make it easier to replace sections of the code base with something else – as long as the new code implements the same interfaces, then we can swap it out with little or no trouble.
How Do I Create One?
You have two options here:
- yoeman and Visual Studio Code
- Windows and Visual Studio 2015 (with update 3)
You could totally do all of this by hand rolling all of the code yourself, but that would be tedious and boring. So let’s allow our tools to help us.
yoeman and Visual Studio Code
It’s back to our old friend the Terminal for the first step. Assuming that you’re in a directory where you would like your source files:
yo aspnet |
You should see something similar to this:

From here, we’re going to choose “Web Application”. Yoeman will do some magic and ask you for a name for the project.
I chose “WebMvcApplication”, but you can choose whatever you want.
Once it’s done it’s magic, you should be able to change into the directory with your new code, restore packages and build:
There will be a pause between steps two and three here
cd WebMvcApplication | |
dotnet restore | |
dotnet build |
Once that’s done, go open the directory containing your code in Visual Studio Code.
Windows and Visual Studio 2015 (with update 3)
If you’re running Windows and Visual Studio 2015, then you can go File > New > Project, select “ASP.NET Core Web Application (.NET Core)” as per the following screen shot:

Then choose WebApplicaiton on the next step.
I’ve got a post all about the different ways and tools that you use to can create projects in .NET Core, in the pipe. So hang tight.
How Do I Add To It?
Let’s add a Model. Add this class (SimpleViewModel.cs) to the Models directory:
using System; | |
namespace WebMvcApplication.ViewModels | |
{ | |
public class SimpleViewModel | |
{ | |
// A message for our visitors | |
public string Message { get; set; } | |
// A hit counter, because it's 1999 apparently | |
public int HitCounter { get; set; } | |
} | |
} |
Seems simple enough, right?
Next, our Controller. You’ll want to replace the contents of the HomeController.cs file with this:
using Microsoft.AspNetCore.Mvc; | |
using WebMvcApplication.ViewModels; | |
using System; | |
namespace WebMvcApplication.Controllers | |
{ | |
public class HomeController : Controller | |
{ | |
public IActionResult Index() | |
{ | |
// Create the best view model ever | |
var viewModel = new SimpleViewModel | |
{ | |
Message = "This is a simple message to the visitor", | |
HitCounter = 1000000; | |
}; | |
// Pass the new view model into the view | |
return View(viewModel); | |
} | |
} | |
} |
And now the View. This time, replacing the contents of the Index.cshtml file (found in the ‘Views/Home’ directory) with this:
@using Microsoft.AspNetCore.Http | |
@using Microsoft.AspNetCore.Http.Authentication | |
// Inform Razor of the model type to use | |
@model WebMvcApplication.ViewModels.SimpleViewModel | |
@{ | |
ViewData["Title"] = "Homepage"; | |
} | |
<h2>@Model.Message</h2> | |
<div class="row"> | |
<div class="col-md-8 col-md-offset-2"> | |
<p>We've had over @Model.HitCounter hits so far!</p> | |
</div> | |
</div> |
The idea here is that when the user navigates to /Home/Index
Or just /Home/ if you haven’t played with the routing.
The Index method of the HomeController will fire
This type of method is also known as an Action Method
This method creates an instance of the SimpleViewModel object and passes it to an MVC method called View.
The View method creates a response by looking for a view with the same name as the Action Method – in this instance, “Index.cshtml” (since we called the Index method). It will first look in a shared views directory, then it will look in a directory with the same name as the controller.

The View method creates an IActionResult by passing the instance of the SimpleViewModel into the Razor pre-processor along with the Index.cshtml view file. The output of that is used to generate a response to the user’s request.
If there was an Index.cshtml file in the Shared directory (where MVC’s routing looks first), but which didn’t take a model of type SimpleViewModel, then that view will not be used, as the type of model for the view has to match the type being passed in.
Here’s an example of what I mean:
@using Microsoft.AspNetCore.Http | |
@using Microsoft.AspNetCore.Http.Authentication | |
// We don't have a view model here | |
@{ | |
ViewData["Title"] = "Homepage"; | |
} | |
<h2>This is not the view you are looking for</h2> | |
<div class="row"> | |
<div class="col-md-8 col-md-offset-2"> | |
<p>At a space port in a galaxy far, far away</p> | |
</div> | |
</div> |
Because the above view’s declaration doesn’t match (due to the type of view model being passed to it), MVC’s routing will discard the above Index.cshtml and keep searching.
If a compatible view is not found, then a runtime exception will be raised and the viewer might see a yellow screen of death.
The routing package will head to the Home directory because it matches the name of the Controller where our request was dealt with.
There it will find an Index.cshtml file which takes an instance of SimpleViewModel, so it will load and parse that razor syntax. The instance of the SimpleViewModel will be passed to it, and raw HTML will come out. This HTML will be sent back to the viewer as the response.
Here is what the resulting HTML will look like:
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
<title>Homepage</title> | |
<link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.6/css/bootstrap.min.css" /> | |
<meta name="x-stylesheet-fallback-test" content="" class="sr-only" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("position","absolute",["\/lib\/bootstrap\/dist\/css\/bootstrap.min.css"]);</script> | |
<link rel="stylesheet" href="/css/site.min.css?v=G7OG5flN0NdPJ13sNYOv3Hwkc-gAxRfBTYgtu6Sl0yk" /> | |
</head> | |
<body> | |
<div class="navbar navbar-inverse navbar-fixed-top"> | |
<div class="container"> | |
<div class="navbar-header"> | |
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> | |
<span class="sr-only">Toggle navigation</span> | |
<span class="icon-bar"></span> | |
<span class="icon-bar"></span> | |
<span class="icon-bar"></span> | |
</button> | |
<a class="navbar-brand" href="/">Ok</a> | |
</div> | |
<div class="navbar-collapse collapse"> | |
<ul class="nav navbar-nav"> | |
<li><a href="/">Home</a></li> | |
<li><a href="/Home/About">About</a></li> | |
<li><a href="/Home/Contact">Contact</a></li> | |
</ul> | |
<ul class="nav navbar-nav navbar-right"> | |
<li><a href="/Account/Register">Register</a></li> | |
<li><a href="/Account/Login">Log in</a></li> | |
</ul> | |
</div> | |
</div> | |
</div> | |
<div class="container body-content"> | |
<h2>This is a simple message to the visitor</h2> | |
<div class="row"> | |
<div class="col-md-8 col-md-offset-2"> | |
<p>We've had over 1000000 hits so far!</p> | |
</div> | |
</div> | |
<hr /> | |
<footer> | |
<p>© 2016</p> | |
</footer> | |
</div> | |
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.3.min.js"></script> | |
<script>(window.jQuery||document.write("\u003Cscript src=\u0022\/lib\/jquery\/dist\/jquery.min.js\u0022\u003E\u003C\/script\u003E"));</script> | |
<script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.6/bootstrap.min.js"></script> | |
<script>(window.jQuery && window.jQuery.fn && window.jQuery.fn.modal||document.write("\u003Cscript src=\u0022\/lib\/bootstrap\/dist\/js\/bootstrap.min.js\u0022\u003E\u003C\/script\u003E"));</script> | |
<script src="/js/site.min.js?v=4YtIaePNzexGu4QQcABZ3hmCTZ5PpZ6UoIpVvTVV2ww"></script> | |
</body> | |
</html> |
I’ve highlighted the rendered HTML from out Index.cshtml page.
The bulk of this HTML is actually content from the _layout.cshtml file (found in the Shared views folder):
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
<title>@ViewData["Title"]</title> | |
<environment names="Development"> | |
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" /> | |
<link rel="stylesheet" href="~/css/site.css" /> | |
</environment> | |
</head> | |
<body> | |
<div class="navbar navbar-inverse navbar-fixed-top"> | |
<div class="container"> | |
<div class="navbar-header"> | |
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> | |
<span class="sr-only">Toggle navigation</span> | |
<span class="icon-bar"></span> | |
<span class="icon-bar"></span> | |
<span class="icon-bar"></span> | |
</button> | |
<a asp-area="" asp-controller="Home" asp-action="Index" class="navbar-brand">Ok</a> | |
</div> | |
<div class="navbar-collapse collapse"> | |
<ul class="nav navbar-nav"> | |
<li><a asp-area="" asp-controller="Home" asp-action="Index">Home</a></li> | |
<li><a asp-area="" asp-controller="Home" asp-action="About">About</a></li> | |
<li><a asp-area="" asp-controller="Home" asp-action="Contact">Contact</a></li> | |
</ul> | |
@await Html.PartialAsync("_LoginPartial") | |
</div> | |
</div> | |
</div> | |
<div class="container body-content"> | |
@RenderBody() | |
<hr /> | |
<footer> | |
<p>© 2016 - Ok</p> | |
</footer> | |
</div> | |
<environment names="Development"> | |
<script src="~/lib/jquery/dist/jquery.js"></script> | |
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script> | |
<script src="~/js/site.js" asp-append-version="true"></script> | |
</environment> | |
@RenderSection("scripts", required: false) | |
</body> | |
</html> |
There’s a lot going on here, but the only line we’re really concerned with is the one which I’ve highlighted. This contains the razor command @RenderBody()
The use of the @ symbol here tells us that it’s a Razor command.
This command takes the rendered result of the action method for the request (in our case, the Index action method), and inserts it into line 36.
The resultant HTML is used in the response sent to the client.
Conclusion
We’ve seen how to create an MVC application with .NET Core (in either yoeman or Visual Studio), added a model, changed a controller and written a view. Its really quite simple. But quite powerful, too.
Like I said at the head of this article, I’m putting together a full tutorial on how to build an MVC application from scratch (including adding our own Controllers, Views and Models, messing with the routing and even dealing with partials).
Most of the MVC stuff in .NET Core is the same as .NET Framework, with the exception that it uses Kestrel over IIS so it requires some set up.