Building An Enterprise Application With ASP Dot NET Core MVC
Building An Enterprise Application With ASP Dot NET Core MVC
Course Overview
Hi everyone, my name is Gill Cleeren, and welcome to my course Building an
Enterprise Application with ASP. NET Core MVC. I'm a solution architect for web and
mobile projects.. NET Core is the future of the. NET framework. It's where all
investment from Microsoft will go to from now on. Since its inception about 15
years ago, this is the biggest change ever made to. NET and ASP. NET. If you're
interested in learning how a real life line of business enterprise application can
be created with this new version of ASP. NET Core MVC, this course will be your
best friend. You'll get to see from a practical perspective how the common
concepts, such as security, logging, caching, testing, and much more are
implemented in a real life and fully working application. Some of the major topics
that we'll cover include security and identity in ASP. NET Core MVC, testing your
code with unit tests, adding logging to your ASP. NET Core MVC pages, improving the
site's performance using caching, and continuous delivery and integration with
VSTS. By the end of this course, you'll know how a real life enterprise application
can be built with ASP. NET Core MVC. Before beginning this course, you should be
familiar, however, with the basics of ASP. NET Core MVC. I hope you will join me on
this journey to master ASP. NET Core MVC with the Building and Enterprise
Application with ASP. NET Core MVC course here at Pluralsight.
Introduction
Module Introduction
Hi there, and welcome to this course named Building an Enterprise Application with
ASP. NET Core MVC. My name is Gill Cleeren, and it's great to see that you want to
learn some interesting stuff about ASP. NET Core MVC together with me. You can
always contact me with any questions you may have about this course or ASP. NET
Core via Twitter or via a discussion board for this course here on the Pluralsight
website. In the next couple of hours that we'll spend together, I'm going to try to
teach you some of the more advanced things about the shiny new release of
Microsoft's web development platform that is ASP. NET Core MVC. We're going to do
this by starting from an existing web application and we're going to extend it with
features that are typically used in a development of enterprise applications. So by
the end of this course, you will have gathered a lot more knowledge that is
indispensible when building real life enterprise applications with ASP. NET Core
MVC. Every module in this course will start with a small overview of what we are
going to do in that very module. So let's start with this tradition right here in
the first module of this course. This module is an introductory module to the
course, so we'll start with a short overview of the course and what you'll learn in
the next couple of hours. Next, we'll take a look at the finished application that
we'll be using and working with throughout this course. Finally, to finish this
module, we'll spend a few minutes making sure that you have everything ready to
follow along with me in this course in the topic getting your machine ready.
Course Overview
So, first things first. Let's take a moment to see what you'll be getting from this
course in a brief overview. Now building a web application isn't rocket science.
When we are building enterprise web applications, we'll come across some challenges
that we'll need to solve, and the goal of this course is helping you understand how
you can solve typical problems that you'll encounter when building an enterprise
website. Now we'll do all this using ASP. NET Core MVC and the tooling that we'll
use is the ASP. NET Core tooling that comes with Visual Studio 2017. And this
course won't be building an application from scratch. Why not, you may be asking.
Well, because I have already created that in a separate course here on Pluralsight
titled, "Building your first ASP. NET Core application. " In this course, we'll
start from the blank slate and we'll build together a fully working, but still
basic web application with ASP. NET Core. You can find the course using the URL
that you can see here at the bottom of the slide. In that course, and therefore
also in this course, we'll be building the web store application for Bethany.
Bethany has a pie shop, and she wants to sell her pies via a new web store front
end. The application is entirely built in ASP. NET Core, and includes a shopping
cart, a couple of other pages, a registration system, and much more, and the site
is also deployed in an Azure app service, which is the final part of the first
course. Now this course, as mentioned, will build on the foundation that we have
laid in the aforementioned course. We'll basically start from the "basic" website.
Throughout this course, we're going to write quite a lot of code, and thus add a
lot of extra functionality, but also a lot of basically invisible features. All of
these will level up Bethany's pie shop from a basic web store with no
administration interface to an enterprise level web application ready for
primetime. The value for you, dear viewer, is of course that you'll learn about
enterprise level features in an ASP. NET Core web application. So, what exactly
will you be learning in this course, you may be asking. Well, let me give you a
short overview of some of the most important topics that we'll be covering in much
depth in this course. We'll dive deep in the ASP. NET Core Identity system. This
was already covered in the first course, so the introductionary course, but we only
have scratched the surface there. I will dive deep in the many options the system
offers here. Next we'll spend quite some time adding security related features to
our site. Any enterprise web application should be armed to withstand the dangers
of the internet. I'm going to show you some of the things that we can do in this
area. We'll be adding a full administration back-end to the application, allowing
the users of the application to manage products, here that would be the pies,
manage the users, and so on. In these type of pages, there will be a lot of data
that is entered by the user. Data entry equals errors, and it's vital that we
validate the data before sending it off to the data store. ASP. NET Core comes with
a lot of options in this space that we will explore. Tag helpers and view
components are probably one of the most touted features of ASP. NET Core. We'll go
deep in both of these features here, explaining the many options they offer beyond
the basic demo. We'll also look at how we can increase the performance of the site
using caching. ASP. NET Core has a lot of new features in this area. I will also
look at the different options that we're getting to log information using the
built-in log system. Finally, we'll look at setting up an automated build using
VSTS, or Visual Studio Team Services. How it is automated built, we'll also deploy
automatically to Azure, so we're setting up continuous integration and continuous
delivery. That's quite a lot of ground that we have to cover, isn't it? Now just to
be on the safe side here, let me set the expectations for this course from my side.
This course is expecting some basic knowledge about ASP. NET Core MVC. I'm not
spending time on explaining the basic concepts in this course. Of course, we're
going to write all of our code in C#, so basic knowledge of C# will do. You do not
need to be a super ninja expert. And yes, we are building a web application, so I
am assuming that you have some basic understanding of HTML and CSS here as well.
Before continuing, I'm going to show you the finished application with some of the
features that we'll add in this course. Let's take a look, shall we? In the first
demo of this course, we are going to take a look at the finished application. As
mentioned already in the slides, this course builds on the application that was
constructed in the Building your first ASP. NET Core Application. You can take a
look at that course here on Pluralsight as well. Simply search for Building your
First ASP. NET Core Application. Now, in this course, we are going to extend the
site both visually and non-visually, and I'm going to take you through the end
result of this course. So the homepage contains a banner, so a sliding banner with
some images, and then a selection of pies of the week. I can click on one of the
pies, and then I will see the details of that particular pie. One of the things
that we'll add in this course are reviews, and you may already notice that someone
has already tried hacking our site, but we'll try to secure ourselves from that as
well. It's possible to add this pie to your cart, and then you will go to this
shopping cart page. You will see at the top a shopping cart icon that contains
currently 1 item. I also have this progress here that we'll construct later as a
custom tag helper. I can then click on the Check out now button. That will take me
to the login page at this point, because I'm not logged in, so I'm going to log in
with my account, so Gill, and enter the password. Notice that it's now also
possible here to log in using Google. I will add that later on as well. So now I'm
logged in. You can see that I now have more features in the menu at the top, and
here I can also complete the order. Let's go back to the homepage for a second.
This version of Bethany's Pie Shop now also supports languages. It's possible to
translate a site, for example, in French. If you understand any French, you'll
notice that tartes de cette semaine, it's French for pies of the week. Now let's
put it back in English, so that hopefully everyone understands what it says on the
screen here. You also have the ability to see a selection of pies per category, so
for example, all cheesecakes. There's a new option here on the site as well called
Send a pie, which will allow us to send a pie and we'll notice later on in this
course that this page opens our site for cross-site request forgery attacks, which
is not good news anyway. There's also a Contact us page, which allows me to contact
Bethany. There's also a Promotions area, which we'll talk about when we discuss
areas. The site now also contains some administration pages. We have the ability to
manage the users, manage the roles, and also create new pies, and we'll discuss
these pages in much detail later in this course. Now behind the scenes there's also
quite some changes that have been done to the site compared to the site that we
create in the introduction course. I'll discuss things like caching, logging, and
continuous integration build using VSTS. So as you can see, it's a pretty complete
site at this point. Now if you want to follow along with everything that I'm doing
in this course, and I encourage you to do so, please download the source code
directly from the Pluralsight website. You can find everything under Course
Downloads. You'll find a starter project per module so that you can follow along.
And also for each demo, the finished project is included.
User Management
Now, first things first. We'll start with the authentication port, and more
specifically, we'll take a look at user management. Now, Bethany's Pie Shop is
live, and she's been selling pies like crazy. So, business is great for her. Her
site is getting a lot of new registrations on a daily basis. Also, Bethany has
decided that she can't keep doing everything by herself, and so she has hired some
extra staff to work with her. Now she's come to our office with a rather simple
question: "I need to be able to manage all these new users from my site. Is that
possible? " We are lucky that ASP. NET Identity has great support for this. It will
actually be rather easy to build this for Bethany. Let's take a look. ASP. NET
Identity is an API that comes with ASP. NET Core. It is the API that we'll be using
for authentication and authorization. Anything that has to do with the user
management can be done with ASP. NET Identity. We can do all kinds of actions on
users, such as enumerating users, creating and updating users, and deleting users,
so the typical CRUD operations, but also much more. The class that makes this
possible is the UserManager. This class handles all the interactions behind the
scenes with the persistent store. So in general, that would be entity framework and
the underlying database, in our case, will be SQL Server. To enable the usage of
ASP. NET Identity in an ASP. NET Core application, we need to perform a few steps
to configure it. First, we need to reference a package. Since the release of Visual
Studio 2017 and. NET Core 1. 1, packages are no longer managed in the project. json
file. Instead, we can add them by editing the re-introduced csproj file. You'll see
me editing this file in numerous times in this course. After add support for
Identity, we need to reference the Microsoft. AspNetCore. Identity.
EntityFrameworkCore package. This is now, as you can see again, XML code. Now with
a reference to the package ready, we need to configure Identity in the Startup
class for our application. In the code that you see here on the slide, which by the
way we created step-by-step in my first ASP. NET Core MVC course, we first
configured application to work with the database of which the connection string is
available in the appsettings file. Next, and more relevant to what we are looking
at here, is the. AddIdentity call, to which we are passing IdentityUser and
IdentityRole. These two built-in classes will be used as configuration for the
Identity system. Using the. AddIdentity framework stores call, we enable the use of
Identity on the backing data store. Next, we also need to add one line of code in
the Configure method here, still part of the startup class. The UseIdentity call
finally enables the use of Identity in our application. Now, if you need more
detailed steps on configuring Identity in your application, take a look at my first
course where I am doing all this in many detailed steps. Now with Identify
configured, we can now start using it. Now, Bethany has asked us that she needed a
way to be able to manage all the users inside of her application, so basically all
users which are registered in the site. Most actions, including enumerating users
in the backing store, are done using the userManager class. It has an IQueryable
property called Users that will give us access to all users in the application.
Here I am returning these users to a view, which can then be used, for example, to
visualize all users inside the application, for example, in an administration page
of the site. Now not only can be enumerate users using the userManager class, we
can also use it to manually create users. Imagine that you want to create a user
administration page where the administrator of the application can create new user
accounts. This will come in handy. Here we can see that we simply call the
CreateAsync method on the userManager class, and then Identity will create user in
the persistent store for us. All the nitty details of how this is handled on a
database level are abtracted away. We really don't need to bother. The first
parameter that we're passing is a user with data such as the name and a second
parameter is a password that we'll want to create the user with in a database. Now
remember that Identity takes care of securely storing passwords in the database,
including salting, so we really don't need to worry about this; it's all taken care
of for us. Now talking about passwords, in today's world, we need to be sure that
our users enter strong passwords. With strong I mean enough characters, at least a
non-alphanumeric character and so on. Identity has the ability to enforce rules on
passwords which are created in the application, where the user registers via the
public registration, or when we will want to create a user manually in the
administration of the application, the used password will then have to follow one
or more rules specified by us as the creators of the application. And we can set up
these rules during the configuration of ASP. NET Identity. So again, in the Startup
class. In the snippet that you see here on the slide, so in the. AddIdentity call,
I'm passing Identity options, and I'm specifying different requirements for both
the password, as well as the username. At least 8 characters are required for the
password, for example. We'll take a look at more rules that we can enforce in the
upcoming demo.
Role Management
We have deployed the application again. Bethany has been using our newly
implemented user management for quite some time. But she has an addition request in
this area for us. She has defined a number of administrators in the application,
but with her growing business, she needs to be able to manage all these users in
groups. Typically groups of users will have the same permissions in the site. Let's
see how we can fix this for her. The requested feature of so-called groups of users
will be handled for Bethany by introducing roles into the application. In the
introduction course, we have already added the administrator role. But real life
enterprise applications will require a more fine-grained control of permission for
groups of users. Role management will make this possible for us. Let's take a look.
ASP. NET Identity has numerous classes on board. We've already seen the
UserManagement as the class to work with users. Well, similarly, a RoleManager
class exists, which, surprise, surprise, will allow us to work with roles in ASP.
NET Core. This class will allow us to create roles in the system. Just like with
users, it will automatically handle the details of storing the information in the
persistent store for us. We don't really need to bother again with the nitty
details. We'll also be able to attach users to roles. Identity in itself therefore
allows us to create roles and attach users to it. However, it doesn't know what a
role really means. That will still be part of the actual MVC code where we will
again work with the authorized attribute. We can use this attribute in combination
with role membership to check if a logged in user has the ability to execute a
given action. Here you can see this in action. We're looking at the AdminController
here. And it's been decorated with the Authorize attribute. We've effectively
authorized users based on their membership of a role. Hence, we refer to this type
as role-based membership. I have, however, added a parameter roles on this
attribute with the value Administrators. The Identity system will now check if the
logged in user is part of the administrator role, and if so, only then will the
user be able to access the actions on this controller. Of course, if you'd only be
able to specify a certain role to be able to access the actions of a certain
controller, you'd end up creating a lot of different roles, perhaps one role per
controller in the worst-case scenario. That is definitely not very productive, and
would lead to a lot of overhead of managing all these roles in the application.
It's therefore possible to add multiple instances of the authorize attribute onto a
controller, each specifying a different role, which has access to the actions of
the controller. In this snippet you can see here on the slide I specify that the
AdminController's actions are accessible to users in the Administrator role, and
also the role SomeOtherRole. It's an or or case here. If you, as a user, are a
member of the Administrator or the SomeOtherRole role, or both, you will have the
permission to call the actions on this controller.
Claims-based Authorization
ASP. NET Identity is in fact the evolution of ASP. NET membership. In membership,
the application itself was basically the only source for all information about a
user, so we could only get information about the user from the application itself.
Now in this scenario, as we've done so far as well, we have basically only allowed
authorization and authentication based on the information stored in our own
database. For many applications, this approach is perfect, and they don't require
anything else. Now ASP. NET Identity does allow a different, less-closed approach.
In this approach, the application itself is not the only source of information,
allowing for a more flexible approach than what we just saw with just roles. Now
the information can come from an external source, and can thus be used in the
application to see on that information what is allowed in the application and what
isn't. And this alternative approach is using something called claims, and it's
also referred to as claims-based authorization. Let's first take a look at what
claims really are. If you look up the word claim, you will typically find something
like saying that something is true. Well, that is not very far away from what we
are using as claims in ASP. NET Identity. A claim is, in fact, a name value pair. A
piece of information, really, that specifies what the subject is, not what it can
do. A claim is typically issued by a trusted third party, although it is very well
possible to work with an internal claim as well. Your travel visa will contain your
date of birth. The date of birth here is the claim, and it's issued probably by
your government, which is the trusted party here. They specify that you were born
on that particular date. That's the name value pair. The claim here is then used
externally. Claims are policy-based, meaning that we as developers will have to
register one or more policies in code. A policy will define the claim requirements.
For example, an example policy could be we need to have the claim date of birth
before we give access to a particular action method. Another policy could be the
date of birth claim not only needs to be present, but the value must be larger than
a given value. Now what does this have to do with ASP. NET Identity then? Well, in
claims-based authorization, we're actually going to get in one or more external
claims that we are going to assume are true, since they are provided to us by a
third party, a trusted third party even. Based on the value they contain, for
example, your date of birth, we are going to give you as the user permissions to
access a certain resource in the system, which could be, again, accessing an action
method of a certain controller. Let's see how we can start introducing claims and
policies in ASP. NET Identity. Now the first step is actually defining the policy.
We'll need to define what claims are required to meet the requirement of the
policy. Typically this is done in the ConfigureServices method of the Startup
class. Take a look at this snippet here on the slide, which contains code that, as
mentioned, should be part of the ConfigureServices method. To the AddAuthorization
I'm passing an options parameter, which is of type Authorization options, and on
that options parameter, I can call the AddPolicy method to add one or more policies
in code. The first parameter is simply the name that I'll be using to refer to this
policy later on. Now the second parameter is an action, in which I define that the
Delete Pie claim should be present on the current identity. So how should you
understand this, then? Well, I'm creating here a policy that I'll use later on and
that policy specifies that the Delete Pie claim should be present in order for it
to allow the requested action. Now that we have defined the policy, we can use it
to authorize. In the snippet you see here on the slide, I'm again using the
Authorize attribute. But instead of defining role, as we did before, I'm now using
the policy parameter, and as value, I'm specifying the DeletePie, which was the
value I have defined in the previous snippet as the name of the policy. So, in
order to execute any actions on this controller, the Identity will have to follow
the requirements specified in this claim. And that was having the DeletePie claim
to be present; otherwise, the logged in identity will not be able to call any
actions on this controller. Know that the authorization system is pretty flexible
in ASP. NET Identity. Here I'm combining several policies. Now pay attention. All
policies must be met in order for the user to be able to call an action on this
controller. So, there must be a claim that satisfies the DeletePie, as well as a
claim that satisfies the AddPie policy, whatever that may be. Now on top of that,
it's also possible to combine these with role-based authorization, so I can even
add an authorized attribute that specifies that the user must be part of the
administrator role.
Summary
Whew! That was a long module! But I hope you now feel more comfortable when you
need to add Identity-related features to your own ASP. NET Core application. You've
seen that ASP. NET Identity offers quite a long list of options. It has built-in
support for working with roles, and can combine this with claims. These claims can
then be used as well in combination with a third-party Identity provider to allow
the user to authenticate using, for example, Google instead of our own application.
I hope you enjoyed this thorough discussion of ASP. NET Identity. In the next
module, well, we will basically continue a bit in the same area, but we will focus
more on how ASP. NET Core will protect us from attacks from the outside. I hope to
see you there as well, and thank you for watching this module.
Sanitizing Input
So the other day, Bethany came to our office again. She wants to expand her site
with yet another very cool new feature allowing users to leave a review for a pie
that they have previously purchased via the site. That's a great idea, and it will
definitely help people find the right pie for the right occasion. Now from a sales
point of view, I would definitely go for it. However, from a developer's
perspective, there are some alerts that go off in my head when I hear a customer
ask about this particular feature. Although I thoroughly believe that we should add
this feature for Bethany, is does require some careful thinking and planning about
how we are going to add it. The main reason is that this way we are letting a lot
of data come into our system. Not only that, but in the case that we will be
displaying other user's reviews, the data will also be displayed in other people's
browsers, and that's exactly where things can go wrong. When we are accepting any
time of input in our application, be it entering address information, uploading an
image, or writing a review, the data will enter our system. Before we do anything
with that input, we should definitely clean it, so that any possible harmful
content is removed. This is often referred to as sanitizing input. Let's take a
look at this in some detail before we look at how we can secure our site this way
in the next demo. I've given you a brief intro about the problem already, but let's
dig in in some more detail. Let's take as example the review option that Bethany
has requested for her site, so we have as visitor of the site perhaps a detail page
of a pie and the ability to write the review on there. So there's an HTML input
field. We get into some text there for the review. Now in the end, the data that
was entered by the user will be stored in the database. Next, another visitor goes
to the same pie detail page, and the application will show all reviews of that pie.
All seems good at this point. The user has entered a review and a second user will
be able to read that review. We're all happy at this point. Now in a perfect world,
nothing ever goes wrong, but sadly, there's no such thing as a perfect world, and
so Bethany's Pie Shop can become the victim of an individual who has other
intentions with her site. Since we have allowed people to enter a review in the
form, a hacker has the ability to enter code, typically a script in the review
form. This data will also be stored in the database, of course, and when another
review now visits the same pie detail page again, this malicious review, so
including perhaps JavaScript code, will be executed in his or her browser as well.
Now when the affected user loads the page, the script will then execute, and this
opens a lot of options for the attacker, such as stealing cookies, redirecting to
another perhaps fake login page, and much more. Now in the end, the affected user
doesn't know what happens, and will effectively blame Bethany's Pie Shop for any
caused misery. It's not Bethany's site that actually caused the misery, but
indirectly it actually was. Why? Because the developers of the site didn't check
the input for malicious input. The input wasn't cleaned, it wasn't sanitized, and
thus the hacker had all the freedom to execute whatever code he wanted to execute
on the victim's machine. This type of attack is very common and it's called XSS, or
in full, cross-site scripting. XSS basically allows an attacker to inject malicious
code into a page, which will then be executed by another user which is visiting the
page. This type of attack is possible because a developer of the page hasn't spent
time sanitizing the input before storing it in the data store. It all comes down to
what I've just said. The input that's entered by users is untrusted. All input
should be seen as untrusted, and should therefore be cleaned before it's stored in
the database of the application. If you want to tackle this problem easily, you
should start at the root of the problem. The data entered in our case in the review
input field should be sanitized before going any further in the system, so before
it will be stored in the database or before it is shown anywhere in the site. And
that will definitely solve the issue. Now which data is untrusted then? Well, in
fact, all HTML input should be seen as a possible source of malicious code. Of
course, an HTML input is a standard way to enter data into an application. But
there are other sources possible as well, such as HTTP headers that we pass in a
page, query strings, which are passed in the code, and much more. When we are
uploading an image and we are passing the attributes, such as the EXIF information,
we should see this information as untrusted. It's possible that an attacker
includes malicious code in all these types of input, and when we use this input in
our system, in an untrusted way, it can cause harm to the application and its
users. So what can we then do in ASP. NET Core to prevent XSS from happening in our
application? HTML encoding is the first measure we can take. If we want to display
untrusted data, so data that is somewhere in the system entered by users inside an
HTML element, for example, we'll want to HTML encode it. The net result of this is
that characters which have a special meaning in HTML will be encoded, taking away
the option for them to cause the execution of code. For example, the greater than
symbol will be replaced by >. This has no special meaning in HTML, and can't
trigger the execution of code. HTML encoding should not only be done when simply
displaying a value, but also when placing untrusted data in an HTML attribute. Also
when we use untrusted data in JavaScript, we should apply the same principles.
Here, too, the data should be encoded. The good news is that Razor is doing
automatic HTML encoding already for us, unless we will disable it explicitly. More
on that in just a minute. Another way to fight XSS from happening is applying a
regular expression when accepting the input. Using a regex, we can check if the
user is trying to enter malicious code or malicious characters into the system. We
can, for example, check the presence of the greater than or the less than
characters, and when we find any of these, we should block the system from
accepting that input. Both options are possible with ASP. NET Core, and we'll soon
see how we can practically apply these in code. Now in previous versions of ASP.
NET, we could use a library called the anti-XSS library, a library from Microsoft
which had on board several ways to sanitize input. Now this library isn't available
for use within an ASP. NET Core application, sadly. At the time of the creation of
this course, Microsoft hasn't announced any plans to support this library for the
new version of ASP. NET, so ASP. NET Core. Finally, request validation. That was a
feature which has been in ASP. NET for a long time. Using request validation, ASP.
NET itself would check the incoming data, for example, entered in an HTML input.
Using request validation, ASP. NET itself will check the incoming data, for
example, entered in an HTML input, for potential threats, such as again the
presence of certain characters. When these were detected, an error was thrown, and
the input was blocked from entering the system. While it would be possible for
Microsoft to create this as a middleware component that we could then add to the
execution pipeline, Microsoft has recently announced that it will not bring this
into ASP. NET Core. This results in the fact that we as developers will have to do
more of the work ourselves. On the other hand, we now have full control, and that
opens doors as well. As mentioned, ASP. NET Core's Razor engine will already take
away a lot of the burden for us. Take a look at the code here on the slide. Assume
that we have a string which contains characters such as the greater than and the
less than. If we were to display this content, the output would be HTML coded, as
you can see at the bottom of the slide here. In most cases, Razor will
automatically perform the encoding for us as soon as we start using the @ symbol.
Although Razor will protect us, there are definitely cases where we'll need to
perform the encoding from the controller code ourselves. ASP. NET comes with three
built-in encoders that we can use to perform the encoding, namely the HtmlEncoder,
the JavaScriptEncoder, and the UrlEncoder. We'll see in the upcoming demo how we
can use these. Now these encoders, just like pretty much everything in ASP. NET
Core, are accessible via the dependency injection system. Here in the code snippet
you can see on the slide, we're looking at a sample controller where we will be
needing access to the HtmlEncoder. As you can see, we aren't instantiating the
HtmlEncoder ourselves. We can simply ask dependency injection system to pass us an
instance using constructor injection.
Preventing CSRF
We've now solved the danger for cross-site scripting in our application. Although
we have solved a very common attack, we're far from finished. In the second part of
this module, we're going to look into a very common attack that is often used on
websites, namely, the CSRF, or the cross-site request forgery attack. Of course,
we'll also prevent this attack from happening, as well as discuss which measures
ASP. NET Core has on board. If you've never heard of the concept of a CSRF attack,
it's definitely useful to start by taking a look at how OWASP defines this attack.
OWASP is a not for profit organization, which maintains a very large site regarding
security on the web, and you can find it at OWASP. org. What you're looking at here
is the definition of a CSRF attack as given by OWASP. Basically, CSRF is an attack
on a site that will force or trick the end user to execute an unwanted action on a
site or application in which they are currently authenticated. So if you think
about it, it comes down to tricking the user into executing an action that he or
she didn't want to do on a site where they have already been logged into. OWASP
continues to say that CSRF is typically a state-changing request, since it's not
possible for the attacker to get access to the response. This all may be still a
bit abstract, so let me explain things with an example. Bethany has again returned
to our office with another new feature request. She's come up with the idea to add
some type of "Send a pie" feature to her site, and using this feature, a logged in
user gets the ability to send a pie to a friend. For this friend, only the address
data will need to be entered in the site. The already linked payment information
for the logged in user will be used automatically, so this should actually be a
simple thing to build. However, if you don't watch out as developers of this
feature, it's very well possible that we expose a CSRF vulnerability this way. This
feature will probably again be a simple form. On the form, it must be possible to
select the pie, and then enter the address for the friend we want to send the pie
to. If you look at this, this is a very simple thing to build. Remember, and this
is very important, the user using the feature in the site is already logged into
the site. The user therefore has a cookie on his or her machine that keeps the user
logged in, and this cookie will travel along with every request that is made. The
form then sends some data to the back-end using a POST request. All is good. The
pie will be sent to the requested address. Now this thing here is actually open to
a CSRF attack. Imagine a hacker which is hungry for pie, actually, free pie. He can
easily detect on Bethany's Pie Shop how this form works, and which request it is
actually building. What he can do now is try to trick a logged in user to execute
the same request, but now with his own address as the data to send the pie to. He
can trick the user, for example, by placing this request, this URL basically, on
social media, and hoping that someone who's already logged into Bethany's Pie Shop
will click the link. If that happens, well, then the request will be sent, the
cookie is sent along, and the server doesn't know that this request is actually
forged. It's fake. The only one who will know is the tricked user, who will have to
pay for the pie that in the end ends up at the doorstep of the hacker. Although
this example might seem a little bit playful, with the hacker trying to get free
pie, imagine that this would be a banking website, where money transfers happen in
the same way. That would, of course, be even more painful. You may be asking, now,
how is this possible? Well, CSRF has a couple of pre-requisites that it needs in
order to succeed. First, the victim needs to be logged into the site. Now
typically, we all remain logged into a site for a long time due to an authorization
cookie. Now, when the attacker sends a forged request, and the browser basically
doesn't know that this request is malicious, in this context, the browser is often
referred to as the confused deputy. It's enabling the malicious request, but it
doesn't know that it's helping. It's confused. Now the second ingredient for CSRF
is social engineering. It's one thing to build up the request; it's another to get
the user to click on that link. In this context, social media or email are often
used to try to trick the user into clicking a link, which behind the scenes will
then try to send the forged request. The user may not even see anything from
happening. Alternatively, and this is even more scary, CSRF is sometimes executed
in combination with XSS where the hacker will inject a script into a page, which
then forces the browser to execute the forged request. Although the payload from
CSRF is often a lot of misery, it is as you can see, not that difficult to set up
for a hacker. How can we solve this danger from CSRF then? Well, the OWASP
organization has a guide called the Cheat Sheets, which give you a number of
solutions for a threat. The one specific for CSRF is available on the link that you
see here on the slide. Now according to this cheat sheet, there are a number of
things that we as developers should do to solve CSRF in our application. First, a
header validation. This solution basically comes down to validating the HTTP
request headers. Headers, of course, can be spoofed, but in the case of CSRF, is
the victim's browser, which is sending the request, which cannot be changed by the
hacker, unless it's combined with XSS. So although it helps, is not a guarantee.
Secondly, we should use the synchronized token pattern. In this case, a random
string is sent back from the server. The client then needs to send back that same
string to the server with the next request. Only if this value is sent back to the
server will the action be executed. The ID here is that this value is not
accessible for the attacker, thus making CSRF impossible. Finally, the cheat sheet
also mentions the double submit cookie. It's pretty similar, although it uses a
random string. In this case, a value is sent back to the client both in the cookie
and a request parameter. Now, when a request is sent to the server, these values
are compared on the server, and only once successful will the action be executed.
Now that we know how we can theoretically prevent a CSRF attack, let's see how ASP.
NET Core can help us solving this problem. ASP. NET Core has the ability to apply
the synchronized token pattern we've just discussed in the form of an attribute
called the antiforgery token. Let's take a look. Upon first time requesting the
page, a unique token is sent back to the client, which is associated with the
user's identity. When the user now wants to place an order in our case, the request
is made, and the token is sent back to the server. The server will then validate
that the token is present and also matches the identity. If that is the case, the
request is allowed. Now if an attacker is trying to trick the user by forging a
request, the request will still be sent. However, the token is unknown to the
hacker, so his request will be sent without a token, or perhaps with an invalid
token. The server will now reject the request. ASP. NET Core comes with this
mechanism built in, known as the antiforgery token. Typically when we want to add
extra functionality to our ASP. NET Core site, we need to add another package.
Well, for a change, this isn't really necessary when we want to add support for
antiforgery in an ASP. NET Core application. Antiforgery is available in a separate
package, Microsoft. AspNetCore. Antiforgery, but this package doesn't require any
manual registration; it is already registered for us, and thus available via the
dependency injection system. It is, however, possible, to add extra configuration
to its behavior in the Startup class. Antiforgery is available to us mostly in the
form of an action filter that we can apply on an action method, on a controller, or
even on the entire application. Applying this token to an item basically means that
ASP. NET Core will perform the check that the request is valid. It should contain
the token and the token should also be valid. Otherwise, ASP. NET Core will
automatically block the request for us. If we have a large application, we
typically will want to protect all POST requests in order to be sure that CSRF
isn't possible in our application. This may for a large application result in a lot
of places where we need to apply this attribute. It's possible to use the
AutoValidateAntiforgeryToken, which will protect all Post requests by default. It
won't apply for safe requests, such as Get or Options, by the way. Finally, the
IgnoreAntiforgeryToken will remove the need to validate on a certain action.
Imagine that you have applied the ValidateAntiforgeryToken on a controller. In this
case, the IgnoreAntiforgeryToken can be used to skip validation on one or more
action methods within the controller. In ASP. NET when a form is generated, MVC
will generate the antiforgery tokens. This is the default when we use the new tag
helpers in ASP. NET Core. If you have been using ASP. NET MVC before, you will
probably remember that you had to manually add the HTML helper antiforgery token.
That's not the case anymore when we use tag helpers. This will result in the
generated code containing an extra input with the name _RequestVerificationToken.
This field will then be used by ASP. NET Core to validate that the request is made
by its user identified by the session. And if that's not the case, the request will
be rejected. In the case that this request was forged by an attacker, this value is
unknown to the attacker, and he therefore is blocked from creating a fake request.
Summary
I think it's time to wrap up this module. We have seen how ASP. NET Core MVC can
protect our applications against two very common attacks that happen on a daily
basis against enterprise applications. We started this module by looking at what
XSS really is, and how we can solve it by sanitizing input. You've also seen that
quite a few things here have changed in comparison to regular MVC. In the second
part of this module, we've discussed another very common vulnerability, namely,
CSRF, and ASP. NET MVC tackles this risk by means of the AntiForgeryToken. Well,
that concludes this module. In the next module, we will shift gears a little bit
and we'll start looking at the different ways that we can validate complex models
sent to our application in ASP. NET Core. I want to thank you for watching this
module, and hope to see you in the next one. Thank you. Bye-bye for now!
Model Binding
Let's start this module as mentioned with a thorough explanation of the model
binding features which are available in ASP. NET Core MVC. Now, before we start
exploring the concepts of model binding, I want to make sure that you have an
understanding of what model binding really is. Through the concept of model
binding, data that is sent from the client to the server in an HTTP request will be
used to create. NET objects. These objects will then be used as parameters for the
action that we are invoking. It's through the process of model binding that these
objects are created automatically for us, and they are passed to the action methods
automatically as well. Basically, all data entered on the client is sent by the
browser in the request. But if you would have to search all places from where the
data can be sent to the server for every request, that would be a time-consuming
and tedious task that we as developers would have to do every single time, again
and again. A tedious task is abstracted away through the use of model binding.
Model binding comes with ASP. NET Core MVC, and it works with simple types, such as
strings or integers, complex types, and even arrays. For all of these, model
binding will do its utter best to create automatically the required objects. It's a
gift, basically, that comes in very handy. Now if you're still a bit unclear on the
process of model binding, let's take a look at the concept in a bit more detail.
Imagine that we're browsing to the detail page of a pie, which is accessible
through the URL, you can see here on the slide. The ID of the pie we want to look
at is 30. There's an action method on the controller called Details, which is
accepting an ID as parameter, and this is of type int. If you want to invoke this
action, we'll need to pass it a value for the id parameter. Now getting a value 30
into that parameter is done by model binding. It will provide the correct data for
these parameters that we require. In this case, the value is a simple type, but
we'll soon see that we can use much more complex types as well. Now the whole model
binding system is actually based on something called model binders. As the name
already implies, these binders will be objects responsible for providing the model
binding system with data from a particular part of the request. Model binding in
general will try to find data values it needs in several locations in the request.
It's actual the model binders that search the values, each in a specific part of
the request. By default, the model binding that comes with ASP. NET Core MVC comes
with binder with three types of values, form values, routing values, and query
string values. ASP. NET Core MVC will also use these three types in this sequence.
First, it will check in the form values. If it can't find the required value there,
it will continue to search in the route values. Only after that will it search in
the query string values. Now that we know how model binding works behind the
scenes, let's take a look at what types of data we can bind, and well, let's start
with simple types. I've already shown you a simple example where we were passing
the value 30 here, which will be passed to the parameter of the action method. This
works as mentioned for simple types, such as numeric values, Boolean values,
strings, and even date values. So by default, ASP. NET Core MVC's model binding
system will create the corresponding object or objects that are required as
parameter for the action that we are invoking. Now you will need to pay some
attention here as well. Take a look at the following URL, where the value in the
URL is now set to ABC. Assume that there's a route that specifies this segment to
be the ID. In this case, of course, the cast will fail, and it actually won't find
a value for the id parameter, thus resulting in this value to be 0, so the default
value. Now this may incur a problem if the value 0 actually means something in your
application, and you need to differentiate between the value 0 and the default
value passed by the model binding system. If that is the case, it's best to
introduce a nullable parameter, which will then default to null, allowing you to
differentiate between the two, so between 0 and null. Now simple types are simple.
If we talk about simple types, it means that there must also be something more
complex as well. Well, indeed. Model binding is actually capable of much more than
just mapping an integer or string value. Assume, for example, that our Action
method is the EditPie action method, which is something you'll typically encounter
in our administration pages. This action method is expecting a pie instance. Model
binding will use reflection on the pie type to identify which properties it will
need to fill. Now once that is finished, it will start searching for the
corresponding values as it did before, so using the model binders again. So in the
format you can see on the slide here, we are, for example, passing a value for
Name. If the pie defines a property name, the model binding system will pass this
value to that property. This way all properties of the pie are given a value and
the object is thus created. Now you may be thinking, what if we're using
composition? So, for example, here we have an EditPie action method, but now it's
expecting a PieEditViewModel. The latter in turn wraps a pie instance, as you can
see here. How can model binding now know which properties it needs to use? It could
very well be that we have in our view model a pie and a customer, which both define
a name property. In this case, we will actually have to help the model binding
system a little bit. In the view code, notice that we now have specified Pie. Name,
which actually specifies that this field is for the Name property of the pie. The
model binding system comes with a number of attributes that we can use to influence
the binding system. The Bind attribute is the first one. It allows us to bind
properties selectively. I'll show this in the next slide with some more detail. The
BindNever attribute can be placed on a property of a model object to indicate that
this property should not get a value through model binding. And the BindRequired
attribute does exactly the opposite. When validating, it will actually cause an
error if no value can be found for a given property. Now model binding is also very
aggressive. It will try to bind everything it finds. Assume that you have an object
where I may not want to get the value for all properties of the type. Model binding
will still try to bind everything, unless I instructed not to do so, and I can do
this using the Bind attribute that we just saw. In the sample here, I can then
specify the property or properties I want to bind. Other properties are ignored.
Here you can see an example of the BindNever attribute. Using this attribute, we
can again on the type indicate that you will never allow model binding to get a
value for the given property. For example, we typically won't allow that the ID
property gets a new value from model binding, and so we can use the BindNever
attribute for this. The last thing before we go to the demo is binding a collection
or an array. Model binding supports binding request data to an array, which is
actually pretty cool. Take a look at this Razor code here, where we are binding to
a list of pies. We're then looping over the items in the array, so the individual
pies, and for each we're displaying a text input. When we submit the form, the
BulkEditPies method will be invoked. Now this method is accepting a list of pies as
its parameter. Model binding will again do all the hard work for us. It will
recreate the list for us, giving us access to the data entered by the user in this
view.
Validating Data
At this point, we've already covered a few options to make sure that model binding
is happening the way that we want it to happen. Although this way we have a nice
and easy way to get data from the client into our server-side code, what we haven't
covered yet is the ability to validate that the data that we are retrieving is
actually valid. And so Bethany has returned again to our office. She's asking us as
the developers of the application if it's somehow possible that we prevent her
users from entering invalid data into the system. Of course, Bethany. We'll show
you how we can fix that for you, we said. Now that we've made a promise to Bethany,
let's dive into the validation options we're getting with ASP. NET Core, to
validate the data that's coming from the client. We're going to start by looking
how we can make sure that the received data can bind to the model, and secondly,
we're going to make sure that we can help the user with correcting the problem.
ASP. NET Core already has some options on board, but we can extend on these to give
an even better experience. Let's take a look. The basic validation options were
already covered in the introduction course. But for completeness, let's take a
quick look and then we'll look at the advanced options in ASP. NET Core MVC. In
this snippet you can see here on the slide we can see an action method called
AddPie. It's expecting a view model, and this is of a class named PieEditViewModel,
which will typically be created by model binding as we've seen in the previous part
of this module. Model binding has done its very best to give a value to all
properties, including nested types, and thus the PieEditViewModel should be ready
for us. However, this has not done any validation yet. We have control over how
this is done. First, we can do validation manually, and that's basically what I'm
doing here in this snippet. For example, I'm going to check that the user hasn't
entered any negative values for the price. If that is the case, then I'm adding an
error to the model state dictionary instance that I get via the model state
property of the controller. The model state dictionary is a dictionary that we can
use to check the state of the model. In this case, we're adding validation errors
to that dictionary. At the point we have done all our manual validation rules in
the controller, we can use the ModelState. IsValid property. This will return true
if there are no validation rules in error. In the snippet that we're looking at
here, I'm going to save the data using the repository, and then redirect the user
to the index action. If, however, something went wrong, we are showing the same
view again, and typically, this will then show what is wrong with the view. I'll
show you how to do that in just a second. Next in manually performing the
validation, we can also use validation attributes on our model properties. Again,
these were already covered in the introduction course, so I'm not going to spend
too much time on these here. ASP. NET Core MVC comes with the following properties
that we can apply on the model properties. The Required attribute allows us to
specify that the value needs to be defined for the property. The StringLength
attributes can be used to make sure that the entered string has a certain length.
The Range attribute is typically used for numeric values, and allows us to specify
that the value needs to fall within a certain range. The RegularExpression
attribute can be used to apply a regex on a property, and thus check if it has a
certain format. Finally, the DataType attribute allows us to specify that the input
needs to be in a certain format. They, however, are not real validation attributes;
they merely give a hint to the browser about the type that is expected. And it can
be set to phone, email, and URL. Here we can see an example of the attributes that
we can use on a model. I'm showing the Order class here, and a couple of
properties. Assume that we are allowing the user to place an order here. specifying
the FirstName property first, and we want to make sure that it's going to be
provided. For that reason, I have applied the Required attribute, and I'm also
adding an ErrorMessage that needs to be displayed when it is not provided. Also, we
want to make sure that the entered data isn't too long, so we're also applying the
StringLength attribute. For the phone number, I've also added another attribute
here, the DataType attribute, and it's set to phone number. It's, of course, of no
use if we are performing validation in the code, and we're not alerting the user
about the fact that something is wrong. For that very reason, we can add a
validation summary to the view. There's a tag helper that comes with ASP. NET Core
MVC called the validation-summary tag helper, and it will detect the asp-
validation-summary attribute. When applied, it will show validation errors added by
the action. The value of the asp-validation-summary attribute can be set to ALL,
model only, or none. All will show all validation errors, model only will only show
the errors which are recorded on the model, so not the individual properties, and
none quite logically will disable the tag helper.
Client-side Validation
So far, the validation that we have seen all happens entirely on the server side.
This means that every time that the user has entered a value, we will need to send
off the data to the server for validation. Only after receiving the response will
the user know if the sent data was okay. Of course, in many occasions, the user
will be getting a better experience in the application if the validation can be
done on the client side, giving direct feedback to the user. This, of course, will
be based on the use of JavaScript, allowing us to perform validation before the
data goes off to the server. Let's take a look at how ASP. NET MVC supports this
out-of-the-box client-side validation. Of course, we can start writing JavaScript
code ourselves, which can then do validation on the client-side forest before the
data is sent to the server. That actually isn't necessary, since a very commonly
used package is available for this very purpose, which is called jquery-validation.
Now ASP. NET Core MVC supports something called unobtrusive validation. Basically
this means that ASP. NET Core will create on the server some HTML attributes, which
will then be interpreted by another library called jquery-validation-unobtrusive.
Because ASP. NET Core MVC includes these HTML attributes, it becomes possible for
this package to configure jquery-validation so that the correct client-side errors
are then generated for the users. Now to get started with this approach for client-
side validation, we need to first add a number of packages to our application.
Since these are client-side packages, they are managed by Bower. You can see in the
code here on the slide that jquery, jquery-validation, and jquery-validation-
unobtrusive have been added to the application. With the packages added to our
code, we now need to start using the code in these packages. To get started, on the
page where we want to perform client-side validation, we'll need to include a
number of scripts. We can see here in the code that I've added jquery, jquery-
validation, and jquery-validation-unobtrusive. Just a small tip, these files need
to be added exactly in this sequence for things to work correctly. Now with the
generated code that comes back from ASP. NET Core MVC, we will see that data-
attributes have been included. Now how did they arrive there? Well, this is thanks
to the tag helpers. The input tag helper that generated the input tag, in the
generated HTML, has noticed that we had specified one or more validation attributes
on the model, and has therefore generated the corresponding data-attributes, and on
the client, these will be picked up by jquery. validation. unobtrusive, resulting
in well-functioning client-side validation.
Remote Validation
We've now seen the model that ASP. NET Core MVC uses, and we've seen how we can add
both server-side and client-side validation to our application. We've seen that
client-side validation gives the user a better experience, and we should aim to
include as much client-side validation to our applications as possible. This, of
course, will need to be combined with server-side validation since client-side
validation can always be disabled, it's just JavaScript. However, there are
situations where server-side validation is still required. For example, imagine
that you're building an application where the user can sign up with his or her
email. Of course, we can only validate on the server side if that email address is
already in use or not. It would be a horrible idea to send the list of all
registered email addressees to the client, so that the client-side code can then
check if the email address is already being used. However, it is still possible to
create a client-side experience in this case. ASP. NET Core MVC allows us to
perform something called remote validation. The name is already hinting at what
this will be doing. We will invoke a method on the server-side from client-side's
script, and this can be done using the Remote attribute. Let's take a look at an
example here. When we are building the administration pages for Bethany's Pie Shop,
we don't want two pies with the same name being added to the system. So we could
typically validate this in server-side code. However, we can add remote validation
here, so we can invoke server-side code from client-side code. Here we are going to
see the Remote attribute in action for this very purpose. We are asynchronously
going to invoke an action method called CheckIfPieNameAlreadyExists on the
PieManagement contoller. Also, we've included the error message that will need to
be shown if the validation fails. However, it is still possible to create a client-
side experience in this case. ASP. NET Core MVC allows us to perform something
called remote validation. The name is already hinting at what this will be doing.
We will invoke a method on the server side from client-side script, and this can be
done using the Remote attribute. Let's take a look at an example here where we are
building the administration pages for Bethany's Pie Shop, we don't want two pies
with the same name being added to the system. So we could typically validate this
is in server-side code. However, we can add remote validation here, so we can
invoke server-side code from client-side code. Here we are going to see the Remote
attribute in action for this very purpose. We are asynchronously going to invoke an
action method called CheckIfPieNameAlreadyExists on the PieManagement controller.
Also, we've included the error message that will need to be shown if the validation
fails. In this case, this will be if the entered name for the new pie already
exists in the system.
Summary
And another module is finished. Congratulations! This module has taught us quite a
lot about how ASP. NET Core MVC handles the data for our application. We started
the module by exploring in a lot of detail how the binding engine works in ASP. NET
Core MVC. Without it, we would have to write a lot more code to find in the
incoming request the data that we need on the server-side. We've then continued to
the validation options that ASP. NET Core MVC has on board out of the box. We've
covered the manual validation options, the validation attributes, as well as how we
can display validation errors in the view using a validation summary. Finally, to
close the module, we've seen how a validation mechanism can be extended to the
development of a custom validation attribute. Now since this module was more about
what happens under the hood, we'll shift gears for the next module, and we'll
explore the many options that ASP. NET Core MVC offers us to build clean view code.
Thanks for watching.
An Overview of Routing
So as mentioned, I'm going to start the module with a brief overview of the routing
engine that comes with ASP. NET Core MVC. In the introduction course, I've already
deeply covered the routing engine and its basic options. The following is just a
summary of what we've seen there. ASP. NET Core MVC comes with a routing engine.
Earlier versions of ASP. NET, so webforms, did not contain this building block. At
the time of webforms, there was a close relationship between the request and a
physical file that was being requested; hence, ASP. NET just had to look for the
file in the file system of the application and save that. With ASP. NET MVC, and
there's also with ASP. NET Core MVC, that close relationship is gone. We're now
making requests to action methods within a controller, so there's no direct link
anymore. Now to solve for this, ASP. NET Core MVC comes with a routing engine. This
routing system will make sure that requests are routed to the correct action method
within the controller. Now let's see how that routing engine works. Routing is
based on routes. Each route is a URL pattern, and that's mapped to a handler. The
handler in the case of MVC is the action on the controller. The fact that we can
use patterns for our routes means that we don't have to type all the possible
routes for our application. What will happen is that URLs are compared to these
patterns. And when a match is found, in fact, I should say when the first match is
found, that pattern will be used to trigger the corresponding action. Now take a
look at this basic route that you see here on the slide, bethanyspieshop.
com/Home/Index. When we look at this URL, we can see that it's made up out of a
number of parts. The host, well, that's bethanyspieshop. com, as well as Home and
Index. These parts are known as segments. Typically the first part will be the
controller and the second part will be the action. Of course, we have to explain
this somehow to the routing engine. It's not that it's something that works by
default. We'll have to create a route that says that when a URL with a pattern with
two segments is encountered, that we'll assume that the first one is the
controller, and the second one is the action. If we now try to browse through
bethanyspieshop. com/Home, there won't be a match. The reason is simple. There's no
second segment here. Only if we by default have a matching number of segments will
there be a match. If you throw Home/index/pies at it, again, there will not be a
match for this pattern. The reason is the same. The number of segments isn't
correct, so there will not be a match. The number of segments is clearly one of the
most fundamental ways for the routing engine to know if a URL matches a pattern or
not. Now routing works for incoming URLs, so for incoming requests. That means that
if I sent a request to the web application, the routing engine will be invoked as
part of the process, and it will try to find an action to execute on a controller.
Next to doing this for incoming requests, the routing engine will also work for
creating outgoing URLs, meaning that it will be invoked when we want to generate a
URL, for example, to generate a link to an action on the different controller that
will be part of the generated page. We'll see later in this module how ASP. NET
Core MVC can be used to generate these outgoing links. Now working with routes has
changed quite a bit with ASP. NET Core MVC compared to previous versions of ASP.
NET MVC. Let's see how we can create routes first in our application. It's not
required, by the way, to add any package to enable the routing engine in an ASP.
NET Core MVC application. We can go ahead and configure routes inside of our
application. Of course, this is really configuration of the application, and it's
therefore something that we do in the Startup class of the application, more
specifically, in the configure method. There we can add code as we can see here on
the slide. I'm passing to the routing engine the routes or patterns that I want my
application to use. Keep in mind that I've said that as soon as a match is found,
ASP. NET Core will take that pattern to perform the matching; therefore, the
sequence that we place these routes in in the application has a very big influence
on which routes will be selected. The route that I've added here is a very basic
one, which contains just two named segments, one to indicate a controller, and one
to indicate which path is the action. Of course, real life applications, and that's
the goal of this course, meet some more complex routes than what you can see here.
Very often we will also want to pass to the route some default values, for example.
This allows us, in this case, to browse to the root URL, and the routing engine
will then know that in fact it should use the index action on the home controller.
Instead of using the syntax we just saw, where we created the defaults with a named
parameter, we can also define these inline, as you can see here. The net result is
exactly the same. When we navigate to the root URL, so without specifying a value
for the controller or the action, we will navigate to the index action of the home
controller. In many cases, we'll want to specify some restriction on what value can
be used for the matching of the parameter value. In that case, we can apply a
constraint. Here we can see an actual segment defined in the route, id. Now, id
will typically be a value that is passed to the action method, and it may be so
that we are expecting an integer parameter. If that's the case, we can specify this
using a route constraint, which as you can see here defines that the id should get
an integer value in order for this to be a match. Also, I've added a question mark
at the end. This is used to indicate that the id segment is optional, meaning that
this route is allowed to be used even if the id parameter cannot be found. There
will still be a match. The route that we've looked at in fact contains only
segments. If we are looking at improving the SEO for our site, so making it
possible for search engines to more easily know what the page is about, we can add
static segments to the route. This is just a string that will be part of the
pattern matching. That's being done by the routing engine only when a request is
made for a URL that starts with shop might there be a possible match for this
pattern.
Attribute-based Routing
Now that we've done a bit of a refresher on the routing engine that comes with ASP.
NET Core MVC, let's dive in the more advanced topics regarding routing. The routes
that we've just looked at are so-called convention-based routes, meaning that we
defined the route in the application's configuration. Well, there is another type
of routing that's often used, and that is attribute routing. Let's take a look at
the options we're getting with this type of routing. In the case of attribute
routing, we can use attributes which are going to be used to map an action to a
route template. Now to start with, to enable attribute routing, nothing really
needs to be configured at the application level. As long as we call app, use MVC in
the Startup class, we're covered. Nothing extra is required. The actual defining of
the routes and which action needs to be executed will now be done in the
controllers themselves. Take a look at the code here on the slides. We're inside
the HomeController and I have an Index action. On the action, I've now added an
attribute called Route. And as value, I've defined Home. Well, this means that when
a request comes in for /Home, there will be a match for the Index action on the
HomeController. We're not constrained to matching an action method with just one
attribute. On the contrary, we can add multiple instances of the route attribute,
each with a different parameter. In this case, a request for the root URL, so one
for Home, and one for Home/Index will all match or be routed to this action method.
Keep in mind that there is now no pattern matching whatsoever going on. Only when
there is a precise match of the defined string will the action be invoked. The name
of both the controller and the action are with this way of working of no influence.
As you may already be seeing, working with this attribute-based routing is perhaps
more work, since we need to define all these individual attributes on the action
methods in our code. Now, on the other hand, it's more fine-grained, and it gives
us more precise control compared to when we are defining the routes in the
configuration of the application. Now, as mentioned, by default, the name of the
controller and the action are of no importance for the selection of an action when
working with attribute-based routing. It is, however, possible, though, to create
routes in a more complex way. That changes this a little bit. Take a look at the
code here on the slide. I now have added another attribute where we are using
something called token replacement. By default, ASP. NET Core knows about three
tokens, controller, action, and area. And when we surround these with square
brackets, they will be replaced with the name of the action, the controller, or the
area where the action is defined. Now, know that we'll look, by the way, at areas
later in this module. Here in the code, I've added controller between square
brackets, and so this will be a match for a request for /Home/Details, and then the
id value also. Now also with attribute-based routing, we can take things a step
further. Here we are looking at the same route again, but now I've defined that the
id parameter is constrained again. The route itself is just a mix between a static
part and a variable part again. Using attribute routing, we can also use the Http
verb, such as HttpGet and HttpPost. These Http verb attributes can accept a route
template, so here in the sample, we see the HttpGet attribute accepting /Pies. This
means that if we now perform a request to /Pies, the ListPies action will be
invoked when the request is of type Get. Of course, this approach also works for
other Http verbs, such as Post. So we can also use the HttpPost attribute, which
will be invoked if we are performing a post request to /Pies. This approach is a
very good way of working when building an API with ASP. NET Core MVC as well.
Instead of defining the route on the action using an attribute, we can also move it
to the controller level. Under controller, I've now defined pies, and on the action
method ListPies, I've added the HttpGet attribute. This way, where we now perform a
request to /Pies, the ListPies method of the PiesController will be invoked. Now to
get to the GetPie action, I've added another HttpGet attribute, but it has the id
value between curly braces. This means that the action will be invoked now when I
perform a request, for example, for /Pies/1 where 1 will be the value for the id.
Just like we have already seen that we can on a single action method add multiple
attributes, we can do the same thing with controllers in our application. Here you
can see that both the controller, as well as the action, have multiple route
attributes defined. Now that we have spent some time exploring attribute routing, I
want you to think about what your preference would be. The good thing is that this
is not a this or that approach. In fact, it's very common to have one application
where you mix both approaches. So, convention-based and attribute-based routing.
Very often you'll see that for controllers convention-based is used more for HTML
pages and attribute-based routing is used more for APIs. Now, for actions, there's
really no rule of thumb here. However, keep in mind that as soon as we add a route
attribute on an action, it can only be reached using the attribute route, and not
anymore with the conventional route. As soon as we define a route attribute on a
controller, well, all its methods become only reachable via the attribute-based
system.
Summary
And we've reached the end of another module in this course, a module in which we
looked at the many options of the routing engine that you are able to use in your
applications. We've covered attribute-based routing, which offers us fine-grained
control over the routes in the application. We've then also looked at using areas,
which has two main advantages. The first one is the manageability of the site's
code base, and the second one was the fact that it can also help us with making
URLs for the site more meaningful. Finally, we've looked at how the routing engine
will also kick in to generate links that become part of the generated HTML. Now in
the next module, we are going to learn how we can write unit tests for our
enterprise ASP. NET Core MVC application. I hope to see you there as well.
Summary
With that, we have reached the end of another module in this course. I hope you
have again learned a lot. Let us summarize what we have seen in this module
together. We have started this module with a short overview of what unit testing is
all about, and where it can help us with basically building a safety net around our
code. We've then looked at how we can write unit tests using xUnit. Achievement
unlocked. We've added unit tests to our code base. In the next module, we'll take a
look at how we can get information from our running site using logging and other
diagnostic tools. I hope you to see you there again as well! Thanks for watching.
Diagnostics Middleware
As said, we'll start the module by exploring the diagnostics middleware. We have
already encountered middleware components before. We can existing middleware
components, or we can even create our own. ASP. NET Core comes with a number of
built-in middleware components, which are basically available by default without us
having to do anything. These include authentication, routing, session support, and
diagnostics. Let's take a look at the diagnostics middleware package to start the
module. As the name implies, the diagnostics middleware will enable ASP. NET Core
developers to get diagnostic information about the running application. It's
basically a feature targeted at developers, and users will typically not get any
benefit from this. Of course, apart from the fact that the developer will know what
went wrong, and can thus improve the application. Using the built-in diagnostics,
developers will also get away to handle exceptions that happened in the
application. As mentioned, we don't need to do add a package to the configuration
ourselves. The diagnostics package, Microsoft. AspNetCore. Diagnostics, is already
added by default in a new ASP. NET Core application. So what exactly can we do with
this package then? First, the UseDeveloperExceptionPage middleware will give
developers information about exceptions that happened when the application was
running. Typically these will be used when we are using the development
environment, which is the default where we are starting a new application. Without
calling this method in the configuration of the application, when an exception is
occurring, we will see the following. Indeed, nothing. No crash information is
shown to the developer, so we don't know what happened. When we instead call the
UseDeveloperExceptionPage, we will get information about the exception which has
occurred. The page that we are seeing here can be seen as a replacement for the
yellow screen of death that ASP. NET applications showed previously when something
went wrong during the execution. Of course, as you can see from the screenshot
here, vital information about the application is being shown here. This is
developer information, so this should never be shown to end users. The
UseStatusCodePages can also be added to the execution pipeline. This can be used to
handle status codes between 400 and 600, which are being returned from the server,
that is. If that is the case, a generic error page showing the status will be
shown. The UseExceptionHandler middleware is a very nice one. When we are running
the application in the development environment, we won't see anything. But if we
are running the application in staging or production and an exception occurs, we
will see a nice error page. It is of course not showing any development information
about the application. When we add it to the Configure method in our application,
we can pass it which page we want to show if something went wrong during the
execution of the application. Finally, the UseWelcomePage middleware can be used to
show a generic page to welcome the user, which can be, for example, done while
we're still building the site. If the end user tries to browse to a different page,
the request will be re-routed to this welcome page. Again, we can specify which
page we want that to be.
Logging Middleware
Our good friend Bethany has returned again to our office with a new question. She
has been receiving some complaints from her customers that seem to be getting some
errors while using her site. Of course, we all know users are saying we're getting
an error, but we typically do not get any more information. Bethany is a bit
worried about this, so she wants to know what we as developers can do about a
problem. Luckily, ASP. NET Core MVC offers us quite a lot of ways to perform
logging in our site, making it possible to more easily see what's going on with the
application at runtime and hopefully find the cause of the error more easily. Let
us explore the logging middleware in our ASP. NET Core applications to get runtime
information. In many cases, that's the only way that we as developers can know what
went wrong when the user got the error he or she was referring to. When we look at
the ASP. NET Core platform in its current state, there are, in fact, three ways of
logging built-in. EventSource is the first one, and it's been around for quite some
time, meaning it wasn't added for just ASP. NET Core. It was already available
since ASP. NET 4. 5. The data that we're getting back is strongly typed, and the
EventSource is therefore also typically used in combination with the logging of the
operating system. ILogger is a second way of doing logging in ASP. NET Core. It's
very much linked with ASP. NET Core, and it will be the one that we typically use
in combination with ASP. NET Core applications. It will typically be added to our
application by adding an ILogger instance using dependency injection, and we'll see
that very soon in the upcoming slides and demos. The third and final option to
perform logging in our ASP. NET Core applications is using DiagnosticSource, which
is very similar to EventSource. Both ILogger and DiagnosticSource are used
internally by ASP. NET Core as well. Now we won't be looking in more detail to this
type of logging in this course. So as many of you probably have already understood
from the previous slide, ILogger is for most logging purposes going to be the way
forward in our ASP. NET Core applications. We'll see in a minute how we can use
this logging system. This logging API is actually based on the system of logging
providers. ASP. NET Core comes with quite a number of built-in providers, which
we'll look at in the next slide. Secondly, it's an extension point, meaning that we
can install other providers for other destinations. There are open source providers
available, and we can also write our own. Now as I said, ASP. NET Core comes with a
number of logging providers built into the platform. The first one, and probably
the most basic one, is a console provider. The relevant package which is built in
by default again is the Microsoft. Extensions. Logging. Console package, and as the
name implies, it will send log information to the console. The debug logging
provider will allow us to write logging information to the outputs using the
system. diagnostics. debug class. We'll see this one in the upcoming demo. The
EventSource provider can be used to implement event tracing. This one only works
with ASP. NET Core 1. 1 or higher. The EventLog provider, available to the
Microsoft. Extensions. Logging. EventLog package, will allow us to send logging
information to the Windows Event Log. The TraceSource provider will allow us to the
use the System. Diagnostic. TraceSource library to perform logging. This is,
however, a bit of a special case. The application has to run on the full. NET
framework instead of. NET Core. Finally, the Azure App Service provider allows us
to log directly to an Azure App Service or an Azure Storage Account. It can then,
for example, send text files to the filesystem of an Azure App Service, or an Azure
Storage Account. This provider also is only available for ASP. NET Core
applications running. NET Core 1. 1 or higher. Let's now take a look at how we can
work with that ILogger interface. First, as always in ASP. NET Core, we need to
start by adding some configuration in our Startup class. More specifically, in the
Configure method. Through the Configure method, we pass the ILoggerFactory, which
will be provided by the ASP. NET Core dependency injection system. Once we have
that, we can continue by calling the extension method for the provider we want to
add. In the code that you see here on the slide, we are adding the console
provider, as well as the debug provider. I'm also passing the LogLevel, and I'll
talk about that in just a minute. Once we have configured logging, we of course
need to start using it from our code. Here we see some sample code of a controller
of the application. Notice at first I'm creating a private field for ILogger
instance. I'm then using the dependency injection system again, so that in my
controller I now will have an instance of the logger. Note that the logger I'm
requesting here is actually an ILogger in HomeController. The fact that I passed
the HomeController type here will result in the fact that a HomeController will be
the category for logging here. I'll explain categories in just a second. Keep that
in mind for now. Once I have the ILogger instance, we are ready to start logging. I
can now call methods on the logger instance. For example, here I'm going to log
information on the logger to actually log something. Now we've seen in a previous
slide that we were using the log information method. In fact, by calling this
method, I'm already going to specify the log level. When we're writing a log, we
need to specify this level. Now, several levels exist. We have the trace, the
debug, the information, the warning, the error, and critical. Depending on what we
are willing to log, we need to select the correct level. If you, for example, just
want to log that something went successfully, we should just be using information.
If we want to log that the time out of the database has occurred and thus that the
application is down, we can use the critical level. Next to using the level for
logging, other information can be added when using logging as well. The log
category is basically a grouping mechanism that we can use to group logged
information. A group is, in fact, nothing more than a string that we can create
ourselves. Typically, however, log categories are the fully qualified name of the
class for where we are performing the logging. The log event ID is, like the name
implies, an ID that we can also specify when writing a log. It's typically used for
grouping some logging events together. Finally, the log format string is a string
that contains parameters that we also want to include in the logged message. Very
often, this is a suiting combination with structured logging, which we'll look at
later in this module.
Adding Filters
We've now covered diagnostics and logging middleware already in this module.
Although they aren't directly linked to this, filters can definitely help in
allowing us to get information about the execution of the code. Let's dive into
filters in ASP. NET Core MVC. Let's first look at what filters are, and then we'll
take a look at different options we get when we work with them. Filters are
basically going to allow us to add logic in the request pipeline of ASP. NET Core
MVC. Typically when we send a request, the corresponding action is executed on the
controller. However, if we want, we can attribute the action method with one or
more filters. A filter will trigger the execution before or after the controller
has executed, and can, in some cases, also be used to completely interrupt the
normal execution flow. Very often, filters are added to ASP. NET Core applications
for the use of so-called cross-cutting concerns. A cross-cutting concern can be
security, or logging, or basically anything that we'll be doing across action
methods. They may not have a direct link to the code that we're executing in the
action method, though. It is typically used so that we can have this code in one
location to avoid the duplication of this functionality in our code. As mentioned,
there's quite a few common usages in typical applications, including authorization,
requiring that a request is sent over HTTPS, logging, and much more. As mentioned,
a filter has the power to also interrupt a normal flow, meaning that if, for
example, a request would come in using plain HTTP, and we have a filter applied
which requires that all requests are made using HTTPS, it can cause an exception
and block the action from executing. If you're still a bit unclear about filters,
let me explain with a little example. Let's again use the example of requiring that
all requests are made using HTTPS. Although this is something that doesn't have
anything to do really with the functionality of an action method, the code inside
the action method will be littered with checks to see that the request that we are
handling is effectively HTTPS. As you can see by the frequent usages of Request.
IsHttps. Apart from the fact that this isn't very clean, it's also duplication of
code. Now using a filter, we can now move this code so that it becomes an attribute
that we can apply on the action method, allowing for easy reuse across the
application's code base. The code itself is much cleaner, and we're avoiding code
duplication. Imagine that at some point we need to make an additional check. In
that case, we only have to change the filter code in one place and all will be
updated. Really good stuff if you ask me. Filters, in fact, run in what is often
referred to as the filter pipeline. When a request is received, it first goes
through the middleware, then the routing middleware, and then the correct action is
selected. If a filter is applied on this action, then the filter pipeline will
start. This also implies that on a single action method, it's very well possible
that one or more filters will be applied. ASP. NET Core MVC comes with different
types of filters that we can apply on our action methods. Each will run in a
certain stage in the filter pipeline. So ASP. NET Core MVC is doing the
orchestration here to make sure that all filters will execute in the correct order.
The first type of filter is the authorization filter, and as that name implies, it
will be used to check if the current user is actually allowed, or authorized, to
make the request to the current action method. If that is not the case, ASP. NET
Core can actually stop the further execution from taking place. We often refer to
this process as ASP. NET Core basically short-circuiting the request. The second
type of filter is resource filters. After the authorization filters have finished,
any resource filters we have applied will run. Here we can, for example, perform
caching, if needed. These filters will also still run before the model binding has
taken place. Next, we have action filters. An action filter can run right before
and right after an action method has been called. They can, for example, change the
result that's being returned from the action. As the fourth type of filter, we have
the exception filters. As the name already is giving away, exception filters can be
used to indicate how we want to handle exceptions that are occurring in the action
method. These are typically applied on a global level in our code, meaning that
they will have an effect on the entire application's code base. We'll look at how
we can apply a filter globally later in this module. Finally, we have result
filters. A result filter can run right before and right after the execution of an
action result. They will only run if the action method has actually run
successfully. All filters that we will create will implement a common interface
called the IFilterMetadata interface, part of the Microsoft ASP. NET Core MVC
filters namespace. This interface has no methods that we need to implement, which
if you think about it, is quite logical. We have different types of filters, so
they all have different purposes. Now before we go and take a look at filters in a
demo, let's take a look at some of these filters in a bit more detail. Let's start
with the simplest one to understand in my opinion, and that's the authorization
filters. As said already, authorization filters are used to check if the user
executing the request is allowed or better authorized to do so. They will run as
the very first filter in the execution pipeline, and also before the actual method
is executing. They only define a before method. Now the Authorize attribute that
we're getting with ASP. NET Core MVC is actually not a filter. It used to be a
filter in earlier versions of ASP. NET, but with the release of ASP. NET Core,
that's not the case anymore. If you want to create our own authorization filter
then, you have to create a class that implements the IAuthorizationFilter
interface. This interface is just defining the OnAuthorization method, in which we
can then write a code to authorize the request. Note that this one is receiving an
AuthorizationFilterContext instance, which contains context data about the actual
request. And now for the filter to be able to check whether or not the user is
authorized to make the request. Here on this slide, we can see a sample of an
actual authorization filter. This filter is the RequireHeaderFilterAttribute class,
and as the name implies, this will check if the request contains, well, the
required header. We're placing this code in an authorization filter. Since this way
we have a way to short-circuit the execution of the action method, should the
required header not be present. As you can see in the implementation, we're
checking the presence of the Referrer header, and if it's present, we're checking
that the value is effectively bethanyspieshop. com. If that's not the case, we
aren't going to throw an exception, since there's no way to catch this. Instead, we
are going to return a 403 Forbidden and prevent that the action method will be
executed. It's going to be short-circuited. The most common type of filters that
we'll be writing are action filters. Action filters are general purpose, and with
that, I basically mean that we can write whatever code we want in them. If we'd
like to log that an action method was invoked, or we want to log the result of an
action method, this would typically be the place to do so. Action filters can be
applied on action method, and based on what we will be implementing in our filter,
they can also interrupt the flow before the action method has been executed, or
change the result of the action method after it has executed. Action filters will
typically implement the IActionFilter interface. Here we can see that interface,
and as you can see, it defines two methods, OnActionExecuting and OnActionExecuted.
As mentioned in a previous slide, using an action filter we can execute code before
the action has executed, or right after. This can be controlled by selecting one of
these methods.
Summary
Whew! That was a long module, wasn't it? I hope that you have learned a lot about
diagnosing your application's runtime issues. We have started this module by
exploring the different options for logging, and we have spent most of the time on
looking at the use of the ILogger type of logging. Next, we have explored using
filters in our application. As you remember, hopefully, filters have enabled us to
move code into a separate class and invoke this as an attribute on action methods.
We finished this module by looking at Azure Application Insights. Using this
framework, we can store and send log information into Azure and get useful
information about the performance of our application. In the next module, we will
try to improve the performance of the application. I hope to see you there as well,
and thanks for watching.
In-memory Caching
Now that you have an idea about the general concept of caching, and where we can
apply it in a web application, let's dive into several options in more detail from
an ASP. NET Core MVC's perspective, and we'll start with in-memory caching. In-
memory caching is probably the simplest form of caching available in ASP. NET Core
MVC. It's basically, as the name implies, some data that will be cached in the
memory of the server. It is represented by the IMemoryCache interface, and we'll
see that one when we look at the code. Since the data that we're going to work with
is simply going to be stored in the memory of the web server, it is tied directly
to that server. That means that in-memory caching is intended for sticky sessions,
meaning that some secret requests should always return to the same server in the
case we're using a webform. In the case that we can't really use sticky sessions,
then we can't really use this type of caching, since we'll probably end up with
inconsistencies in the data that is being cached. Distributed caching will probably
be a solution for this setup, and we'll take a look at that later in this module.
Since we're storing data objects in-memory for retrieval later on, we're basically
not limited to what we are storing. Typically we'll want to store expensive
objects, expensive data, things that require a lot of processing before they are
available again. Now to start using in-memory caching, we'll need to first add a
package reference in our application again. In this case, the required package is
the Microsoft. Extensions. Caching. Memory package. Now with the package added to
our project, we can start configuring the application so it can use in-memory
caching. Like most things in ASP. NET Core MVC, we can use it through dependency
injection. Now for the ISystem to know about it, we need to register it, of course,
and this can be done by calling services. AddMemoryCache in the ConfigureServices
method of the Startup class. Once IMemoryCache is configured for use with the DI
container, we can get a hold of it in our controllers. Here on this slide, you'll
see a basic controller which is expecting an IMemoryCache instance as a constructor
parameter. Because IMemoryCache was registered in the DI container, we'll get back
an instance that we can then use in our controller code. Now using IMemoryCache
isn't very difficult. It has a very straightforward API that we can use. You can
think of an in-memory cache as an in-memory dictionary, where each cached value is
addressable using a key. Of course, we don't know up front whether or not a value
for a given key is already available in the cache. Therefore, we need to perform a
check before trying to use a value. Here in the slide, we can see how I can perform
that check using the TryGetValue method of the IMemoryCache interface. I pass it
the key. Here that is the PiesOfTheWeek constant. If a value is found, it's
returned using the out parameter. If not, well, then we need to perform code to
retrieve the actual value, and then we'll store it in the IMemoryCache instance
using the set method, passing along again the same constant string as key. When we
are working with cached data, one of the important things is that we'll need to
find a balance between performance game and the amount of time that we keep the
data cached. We shouldn't keep the data cached for too long, since then we may have
a risk that we're sharing out-of-date data to the user. In many cases, it is not a
good idea to cache something indefinitely. ASP. NET Core uses the
MemoryCacheEntryOptions, which among others, allow us to manage when a cache should
expire. We can specify an absolute expiration, which is a specified point in time.
We can, for example, say that we will cache for one hour, and then, upon the next
request that we receive for that particular piece of data, the cache is cleared and
the data must be retrieved again from the source. Alternatively, we can also use a
sliding expiration, meaning that every time the cache data is used, the timer for
expiration is being reset. We also have the ability through the use of the
MemoryCacheEntryOptions to change the cache priority. Using this, we can indicate
to ASP. NET Core MVC how important the piece of data really is. Should there be a
shortage of memory, ASP. NET will remove the items with the lowest priority first.
And finally, there's also the option to create a delegate that will point to a
function that needs to run should the data be forcibly removed from memory. That is
the dramatic-sounding PostEvictionDelegate.
Distributed Cache
So far, we've discussed options where we are storing the data in the server's
memory, and we've discussed that this may not always be the best solution. Now
let's take a look at using distributed caching. I've already talked about the fact
that for the purpose of caching, it is not always a good idea to cache data in the
memory of the server. While that is okay for simple applications that don't
generate a lot of load on the server, it is not always the best solution for larger
systems, typical for the enterprise. In the case that we have a large application,
or perhaps an application that starts small, but grows over time, another approach
may be better, both for performance and scalability of the application. Using so-
called distributed caching might help in achieving this. A distributed cache
basically means that the data won't be cached directly in the memory of a web
server, instead a shared cache is set up which is available for all servers. Using
a distributed cache removes the need to work with sticky sessions, which may not
always be good for performance anyway. The data that's in the cache is available to
all servers. It's also much easier to scale the system. We can install an extra
server, and it can go ahead and use the same shared cache. Also, without
distributed caching, the cache data is lost when the server needs to reboot. We
then distribute that cache that is no longer a problem. And in general, using a
distributed cache will result in better performance, which is what we're after in
this module anyway. Now using a distributed cache is quite logically more complex
compared to an in-memory cache. The good news is that ASP. NET Core MVC comes with
support for two built-in approaches, and that is Redis and SQL Server. We'll look
at a way to implement this in the next demo. Just like with the IMemoryCache
interface for in-memory caching purposes, we have another interface that we can use
with distributed caches. Microsoft has kept the name quite simple, and I think it's
pretty obvious where the name is coming front. IDistributedCache is the interface
that we will be using to work with a distributed cache. The cache here finds a
number of methods, each which has a synchronous and an asynchronous implementation
available. We have the Get and GetAsync, which accept a string as key and returns
the cache data if found using a byte array. Quite logically, the Set and SetAsync
will store data, again, as a byte array based on the key we're giving it. Refresh
and RefreshAsync are used to refresh the sliding expiration for a cache item, and I
think you can figure out that Remove and RemoveAsync will remove an item from the
cache based on its key.
Response Caching
So far, we've focused on the optimizations we can do from the server-side in our
application. Another option is using response caching. Let us explore what we can
do in this area to improve the performance of the application. As the name implies,
using response caching, we can now add the correct headers, cache-related headers,
to the response. These headers can then be used down the line, so on the client,
but also on intermediates, such as proxy, to cache the entire response and reuse
that. This, of course, will make the application look faster, as well as bring down
the load on the server, since the same data only needs to be returned once. Working
with response cache in ASP. NET Core MVC can be done using an attribute called the
ResponseCache attribute. This attribute will allow us to specify some parameters
that will then result in setting the correct headers in the resulting response.
Let's take a look at how we can use this ResponseCache attribute. Here in the
sample code that you see here on the slide, we have a small example. To the
attribute, I've added several parameters. To enable response caching, we need to
specify a value for the duration parameter. Here I've specified 30 minutes, which
will result in the max-age header being set in the response. Next I've also used
the VaryByHeader and I specified the User-Agent here. Setting these headers, as
mentioned, will result in headers being set in the generated response that is being
sent down to the client. Here you can see a screenshot where these headers have
been added. We have the ability to specify several types of headers to control
this. The location header allows us to specify where the caching can happen. For
response caching to work, it must be set to any or client. If we set it to none, no
caching will happen. Duration is the second one. We've already seen that on in the
previous slide, it gives us control over what the duration of caching or the
response will be. NoStore allows us to control that the caching should not happen
at all. This just results in the cache control header to be set to NoStore no
cache. Finally, the VaryByHeader allows us to specify what header we want to check
on. We can use the ResponseCache attribute throughout our code base, with the risk
that we have to change it and thus make changes in several places. It's easier to
fix this by creating something called a CacheProfile. A CacheProfile is registered
in the ConfigureServices method of the Startup class, and it allows us to create
one or more named profiles, each of which has a number of parameters set. On the
slide here, we're defining a couple of profiles, and we can then from actual code
refer to these profiles. And as we can see, each profile has a number of properties
set.
Summary
We've reached the end of this module. Time for a quick recap of what we have seen
in this module. We started our journey to improve the performance of Bethany's Pie
Shop by looking at several ways to perform server-side caching. We've seen in-
memory caching here, and we've also seen how distributed caching works. Next, we
have spent time looking at how we can from the server add the correct headers to
the response, so that the client will be able to cache a version. Finally, we've
looked at how we can improve the performance even more by compressing the response
before it's even sent to the client. In the next and last module of this course, we
are going to take a look at how we can automate the builds and deployments for our
site. Thanks for watching this module.
An Overview of VSTS
As promised, to make sure that everyone is on board with VSTS, a small overview of
the platform is in place. Pluralsight has quite a few courses that go in much more
depth on this topic. VSTS, short for Visual Studio Team Services, is the online
component of the Visual Studio family. Previously known as Visual Studio Online,
VSTS is an integrated environment for the entire development team. Visual Studio
Online gave people the idea of working with an in-browser version of Visual Studio.
And although you can do some editing from within VSTS, it is much more than just
code editing. Using VSTS, we can check in code to a source control system, which is
built in, but we can also automate builds, perform deployments, plan the work for
the entire team, and so much more. It is, in fact, a complete DevOps solution. VSTS
prides itself also in the fact that it is not bound to a certain technology. VSTS
is very open, meaning that we can use of course for ASP. NET, C#, WPF, and
basically all. NET applications that we're building. But also VSTS can work with
Java, iOS, Android, and other platforms. It also integrates with many third-party
tools, such as Jenkins or Jira. Internally, it is also based on Azure. That's
right, VSTS is a software as a service solution, meaning that it is, in fact, TFS,
which is being hosted by Microsoft in the cloud for us. That being said, many, if
not most things that you'll learn in this module are one-on-one applicable in TFS,
so Team Foundation Server. So what does VSTS bring to the table then? Source
control. That's, of course, an important one. VSTS supports Team Foundation Source
Control, or TFSC, as well as git. Using VSTS, we can also plan the work for the
entire team by creating work items, registering bugs, assigning tasks, and much
more. VSTS also comes with a built-in planning board, and it does allow us to
follow up on the project's progress. VSTS can also be used as a build server, so in
fact, it's also a continuous integration tool. This makes it possible to set up a
build with every check-in, for example. But we can also configure from VSTS a
nightly build. VSTS also contains something called release management. As the name
implies, this allows us to manage and orchestrate releases to staging, production,
and so on. For testing, VSTS also features a lot of useful parts. Basically, I can
go on and on and on about all the features in VSTS, but that would take us way too
far.