Little ASP Net Core Book
Little ASP Net Core Book
of Contents
Introduction 1.1
2
Require authentication 1.7.2
Conclusion 1.10
3
Introduction
Introduction
Thanks for picking up the Little ASP.NET Core Book! I wrote this short
book to help developers and people interested in web programming
learn about ASP.NET Core 2.0, a new framework for building web
applications and APIs.
Don't worry, you don't need to know anything about ASP.NET Core (or
any of the above) to get started.
The book itself is updated frequently with bug fixes and new content. If
you're reading a PDF, e-book, or print version, check the official website
(littleasp.net/book) to see if there's an updated version available. The
4
Introduction
Turkish - https://sahinyanlik.gitbooks.io/kisa-asp-net-core-kitabi/
Chinese - https://windsting.github.io/little-aspnetcore-book/book/
If you already code in a backend language like Node, Python, Ruby, Go,
or Java, you'll notice a lot of familiar ideas like MVC, view templates,
and dependency injection. The code will be in C#, but it won't look too
different from what you already know.
5
Introduction
using backend and frontend code, how to interact with a database, and
how to test and deploy the app to the world.
6
Introduction
You may also hear about .NET Core and .NET Standard. The naming
gets confusing, so here's a simple explanation:
7
Introduction
.NET Core is the .NET runtime that can be installed on Windows, Mac,
or Linux. It implements the APIs defined in the .NET Standard interface
with the appropriate platform-specific code on each operating system.
This is what you'll install on your own machine to build and run
ASP.NET Core applications.
If you're confused by all this naming, no worries! We'll get to some real
code in a bit.
Because of the Katana legacy, the Startup class is front and center,
and there's no more Application_Start or Global.asax . The entire
pipeline is driven by middleware, and there's no longer a split between
MVC and Web API: controllers can simply return views, status codes, or
data. Dependency injection comes baked in, so you don't need to install
and configure a container like StructureMap or Ninject if you don't want
to. And the entire framework has been optimized for speed and runtime
efficiency.
8
Introduction
9
Your first application
Your favorite code editor. You can use Atom, Sublime, Notepad, or
whatever editor you prefer writing code in. If you don't have a favorite,
give Visual Studio Code a try. It's a free, cross-platform code editor that
has rich support for writing C#, JavaScript, HTML, and more. Just
search for "download visual studio code" and follow the instructions.
If you're on Windows, you can also use Visual Studio to build ASP.NET
Core applications. You'll need Visual Studio 2017 version 15.3 or later
(the free Community Edition is fine). Visual Studio has great code
completion and other features specific to C#, although Visual Studio
Code is close behind.
The .NET Core SDK. Regardless of the editor or platform you're using,
you'll need to install the .NET Core SDK, which includes the runtime,
base libraries, and command line tools you need for building ASP.NET
Core apps. The SDK can be installed on Windows, Mac, or Linux.
10
Get the SDK
dotnet --version
2.0.0
You can get more information about your platform with the --info flag:
dotnet --info
Product Information:
Version: 2.0.0
Commit SHA-1 hash: cdcd1928c9
Runtime Environment:
OS Name: Mac OS X
OS Version: 10.12
(more details...)
11
Hello World in C#
Hello World in C#
Before you dive into ASP.NET Core, try creating and running a simple
C# application.
You can do this all from the command line. First, open up the Terminal
(or PowerShell on Windows). Navigate to the location you want to store
your projects, such as your Documents directory:
cd Documents
This creates a basic C# program that writes some text to the screen.
The program is comprised of two files: a project file (with a .csproj
extension) and a C# code file (with a .cs extension). If you open the
former in a text or code editor, you'll see this:
CsharpHelloWorld.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
</Project>
12
Hello World in C#
The project file is XML-based and defines some metadata about the
project. Later, when you reference other packages, those will be listed
here (similar to a package.json file for npm). You won't have to edit this
file by hand often.
Program.cs
using System;
namespace CsharpHelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
From inside the project directory, use dotnet run to run the program.
You'll see the output written to the console after the code compiles:
dotnet run
Hello World!
That's all it takes to scaffold and run a .NET program! Next, you'll do the
same thing for an ASP.NET Core application.
13
Hello World in C#
14
Create an ASP.NET Core project
cd ..
Next, create a new project with dotnet new , this time with some extra
options:
This creates a new project from the mvc template, and adds some
additional authentication and security bits to the project. (I'll cover
security in the Security and identity chapter.)
dotnet run
Instead of printing to the console and exiting, this program starts a web
server and waits for requests on port 5000.
15
Create an ASP.NET Core project
The Program.cs and Startup.cs files set up the web server and
ASP.NET Core pipeline. The Startup class is where you can add
middleware that handles and modifies incoming requests, and
serves things like static content or error pages. It's also where you
add your own services to the dependency injection container (more
on this later).
minified automatically.
16
Create an ASP.NET Core project
build .
17
Create an ASP.NET Core project
If you use Git or GitHub to manage your source code, now is a good
time to do git init and initialize a Git repository in the project
directory. Make sure you add a .gitignore file that ignores the bin
and obj directories. The Visual Studio template on GitHub's gitignore
template repo (https://github.com/github/gitignore) works great.
18
MVC basics
MVC basics
In this chapter, you'll explore the MVC system in ASP.NET Core. MVC
(Model-View-Controller) is a pattern for building web applications that's
used in almost every web framework (Ruby on Rails and Express are
popular examples), as well as frontend JavaScript frameworks like
Angular. Mobile apps on iOS and Android use a variation of MVC as
well.
As the name suggests, MVC has three components: models, views, and
controllers. Controllers handle incoming requests from a client or web
browser and make decisions about what code to run. Views are
templates (usually HTML plus some templating language like
Handlebars, Pug, or Razor) that get data added to them and then are
displayed to the user. Models hold the data that is added to views, or
data that is entered by the user.
If you've worked with MVC in other languages, you'll feel right at home
in ASP.NET Core MVC. If you're new to MVC, this chapter will teach you
the basics and will help get you started.
19
MVC basics
The "Hello World" exercise of MVC is building a to-do list application. It's
a great project since it's small and simple in scope, but it touches each
part of MVC and covers many of the concepts you'd use in a larger
application.
In this book, you'll build a to-do app that lets the user add items to their
to-do list and check them off once complete. You'll build the server (the
"backend") using ASP.NET Core, C#, and the MVC pattern. You'll use
HTML, CSS, and JavaScript in the views (also called the "frontend").
able to build and run the project and see the default welcome screen.
20
Create a controller
Create a controller
There are already a few controllers in the project's Controllers folder,
including the HomeController that renders the default welcome screen
you see when you visit http://localhost:5000 . You can ignore these
controllers for now.
Controllers/TodoController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace AspNetCoreTodo.Controllers
{
public class TodoController : Controller
{
// Actions go here
}
}
Routes that are handled by controllers are called actions, and are
represented by methods in the controller class. For example, the
HomeController includes three action methods ( Index , About , and
21
Create a controller
customize this behavior if you'd like, but for now, we'll stick to the default
conventions.
Action methods can return views, JSON data, or HTTP status codes like
200 OK or 404 Not Found . The IActionResult return type gives you
Before you can write the rest of the controller code, you need to create a
model and a view.
22
Create a controller
23
Create models
Create models
There are two separate model classes that need to be created: a model
that represents a to-do item stored in the database (sometimes called
an entity), and the model that will be combined with a view (the MV in
MVC) and sent back to the user's browser. Because both of them can
be referred to as "models", I'll refer to the latter as a view model.
Models/TodoItem.cs
using System;
namespace AspNetCoreTodo.Models
{
public class TodoItem
{
public Guid Id { get; set; }
This class defines what the database will need to store for each to-do
item: an ID, a title or name, whether the item is complete, and what the
due date is. Each line defines a property of the class:
24
Create models
The Title property is a string. This will hold the name or description
of the to-do item.
Notice the ? question mark after the DateTimeOffset type? That marks
the DueAt property as nullable, or optional. If the ? wasn't included,
every to-do item would need to have a due date. The Id and IsDone
properties aren't marked as nullable, so they are required and will
always have a value (or a default value).
25
Create models
like in C# so you don't have to worry about the low-level database stuff
in your code. This simple style of model is sometimes called a "plain old
C# object" or POCO.
Because of this, the view model should be a separate class that holds
an array of TodoItem s:
Models/TodoViewModel.cs
using System.Collections.Generic;
namespace AspNetCoreTodo.Models
{
public class TodoViewModel
{
public IEnumerable<TodoItem> Items { get; set; }
}
}
contains zero, one, or many TodoItem s. (In technical terms, it's not
quite an array, but rather an array-like interface for any sequence that
can be enumerated or iterated over.)
26
Create models
Now that you have some models, it's time to create a view that will take
a TodoViewModel and render the right HTML to show the user their to-do
list.
27
Create a view
Create a view
Views in ASP.NET Core are built using the Razor templating language,
which combines HTML and C# code. (If you've written pages using
Jade/Pug or Handlebars moustaches in JavaScript, ERB in Ruby on
Rails, or Thymeleaf in Java, you've already got the basic idea.)
Most view code is just HTML, with the occasional C# statement added in
to pull data out of the view model and turn it into text or HTML. The C#
statements are prefixed with the @ symbol.
The file name of the view is the name of the action with a .cshtml
extension.
Views/Todo/Index.cshtml
@model TodoViewModel
@{
ViewData["Title"] = "Manage your todo list";
}
28
Create a view
At the very top of the file, the @model directive tells Razor which model
to expect this view to be bound to. The model is accessed through the
Model property.
29
Create a view
You might be wondering where the rest of the HTML is: what about the
<body> tag, or the header and footer of the page? ASP.NET Core uses
a layout view that defines the base structure that the rest of the views
are rendered inside of. It's stored in Views/Shared/_Layout.cshtml .
wwwroot/css/site.css
div.todo-panel {
margin-top: 15px;
}
table tr.done {
text-decoration: line-through;
color: #888;
}
You can use CSS rules like these to completely customize how your
pages look and feel.
ASP.NET Core and Razor can do much more, such as partial views and
server-rendered view components, but a simple layout and view is all
you need for now. The official ASP.NET Core documentation (at
https://docs.asp.net ) contains a number of examples if you'd like to
learn more.
30
Create a view
31
Add a service class
You could write this database code directly in the controller, but it's a
better practice to keep all the database code in a separate class called a
service. This helps keep the controller as simple as possible, and
makes it easier to test and change the database code later.
First, create an interface that will represent the service that can interact
with to-do items in the database. By convention, interfaces are prefixed
with "I". Create a new file in the Services directory:
Services/ITodoItemService.cs
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using AspNetCoreTodo.Models;
namespace AspNetCoreTodo.Services
{
public interface ITodoItemService
{
32
Add a service class
Task<IEnumerable<TodoItem>> GetIncompleteItemsAsync();
}
}
The type or namespace name 'TodoItem' could not be found (are you
missing a using directive or an assembly reference?)
Since this is an interface, there isn't any actual code here, just the
definition (or method signature) of the GetIncompleteItemsAsync
method. This method requires no parameters and returns a
Task<IEnumerable<TodoItem>> .
If this syntax looks confusing, think: "a Task that contains a list of
TodoItems".
The Task type is similar to a future or a promise, and it's used here
because this method will be asynchronous. In other words, the method
may not be able to return the list of to-do items right away because it
needs to go talk to the database first. (More on this later.)
Now that the interface is defined, you're ready to create the actual
service class. I'll cover database code in depth in the Use a database
chapter, but for now you'll just fake it and return hard-coded values:
33
Add a service class
Services/FakeTodoItemService.cs
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using AspNetCoreTodo.Models;
namespace AspNetCoreTodo.Services
{
public class FakeTodoItemService : ITodoItemService
{
public Task<IEnumerable<TodoItem>> GetIncompleteItemsAsync
()
{
// Return an array of TodoItems
IEnumerable<TodoItem> items = new[]
{
new TodoItem
{
Title = "Learn ASP.NET Core",
DueAt = DateTimeOffset.Now.AddDays(1)
},
new TodoItem
{
Title = "Build awesome apps",
DueAt = DateTimeOffset.Now.AddDays(2)
}
};
return Task.FromResult(items);
}
}
}
34
Add a service class
35
Use dependency injection
using AspNetCoreTodo.Services;
The first line of the class declares a private variable to hold a reference
to the ITodoItemService . This variable lets you use the service from the
Index action method later (you'll see how in a minute).
36
Use dependency injection
ITodoItemService interface.
Now you can finally use the ITodoItemService (via the private variable
you declared) in your action method to get to-do items from the service
layer:
// ...
}
method won't necessarily have a result right away, but you can use the
await keyword to make sure your code waits until the result is ready
37
Use dependency injection
The Task pattern is common when your code calls out to a database or
an API service, because it won't be able to return a real result until the
database (or network) responds. If you've used promises or callbacks in
JavaScript or other languages, Task is the same idea: the promise that
there will be a result - sometime in the future.
The only catch is that you need to update the Index method signature
to return a Task<IActionResult> instead of just IActionResult , and
mark it as async :
38
Use dependency injection
Declaring (or "wiring up") which concrete class to use for each interface
is done in the ConfigureServices method of the Startup class. Right
now, it looks something like this:
Startup.cs
services.AddMvc();
}
services.AddSingleton<ITodoItemService, FakeTodoItemService>();
39
Use dependency injection
you write a different service class that talks to a database, you'll use a
different approach (called scoped) instead. I'll explain why in the Use a
database chapter.
40
Finish the controller
Controllers/TodoController.cs
return View(model);
}
If you haven't already, make sure these using statements are at the
top of the file:
using AspNetCoreTodo.Services;
using AspNetCoreTodo.Models;
If you're using Visual Studio or Visual Studio Code, the editor will
suggest these using statements when you put your cursor on a red
squiggly line.
Test it out
41
Finish the controller
To start the application, press F5 (if you're using Visual Studio or Visual
Studio Code), or just run dotnet run in the terminal. If the code
compiles without errors, the server will spin up on port 5000 by default.
42
Add external packages
NuGet is both the package manager tool and the official package
repository (at https://www.nuget.org). You can search for NuGet
packages on the web, and install them from your local machine through
the terminal (or the GUI, if you're using Visual Studio).
The due date column is displaying dates in a format that's good for
machines (called ISO 8601), but clunky for humans. Wouldn't it be nicer
if it simply read "X days from now"? You could write code that converted
a date into a human-friendly string, but fortunately, there's a faster way.
43
Add external packages
dates, times, durations, numbers, and so on. It's a fantastic and useful
open-source project that's published under the permissive MIT license.
Since Humanizer will be used to rewrite dates rendered in the view, you
can use it directly in the view itself. First, add a @using statement at the
top of the view:
Views/Todo/Index.cshtml
@model TodoViewModel
@using Humanizer
// ...
Then, update the line that writes the DueAt property to use Humanizer's
Humanize method:
<td>@item.DueAt.Humanize()</td>
44
Add external packages
In the next chapter, you'll use another set of NuGet packages (a system
called Entity Framework Core) to write code that interacts with a
database.
45
Use a database
Use a database
Writing database code can be tricky. Unless you really know what you're
doing, it's a bad idea to paste raw SQL query strings into your
application code. An object-relational mapper (ORM) makes it easier
to write code that interacts with a database by adding a layer of
abstraction between your code and the database itself. Hibernate in
Java and ActiveRecord in Ruby are two well-known ORMs.
There are a number of ORMs for .NET, including one built by Microsoft
and included in ASP.NET Core by default: Entity Framework Core.
Entity Framework Core makes it easy to connect to a number of
different database types, and lets you use C# code to create database
queries that are mapped back into C# models (POCOs).
Entity Framework Core can connect to SQL database like SQL Server
and MySQL, and also works with NoSQL (document) databases like
Mongo. You'll use a SQLite database for this project, but you can plug in
a different database provider if you'd like.
46
Connect to a database
Connect to a database
There are a few things you need to use Entity Framework Core to
connect to a database. Since you used dotnet new and the MVC +
Individual Auth template to set your project, you've already got them:
Entity Framework Core uses the database context, together with the
connection string, to establish a connection to the database. You need
to tell Entity Framework Core which context, connection string, and
database provider to use in the ConfigureServices method of the
Startup class. Here's what's defined for you, thanks to the template:
services.AddDbContext<ApplicationDbContext>(options =>
47
Connect to a database
options.UseSqlite(Configuration.GetConnectionString("DefaultCo
nnection")));
As you can see, dotnet new creates a lot of stuff for you! The database
is set up and ready to be used. However, it doesn't have any tables for
storing to-do items. In order to store your TodoItem entities, you'll need
to update the context and migrate the database.
48
Update the context
Data/ApplicationDbContext.cs
public ApplicationDbContext(DbContextOptions<ApplicationDbContext>
options)
: base(options)
{
}
// ...
49
Update the context
Core that you want to store TodoItem entities in a table called Items .
You've updated the context class, but now there's one small problem:
the context and database are now out of sync, because there isn't
actually an Items table in the database. (Just updating the code of the
context class doesn't change the database itself.)
In order to update the database to reflect the change you just made to
the context, you need to create a migration.
50
Create a migration
Create a migration
Migrations keep track of changes to the database structure over time.
They make it possible to undo (roll back) a set of changes, or create a
second database with the same structure as the first. With migrations,
you have a full history of modifications like adding or removing columns
(and entire tables).
In the previous chapter, you added an Items set to the context. Since
the context now includes a set (or table) that doesn't exist in the
database, you need to create a migration to update the database:
commands must be run from the project root directory (where the
Program.cs file is).
51
Create a migration
If you open your migration file, you'll see two methods called Up and
Down :
Data/Migrations/<date>_AddItems.cs
migrationBuilder.CreateTable(
name: "Items",
columns: table => new
{
Id = table.Column<Guid>(type: "BLOB", nullable: false)
,
DueAt = table.Column<DateTimeOffset>(type: "TEXT", nul
lable: true),
IsDone = table.Column<bool>(type: "INTEGER", nullable:
false),
Title = table.Column<string>(type: "TEXT", nullable: t
rue)
},
constraints: table =>
{
table.PrimaryKey("PK_Items", x => x.Id);
});
// (some code...)
}
migrationBuilder.DropTable(
52
Create a migration
name: "Items");
// (some code...)
}
The Up method runs when you apply the migration to the database.
Since you added a DbSet<TodoItem> to the database context, Entity
Framework Core will create an Items table (with columns that match a
TodoItem ) when you apply the migration.
The Down method does the opposite: if you need to undo (roll back) the
migration, the Items table will be dropped.
If you use a full-fledged SQL database, like SQL Server or MySQL, this
won't be an issue and you won't need to do this (admittedly hackish)
workaround.
53
Create a migration
This command will cause Entity Framework Core to create the Items
table in the database.
If you want to roll back the database, you can provide the name of
the previous migration: dotnet ef database update
CreateIdentitySchema This will run the Down methods of any
If you need to completely erase the database and start over, run
dotnet ef database drop followed by dotnet ef database update
That's it! Both the database and the context are ready to go. Next, you'll
use the context in your service layer.
54
Create a new service class
Services/TodoItemService.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AspNetCoreTodo.Data;
using AspNetCoreTodo.Models;
using Microsoft.EntityFrameworkCore;
namespace AspNetCoreTodo.Services
{
public class TodoItemService : ITodoItemService
{
private readonly ApplicationDbContext _context;
return items;
}
}
55
Create a new service class
You'll notice the same dependency injection pattern here that you saw in
the MVC basics chapter, except this time it's the ApplicationDbContext
that gets injected into the service. The ApplicationDbContext is already
being added to the service container in the ConfigureServices method,
so it's available for injection here.
Then, the Where method is used to filter only the items that are not
complete:
NoSQL database.
Finally, the ToArrayAsync method tells Entity Framework Core to get all
the entities that matched the filter and return them as an array. The
ToArrayAsync method is asynchronous (it returns a Task ), so it must
To make the method a little shorter, you can remove the intermediate
items variable and just return the result of the query directly (which
56
Create a new service class
services.AddScoped<ITodoItemService, TodoItemService>();
AddScoped adds your service to the service container using the scoped
Test it out
57
Create a new service class
In the next chapter, you'll add more features to the application, starting
with the ability to create new to-do items.
58
Add more features
59
Add new to-do items
$(document).ready(function() {
// Wire up the Add button to send the new item to the server
$('#add-item-button').on('click', addItem);
});
60
Add new to-do items
function addItem() {
$('#add-item-error').hide();
var newTitle = $('#add-item-title').val();
Add an action
The above JavaScript code won't work yet, because there isn't any
action that can handle the /Todo/AddItem route. If you try it now,
ASP.NET Core will return a 404 Not Found error.
61
Add new to-do items
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
return Ok();
}
Models/NewTodoItem.cs
using System;
using System.ComponentModel.DataAnnotations;
namespace AspNetCoreTodo.Models
{
public class NewTodoItem
{
[Required]
public string Title { get; set; }
}
}
This model definition (one property called Title ) matches the data
you're sending to the action with jQuery:
62
Add new to-do items
After binding the request data to the model, ASP.NET Core also
performs model validation. The [Required] attribute on the Title
property informs the validator that the Title property should not be
missing (blank). The validator won't throw an error if the model fails
validation, but the validation status will be saved so you can check it in
the controller.
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Next, the controller calls into the service layer to do the actual database
operation:
63
Add new to-do items
As a last step, you need to add a method to the service layer. First, add
it to the interface definition in ITodoItemService :
64
Add new to-do items
IsDone = false,
Title = newItem.Title,
DueAt = DateTimeOffset.Now.AddDays(3)
};
_context.Items.Add(entity);
This method creates a new TodoItem (the model that represents the
database entity) and copies the Title from the NewTodoItem model.
Then, it adds it to the context and uses SaveChangesAsync to persist the
entity in the database.
Try it out
Run the application and add some items to your to-do list with the form.
Since the items are being stored in the database, they'll still be there
even after you stop and start the application again.
65
Add new to-do items
66
Complete items with a checkbox
The item's ID (a guid) is saved in the name attribute of the element. You
can use this ID to tell your ASP.NET Core code to update that entity in
the database when the checkbox is checked.
wwwroot/js/site.js
$(document).ready(function() {
// ...
67
Complete items with a checkbox
$('.done-checkbox').on('click', function(e) {
markCompleted(e.target);
});
});
function markCompleted(checkbox) {
checkbox.disabled = true;
If you open the Network Tools in your web browser and click on a
checkbox, you'll see a request like:
POST http://localhost:5000/Todo/MarkDone
Content-Type: application/x-www-form-urlencoded
id=<some guid>
68
Complete items with a checkbox
return Ok();
}
Let's step through each piece of this action method. First, the method
accepts a Guid parameter called id in the method signature. Unlike
the AddItem action, which used a model (the NewTodoItem model) and
model binding/validation, the id parameter is very simple. If the
incoming request includes a parameter called id , ASP.NET Core will
try to parse it as a guid.
There's no ModelState to check for validity, but you can still check to
make sure the guid was valid. If for some reason the id parameter in
the request was missing couldn't be parsed as a guid, it will have a
value of Guid.Empty . If that's the case, the action can return early:
Next, the controller needs to call down into the service to update the
database. This will be handled by a new method called MarkDoneAsync
on the ITodoItemService , which will return true or false depending on if
the update succeeded:
69
Complete items with a checkbox
Services/ITodoItemService.cs
Services/TodoItemService.cs
item.IsDone = true;
This method uses Entity Framework Core and Where to find an entity
by ID in the database. The SingleOrDefaultAsync method will return
either the item (if it exists) or null if the ID was bogus. If it didn't exist,
the code can return early.
70
Complete items with a checkbox
Once you're sure that item isn't null, it's a simple matter of setting the
IsDone property:
item.IsDone = true;
Changing the property only affects the local copy of the item until
SaveChangesAsync is called to persist your changes back to the
Try it out
Run the application and try checking some items off the list. Refresh the
page and they'll disappear completely, because of the Where filter in
the GetIncompleteItemsAsync method.
Right now, the application contains a single, shared to-do list. It'd be
even more useful if it kept track of individual to-do lists for each user. In
the next chapter, you'll use ASP.NET Core Identity to add security and
authentication features to the project.
71
Security and identity
ASP.NET Core can help make all of this easier to implement. The first
two (protection against SQL injection and cross-domain attacks) are
already built-in, and you can add a few lines of code to enable HTTPS
support. This chapter will mainly focus on the identity aspects of
security: handling user accounts (registration, login), authenticating
(logging in) your users securely, and making authorization decisions
once they are authenticated.
72
Security and identity
ASP.NET Core Identity takes care of storing user accounts, hashing and
storing passwords, and managing roles for users. It supports
email/password login, multi-factor authentication, social login with
providers like Google and Facebook, as well as connecting to other
services using protocols like OAuth 2.0 and OpenID Connect.
The Register and Login views that ship with the MVC + Individual Auth
template already take advantage of ASP.NET Core Identity, and they
already work! Try registering for an account and logging in.
73
Add Facebook login
Next, set up Facebook Login and then click Settings on the left side,
under Facebook Login:
74
Add Facebook login
Add the following URL to the Valid OAuth redirect URIs box:
http://localhost:5000/signin-facebook
The port that your application runs on may differ. It's typically port
5000 if you use dotnet start , but if you're on Windows, it could
be a random port like 54574. Either way, you can always see the
port your application is running on in the address bar of your web
browser.
Click Save Changes and then head over to the Dashboard page. Here
you can see the app ID and secret generated by Facebook, which you'll
need in a moment (keep this tab open).
services
75
Add Facebook login
.AddAuthentication()
.AddFacebook(options =>
{
options.AppId = Configuration["Facebook:AppId"];
options.AppSecret = Configuration["Facebook:AppSecret"];
});
Instead of hardcoding the Facebook app ID and secret in your code, the
values are pulled from the configuration system. The appsettings.json
file is normally the place to store configuration data for your project.
However, since it's checked into source control, it's not good for
sensitive data like an app secret. (If your app secret was pushed to
GitHub, for example, anyone could steal it and do bad things on your
behalf.)
Copy the app ID and secret from the Facebook app dashboard and use
the set command to save the values in the Secrets Manager:
The values from the Secrets Manager are loaded into the
Configuration property when your application starts up, so they're
Run your application and click the Login link in the navbar. You'll see a
new button for logging in with Facebook:
76
Add Facebook login
77
Require authentication
Require authentication
Often you'll want to require the user to log in before they can access
certain parts of your application. For example, it makes sense to show
the home page to everyone (whether you're logged in or not), but only
show your to-do list after you've logged in.
[Authorize]
public class TodoController : Controller
{
// ...
}
using Microsoft.AspNetCore.Authorization;
Try running the application and accessing /todo without being logged
in. You'll be redirected to the login page automatically.
78
Require authentication
79
Using identity in the application
do view, you can filter the database query based on who is logged in.
Controllers/TodoController.cs
[Authorize]
public class TodoController : Controller
{
private readonly ITodoItemService _todoItemService;
private readonly UserManager<ApplicationUser> _userManager;
// ...
}
using Microsoft.AspNetCore.Identity;
The UserManager class is part of ASP.NET Core Identity. You can use it
to look up the current user in the Index action:
80
Using identity in the application
return View(model);
}
The new code at the top of the action method uses the UserManager to
get the current user from the User property available in the action:
idea to do a sanity check, just in case. You can use the Challenge()
method to force the user to log in again if their information is missing:
interface:
Services/ITodoItemService.cs
81
Using identity in the application
// ...
}
The next step is to update the database query and show only items
owned by the current user.
Since you updated the entity model used by the database context, you
also need to migrate the database. Create a new migration using dotnet
ef in the terminal:
This creates a new migration called AddItemOwner which will add a new
column to the Items table, mirroring the change you made to the
TodoItem entity model.
82
Using identity in the application
Services/TodoItemService.cs
If you run the application and register or log in, you'll see an empty to-do
list once again. Unfortunately, any items you try to add disappear into
the ether, because you haven't updated the Add Item operation to save
the current user to new items.
83
Using identity in the application
return Ok();
}
return Ok();
}
84
Using identity in the application
For the AddItemAsync method, set the Owner property when you
construct a new TodoItem :
// ...
}
// ...
}
All done! Try using the application with two different user accounts. The
to-do items stay private for each account.
85
Using identity in the application
86
Authorization with roles
Controllers/ManageUsersController.cs
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using AspNetCoreTodo.Models;
using Microsoft.EntityFrameworkCore;
namespace AspNetCoreTodo.Controllers
{
[Authorize(Roles = "Administrator")]
public class ManageUsersController : Controller
{
private readonly UserManager<ApplicationUser> _userManager
;
public ManageUsersController(UserManager<ApplicationUser>
userManager)
{
_userManager = userManager;
}
87
Authorization with roles
{
var admins = await _userManager
.GetUsersInRoleAsync("Administrator");
return View(model);
}
}
}
Models/ManageUsersViewModel.cs
using System.Collections.Generic;
using AspNetCoreTodo.Models;
namespace AspNetCoreTodo
{
public class ManageUsersViewModel
{
public IEnumerable<ApplicationUser> Administrators { get;
set; }
88
Authorization with roles
Views/ManageUsers/Index.cshtml
@model ManageUsersViewModel
@{
ViewData["Title"] = "Manage users";
}
<h2>@ViewData["Title"]</h2>
<h3>Administrators</h3>
<table class="table">
<thead>
<tr>
<td>Id</td>
<td>Email</td>
</tr>
</thead>
<h3>Everyone</h3>
<table class="table">
<thead>
<tr>
<td>Id</td>
<td>Email</td>
</tr>
</thead>
89
Authorization with roles
</tr>
}
</table>
Start up the application and try to access the /ManageUsers route while
logged in as a normal user. You'll see this access denied page:
Startup.cs
if (env.IsDevelopment())
{
// (... some code)
90
Authorization with roles
Add the two new methods below the Configure method. First, the
EnsureRolesAsync method:
if (alreadyExists) return;
value:
91
Authorization with roles
Constants.cs
namespace AspNetCoreTodo
{
public static class Constants
{
public const string AdministratorRole = "Administrator";
}
}
Startup.cs
92
Authorization with roles
sure they finish before Configure moves on. You'd normally use
await for this, but for technical reasons you can't use await in
everywhere else!
When you start the application next, the [email protected] account will
be created and assigned the Administrator role. Try logging in with this
account, and navigating to http://localhost:5000/ManageUsers . You'll
see a list of all users registered for the application.
You can inject the UserManager directly into a view to do these types of
authorization checks. To keep your views clean and organized, create a
new partial view that will add an item to the navbar in the layout:
Views/Shared/_AdminActionsPartial.cshtml
@using Microsoft.AspNetCore.Identity
@using AspNetCoreTodo.Models
@if (SignInManager.IsSignedIn(User))
93
Authorization with roles
{
var currentUser = await UserManager.GetUserAsync(User);
if (isAdmin) {
<ul class="nav navbar-nav navbar-right">
<li><a asp-controller="ManageUsers" asp-action="Index">
Manage Users</a></li>
</ul>
}
}
To include this partial in the main layout, edit _Layout.cshtml and add it
in the navbar section:
Views/Shared/_Layout.cshtml
94
Authorization with roles
@await Html.PartialAsync("_LoginPartial")
@await Html.PartialAsync("_AdminActionsPartial")
</div>
When you log in with an administrator account, you'll now see a new
item on the top right:
Wrap up
ASP.NET Core Identity is a powerful security and identity system that
helps you add authentication and authorization checks, and makes it
easy to integrate with external identity providers. The dotnet new
templates give you pre-built views and controllers that handle common
scenarios like login and registration so you can get up and running
quickly.
There's much more that ASP.NET Core Identity can do. You can learn
more in the documentation and examples available at
https://docs.asp.net.
95
Automated testing
Automated testing
Writing tests is an important part of building any application. Testing
your code helps you spot and avoid bugs, and makes it easier to
refactor your code later without breaking functionality or introducing new
problems.
In this chapter you'll learn how to write both unit tests and integration
tests that exercise your ASP.NET Core application. Unit tests are small
tests that make sure a single method or a few lines of code are working
properly. Integration tests (sometimes called functional tests) are larger
tests that simulate real-world scenarios and exercise multiple layers or
parts of your application.
96
Unit testing
Unit testing
Unit tests are small, quick tests that check the behavior of a single
method or chunk of logic. Instead of testing a whole group of classes, or
the entire system (as integration tests do), unit tests rely on mocking or
replacing the objects the method-under-test depends on.
When you write a unit test, on the other hand, you'll manually inject
mock or test-only versions of those dependencies. This means you can
isolate just the logic in the class or method you are testing. (If you're
testing a service, you don't want to also be accidentally writing to your
database!)
97
Unit testing
mkdir AspNetCoreTodo.UnitTests
cd AspNetCoreTodo.UnitTests
dotnet new xunit
xUnit.NET is a popular test framework for .NET code that can be used to
write both unit and integration tests. Like everything else, it's a set of
NuGet packages that can be installed in any project. The dotnet new
xunit template already includes everything you need.
AspNetCoreTodo/
AspNetCoreTodo/
AspNetCoreTodo.csproj
Controllers/
(etc...)
AspNetCoreTodo.UnitTests/
AspNetCoreTodo.UnitTests.csproj
Since the test project will use the classes defined in your main project,
you'll need to add a reference to the main project:
98
Unit testing
_context.Items.Add(entity);
These decisions make sense, and it also makes sense to have a test
that ensures that this logic doesn't change down the road. (Imagine if
you or someone else refactored the AddItemAsync method and forgot
about one of these assumptions. It might be unlikely when your services
are simple, but it becomes important to have automated checks as your
application becomes more complicated.)
99
Unit testing
To write a unit test that will verify the logic in the TodoItemService ,
create a new class in your test project:
AspNetCoreTodo.UnitTests/TodoItemServiceShould.cs
using System;
using System.Threading.Tasks;
using AspNetCoreTodo.Data;
using AspNetCoreTodo.Models;
using AspNetCoreTodo.Services;
using Microsoft.EntityFrameworkCore;
using Xunit;
namespace AspNetCoreTodo.UnitTests
{
public class TodoItemServiceShould
{
[Fact]
public async Task AddNewItem()
{
// ...
}
}
}
The [Fact] attribute comes from the xUnit.NET package, and it marks
this method as a test method.
There are many different ways of naming and organizing tests, all
with different pros and cons. I like postfixing my test classes with
Should to create a readable sentence with the test method
100
Unit testing
database exists in memory, it's wiped out every time the test is
restarted. And, since it's a proper Entity Framework Core provider, the
TodoItemService won't know the difference!
The last line creates a new to-do item called Testing? , and tells the
service to save it to the (in-memory) database. To verify that the
business logic ran correctly, retrieve the item:
101
Unit testing
The first verification step is a sanity check: there should never be more
than one item saved to the in-memory database. Assuming that's true,
the test retrieves the saved item with FirstAsync and then asserts that
the properties are set to the expected values.
Both unit and integration tests typically follow the AAA (Arrange-
Act-Assert) pattern: objects and data are set up first, then some
action is performed, and finally the test checks (asserts) that the
expected behavior occurred.
AspNetCoreTodo.UnitTests/TodoItemServiceShould.cs
102
Unit testing
dotnet test
The test command scans the current project for tests (marked with
[Fact] attributes in this case), and runs all the tests it finds. You'll see
103
Unit testing
You now have one test providing test coverage of the TodoItemService .
As an extra-credit challenge, try writing unit tests that ensure:
particular user
104
Integration testing
Integration testing
Compared to unit tests, integration tests exercise the whole application
stack (routing, controllers, services, database). Instead of isolating one
class or component, integration tests ensure that all of the components
of your application are working together properly.
Integration tests are slower and more involved than unit tests, so it's
common for a project to have lots of unit tests but only a handful of
integration tests.
To write integration tests that make HTTP requests, you could manually
start your application run tests that make requests to
http://localhost:5000 (and hope the app is still running). ASP.NET
Core provides a nicer way to host your application for testing, however:
using the TestServer class. TestServer can host your application for
the duration of the test, and then stop it automatically when the test is
complete.
project:
105
Integration testing
mkdir AspNetCoreTodo.IntegrationTests
cd AspNetCoreTodo.IntegrationTests
dotnet new xunit
AspNetCoreTodo/
AspNetCoreTodo/
AspNetCoreTodo.csproj
Controllers/
(etc...)
AspNetCoreTodo.UnitTests/
AspNetCoreTodo.UnitTests.csproj
AspNetCoreTodo.IntegrationTests/
AspNetCoreTodo.IntegrationTests.csproj
Since the test project will use the classes defined in your main project,
you'll need to add a reference to the main project:
106
Integration testing
There are a few things that need to be configured on the test server
before each test. Instead of cluttering the test with this setup code, you
can factor out this setup to a separate class. Create a new class called
TestFixture :
AspNetCoreTodo.IntegrationTests/TestFixture.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Configuration;
namespace AspNetCoreTodo.IntegrationTests
{
public class TestFixture : IDisposable
{
private readonly TestServer _server;
public TestFixture()
{
var builder = new WebHostBuilder()
.UseStartup<AspNetCoreTodo.Startup>()
.ConfigureAppConfiguration((context, configBuilder
) =>
{
configBuilder.SetBasePath(Path.Combine(
Directory.GetCurrentDirectory(), "..\\..\\
..\\..\\AspNetCoreTodo"));
configBuilder.AddJsonFile("appsettings.json");
107
Integration testing
Client = _server.CreateClient();
Client.BaseAddress = new Uri("http://localhost:5000");
}
This class takes care of setting up a TestServer , and will help keep the
tests themselves clean and tidy.
Now you're (really) ready to write an integration test. Create a new class
called TodoRouteShould :
AspNetCoreTodo.IntegrationTests/TodoRouteShould.cs
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Xunit;
namespace AspNetCoreTodo.IntegrationTests
{
public class TodoRouteShould : IClassFixture<TestFixture>
{
108
Integration testing
[Fact]
public async Task ChallengeAnonymousUser()
{
// Arrange
var request = new HttpRequestMessage(HttpMethod.Get, "
/todo");
Run the test in the terminal with dotnet test . If everything's working
right, you'll see a success message:
109
Integration testing
Wrap up
Testing is a broad topic, and there's much more to learn. This chapter
doesn't touch on UI testing or testing frontend (JavaScript) code, which
probably deserve entire books of their own. You should, however, have
the skills and base knowledge you need to practice and learn more
about writing tests for your own applications.
110
Deploy the application
Deployment options
ASP.NET Core applications are typically deployed to one of these
environments:
Linux (with Nginx). If you don't want to go the Docker route, you
can still host your application on any Linux server (this includes
Amazon EC2 and DigitalOcean virtual machines). It's typical to pair
ASP.NET Core with the Nginx reverse proxy. (More about Nginx
below.)
111
Deploy the application
Windows. You can use the IIS web server on Windows to host
ASP.NET Core applications. It's usually easier (and cheaper) to just
deploy to Azure, but if you prefer managing Windows servers
yourself, it'll work just fine.
On Linux (and in Docker containers), you can use Nginx or the Apache
web server to receive incoming requests from the internet and route
them to your application hosted with Kestrel. If you're on Windows, IIS
does the same thing.
If you're using Azure to host your application, this is all taken care of for
you automatically. I'll cover setting up Nginx as a reverse proxy in the
Docker section.
112
Deploy to Azure
Deploy to Azure
Deploying your ASP.NET Core application to Azure only takes a few
steps. You can do it through the Azure web portal, or on the command
line using the Azure CLI. I'll cover the latter.
.deployment
[config]
project = AspNetCoreTodo/AspNetCoreTodo.csproj
Make sure you save the file as .deployment with no other parts to the
name. (On Windows, you may need to put quotes around the filename,
like ".deployment" , to prevent a .txt extension from being added.)
113
Deploy to Azure
.deployment
AspNetCoreTodo
AspNetCoreTodo.IntegrationTests
AspNetCoreTodo.UnitTests
az login
and follow the prompts to log in on your machine. Then, create a new
Resource Group for this application:
Next, create an App Service plan in the group you just created:
114
Deploy to Azure
git init
git add .
git commit -m "First commit!"
115
Deploy to Azure
https://[email protected]/MyTodoApp.git
Copy the URL to the clipboard, and use it to add a Git remote to your
local repository:
You only need to do these steps once. Now, whenever you want to push
your application files to Azure, check them in with Git and run
116
Deploy with Docker
Docker can make scaling your app across multiple servers easier, too.
Once you have an image, using it to create 1 container is the same
process as creating 100 containers.
Before you start, you need the Docker CLI installed on your
development machine. Search for "get docker for (mac/windows/linux)"
and follow the instructions on the official Docker website. You can verify
that it's installed correctly with
docker --version
Add a Dockerfile
The first thing you'll need is a Dockerfile, which is like a recipe that tells
Docker what your application needs.
117
Deploy with Docker
FROM microsoft/dotnet:latest
This tells Docker to start your image from an existing image that
Microsoft publishes. This will make sure the container has everything it
needs to run an ASP.NET Core app.
COPY . /app
The COPY command copies the contents of your local directory (the
source code of your application) into a directory called /app in the
Docker image.
WORKDIR /app
commands in the Dockerfile will run from inside the /app folder.
EXPOSE 5000/tcp
118
Deploy with Docker
The last line of the Dockerfile starts up your application with the dotnet
run command. Kestrel will start listening on port 5000, just like it does
Dockerfile
FROM microsoft/dotnet:latest
COPY . /app
WORKDIR /app
RUN ["dotnet", "restore"]
RUN ["dotnet", "build"]
EXPOSE 5000/tcp
ENV ASPNETCORE_URLS http://*:5000
ENTRYPOINT ["dotnet", "run"]
Create an image
Make sure the Dockerfile is saved, and then use docker build to create
an image:
119
Deploy with Docker
Don't miss the trailing period! That tells Docker to look for a Dockerfile in
the current directory.
Once the image is created, you can run docker images to to list all the
images available on your local machine. To test it out in a container, run
The -it flag tells Docker to run the container in interactive mode.
When you want to stop the container, press Control-C .
Set up Nginx
At the beginning of this chapter, I mentioned that you should use a
reverse proxy like Nginx to proxy requests to Kestrel. You can use
Docker for this, too.
The Nginx container needs its own Dockerfile. To keep it from colliding
with the Dockerfile you just created, make a new directory in the web
application root:
mkdir nginx
nginx/Dockerfile
FROM nginx
COPY nginx.conf /etc/nginx/nginx.conf
120
Deploy with Docker
nginx/nginx.conf
http {
server {
listen 80;
location / {
proxy_pass http://kestrel:5000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'keep-alive';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
}
moment.)
docker-compose.yml
nginx:
build: ./nginx
links:
- kestrel:kestrel
ports:
- "80:80"
kestrel:
build: .
ports:
121
Deploy with Docker
- "5000"
Docker Compose is a tool that helps you create and run multi-container
applications. This configuration file defines two containers: nginx from
the ./nginx/Dockerfile recipe, and kestrel from the ./Dockerfile
recipe. The containers are explicitly linked together so they can
communicate.
docker-compose up
122
Conclusion
Conclusion
Thanks for making it to the end of the Little ASP.NET Core Book!
There's a lot more to ASP.NET Core can do that couldn't fit in this short
book, including
The official ASP.NET Core documentation also covers these topics, and
more: https://docs.asp.net
Happy coding!
Special thanks
To Jennifer, who always supports my crazy ideas.
123
Conclusion
0xNF
sahinyanlik (Turkish)
windsting, yuyi (Simplified Chinese)
Changelog
1.0.3 (2017-11-13): Typo fixes and small improvements suggested by
readers.
1.0.2 (2017-10-20): More bug fixes and small improvements. Added link
to translations.
124