Lesson 4 - First web application in ASP.NET Core MVC
In the previous lesson, Introduction to the MVC architecture in ASP.NET Core, we introduced the MVC architecture. Today, we're gonna use this knowledge in practice and program our first web application in ASP.NET Core MVC.
The course assumes basic knowledge of C# .NET and HTML. In case you won't understand it, first read the basic courses for these technologies where everything is explained.
I write these tutorials for Visual Studio 2017, the interface of other versions may be slightly different, but not dramatically, for sure, so you should find it all there. For developing in ASP.NET Core 2 and higher, however, you need at least Visual Studio 2017 version 15.3.0 or higher and have the .NET Core 2.x SDK installed (or eventually, you can download it at https://www.microsoft.com/…ownload/core).
Creating a project
Our first web application will be a random number generator. Let's start by creating a new project in Visual Studio. Create a new project using the main menu as File -> New -> Project.
In the dialog, we select C# as the language, ASP.NET Core Web Application as
the project type and we enter MVCRadomNumber
as the name. If you
don't have the "ASP.NET Core" category there, use the "Open Visual Studio
Installer" link in the left part of the window. You can find ASP.NET Core in the
Web & Cloud category, then you need to check .NET Core development tools for
Web in the right part.
Once we confirm the dialog, the next one will appear with the template selection. The template is a predefined project structure which can be generated for us. We'll start with an empty project and therefore we'll choose Empty. Make sure you've selected ASP.NET Core version 2.0 or higher at the top. We confirm the form.
You may have there an option to enable HTTPS. If you keep it checked you might get two confirmation dialogs when you try to run the project for the first time. If you don't accept the dialogs, you'll get an annoying warning about security risks each time you run your app because the generated SSL certificate is self-signed and not seen as trusted by browsers.
Directory structure
Although we've created an empty project, Visual Studio still generated several new files. This is because ASP.NET Core is a framework, a ready-made solution that we only adapt for our purpose.
In Solution Explorer on the right, we start by right-clicking on the project
and choosing Add -> New folder to create the Models/
,
Views/
and Controllers/
folders. That's where we're
gonna add the components of our application as we explained in the previous
lesson.
Model
Let's start with the model. We'll add a simple class named
Generator
to the Models/
folder. It'll have the
following contents:
public class Generator { private Random random = new Random(); public int GetNumber() { return random.Next(100); } }
The class does nothing else than return a random number using a private
instance of the Random
class. Practically, such a model does make
little sense, but it's the principle which is important for us, and in the
future, we'll return e.g. an article from the database in the same way. So we've
created the logical part of our application.
Controller
Now, we'll add Controller
to the Controllers/
folder by right-clicking it and selecting Add -> Controller. We select MVC
Controller - Empty as the type. Other types allow us e.g. to generate views
which we'll use further in the course.
As the name of the controller, we'll enter HomeController
. The
controller name should always end with Controller
.
HomeController
is the standard name of the controller which is
called when we open the website (without specifying other parameters, so it
displays the homepage).
Visual Studio has generated a new class for us that looks like this:
public class HomeController : Controller { public IActionResult Index() { return View(); } }
The Index()
method is called on the controller at the moment
when the user requests the page the given controller handles. This method is the
place where we create an instance of the model, get data from it, and pass these
data to the view.
The method uses the IActionResult
interface as the return type,
which represents an object that we send back to the browser when the request is
complete. In our case, we send a template (the View
object) to the
browser. We can also send a file, redirect request, or even JSON data. We could
even return just a string
that would be then printed in the
browser.
Passing data to the view
We have 3 collections available for passing data, accessible both in the controller and later in the view. In the controller, we'll fill the collection with the data from the model and, in the view, render the data from this collection to the prepared HTML template.
We can use the following collections:
ViewData
- A collection of theDictionary
kind, where we put individual variables for the template under string keys. It was used especially before C# introduced thedynamic
keyword.ViewBag
-ViewBag
uses dynamic properties that have been in C# .NET since the version 4.0. Instead of keys, we write directly into the properties which are created onViewBag
.TempData
- A quite confusing collection used to pass data, especially during redirection. Data is deleted when the request is complete.
It doesn't matter whether we'll pass the data to the template using
ViewBag
or ViewData
, since ViewBag
uses
ViewData
internally for its properties. This means that whatever
you store into ViewBag
is also accessible as a
ViewData
item and vice versa. However, the advantage of
ViewBag
is that we don't have to perform typecasting to get a
concrete type. Microsoft mostly uses its older ViewData
in its
documentation, partly because ViewBag
isn't currently available in
Razor Pages (another page type that can be created using Core). The choice of
the collection doesn't matter.
Sometimes (with the MVVM architecture - Model-View-ViewModel), you can also encounter passing data to the view through a special data object, a ViewModel, but we'll show you this way further in the course.
Let's modify the Index()
method in the controller so that the
method gets data from the model before the view is returned and saves it to
ViewBag
:
public IActionResult Index() { Generator generator = new Generator(); ViewBag.Number = generator.GetNumber(); return View(); }
In order to access the model, we need to add using
. Even though
you all know it, just to be sure, I repeat that you click on the red underlined
name of the Generator
class and then click on the bulb on the left,
and add the given using
. If it doesn't work, you can add it
manually as using MVCRandomNumber.Models
to the very top of the
file. We're done with the controller. We responded to the index page request and
wired the model with the view.
View
In our application, we are now missing the template (view) in which we'll
display the output for the user. I'll use both the "template" and "view" terms
in the course, and I'll always mean view. We add the view easily in the relevant
controller. Right-click anywhere in the Index()
method and choose
Add View.
We name the view as same as the method. We confirm.
We've generated an HTML template with the following contents:
@{ ViewData["Title"] = "Index"; } <h2>Index</h2>
At the top, we can see the at-sign and C# code. That's the Razor engine syntax, which is used to embed C# code into HTML. There are several other rendering engines, but they aren't very popular.
We already know that all the logic should be in the models. In the views, we'll use C# just to print the final data we've obtained from the models. We should keep the smallest number of Razor directives as possible in our templates.
Based on the contents of the generated template, you've certainly found out
that the template isn't the final HTML page, but only a part of it which will be
inserted into the layout. Since we haven't defined any layout yet, the output
won't be valid HTML. We'll keep it like this in this first example. The template
will only contain one particular subpage of our website. We'll set the title to
the template and insert the number from the model using ViewBag
and
the Razor @
directive. The template's code is now going to be as
follows:
@{ ViewBag.Title = "Online generator of random numbers"; } <h2>Random number</h2> <p style="font-size: 2em;">@ViewBag.Number</p>
I've replaced the original code setting the title using ViewData
by ViewBag
to make it the same but this change isn't necessary. We
print the number from ViewBag
where it was stored by the
controller. It got it from the model that generated it.
Middleware, request handling, and routing
If we run our application now (Ctrl + F5), it'll print
only the "Hello World" message and our controller won't be called at all. Since
we've chosen an empty template at the beginning for the sake of clarity, we must
first route the HomeController
to be called as default. This
mechanism wiring URLs to controllers or other parts of the application is called
routing and we'll find it in Startup.cs
.
The file contents look like this:
// This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); }
We'll put middleware into this method. You can imagine it in ASP.NET Core as
a series of filters through which the user's request passes before finding the
right one which processes it. They are extension methods on the
IApplicationBuilder
interface (some of you may recall the Chain
of Responsibility design pattern).
Each middleware in the chain has only a limited and specific
role in processing the request - the first one can only e.g. perform a logger
function, another middleware can search for a cookie or authorization token, and
if it doesn't find it, it returns an error message or redirects the user. For
example, the UseFileServer()
middleware allows us to return static
contents as the response (scripts in JavaScript,
images, CSS files, etc.) of our project and so on.
In order to route the user to HomeController
immediately after
opening our application, we can use the
app.UseMvcWithDefaultRoute()
middleware, which we put in the middle
of the Configure()
method.
These types of middleware, which route the request to controllers, are called routes.
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseMvcWithDefaultRoute(); // We added this line app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); }
Once the application starts, the HomeController
is now created,
and its public Index()
method called. An equivalent to the default
routing using UseMvcWithDefaultRoute()
would be the following
callback, which would explicitly set the controller and its method:
app.UseMvc(routeBuilder => routeBuilder.MapRoute(name: "Default", template: "{controller}/{action}/{id?}", defaults: new { controller = "Home", action = "Index" } ));
However, let's keep the previous variant, we'll come back to the routing later. Unfortunately, when we start the project now, we have an unpleasant error message waiting for us (or the exception in Visual Studio):
The ASP.NET Core Framework consists of a large number of "granular" services and components that are needed for the MVC to work properly. To work as we expect, we must first add these services to our application (which is also what the exception text tells us to).
Therefore, we move to the ConfigureServices()
method and by
calling AddMvc()
we add everything needed for the MVC to work to
the services collection of our application:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); }
We run the project. You'll see the following, this time, correct result:
You'll have a different port than I have on the screenshot.
Summary
Let's finally sum up how the whole application works.
First, the user request is processed by our middlewares and routed to the
HomeController
. Then the Index()
method is called. It
asks the model for data and stores the data in ViewBag
. Next, the
view is rendered, which, using the Razor syntax, prints data from the
Viewbag
to the specific places in the template. The finished page
is sent to the user.
The source code of today's project is downloadable below, as in all the lessons. If you have failed at anything, you can correct your mistake easily by using our project. In the next lesson, Form handling in ASP.NET Core MVC, we'll look at the forms handling.
Did you have a problem with anything? Download the sample application below and compare it with your project, you will find the error easily.
Download
By downloading the following file, you agree to the license terms
Downloaded 18x (853.1 kB)
Application includes source codes in language C#