KEMBAR78
ASP.NET Core in Action (2018).pdf
M A N N I N G
Andrew Lock
Useful .NET CLI (dotnet) commands.
Use --help to see all the optional arguments
Command Description Run from
dotnet restore Restores the NuGet packages for all the
projects in the solution.
Solution folder
dotnet build Builds all the projects in the solution. To
build in Release mode, use the -c
switch.
Solution folder
dotnet run Runs the project in the current folder. Use
during development to run your app.
Project folder
dotnet publish -c Release –o
<Folder>
Publishes the project to the provided
folder. This copies all the required files to
the provided output folder so that it can be
deployed.
Project folder
dotnet test Builds the project and executes any unit
tests found in the project. Requires the
.NET Test SDK and a testing framework
adapter (see chapter 20).
Project folder
dotnet add package <Name> Installs the NuGet package with the pro-
vided name into the current project.
Optionally, specify a package version, for
example -v 2.1.0.
Project folder
dotnet new --list Views all the installed templates for creat-
ing ASP.NET Core apps, libraries, test proj-
ects, solution files, and much more.
Anywhere
dotnet new --install
<PackageName>
Installs a new template from the provided
NuGet package name. For example,
dotnet new --install
"NetEscapades.Templates::*".
Anywhere
dotnet new <Template> –o <Folder>
-n <NewName>
Creates a new item from the provided tem-
plate, specifying the folder in which to
place the item, and the name for the item.
Anywhere, empty
folder for new
projects
dotnet ef --help Displays help for managing your database
and migrations using EF Core. Requires
the EF Core tools (see chapter 12).
Project folder
dotnet ef migrations add <Name> Adds a new EF Core migration to the proj-
ect with the provided name.
Project folder
dotnet ef database update Applies pending EF Core migrations to a
database. Warning—this will modify your
database!
Project folder
ASP.NET Core in Action
ASP.NET
Core in Action
ANDREW LOCK
M A N N I N G
SHELTER ISLAND
For online information and ordering of this and other Manning books, please visit
www.manning.com. The publisher offers discounts on this book when ordered in quantity.
For more information, please contact
Special Sales Department
Manning Publications Co.
20 Baldwin Road
PO Box 761
Shelter Island, NY 11964
Email: orders@manning.com
©2018 by Manning Publications Co. All rights reserved.
No part of this publication may be reproduced, stored in a retrieval system, or transmitted, in
any form or by means electronic, mechanical, photocopying, or otherwise, without prior written
permission of the publisher.
Many of the designations used by manufacturers and sellers to distinguish their products are
claimed as trademarks. Where those designations appear in the book, and Manning
Publications was aware of a trademark claim, the designations have been printed in initial caps
or all caps.
Recognizing the importance of preserving what has been written, it is Manning’s policy to have
the books we publish printed on acid-free paper, and we exert our best efforts to that end.
Recognizing also our responsibility to conserve the resources of our planet, Manning books
are printed on paper that is at least 15 percent recycled and processed without the use of
elemental chlorine.
Manning Publications Co. Development editor: Marina Michaels
20 Baldwin Road Technical development editor: Kris Athi
PO Box 761 Review editor: Ivan Martinović
Shelter Island, NY 11964 Project manager: David Novak
Copyeditor: Safis Editing
Proofreader: Elizabeth Martin
Technical proofreader: Tanya Wilke
Typesetter: Dottie Marsico
Cover designer: Marija Tudor
ISBN 9781617294617
Printed in the United States of America
1 2 3 4 5 6 7 8 9 10 – EBM – 23 22 21 20 19 18
v
brief contents
PART 1 GETTING STARTED WITH MVC..........................................1
1 ■ Getting started with ASP.NET Core 3
2 ■ Your first application 28
3 ■ Handling requests with the middleware pipeline 61
4 ■ Creating web pages with MVC controllers 93
5 ■ Mapping URLs to methods using conventional routing 120
6 ■ The binding model: retrieving and validating user input 148
7 ■ Rendering HTML using Razor views 174
8 ■ Building forms with Tag Helpers 204
9 ■ Creating a Web API for mobile and client applications
using MVC 234
PART 2 BUILDING COMPLETE APPLICATIONS.............................. 265
10 ■ Service configuration with dependency injection 267
11 ■ Configuring an ASP.NET Core application 303
12 ■ Saving data with Entity Framework Core 334
13 ■ The MVC filter pipeline 369
14 ■ Authentication: adding users to your application with
Identity 400
15 ■ Authorization: securing your application 432
16 ■ Publishing and deploying your application 461
BRIEF CONTENTS
vi
PART 3 EXTENDING YOUR APPLICATIONS...................................499
17 ■ Monitoring and troubleshooting errors with logging 501
18 ■ Improving your application’s security 534
19 ■ Building custom components 572
20 ■ Testing your application 607
vii
contents
preface xvii
acknowledgments xix
about this book xxi
about the author xxv
about the cover illustration xxvi
PART 1 GETTING STARTED WITH MVC...........................1
1 Getting started with ASP.NET Core 3
1.1 An introduction to ASP.NET Core 4
Using a web framework 4 ■
The benefits and limitations of
ASP.NET 5 ■
What is ASP.NET Core? 6
1.2 When to choose ASP.NET Core 8
What type of applications can you build? 8 ■
If you’re new to .NET
development 10 ■
If you’re a .NET Framework developer creating a
new application 12 ■
Converting an existing ASP.NET
application to ASP.NET Core 16
1.3 How does ASP.NET Core work? 17
How does an HTTP web request work? 17 ■
How does ASP.NET
Core process a request? 19
1.4 Choosing a platform for ASP.NET Core 21
Advantages of using .NET Framework 21 ■
Advantages of using
.NET Core 22
CONTENTS
viii
1.5 Preparing your development environment 23
If you’re a Windows user 24 ■
If you’re a Linux or
macOS user 25
2 Your first application 28
2.1 A brief overview of an ASP.NET Core application 29
2.2 Creating your first ASP.NET Core application 32
Using a template to get started 32 ■
Building the application 35
2.3 Running the web application 36
2.4 Understanding the project layout 38
2.5 The csproj project file: defining your dependencies 40
2.6 The Program class: building a web host 41
2.7 The Startup class: configuring your application 44
Adding and configuring services 45 ■
Defining how
requests are handled with middleware 47
2.8 MVC middleware and the home controller 52
2.9 Generating HTML with Razor template views 54
3 Handling requests with the middleware pipeline 61
3.1 What is middleware? 63
3.2 Combining middleware in a pipeline 66
Simple pipeline scenario 1: a holding page 67 ■
Simple pipeline
scenario 2: Handling static files 70 ■
Simple pipeline scenario 3:
An MVC web application 73
3.3 Handling errors using middleware 78
Viewing exceptions in development:
DeveloperExceptionPage 80 ■
Handling exceptions
in production: ExceptionHandlerMiddleware 82
Handling other errors: StatusCodePagesMiddleware 86
Disabling error handling middleware for Web APIs 90
4 Creating web pages with MVC controllers 93
4.1 An introduction to MVC 95
The MVC design pattern 95 ■
MVC in ASP.NET Core 98
Adding the MvcMiddleware to your application 104 ■
What
makes a controller a controller? 111
4.2 MVC controllers and action methods 113
Accepting parameters to action methods 114 ■
Using
ActionResult 116
CONTENTS ix
5 Mapping URLs to methods using conventional routing 120
5.1 What is routing? 122
5.2 Routing to MVC controllers and actions 125
5.3 Routing using conventions 129
Understanding route templates 131 ■
Using optional and
default values 133 ■
Adding additional constraints to route
parameters 135 ■
Defining default values and constraints
using anonymous objects 138 ■
Matching arbitrary URLs
with the catch-all parameter 139
5.4 Handling multiple matching actions for a route 140
5.5 Generating URLs from route parameters 142
Generating URLs based on an action name 143 ■
Generating
URLs based on a route name 144 ■
Generating URLs with
ActionResults 145
6 The binding model: retrieving and validating
user input 148
6.1 Understanding the M in MVC 149
6.2 From request to model: making the request useful 152
Binding simple types 155 ■
Binding complex types 158
Choosing a binding source 162
6.3 Handling user input with model validation 164
The need for validation 164 ■
Using DataAnnotations
attributes for validation 165 ■
Validating on the server for
safety 168 ■
Validating on the client for user experience 171
7 Rendering HTML using Razor views 174
7.1 Views: rendering the user interface in MVC 175
7.2 Creating Razor views 179
Selecting a view from a controller 181 ■
Introducing Razor
templates 182 ■
Passing data to views 185
7.3 Creating dynamic web pages with Razor 187
Using C# in Razor templates 188 ■
Adding loops and conditionals
to Razor templates 189 ■
Rendering HTML with Raw 191
7.4 Layouts, partial views, and _ViewStart 193
Using layouts for shared markup 194 ■
Overriding parent
layouts using sections 196 ■
Using partial views to encapsulate
markup 198 ■
Running code on every view with _ViewStart
and _ViewImports 200
CONTENTS
x
8 Building forms with Tag Helpers 204
8.1 Catering to editors with Tag Helpers 206
8.2 Creating forms using Tag Helpers 209
The Form Tag Helper 214 ■
The Label Tag Helper 215
The Input and Textarea Tag Helpers 217 ■
The Select Tag
Helper 220 ■
The Validation Message and Validation Summary
Tag Helpers 225
8.3 Generating links with the Anchor Tag Helper 228
8.4 Cache-busting with the Append Version Tag Helper 229
8.5 Using conditional markup with the Environment Tag
Helper 230
9 Creating a Web API for mobile and client applications using
MVC 234
9.1 What is a Web API and when should you use one? 235
9.2 Creating your first Web API Controller 238
9.3 Applying the MVC design pattern to a Web API 242
9.4 Attribute routing: taking fine-grained control of your
URLs 246
Ordering of conventional and attribute routes 249
Combining route attributes to keep your route templates
DRY 251 ■
Using token replacement to reduce duplication
in attribute routing 253 ■
Handling multiple matching actions
with attribute routing 254
9.5 Enabling additional input formatters: binding to XML
data 255
9.6 Generating a response from a model 258
Customizing the default formatters: adding XML support 260
Choosing a response format with content negotiation 262
PART 2 BUILDING COMPLETE APPLICATIONS...............265
10 Service configuration with dependency injection 267
10.1 Introduction to dependency injection 268
Understanding the benefits of dependency injection 269
Creating loosely coupled code 274 ■
Dependency
injection in ASP.NET Core 276
CONTENTS xi
10.2 Using the dependency injection container 278
Adding ASP.NET Core framework services to the container 278
Registering your own services with the container 279
Registering services using objects and lambdas 282
Registering a service in the container multiple times 286
Injecting services into action methods and view templates 289
10.3 Understanding lifetimes: when are services created? 292
Transient: everyone is unique 295 ■
Scoped: let’s stick together,
guys 296 ■
Singleton: there can be only one 297
Keeping an eye out for captured dependencies 298
11 Configuring an ASP.NET Core application 303
11.1 Introducing the ASP.NET Core configuration
model 304
11.2 Configuring your application with
CreateDefaultBuilder 306
11.3 Building a configuration object for your app 308
Adding a configuration provider in Program.cs 310
Using multiple providers to override configuration
values 313 ■
Storing configuration secrets safely 315
Reloading configuration values when they change 318
11.4 Using strongly typed settings with the options
pattern 320
Introducing the IOptions interface 321 ■
Reloading strongly typed
options with IOptionsSnapshot 323 ■
Designing your options
classes for automatic binding 324
11.5 Configuring an application for multiple
environments 326
Identifying the hosting environment 326 ■
Loading
environment-specific configuration files 327 ■
Setting
the hosting environment 329
12 Saving data with Entity Framework Core 334
12.1 Introducing Entity Framework Core 336
What is EF Core? 336 ■
Why use an object-relational
mapper? 337 ■
When should you choose EF Core? 339
Mapping a database to your application code 340
12.2 Adding EF Core to an application 342
Choosing a database provider and installing EF Core 344
Building a data model 345 ■
Registering a data context 348
CONTENTS
xii
12.3 Managing changes with migrations 349
Creating your first migration 350 ■
Adding a second
migration 353
12.4 Querying data from and saving data to the database 355
Creating a record 356 ■
Loading a list of records 358
Loading a single record 360 ■
Updating a model with
changes 361
12.5 Using EF Core in production applications 366
13 The MVC filter pipeline 369
13.1 Understanding filters and when to use them 370
The MVC filter pipeline 372 ■
Filters or middleware: which
should you choose? 373 ■
Creating a simple filter 375
Adding filters to your actions, your controllers, and globally 377
Understanding the order of filter execution 379
13.2 Creating custom filters for your application 381
Authorization filters: protecting your APIs 383 ■
Resource filters:
short-circuiting your action methods 384 ■
Action filters:
customizing model binding and action results 386 ■
Exception
filters: custom exception handling for your action methods 391
Result filters: customizing action results before they execute 392
13.3 Understanding pipeline short-circuiting 394
13.4 Using dependency injection with filter attributes 396
14 Authentication: adding users to your application with
Identity 400
14.1 Introducing authentication and authorization 402
Understanding users and claims in ASP.NET Core 402
Authentication in ASP.NET Core: services and middleware 403
Authentication for APIs and distributed applications 406
14.2 What is ASP.NET Core Identity? 410
14.3 Creating a project that uses ASP.NET Core Identity 412
Creating the project from a template 412 ■
Exploring the
template in Solution Explorer 414 ■
The ASP.NET Core Identity
data model 416 ■
Interacting with ASP.NET Core Identity: the
MVC controllers 418
14.4 Adding ASP.NET Core Identity to an existing project 423
Configuring the ASP.NET Core Identity services and
middleware 424 ■
Updating the EF Core data model to
CONTENTS xiii
support Identity 426 ■
Adding the controllers, view models,
and views 427
14.5 Managing users: adding new claims to users 428
15 Authorization: securing your application 432
15.1 Introduction to authorization 433
15.2 Authorization in ASP.NET Core 436
Preventing anonymous users from accessing your
application 436 ■
Handling unauthorized requests 439
15.3 Using policies for claims-based authorization 440
15.4 Creating custom policies for authorization 443
Requirements and handlers: the building blocks of a policy 444
Creating a policy with a custom requirement and handler 445
15.5 Controlling access with resource-based
authorization 451
Manually authorizing requests with IAuthorizationService 452
Creating a resource-based AuthorizationHandler 454
15.6 Hiding elements in Razor templates
from unauthorized users 456
16 Publishing and deploying your application 461
16.1 Understanding the ASP.NET Core hosting model 463
Running vs. publishing an ASP.NET Core app 465
Choosing a deployment method for your application 468
16.2 Publishing your app to IIS 470
Configuring IIS for ASP.NET Core 470
Preparing and publishing your application to IIS 473
16.3 Hosting an application on Linux 475
Running an ASP.NET Core app behind a reverse proxy on
Linux 476 ■
Preparing your app for deployment to Linux 478
16.4 Configuring the URLs for your application 480
Using an environment variable 480 ■
Using configuration
values 481
16.5 Optimizing your client-side assets
using BundlerMinifier 485
Speeding up an app using bundling and minification 488
Adding BundlerMinifier to your application 490 ■
Using
minified files in production with the environment tag
helper 494 ■
Serving common files from a CDN 495
CONTENTS
xiv
PART 3 EXTENDING YOUR APPLICATIONS ...................499
17 Monitoring and troubleshooting errors with logging 501
17.1 Using logging effectively in a production app 503
Highlighting problems using custom log messages 504
The ASP.NET Core logging abstractions 505
17.2 Adding log messages to your application 507
Log level: how important is the log message? 509 ■
Log category:
which component created the log 511 ■
Formatting messages and
capturing parameter values 512
17.3 Controlling where logs are written using logging
providers 514
Adding a new logging provider to your application 515
Replacing the default ILoggerFactory with Serilog 517
17.4 Changing log verbosity with filtering 521
17.5 Structured logging: creating searchable, useful logs 526
Adding a structured logging provider to your app 527 ■
Using
scopes to add additional properties to your logs 530
18 Improving your application’s security 534
18.1 Adding HTTPS to an application 535
Setting up IIS and IIS Express to use encryption 58
Creating a self-signed certificate for local development 541
Using HTTPS directly in Kestrel 544 ■
Enforcing HTTPS
for your whole app 545
18.2 Defending against cross-site scripting (XSS) attacks 548
18.3 Protecting from cross-site request forgery
(CSRF) attacks 551
18.4 Calling your web APIs from other domains using CORS 556
Understanding CORS and how it works 557 ■
Adding
CORS to your whole app with middleware 559 ■
Adding
CORS to specific MVC actions with EnableCorsAttribute 561
Configuring CORS policies 562
18.5 Exploring other attack vectors 564
Detecting and avoiding open redirect attacks 564 ■
Avoiding
SQL injection attacks with EF Core and parameterization 566
Preventing insecure direct object references 568 ■
Protecting your
users’ passwords and data 568
CONTENTS xv
19 Building custom components 572
19.1 Customizing your middleware pipeline 574
Creating simple endpoints with the Run extension 575
Branching middleware pipelines with the Map extension 576
Adding to the pipeline with the Use extension 578 ■
Building a
custom middleware component 581
19.2 Handling complex configuration requirements 583
Partially building configuration to configure additional
providers 584 ■
Using services to configure IOptions with
IConfigureOptions 586
19.3 Using a third-party dependency injection container 589
19.4 Creating a custom Razor Tag Helper 591
Printing environment information with a custom Tag
Helper 592 ■
Creating a custom Tag Helper to conditionally
hide elements 595
19.5 View components: adding logic to partial views 597
19.6 Building a custom validation attribute 601
20 Testing your application 607
20.1 An introduction to testing in ASP.NET Core 608
20.2 Unit testing with xUnit 610
Creating your first test project 611 ■
Running tests with
dotnet test 613 ■
Referencing your app from your test
project 614 ■
Adding Fact and Theory unit tests 617
Testing failure conditions 620
20.3 Unit testing custom middleware 621
20.4 Unit testing MVC controllers 624
20.5 Integration testing: testing your whole app
with a Test Host 627
Creating a TestServer using the Test Host package 628
Using your application’s Startup file for integration tests 630
Rendering Razor views in TestServer integration tests 632
Extracting common setup into an xUnit test fixture 635
20.6 Isolating the database with an in-memory
EF Core provider 637
appendix A Getting to grips with .NET Core and .NET Standard 644
appendix B Useful references 663
index 669
xvii
preface
ASP.NET has a long history—Microsoft released the first version in 2002 as part of the
original .NET Framework 1.0. Since then, it’s been through multiple iterations, each
version bringing added features and extensibility. But each iteration was built on the
same underlying framework provided by System.Web.dll. This library is a part of the
.NET Framework, and so comes pre-installed in all versions of Windows.
This brings mixed blessings. On the one hand, the ASP.NET 4.X framework is a
reliable, battle-tested platform for building modern applications on Windows. On the
other hand, it’s also limited by this reliance—changes to the underlying System
.Web.dll are far-reaching and, consequently, slow to roll out. It also fundamentally
excludes the many developers building on and deploying to Linux or macOS.
When I first began looking into ASP.NET Core, I was one of those developers. A
Windows user at heart, I was issued with a Mac by my employer, and so was stuck work-
ing on a virtual machine all day. ASP.NET Core promised to change all that, allowing
me to develop natively on both my Windows machine and Mac.
I was relatively late to the party in many respects, only taking an active interest just
before the time of the RC2 release of ASP.NET Core. By that point, there had already
been eight (!) beta releases, many of which contained significant breaking changes; by
not diving in fully until RC2, I was spared the pain of dodgy tooling and changing APIs.
What I saw really impressed me. ASP.NET Core let developers leverage their exist-
ing knowledge of the .NET framework, and of ASP.NET MVC applications in particu-
lar, while baking-in current best practices like dependency injection, strongly typed
configuration, and logging. On top of that, you could build and deploy cross-plat-
form. I was sold.
PREFACE
xviii
This book came about largely due to my approach to learning about ASP.NET
Core. Rather than simply reading documentation and blog posts, I decided to try
something new and start writing about what I learned. Each week, I would dedicate
some time to exploring a new aspect of ASP.NET Core, and I’d write a blog post about
it. When the possibility of writing a book came about, I jumped at the chance—
another excuse to dive further into the framework!
Since I started this book, a lot has changed, both with the book and ASP.NET Core.
The first major release of the framework, in June 2016, still had many rough edges; the
tooling experience in particular. But with the release of ASP.NET Core 2.0 in August
2017, the framework has really come into its own. I think ASP.NET Core 2.0 is a no-
brainer for most people, so the book has evolved from targeting version 1.0 when I first
started, to targeting 2.0 now. I describe the major differences between the versions in
the book where appropriate, but I strongly encourage you to use ASP.NET Core 2.0
when you have the choice.
This book covers everything you need to get started with ASP.NET Core, whether
you’re completely new to web development or an existing ASP.NET developer. It
focuses very much on the framework itself, so I don’t go into details about client-side
frameworks such as Angular and React, or technologies like Docker. Personally, I find
it a joy to work with ASP.NET Core apps compared to apps using the previous version
of ASP.NET, and I hope that passion comes through in this book!
xix
acknowledgments
While there’s only one name on the cover of this book, a plethora of people contrib-
uted to both its writing and production. In this section, I’d like to thank everyone who
has encouraged me, contributed, and put up with me for the past year.
First, and most important, I’d like to thank my girlfriend, Becky. Your continual
support and encouragement means the world to me and has kept me going through
such a busy time. You’ve taken the brunt of my stress and pressure and I’m eternally
grateful. I love you always.
I’d also like to thank my whole family for their support—in particular, my parents,
Jan and Bob, for putting up with my ranting and supplying me with coffee when holed
up working on my visits back home. To my sister, Amanda, thanks for your always
upbeat chats, and to Kathy and Jon, thanks for all the words of encouragement.
On a professional level, I’d like to thank Manning for giving me this opportunity.
Brian Sawyer “discovered” me and encouraged me to submit a proposal for what even-
tually became this book. Dan Maharry initiated the project as my development editor
and helped craft the approach for the book and its target audience. Those first few
months and rewrites were hard work, but the book is better for it. Dan passed the
baton to Marina Michaels, who served as development editor for the remainder of the
book. With a keen eye and daunting efficiency, Marina was instrumental in getting
this book to the finish line.
Kris Athi read through numerous drafts and provided invaluable feedback. Tanya
Wilke was the technical proofer, coming on board at the last minute with a grueling
schedule to meet.
To everyone at Manning who helped get this book published, heartfelt thanks.
Thanks to all of the MEAP reviewers for their comments, which helped improve the
ACKNOWLEDGMENTS
xx
book in innumerable ways: Adam Bell, Andy Kirsch, Björn Nordblom, Emanuele
Origgi, George Onofrei, Jason Pike, Joel Clermont, Jonathan Wood, Jose Diaz, Ken
Muse, Ludvig Gislason, Mark Elston, Mark Harris, Matthew Groves, Nicolas Paez,
Pasquale Zirpoli, Richard Michaels, Ronald E Lease, and Wayne Mather. A special
thanks to Dave Harney for his meticulous comments in the forums.
I would have never been in the position to write this book if it weren’t for the
excellent content produced by the .NET community and those I follow on Twitter. In
particular, thanks to Jon Galloway for regularly featuring my blog on the ASP.NET
community standup.
David Pine and Mike Brind were instrumental in my achieving the Microsoft Val-
ued Professional (MVP). MVPs themselves, I thank them both for their hard work in
the community.
Thanks to Chris Hayford for giving me my first ASP.NET job while I was still finish-
ing my PhD and for giving me my first chance to explore ASP.NET Core.
Finally, thanks to Mick Delany for the chance to really put ASP.NET Core through
its paces, and for giving me the time to work on this book.
xxi
about this book
This book is about the ASP.NET Core framework, what it is, and how you can use it to
build web applications. While some of this content is already available online, it’s scat-
tered around the internet in disparate documents and blog posts. This book guides
you through building your first applications, introducing additional complexity as you
cement previous concepts.
I present each topic using relatively short examples, rather than building on a sin-
gle example application throughout the book. There are merits to both approaches,
but I wanted to ensure the focus remained on the specific topics being taught, without
the mental overhead of navigating an increasingly large project.
By the end of the book, you should have a solid understanding of how to build
apps with ASP.NET Core, its strengths and weaknesses, and how to leverage its fea-
tures to build apps securely. While I don’t spend a lot of time on application architec-
ture, I do make sure to point out best practices, especially where I only superficially
cover architecture in the name of brevity.
Who should read this book
This book is for C# developers who are interested in learning a cross-platform web
framework. It doesn’t assume you have any experience building web applications; you
may be a mobile or desktop developer, for example, though previous experience with
ASP.NET or another web framework is undoubtedly beneficial.
Other than a working knowledge of C# and .NET, I assume some knowledge of
common object-oriented practices and a basic understanding of relational databases
in general. To avoid the scope of the book ballooning, I assume a passing familiarity
with HTML and CSS, and of JavaScript’s place as a client-side scripting language. You
ABOUT THIS BOOK
xxii
don’t need to know any JavaScript or CSS frameworks for this book, though ASP.NET
Core works well with both if that is your forte.
Web frameworks naturally touch on a wide range of topics, from the database and
network, to visual design and client-side scripting. I provide as much context as possi-
ble, but where the subject matter is too great, I include links to sites and books where
you can learn more.
How this book is organized
This book is divided into three parts, twenty chapters, and two appendices. Ideally,
you’ll read the book cover to cover and then use it as a reference, but I realize that
won’t suit everyone. While I use small sample apps to demonstrate a topic, some chap-
ters build on the work of previous ones, so the content will make more sense when
read sequentially.
I strongly suggest reading the chapters in part 1 in sequence, as each chapter
builds on topics introduced in the previous chapters. Part 2 is best read sequentially,
though most of the chapters are independent if you wish to jump around. You can
read the chapters in part 3 out of order, though I recommend only doing so after
you’ve covered parts 1 and 2.
Part 1 provides a general introduction to ASP.NET Core and the overall architec-
ture of a web application built using the framework. Once we have covered the basics,
we dive into the Model-View-Controller (MVC) architecture, which makes up the bulk
of most ASP.NET Core web applications.
 Chapter 1 introduces ASP.NET Core and its place in the web development land-
scape. It discusses when you should and shouldn’t use ASP.NET Core, the basics
of web requests in ASP.NET Core, and the options available for a development
environment.
 Chapter 2 walks through all the components of a basic ASP.NET Core applica-
tion, discussing their roles and how they combine to generate a response to a
web request.
 Chapter 3 describes the middleware pipeline, the main application pipeline in
ASP.NET Core. It defines how incoming requests are processed and how a
response should be generated.
 Chapter 4 gives an overview of the MVC middleware component. This large
component is responsible for managing the pages and APIs in your application,
and for generating responses to web requests.
 Chapter 5 describes the MVC routing system. Routing is the process of mapping
incoming request URLs to a specific class and method, which executes to gener-
ate a response.
 Chapter 6 looks at model binding, the process of mapping form data and URL
parameters passed in a request to concrete C# objects.
ABOUT THIS BOOK xxiii
 Chapter 7 shows how to generate HTML web pages using the Razor template
language.
 Chapter 8 builds on chapter 7 by introducing Tag Helpers, which can greatly
reduce the amount of code required to build forms and web pages.
 Chapter 9 describes how to use the MVC middleware to build APIs that can be
called by client-side apps.
Part 2 covers important topics related to building fully featured web applications,
once you’ve understood the basics.
 Chapter 10 describes how to use ASP.NET Core’s built-in dependency injection
container to configure your application’s services.
 Chapter 11 discusses how to read settings and secrets in ASP.NET Core, and
how to map them to strongly typed objects.
 Chapter 12 introduces Entity Framework Core for saving data into a relational
database.
 Chapter 13 builds on the topics in part 1 by introducing the MVC filter pipeline.
 Chapter 14 describes how to add user profiles and authentication to your appli-
cation using ASP.NET Core Identity.
 Chapter 15 builds on the previous chapter by introducing authorization for
users, so you can restrict which pages a signed-in user can access.
 Chapter 16 looks at how to publish your app, how to configure your app for a
production environment, and how to optimize your client-side assets.
The four chapters that make up part 3 cover important, cross-cutting aspects of
ASP.NET Core development.
 Chapter 17 shows how to configure logging in your application, and how to
write log messages to multiple locations.
 Chapter 18 explores some of the security considerations you should take into
account when developing your application, including how to configure your
application for HTTPS.
 Chapter 19 describes how to build and use a variety of custom components,
including custom middleware, custom Tag Helpers, and custom validation attri-
butes.
 Chapter 20 shows how to test an ASP.NET Core application with the xUnit test-
ing framework. It covers both unit tests and integration tests using the Test
Host.
The two appendices provide supplementary information. Appendix A provides some
background to .NET Core and .NET Standard, how they fit in the .NET landscape,
and how you should use them in your apps. Appendix B contains a number of links
that I’ve found useful in learning about ASP.NET Core.
ABOUT THIS BOOK
xxiv
About the code
Source code is provided for all chapters except chapters 1 and 5. You can view the
source code for each chapter in my GitHub repository at https://github.com/
andrewlock/asp-dot-net-core-in-action. A ZIP file containing all the source code is also
available from the publisher’s website at https://www.manning.com/books/asp-dot-
net-core-in-action.
All the code examples in this book use ASP.NET Core 2.0 and were built using
both Visual Studio and Visual Studio Code. Equivalent examples for ASP.NET Core
1.x are available for chapters 3–13 in the source code but are not referenced in the
book itself. To build and run the examples, you’ll need to install the .NET Core SDK,
as described in chapter 1.
The source code is formatted in a fixed-width font like this to separate it from
ordinary text. Sometimes code is also in bold to highlight code that has changed
from previous steps in the chapter, such as when a new feature adds to an existing
line of code.
In many cases, the original source code has been reformatted; we’ve added line
breaks and reworked indentation to accommodate the available page space in the
book. In rare cases, even this was not enough, and listings include line-continuation
markers (➥). Additionally, comments in the source code have often been removed
from the listings when the code is described in the text. Code annotations accompany
many of the listings, highlighting important concepts.
Book forum
Purchase of ASP.NET Core in Action includes free access to a private web forum run by
Manning Publications where you can make comments about the book, ask technical
questions, and receive help from the author and from other users. To access the forum,
go to https://forums.manning.com/forums/asp-dot-net-core-in-action. You can also
learn more about Manning's forums and the rules of conduct at https://forums
.manning.com/forums/about.
Manning’s commitment to our readers is to provide a venue where a meaningful
dialogue between individual readers and between readers and the author can take
place. It is not a commitment to any specific amount of participation on the part of
the author, whose contribution to the forum remains voluntary (and unpaid). We sug-
gest you try asking the author some challenging questions lest his interest stray! The
forum and the archives of previous discussions will be accessible from the publisher’s
website as long as the book is in print.
xxv
about the author
ANDREW LOCK graduated with an engineering degree from Cam-
bridge University, specializing in software engineering, and went
on to obtain a PhD in digital image processing. He has been
developing professionally using .NET for the last seven years,
using a wide range of technologies, including WinForms,
ASP.NET WebForms, ASP.NET MVC, and ASP.NET Web Pages.
His focus is currently on the new ASP.NET Core framework,
exploring its capabilities and deploying several new applications.
Andrew has a very active blog at https://andrewlock.net, dedicated to ASP.NET Core.
It’s frequently featured in the community spotlight by the ASP.NET team at Microsoft,
on the .NET blog, and in the weekly community stand-ups, and recently earned him
the Microsoft Valued Profossional (MVP) award.
xxvi
about the cover illustration
The caption for the illustration on the cover of ASP.NET Core in Action is “The Captain
Pasha. Kapudan pasha, admiral of the Turkish navy.” The Kapudan Pasha was the
highest military rank of the Ottoman Navy from 1567 until 1867 when the post was
abolished and replaced with the Naval Minister. The illustration is taken from a collec-
tion of costumes of the Ottoman Empire published on January 1, 1802, by William
Miller of Old Bond Street, London. The title page is missing from the collection and
we have been unable to track it down to date. The book’s table of contents identifies
the figures in both English and French, and each illustration bears the names of two
artists who worked on it, both of whom would no doubt be surprised to find their art
gracing the front cover of a computer programming book ... two hundred years later.
The collection was purchased by a Manning editor at an antiquarian flea market in
the “Garage” on West 26th Street in Manhattan. The seller was an American based in
Ankara, Turkey, and the transaction took place just as he was packing up his stand for
the day. The Manning editor didn’t have on his person the substantial amount of cash
that was required for the purchase, and a credit card and check were both politely
turned down. With the seller flying back to Ankara that evening, the situation was get-
ting hopeless. What was the solution? It turned out to be nothing more than an old-
fashioned verbal agreement sealed with a handshake. The seller simply proposed that
the money be transferred to him by wire, and the editor walked out with the bank
information on a piece of paper and the portfolio of images under his arm. Needless
to say, we transferred the funds the next day, and we remain grateful and impressed by
this unknown person’s trust in one of us.
We at Manning celebrate the inventiveness, the initiative, and, yes, the fun of the
computer business with book covers based on the rich diversity of regional life of two
centuries ago‚ brought back to life by the pictures from this collection.
Part 1
Getting started with MVC
Web applications are everywhere these days, from social media web apps
and news sites, to the apps on your phone. Behind the scenes, there is almost
always a server running a web application or web API. Web applications are
expected to be infinitely scalable, deployed to the cloud, and highly performant.
Getting started can be overwhelming at the best of times and doing so with such
high expectations can be even more of a challenge.
The good news for you as readers is that ASP.NET Core was designed to meet
those requirements. Whether you need a simple website, a complex e-commerce
web app, or a distributed web of microservices, you can use your knowledge of
ASP.NET Core to build lean web apps that fit your needs. ASP.NET Core lets you
build and run web apps on Windows, Linux, or macOS. It’s highly modular, so
you only use the components you need, keeping your app as compact and per-
formant as possible.
In part 1, you’ll go from a standing start all the way to building your first web
applications and APIs. Chapter 1 gives a high-level overview of ASP.NET Core,
which you’ll find especially useful if you’re new to web development in general.
You’ll get your first glimpse of a full ASP.NET Core application in chapter 2, in
which we look at each component of the app in turn and see how they work
together to generate a response.
Chapter 3 looks in detail at the middleware pipeline, which defines how
incoming web requests are processed and how a response is generated. We take a
detailed look at one specific piece of middleware, the MVC middleware, in chap-
ters 4 through 6. This is the main component used to generate responses in
ASP.NET Core apps, so we examine the behavior of the middleware itself, routing,
2 PART 1 Getting started with MVC
and model binding. In Chapters 7 and 8, we look at how to build a UI for your appli-
cation using the Razor syntax and Tag Helpers, so that users can navigate and interact
with your app. Finally, in chapter 9, we explore specific features of ASP.NET Core that
let you build web APIs, and how that differs from building UI-based applications.
There’s a lot of content in part 1, but by the end, you’ll be well on your way to
building simple applications with ASP.NET Core. Inevitably, I gloss over some of the
more complex configuration aspects of the framework, but you should get a good
understanding of the MVC middleware and how you can use it to build dynamic web
apps. In later parts of this book, we’ll dive deeper into the framework, where you’ll
learn how to configure your application and add extra features, such as user profiles.
3
Getting started
with ASP.NET Core
Choosing to learn and develop with a new framework is a big investment, so it’s
important to establish early on whether it’s right for you. In this chapter, I’ll pro-
vide some background about ASP.NET Core, what it is, how it works, and why you
should consider it for building your web applications.
If you’re new to .NET development, this chapter will help you to choose a devel-
opment platform for your future apps. For existing .NET developers, I’ll also pro-
vide guidance on whether now is the right time to consider moving your focus to
.NET Core, and the advantages ASP.NET Core can bring over previous versions of
ASP.NET.
By the end of this chapter, you should have a good idea of the model you intend to
follow, and the tools you’ll need to get started—so without further ado, let’s dive in!
This chapter covers
 What is ASP.NET Core?
 How ASP.NET Core works
 Choosing between .NET Core and .NET
Framework
 Preparing your development environment
4 CHAPTER 1 Getting started with ASP.NET Core
1.1 An introduction to ASP.NET Core
ASP.NET Core is the latest evolution of Microsoft’s popular ASP.NET web framework,
released in June 2016. Recent versions of ASP.NET have seen many incremental
updates, focusing on high developer productivity and prioritizing backwards compati-
bility. ASP.NET Core bucks that trend by making significant architectural changes that
rethink the way the web framework is designed and built.
ASP.NET Core owes a lot to its ASP.NET heritage and many features have been car-
ried forward from before, but ASP.NET Core is a new framework. The whole technol-
ogy stack has been rewritten, including both the web framework and the underlying
platform.
At the heart of the changes is the philosophy that ASP.NET should be able to hold
its head high when measured against other modern frameworks, but that existing
.NET developers should continue to be left with a sense of familiarity.
1.1.1 Using a web framework
If you’re new to web development, it can be daunting moving into an area with so
many buzzwords and a plethora of ever-changing products. You may be wondering if
they’re all necessary—how hard can it be to return a file from a server?
Well, it’s perfectly possible to build a static web application without the use of a
web framework, but its capabilities will be limited. As soon as you want to provide any
kind of security or dynamism, you’ll likely run into difficulties, and the original sim-
plicity that enticed you will fade before your eyes!
Just as you may have used desktop or mobile development frameworks for building
native applications, ASP.NET Core makes writing web applications faster, easier, and
more secure. It contains libraries for common things like
 Creating dynamically changing web pages
 Letting users log in to your web app
 Letting users use their Facebook account to log in to your web app using OAuth
 Providing a common structure to build maintainable applications
 Reading configuration files
 Serving image files
 Logging calls to your web app
The key to any modern web application is the ability to generate dynamic web pages.
A dynamic web page displays different data depending on the current logged-in user, for
example, or it could display content submitted by users. Without a dynamic frame-
work, it wouldn’t be possible to log in to websites or to have any sort of personalized
data displayed on a page. In short, websites like Amazon, eBay, and Stack Overflow
(seen in figure 1.1) wouldn’t be possible.
Hopefully, it’s clear that using a web framework is a sensible idea for building high-
quality web applications. But why choose ASP.NET Core? If you’re a C# developer, or
even if you’re new to the platform, you’ve likely heard of, if not used, the previous ver-
sion of ASP.NET—so why not use that instead?
5
An introduction to ASP.NET Core
1.1.2 The benefits and limitations of ASP.NET
To understand why Microsoft decided to build a new framework, it’s important to
understand the benefits and limitations of the existing ASP.NET web framework.
The first version of ASP.NET was released in 2002 as part of .NET Framework 1.0,
in response to the then conventional scripting environments of classic ASP and PHP.
ASP.NET Web Forms allowed developers to rapidly create web applications using a
graphical designer and a simple event model that mirrored desktop application-
building techniques.
The ASP.NET framework allowed developers to quickly create new applications,
but over time, the web development ecosystem changed. It became apparent that
ASP.NET Web Forms suffered from many issues, especially when building larger appli-
cations. In particular, a lack of testability, a complex stateful model, and limited influ-
ence over the generated HTML (making client-side development difficult) led
developers to evaluate other options.
In response, Microsoft released the first version of ASP.NET MVC in 2009, based
on the Model-View-Controller pattern, a common web design pattern used in other
frameworks such as Ruby on Rails, Django, and Java Spring. This framework allowed
you to separate UI elements from application logic, made testing easier, and provided
tighter control over the HTML-generation process.
ASP.NET MVC has been through four more iterations since its first release, but
they have all been built on the same underlying framework provided by the System
.Web.dll file. This library is part of .NET Framework, so it comes pre-installed with all
User otifications
n
Currently logged in user
Viewing statistics
Events and jobs
based on location
and user profile
Questions
submitted
u
by sers
User votes
update scores
on the server
Answers
submitted
by users
Figure 1.1 The Stack Overflow website (http://stackoverflow.com) is built using ASP.NET and is almost entirely
dynamic content.
6 CHAPTER 1 Getting started with ASP.NET Core
versions of Windows. It contains all the core code that ASP.NET uses when you build a
web application.
This dependency brings both advantages and disadvantages. On the one hand, the
ASP.NET framework is a reliable, battle-tested platform that’s a great choice for build-
ing modern applications on Windows. It provides a wide range of features, which
have seen many years in production, and is well known by virtually all Windows web
developers.
On the other hand, this reliance is limiting—changes to the underlying System
.Web.dll are far-reaching and, consequently, slow to roll out. This limits the extent to
which ASP.NET is free to evolve and results in release cycles only happening every few
years. There’s also an explicit coupling with the Windows web host, Internet Informa-
tion Service (IIS), which precludes its use on non-Windows platforms.
In recent years, many web developers have started looking at cross-platform web
frameworks that can run on Windows, as well as Linux and macOS. Microsoft felt the
time had come to create a framework that was no longer tied to its Windows legacy,
thus ASP.NET Core was born.
1.1.3 What is ASP.NET Core?
The development of ASP.NET Core was motivated by the desire to create a web frame-
work with four main goals:
 To be run and developed cross-platform
 To have a modular architecture for easier maintenance
 To be developed completely as open source software
 To be applicable to current trends in web development, such as client-side
applications and deploying to cloud environments
In order to achieve all these goals, Microsoft needed a platform that could provide
underlying libraries for creating basic objects such as lists and dictionaries, and per-
forming, for example, simple file operations. Up to this point, ASP.NET development
had always been focused, and dependent, on the Windows-only .NET Framework. For
ASP.NET Core, Microsoft created a lightweight platform that runs on Windows,
Linux, and macOS called .NET Core, as shown in figure 1.2.
.NET Core shares many of the same APIs as .NET Framework, but it’s smaller and
currently only implements a subset of the features .NET Framework provides, with the
goal of providing a simpler implementation and programming model. It’s a com-
pletely new platform, rather than a fork of .NET Framework, though it uses similar
code for many of its APIs.
With .NET Core alone, it’s possible to build console applications that run cross-
platform. Microsoft created ASP.NET Core to be an additional layer on top of console
applications, such that converting to a web application involves adding and compos-
ing libraries, as shown in figure 1.3.
7
An introduction to ASP.NET Core
Figure 1.2 The relationship between ASP.NET Core, ASP.NET, .NET Core, and
.NET Framework. ASP.NET Core runs on both .NET Framework and .NET Core, so
it can run cross-platform. Conversely, ASP.NET runs on .NET Framework only, so
is tied to the Windows OS.
ASP.NET Core
ASP.NET /
ASP.NET MVC
.NET Core .NET Framework
Web ramework
f
.NET latform
p
Windows
Linux
macOS
Windows
Operating ystem
s
.NET Core runs on
.
multiple platforms
.NET Framework runs
.
on Windows only
ASP
.NET Core runs on
both .NET Core and
.
.NET Framework
ASP
.NET 4.5 runs on
.NET Framework only.
ASP.NET Core console application
ASP.NET Core Kestrel
web server
Web application logic
You write a .NET Core console
app that starts up an instance
.
of an ASP
.NET Core web server
Configuration
Logging Static files
HTML
generation
Your web application logic is run by
Kestrel. You’ll use various libraries
to enable features such as logging
and HTML generation as required.
Microsoft provides, by default,
a cross-platform web server
called Kestrel.
Figure 1.3 The ASP.NET Core application model. The .NET Core platform
provides a base console application model for running command-line apps.
Adding a web server library converts this into an ASP.NET Core web app.
Additional features, such as configuration and logging, are added by way of
additional libraries.
8 CHAPTER 1 Getting started with ASP.NET Core
By adding an ASP.NET Core web server to your .NET Core app, your application can
run as a web application. ASP.NET Core is composed of many small libraries that you
can choose from to provide your application with different features. You’ll rarely need
all the libraries available to you and you only add what you need. Some of the libraries
are common and will appear in virtually every application you create, such as the ones
for reading configuration files or performing logging. Other libraries build on top of
these base capabilities to provide application-specific functionality, such as third-party
logging-in via Facebook or Google.
Most of the libraries you’ll use in ASP.NET Core can be found on GitHub, in the
Microsoft ASP.NET Core organization repositories at https://github.com/aspnet. You
can find the core libraries here, such as the Kestrel web server and logging libraries, as
well as many more peripheral libraries, such as the third-party authentication libraries.
All ASP.NET Core applications will follow a similar design for basic configuration,
as suggested by the common libraries, but in general the framework is flexible, leaving
you free to create your own code conventions. These common libraries, the extension
libraries that build on them, and the design conventions they promote make up the
somewhat nebulous term ASP.NET Core.
1.2 When to choose ASP.NET Core
Hopefully, you now have a general grasp of what ASP.NET Core is and how it was
designed. But the question remains: should you use it? Microsoft will be heavily pro-
moting ASP.NET Core as its web framework of choice for the foreseeable future, but
switching to or learning a new web stack is a big ask for any developer or company.
This section describes some of the highlights of ASP.NET Core and gives advice on the
sort of applications you should build with it, as well as the sort of applications you
should avoid.
1.2.1 What type of applications can you build?
ASP.NET Core provides a generalized web framework that can be used on a variety of
applications. It can most obviously be used for building rich, dynamic websites,
whether they’re e-commerce sites, content-based sites, or large n-tier applications—
much the same as the previous version of ASP.NET.
Currently, there’s a comparatively limited number of third-party libraries available
for building these types of complex applications, but there are many under active
development. Many developers are working to port their libraries to work with
ASP.NET Core—it will take time for more to become available. For example, the open
source content management system (CMS), Orchard1
(figure 1.4), is currently avail-
able as a beta version of Orchard Core, running on ASP.NET Core and .NET Core.
Traditional, server-side-rendered web applications are the bread and butter of
ASP.NET development, both with the previous version of ASP.NET and ASP.NET
1
The Orchard project (www.orchardproject.net/). Source code at https://github.com/OrchardCMS/.
9
When to choose ASP.NET Core
Core. Additionally, single-page applications (SPAs), which use a client-side framework
that commonly talks to a REST server, are easy to create with ASP.NET Core. Whether
you’re using Angular, Ember, React, or some other client-side framework, it’s easy to
create an ASP.NET Core application to act as the server-side API.
DEFINITION REST stands for REpresentational State Transfer. RESTful appli-
cations typically use lightweight and stateless HTTP calls to read, post (cre-
ate/update), and delete data.
ASP.NET Core isn’t restricted to creating RESTful services. It’s easy to create a web
service or remote procedure call (RPC)-style service for your application, depending
on your requirements, as shown in figure 1.5. In the simplest case, your application
might expose only a single endpoint, narrowing its scope to become a microservice.
ASP.NET Core is perfectly designed for building simple services thanks to its cross-
platform support and lightweight design.
You should consider multiple factors when choosing a platform, not all of which
are technical. One example is the level of support you can expect to receive from its
creators. For some organizations, this can be one of the main obstacles to adopting
open source software. Luckily, Microsoft has pledged to provide full support for each
Figure 1.4 The ASP.NET Community Blogs website (https://weblogs.asp.net) is built using the Orchard CMS.
Orchard 2 is available as a beta version for ASP.NET Core development.
10 CHAPTER 1 Getting started with ASP.NET Core
major and minor point release of the ASP.NET Core framework for three years2
. And
as all development takes place in the open, you can sometimes get answers to your
questions from the general community, as well as Microsoft directly.
When deciding whether to use ASP.NET Core, you have two primary dimensions
to consider: whether you’re already a .NET developer, and whether you’re creating a
new application or looking to convert an existing one.
1.2.2 If you’re new to .NET development
If you’re new to .NET development and are considering ASP.NET Core, then wel-
come! Microsoft is pushing ASP.NET Core as an attractive option for web develop-
ment beginners, but taking .NET cross-platform means it’s competing with many
other frameworks on their own turf. ASP.NET Core has many selling points when
compared to other cross-platform web frameworks:
 It’s a modern, high-performance, open source web framework.
 It uses familiar design patterns and paradigms.
2
View the support policy at www.microsoft.com/net/core/support.
Figure 1.5 ASP.NET Core can act as the server-side application for a variety of different
clients: it can serve HTML pages for traditional web applications, it can act as a REST API for
client-side SPA applications, or it can act as an ad-hoc RPC service for client applications.
Browser
Traditional
web application
Server
Client
Synchronous
request via HTTP
Response:
HTML web page
SPA web application REST API
Asynchronous
request via HTTP
Response: partial page
data as JSON or XML
Client application RPC service
Synchronous or
asynchronous
request via HTTP
Response:
data as JSON,
XML or binary
11
When to choose ASP.NET Core
 C# is a great language (or you can use VB.NET or F# if you prefer).
 You can build and run on any platform.
ASP.NET Core is a re-imagining of the ASP.NET framework, built with modern soft-
ware design principles on top of the new .NET Core platform. Although new in one
sense, .NET Core has drawn significantly from the mature, stable, and reliable .NET
Framework, which has been used for well over a decade. You can rest easy knowing
that by choosing ASP.NET Core and .NET Core, you’ll be getting a dependable plat-
form as well as a fully-featured web framework.
Many of the web frameworks available today use similar, well-established design
patterns, and ASP.NET Core is no different. For example, Ruby on Rails is known for
its use of the Model-View-Controller (MVC) pattern; Node.js is known for the way it
processes requests using small discrete modules (called a pipeline); and dependency
injection is found in a wide variety of frameworks. If these techniques are familiar to
you, you should find it easy to transfer them across to ASP.NET Core; if they’re new to
you, then you can look forward to using industry best practices!
NOTE You’ll encounter MVC in chapter 4, a pipeline in chapter 3, and
dependency injection in chapter 10.
The primary language of .NET development, and ASP.NET Core in particular, is C#.
This language has a huge following, and for good reason! As an object-oriented C-
based language, it provides a sense of familiarity to those used to C, Java, and many
other languages. In addition, it has many powerful features, such as Language Inte-
grated Query (LINQ), closures, and asynchronous programming constructs. The C#
language is also designed in the open on GitHub, as is Microsoft’s C# compiler, code-
named Roslyn.3
NOTE I will use C# throughout this book and will highlight some of the newer
features it provides, but I won’t be teaching the language from scratch. If you
want to learn C#, I recommend C# in Depth by Jon Skeet (Manning, 2008).
One of the major selling points of ASP.NET Core and .NET Core is the ability to develop
and run on any platform. Whether you’re using a Mac, Windows, or Linux, you can run
the same ASP.NET Core apps and develop across multiple environments. As a Linux
user, a wide range of distributions are supported (RHEL, Ubuntu, Debian, CentOS,
Fedora, and openSUSE, to name a few), so you can be confident your operating system
of choice will be a viable option. Work is even underway to enable ASP.NET Core to run
on the tiny Alpine distribution, for truly compact deployments to containers.
3
The C# language and .NET Compiler Platform GitHub source code repository can be found at https://
github.com/dotnet/roslyn.
12 CHAPTER 1 Getting started with ASP.NET Core
As well as running on each platform, one of the selling points of .NET is the ability to
write and compile only once. Your application is compiled to Intermediate Language
(IL) code, which is a platform-independent format. If a target system has the .NET
Core platform installed, then you can run compiled IL from any platform. That
means you can, for example, develop on a Mac or a Windows machine and deploy the
exact same files to your production Linux machines. This compile-once, run-anywhere
promise has finally been realized with ASP.NET Core and .NET Core.
1.2.3 If you’re a .NET Framework developer creating a new application
If you’re currently a .NET developer, then the choice of whether to invest in ASP.NET
Core for new applications is a question of timing. Microsoft has pledged to provide
continued support for the older ASP.NET framework, but it’s clear their focus is pri-
marily on the newer ASP.NET Core framework. In the long term then, if you want to
take advantage of new features and capabilities, it’s likely that ASP.NET Core will be
the route to take.
Whether ASP.NET Core is right for you largely depends on your requirements and
your comfort with using products that are early in their lifecycle. The main benefits
over the previous ASP.NET framework are
 Cross-platform development and deployment
 A focus on performance as a feature
 A simplified hosting model
 Regular releases with a shorter release cycle
 Open source
 Modular features
Built with containers in mind
Traditionally, web applications were deployed directly to a server, or more recently, to
a virtual machine. Virtual machines allow operating systems to be installed in a layer
of virtual hardware, abstracting away the underlying hardware. This has several
advantages over direct installation, such as easy maintenance, deployment, and
recovery. Unfortunately, they’re also heavy both in terms of file size and resource use.
This is where containers come in. Containers are far more lightweight and don’t have
the overhead of virtual machines. They’re built in a series of layers and don’t require
you to boot a new operating system when starting a new one. That means they’re
quick to start and are great for quick provisioning. Containers, and Docker in partic-
ular, are quickly becoming the go-to platform for building large, scalable systems.
Containers have never been a particularly attractive option for ASP.NET applications,
but with ASP.NET Core, .NET Core, and Docker for Windows, that’s all changing. A
lightweight ASP.NET Core application running on the cross-platform .NET Core frame-
work is perfect for thin container deployments. You can learn more about your deploy-
ment options in chapter 16.
13
When to choose ASP.NET Core
As a .NET developer, if you aren’t using any Windows-specific constructs, such as the
Registry, then the ability to build and deploy applications cross-platform opens the
door to a whole new avenue of applications: take advantage of cheaper Linux VM
hosting in the cloud, use Docker containers for repeatable continuous integration, or
write .NET code on your Mac without needing to run a Windows virtual machine.
ASP.NET Core, in combination with .NET Core, makes all of this possible.
It’s important to be aware of the limitations of cross-platform applications—not all
the .NET Framework APIs are available in .NET Core. It’s likely that most of the APIs
you need will make their way to .NET Core over time, but it’s an important point to be
aware of. See the “Choosing a platform for ASP.NET Core” section later in this chap-
ter to determine if cross-platform is a viable option for your application.
NOTE With the release of .NET Core 2.0 in August 2017, the number of APIs
available dramatically increased, more than doubling the API surface area.
The hosting model for the previous ASP.NET framework was a relatively complex one,
relying on Windows IIS to provide the web server hosting. In a cross-platform environ-
ment, this kind of symbiotic relationship isn’t possible, so an alternative hosting
model has been adopted, one which separates web applications from the underlying
host. This opportunity has led to the development of Kestrel: a fast, cross-platform
HTTP server on which ASP.NET Core can run.
Instead of the previous design, whereby IIS calls into specific points of your appli-
cation, ASP.NET Core applications are console applications that self-host a web server
and handle requests directly, as shown in figure 1.6. This hosting model is conceptu-
ally much simpler and allows you to test and debug your applications from the com-
mand line, though it doesn’t remove the need to run IIS (or equivalent) in
production, as you’ll see in section 1.3.
Changing the hosting model to use a built-in HTTP web server has created
another opportunity. Performance has been somewhat of a sore point for ASP.NET
applications in the past. It’s certainly possible to build high-performing applications—
Stack Overflow (http://stackoverflow.com) is testament to that—but the web frame-
work itself isn’t designed with performance as a priority, so it can end up being some-
what of an obstacle.
To be competitive cross-platform, the ASP.NET team have focused on making the
Kestrel HTTP server as fast as possible. TechEmpower (www.techempower.com/
benchmarks) has been running benchmarks on a whole range of web frameworks
from various languages for several years now. In Round 13 of the plain text bench-
marks, TechEmpower announced that ASP.NET Core with Kestrel was the fastest
mainstream full-stack web framework, in the top ten of all frameworks!4
4
As always in web development, technology is in a constant state of flux, so these benchmarks will evolve over
time. Although ASP.NET Core may not maintain its top ten slot, you can be sure that performance is one of
the key focal points of the ASP.NET Core team.
14 CHAPTER 1 Getting started with ASP.NET Core
ASP.NET application
A request is
sent to IIS.
Request
Response
IIS calls into specific
methods in
the ASP
.NET
.
application
Control transfers
back and forth between
IIS and ASP
.NET as
.
events are raised
Application_BeginRequest() {}
Application_AuthenticateRequest(){}
Application_AuthorizeRequest(){}
Application_ProcessRequest() {}
Application_EndRequest() {}
Application_HandleError() {}
IIS
ASP.NET Core application
A request is
sent to IIS.
Request
Response
IIS passes raw request
.
to Kestrel web server
Kestrel processes the
incoming request and
passes it to the rest of
.
the application
IIS /
Apache /
Nginx
ASP.NET Core Kestrel
web server
Application handles
request and generates
response
Figure 1.6 The difference between hosting models in ASP.NET (top) and ASP.NET Core
(bottom). With the previous version of ASP.NET, IIS is tightly coupled with the application.
The hosting model in ASP.NET Core is simpler; IIS hands off the request to a self-hosted
web server in the ASP.NET Core application and receives the response, but has no deeper
knowledge of the application.
Web servers: naming things is hard
One of the difficult aspects of programing for the web is the confusing array of often
conflicting terminology. For example, if you’ve used IIS in the past, you may have
described it as a web server, or possibly a web host. Conversely, if you’ve ever built
an application using Node.js, you may have also referred to that application as a web
server. Alternatively, you may have called the physical machine on which your appli-
cation runs a web server!
Similarly, you may have built an application for the internet and called it a website or
a web application, probably somewhat arbitrarily based on the level of dynamism it
displayed.
15
When to choose ASP.NET Core
Many of the performance improvements made to Kestrel did not come from the
ASP.NET team themselves, but from contributors to the open source project on
GitHub.5
Developing in the open means you typically see fixes and features make
their way to production faster than you would for the previous version of ASP.NET,
which was dependent on .NET Framework and, as such, had long release cycles.
In contrast, ASP.NET Core is completely decoupled from the underlying .NET
platform. The entire web framework is implemented as NuGet packages, independent
of the underlying platform on which it builds.
NOTE NuGet is a package manager for .NET that enables importing libraries
into your projects. It’s equivalent to Ruby Gems, npm for JavaScript, or Maven
for Java.
To enable this, ASP.NET Core was designed to be highly modular, with as little cou-
pling to other features as possible. This modularity lends itself to a pay-for-play
approach to dependencies, where you start from a bare-bones application and only
add the additional libraries you require, as opposed to the kitchen-sink approach of
previous ASP.NET applications. Even MVC is an optional package! But don’t worry,
this approach doesn’t mean that ASP.NET Core is lacking in features; it means you
need to opt in to them. Some of the key infrastructure improvements include
 Middleware “pipeline” for defining your application’s behavior
 Built-in support for dependency injection
 Combined UI (MVC) and API (Web API) infrastructure
 Highly extensible configuration system
 Scalable for cloud platforms by default using asynchronous programming
5
The Kestrel HTTP server GitHub project can be found at https://github.com/aspnet/KestrelHttpServer.
In this book, when I say “web server” in the context of ASP.NET Core, I am referring
to the HTTP server that runs as part of your ASP.NET Core application. By default, this
is the Kestrel web server, but that’s not a requirement. It would be possible to write
a replacement web server and substitute if for Kestrel if you desired.
The web server is responsible for receiving HTTP requests and generating responses.
In the previous version of ASP.NET, IIS took this role, but in ASP.NET Core, Kestrel is
the web server.
I will only use the term web application to describe ASP.NET Core applications in this
book, regardless of whether they contain only static content or are completely
dynamic. Either way, they’re applications that are accessed via the web, so that name
seems the most appropriate!
16 CHAPTER 1 Getting started with ASP.NET Core
Each of these features was possible in the previous version of ASP.NET but required a
fair amount of additional work to set up. With ASP.NET Core, they’re all there, ready,
and waiting to be connected!
Microsoft fully supports ASP.NET Core, so if you have a new system you want to
build, then there’s no significant reason not to. The largest obstacle you’re likely to
come across is a third-party library holding you back, either because they only support
older ASP.NET features, or they haven’t been converted to work with .NET Core yet.
Hopefully, this section has whetted your appetite with some of the many reasons to
use ASP.NET Core for building new applications. But if you’re an existing ASP.NET
developer considering whether to convert an existing ASP.NET application to
ASP.NET Core, that’s another question entirely.
1.2.4 Converting an existing ASP.NET application to ASP.NET Core
In contrast with new applications, an existing application is presumably already pro-
viding value, so there should always be a tangible benefit to performing what may
amount to a significant rewrite in converting from ASP.NET to ASP.NET Core. The
advantages of adopting ASP.NET Core are much the same as for new applications:
cross-platform deployment, modular features, and a focus on performance. Determin-
ing whether or not the benefits are sufficient will depend largely on the particulars of
your application, but there are some characteristics that are clear indicators against
conversion:
 Your application uses ASP.NET Web Forms
 Your application is built using WCF or SignalR
 Your application is large, with many “advanced” MVC features
If you have an ASP.NET Web Forms application, then attempting to convert it to
ASP.NET Core isn’t advisable. Web Forms is inextricably tied to System.Web.dll, and as
such will likely never be available in ASP.NET Core. Converting an application to
ASP.NET Core would effectively involve rewriting the application from scratch, not
only shifting frameworks but also shifting design paradigms. A better approach would
be to slowly introduce Web API concepts and try to reduce the reliance on legacy Web
Forms constructs such as ViewData. You can find many resources online to help you
with this approach, in particular, the www.asp.net/web-api website.
Similarly, if your application makes heavy use of SignalR, then now may not be the
time to consider an upgrade. ASP.NET Core SignalR is under active development but
has only been released in alpha form at the time of writing. It also has some significant
architectural changes compared to the previous version, which you should take into
account.
Windows Communication Foundation (WCF) is currently not supported either,
but it’s possible to consume WCF services by jumping through some slightly obscure
hoops. Currently, there’s no way to host a WCF service from an ASP.NET Core applica-
tion, so if you need the features WCF provides and can’t use a more conventional
REST service, then ASP.NET Core is probably best avoided.
17
How does ASP.NET Core work?
If your application was complex and made use of the previous MVC or Web API
extensibility points or message handlers, then porting your application to ASP.NET
Core could be complex. ASP.NET Core is built with many similar features to the previ-
ous version of ASP.NET MVC, but the underlying architecture is different. Several of
the previous features don’t have direct replacements, and so will require rethinking.
The larger the application, the greater the difficulty you’re likely to have convert-
ing your application to ASP.NET Core. Microsoft itself suggests that porting an appli-
cation from ASP.NET MVC to ASP.NET Core is at least as big a rewrite as porting from
ASP.NET Web Forms to ASP.NET MVC. If that doesn’t scare you, then nothing will!
So, when should you port an application to ASP.NET Core? As I’ve already men-
tioned, the best opportunity for getting started is on small, green-field, new projects
instead of existing applications. That said, if the application in question is small, with
little custom behavior, then porting might be a viable option. Small implies reduced
risk and probably reduced complexity. If your application consists primarily of MVC
or Web API controllers and associated Razor views, then moving to ASP.NET Core
may be feasible.
1.3 How does ASP.NET Core work?
By now, you should have a good idea of what ASP.NET Core is and the sort of applica-
tions you should use it for. In this section, you’ll see how an application built with
ASP.NET Core works, from the user requesting a URL, to a page being displayed on
the browser. To get there, first you’ll see how an HTTP request works for any web
server, and then you’ll see how ASP.NET Core extends the process to create dynamic
web pages.
1.3.1 How does an HTTP web request work?
As you know, ASP.NET Core is a framework for building web applications that serve
data from a server. One of the most common scenarios for web developers is building
a web app that you can view in a web browser. The high-level process you can expect
from any web server is shown in figure 1.7.
The process begins when a user navigates to a website or types a URL in their
browser. The URL or web address consists of a hostname and a path to some resource on
the web app. Navigating to the address in your browser sends a request from the user’s
computer to the server on which the web app is hosted, using the HTTP protocol.
DEFINITION The hostname of a website uniquely identifies its location on the
internet by mapping via the Domain Name Service (DNS) to an IP Address.
Examples include microsoft.com, www.google.co.uk, and facebook.com.
The request passes through the internet, potentially to the other side of the world,
until it finally makes its way to the server associated with the given hostname on which
the web app is running. The request is potentially received and rebroadcast at multi-
ple routers along the way, but it’s only when it reaches the server associated with the
hostname that the request is processed.
18 CHAPTER 1 Getting started with ASP.NET Core
Once the server receives the request, it will check that it makes sense, and if it does,
will generate an HTTP response. Depending on the request, this response could be a
web page, an image, a JavaScript file, or a simple acknowledgment. For this example,
I’ll assume the user has reached the homepage of a web app, and so the server
responds with some HTML. The HTML is added to the HTTP response, which is then
sent back across the internet to the browser that made the request.
As soon as the user’s browser begins receiving the HTTP response, it can start dis-
playing content on the screen, but the HTML page may also reference other pages
and links on the server. To display the complete web page, instead of a static, colorless,
http://thewebsite.com/the/page.html
1. User requests a web page by a URL.
http://thewebsite.com/the/page.html
Welcome to the web page!
5. Browser renders HTML on page.
4. Server sends HTML in HTTP
response back to browser.
HTTP response
3. Server interprets request and
generates appropriate HTML.
2. Browser sends HTTP
request to server.
HTTP request
<HTML>
<HEAD></HEAD
<BODY></BODY>
</HTML>
Figure 1.7 Requesting a web page. The user starts by requesting a web page, which
causes an HTTP request to be sent to the server. The server interprets the request,
generates the necessary HTML, and sends it back in an HTTP response. The browser
can then display the web page.
19
How does ASP.NET Core work?
raw HTML file, the browser must repeat the request process, fetching every refer-
enced file. HTML, images, CSS for styling, and JavaScript files for extra behavior are
all fetched using the exact same HTTP request process.
Pretty much all interactions that take place on the internet are a facade over this
same basic process. A basic web page may only require a few simple requests to fully
render, whereas a modern, large web page may take hundreds. The Amazon.com
homepage (www.amazon.com), for example, currently makes 298 requests, including
6 CSS files, 14 JavaScript files, and 245 image files!
Now you have a feel for the process, let’s see how ASP.NET Core dynamically gen-
erates the response on the server.
1.3.2 How does ASP.NET Core process a request?
When you build a web application with ASP.NET Core, browsers will still be using the
same HTTP protocol as before to communicate with your application. ASP.NET Core
itself encompasses everything that takes place on the server to handle a request,
including verifying the request is valid, handling login details, and generating HTML.
Just as with the generic web page example, the request process starts when a user’s
browser sends an HTTP request to the server, as shown in figure 1.8. A reverse-proxy
server captures the request, before passing it to your application. In Windows, the
reverse-proxy server will typically be IIS, and on Linux or macOS it might be NGINX
or Apache.
DEFINITION A reverse proxy is software responsible for receiving requests and
forwarding them to the appropriate web server. The reverse proxy is exposed
directly to the internet, whereas the underlying web server is exposed only to
the proxy. This setup has several benefits, primarily security and performance
for the web servers.
The request is forwarded from the reverse proxy to your ASP.NET Core application.
Every ASP.NET Core application has a built-in web server, Kestrel by default, which is
responsible for receiving raw requests and constructing an internal representation of
the data, an HttpContext object, which can be used by the rest of the application.
From this representation, your application should have all the details it needs to
create an appropriate response to the request. It can use the details stored in Http-
Context to generate an appropriate response, which may be to generate some HTML,
to return an “access denied” message, or to send an email, all depending on your
application’s requirements.
Once the application has finished processing the request, it will return the
response to the web server. The ASP.NET Core web server will convert the representa-
tion into a raw HTTP response and send it back to the reverse proxy, which will for-
ward it to the user’s browser.
20 CHAPTER 1 Getting started with ASP.NET Core
To the user, this process appears to be the same as for the generic HTTP request
shown in figure 1.7—the user sent an HTTP request and received an HTTP response.
All the differences are server-side, within our application.
You may be thinking that having a reverse proxy and a web server is somewhat
redundant. Why not have one or the other? Well, one of the benefits is the decoupling
of your application from the underlying operating system. The same ASP.NET Core
web server, Kestrel, can be cross-platform and used behind a variety of proxies without
putting any constraints on a particular implementation. Alternatively, if you wrote a
new ASP.NET Core web server, you could use that in place of Kestrel without needing
to change anything else about your application.
Another benefit of a reverse proxy is that it can be hardened against potential
threats from the public internet. They’re often responsible for additional aspects,
ASP.NET Core infrastructure
and application logic
Reverse proxy
(IIS/NGINX/Apache)
ASP.NET Core web server
(Kestrel)
1. HTTP request is made to the
server and is received by the
reverse proxy.
7. HTTP response
is sent to browser.
2. Request is forwarded by IIS/
NGINX/Apache to ASP.NETCore.
3. ASP
.NET Core web
server receives the HTTP
request and passes it to
the middleware.
Request Response
5. Response passes through
middleware back to web server.
4. Request is processed by the application,
which generates a response.
ASP.NET Core application
6. Web server forwards
response to reverse proxy.
Figure 1.8 How an ASP.NET Core application processes a request. A request is received from a
browser at the reverse proxy, which passes the request to the ASP.NET Core application, which
runs a self-hosted web server. The web server processes the request and passes it to the body of
the application, which generates a response and returns it to the web server. The web server
relays this to the reverse proxy, which sends the response to the browser.
21
Choosing a platform for ASP.NET Core
such as restarting a process that has crashed. Kestrel can stay as a simple HTTP server
without having to worry about these extra features when it’s used behind a reverse
proxy. Think of it as a simple separation of concerns: Kestrel is concerned with gener-
ating HTTP responses; a reverse proxy is concerned with handling the connection to
the internet.
You’ve seen how requests and responses find their way to and from an ASP.NET
Core application, but I haven’t yet touched on how the response is generated. In part
1 of this book, we’ll look at the components that make up a typical ASP.NET Core appli-
cation and how they all fit together. A lot goes into generating a response in ASP.NET
Core, typically all within a fraction of a second, but over the course of the book we’ll
step through an application slowly, covering each of the components in detail.
Before we dive in, you need to choose an underlying platform for your first
ASP.NET Core application and set up a development environment in which to build it.
1.4 Choosing a platform for ASP.NET Core
ASP.NET Core was developed along with .NET Core and is often mentioned in the
same breath, so it can be easy to forget that ASP.NET Core is platform-agnostic. You
can build and run an ASP.NET Core application on both .NET Core or .NET Frame-
work. The same features will be available in both cases, so why would you choose one
over the over? Which route is right for you depends on both your history and the
application you’re looking to build, so in this section I’ve highlighted some advan-
tages and disadvantages to consider.
1.4.1 Advantages of using .NET Framework
One of the most significant advantages of the full .NET framework is its maturity—it
has been developed for 16 years, has been battle-hardened, and extensively deployed.
For some, this maturity will be a significant deciding factor. It will already be installed
on your servers and building an ASP.NET Core on top involves (relatively) little risk to
your existing environment.
For others, particularly existing ASP.NET developers, the cross-platform and
container-friendly .NET Core won’t hold any appeal. These developers will, by neces-
sity, be used to deploying to Windows servers, and it’s perfectly reasonable to want to
continue to do so, while still taking advantage of all ASP.NET Core has to offer.
The biggest reason to stick with the full .NET Framework when .NET Core was first
released was because you needed to make use of Windows-specific features, such as
the Registry or Directory Services. Microsoft have since released a compatibility pack6
that makes these APIs available in .NET Core, but they’re only available when running
.NET Core on Windows, not on Linux or macOS. If you know your app relies on many
Windows-only features, then .NET Framework may be the easiest option.
6
The Windows Compatibility Pack is designed to help port code from .NET Framework to .NET Core. See
http://mng.bz/50hu.
22 CHAPTER 1 Getting started with ASP.NET Core
WARNING If you choose to run on .NET Framework only, you won’t be able
to easily run your application cross-platform.
One advantage of using .NET Framework is that it has the greatest library support, in
the form of NuGet packages. Library authors are being encouraged to make their
libraries work identically on both .NET Framework and .NET Core by targeting .NET
Standard, but that transition is a slow process.
.NET Standard7
defines the APIs that are available on a given .NET platform. It’s
made up of multiple versions (for example, 1.1 and 1.2), each of which adds addi-
tional APIs compared to previous versions. Think of it as an “interface” for various
.NET frameworks; the frameworks (such as .NET Core, .NET Framework, and Mono)
all “implement” a version of .NET Standard.
TIP You can create a new type of library that targets .NET Standard instead
of targeting a specific framework. That allows you to use your library across
multiple platforms, including .NET Core and .NET Framework. See appendix
A for further details.
.NET Standard 2.0 vastly increases the number of APIs available to libraries that target
it, covering almost the same area as .NET Framework 4.6.1. At the time of writing,
56% of packages on NuGet.org target the full framework rather than .NET Standard,
so if your application currently relies on one of those packages, you’ll have to choose
.NET Framework for your ASP.NET Core application.
TIP .NET Standard 2.0 contains a compatibility shim that allows you to refer-
ence .NET Framework 4.6.1 libraries from a .NET Standard library. For
details, see http://mng.bz/jH8Y and appendix A.
1.4.2 Advantages of using .NET Core
If you’re considering ASP.NET Core for a project, the chances are you’re also inter-
ested in the associated features .NET Core brings, such as the cross-platform capabili-
ties. If that’s the case, then those features are obvious reasons to choose .NET Core as
the underlying platform to use with ASP.NET Core.
The open source nature of .NET Core development can be a big deciding factor
for some people. Open source development means you can clearly see how features
and bugs are being addressed. If there’s a particular feature you feel strongly about or
a bug that’s plaguing you, you can always submit a pull-request and see your code in
the .NET Core platform!
Related to this, and the highly modular design of .NET Core, it’s likely that the plat-
form will see a faster release cycle than other platforms. Updates to .NET Framework
require a massive amount of regression testing to ensure there are no subtle interac-
tions that could break old applications. In contrast, installations of .NET Core are
7
The .NET Standard GitHub repository can be found at https://github.com/dotnet/standard/blob/master/
docs/faq.md.
23
Preparing your development environment
independent of one another, so you can install multiple versions of .NET Core side-by-
side. .NET Core also follows semantic versioning (SemVer), so you can be sure that
your old applications won’t be affected by installing a new version of the framework.
WARNING Be aware that the faster release cycle generally means larger
changes between .NET Core versions when you update your apps. For exam-
ple, upgrading from .NET Core 1.0 to 2.0 is a significant and potentially
breaking change.
Which platform you choose will depend on your use case. The full .NET Framework is
still supported, and is being actively developed, but it’s clear the focus of Microsoft is
with .NET Core right now. If you’re starting a new application from scratch and the
libraries you require have been updated to use .NET Standard, then .NET Core seems
to make the most logical choice for the future.
One final option is to multitarget your application, allowing it to run on both .NET
Core and .NET Framework. This requires a little more effort to set up and maintain in
terms of dependency wrangling, but it’s a viable option if you’re going to need to run
in both environments. In this book, I’m going to be targeting the .NET Core plat-
form, but all the examples should work equally with .NET Framework without any
modification.
Once you’ve selected a platform for your ASP.NET Core applications, it’s time to
prepare your development environment—the last step before you build your first
ASP.NET Core application!
1.5 Preparing your development environment
For .NET developers in a Windows-centric world, Visual Studio was pretty much a
developer requirement in the past. But with .NET Core and ASP.NET Core going
cross-platform, that’s no longer the case.
All of ASP.NET Core (creating new projects, building, testing, and publishing) can
be run from the command line for any supported operating system. All you need is
the .NET Core SDK and tooling, which provides the .NET Command Line Interface
(CLI). Alternatively, if you’re on Windows, and not comfortable with the command
line, you can still use File > New Project in Visual Studio to dive straight in. With
ASP.NET Core, it’s all about choice!
In a similar vein, you can now get a great editing experience outside of Visual Stu-
dio thanks to the OmniSharp project.8
This is a set of libraries and editor plugins that
provide code suggestions and autocomplete (IntelliSense) across a wide range of edi-
tors and operating systems. How you setup your environment will likely depend on
which operating system you’re using and what you’re used to.
Remember that, if you’re using .NET Core, the operating system you choose for
development has no bearing on the final systems you can run on—whether you
8
Information about the OmniSharp project can be found at www.omnisharp.net. Source code can be found
at https://github.com/omnisharp.
24 CHAPTER 1 Getting started with ASP.NET Core
choose Windows, macOS, or Linux for development, you can deploy to any supported
system.
1.5.1 If you’re a Windows user
For a long time, Windows has been the best system for building .NET applications,
and with the availability of Visual Studio that’s still the case.
Visual Studio (figure 1.9) is a full-featured integrated development environment
(IDE), which provides one of the best all-around experiences for developing ASP.NET
Core applications. Luckily, the Visual Studio Community edition is now free for open
source, students, and small teams of developers! Visual studio comes loaded with a
whole host of templates for building new projects, debugging, and publishing, with-
out ever needing to touch a command prompt.
Sometimes, though, you don’t want a full-fledged IDE. Maybe you want to quickly view
or edit a file, or you don’t like the sometimes unpredictable performance of Visual
Studio. In those cases, a simple editor may be all you want or need, and Visual Studio
Code is a great choice. Visual Studio Code (figure 1.10) is an open source, lightweight
editor that provides editing, IntelliSense, and debugging for a wide range of lan-
guages, including C# and ASP.NET Core.
Figure 1.9 Visual Studio provides the most complete ASP.NET Core development environment for Windows users.
25
Preparing your development environment
Whether you install Visual Studio or another editor, such as Visual Studio Code, you’ll
need to install the .NET Core tooling to start building ASP.NET Core apps. You can
either download it from the ASP.NET website (https://get.asp.net) or select the .NET
Core cross-platform development workload during Visual Studio 2017 installation.
1.5.2 If you’re a Linux or macOS user
As a Linux or macOS user, you have a whole host of choices. OmniSharp has plugins
for most popular editors, such as Vim, Emacs, Sublime, Atom, and Brackets, not to
mention the cross-platform Visual Studio Code. Install the appropriate plugin to your
favorite and you’ll be writing C# in no time.
Again, you’ll need to install the .NET Core SDK from the ASP.NET website
(https://get.asp.net) to begin .NET Core and ASP.NET Core development. This will
give you the .NET Core runtime and the .NET CLI to start building ASP.NET Core
applications.
The .NET CLI contains everything you need to get started, including several pro-
ject templates. You don’t get a huge number to choose from by default, but you can
Figure 1.10 Visual Studio Code provides cross-platform IntelliSense and debugging.
26 CHAPTER 1 Getting started with ASP.NET Core
install new ones from GitHub or NuGet if you want more variety. You can easily create
applications from the predefined templates to quick-start your development, as shown
in figure 1.11.
In addition, in May 2017, Microsoft released Visual Studio for Mac. With VS for
Mac you can build cross ASP.NET Core apps, using a similar editor experience to
Visual Studio, but on an app designed natively for macOS. VS for Mac is still young,
but if you’re a macOS user, then it’s a great choice and will no doubt see many
updates.
In this book, I’ll be using Visual Studio for most of the examples, but you’ll be able
to follow along using any of the tools I’ve mentioned. The rest of the book assumes
you’ve successfully installed .NET Core and an editor on your computer.
You’ve reached the end of this chapter; whether you’re new to .NET or an exist-
ing .NET developer, there’s a lot to take in—frameworks, platforms, .NET Framework
(which is a platform!). But take heart: you now have all the background you need
and, hopefully, a development environment to start building applications using
ASP.NET Core.
Figure 1.11 The .NET CLI includes several templates by default, as shown here. You can also install
additional templates or create your own.
27
Summary
In the next chapter, you’ll create your first application from a template and run it.
We’ll walk through each of the main components that make up your application and
see how they all work together to render a web page.
Summary
 ASP.NET Core is a new web framework built with modern software architecture
practices and modularization as its focus.
 It’s best used for new, “green-field” projects with few external dependencies.
 Existing technologies such as WCF and SignalR can’t currently be used with
ASP.NET Core, but work is underway to integrate them.
 Fetching a web page involves sending an HTTP request and receiving an HTTP
response.
 ASP.NET Core allows dynamically building responses to a given request.
 An ASP.NET Core application contains a web server, which serves as the entry-
point for a request.
 ASP.NET Core apps are protected from the internet by a reverse-proxy server,
which forwards requests to the application.
 ASP.NET Core can run on both .NET Framework and .NET Core. If you need
Windows-specific features such as the Windows Registry, you should use .NET
Framework, but you won’t be able to run cross-platform. Otherwise, choose
.NET Core for the greatest reach and hosting options.
 The OmniSharp project provides C# editing plugins for many popular editors,
including the cross-platform Visual Studio Code editor.
 On Windows, Visual Studio provides the most complete all-in-one ASP.NET
Core development experience, but development using the command line and
an editor is as easy as on other platforms.
28
Your first application
After reading chapter 1, you should have a general idea of how ASP.NET Core
applications work and when you should use them. You should have also set up a
development environment to start building applications. In this chapter, you’ll dive
right in by creating your first web app. You’ll get to kick the tires and poke around
a little to get a feel for how it works, and in later chapters, I’ll show how you go
about customizing and building your own applications.
As you work through this chapter, you should begin to get a grasp of the various
components that make up an ASP.NET Core application, as well as an understand-
ing of the general application-building process. Most applications you create will
start from a similar template, so it’s a good idea to get familiar with the setup as soon
as possible.
This chapter covers
 Creating your first ASP.NET Core web application
 Running your application
 Understanding the components of your
application
29
A brief overview of an ASP.NET Core application
DEFINITION A template provides the basic code required to build an application.
You can use a template as the starting point for building your own apps.
I’ll start by showing how to create a basic ASP.NET Core application using one of the
Visual Studio templates. If you’re using other tooling, such as the .NET CLI, then
you’ll have similar templates available. I use Visual Studio 2017 and ASP.NET Core 2.0
in this chapter, but I also provide tips for working with the .NET CLI.
TIP You can view the application code for this chapter in the GitHub repository
for the book at https://github.com/andrewlock/asp-dot-net-core-in-action.
Once you’ve created your application, I’ll show you how to restore all the necessary
dependencies, compile your application, and run it to see the HTML output. The
application will be simple in some respects—it will only have three different pages—
but it’ll be a fully configured ASP.NET Core application.
Having run your application, the next step is to understand what’s going on! We’ll
take a journey through all the major parts of an ASP.NET Core application, looking at
how to configure the web server, the middleware pipeline, and HTML generation,
among other things. We won’t go into detail at this stage, but you’ll get a feel for how
they all work together to create a complete application.
We’ll begin by looking at the plethora of files created when you start a new project
and learn how a typical ASP.NET Core application is laid out. In particular, I’ll focus
on the Program.cs and Startup.cs files. Virtually the entire configuration of your appli-
cation takes place in these two files, so it’s good to get to grips with what they are for
and how they’re used. You’ll see how to define the middleware pipeline for your appli-
cation, and how you can customize it.
Finally, you’ll see how the app generates HTML in response to a request, looking
at each of the components that make up the MVC middleware. You’ll see how it con-
trols what code is run in response to a request, and how to define the HTML that
should be returned for a particular request.
At this stage, don’t worry if you find parts of the project confusing or complicated;
you’ll be exploring each section in detail as you move through the book. By the end of
the chapter, you should have a basic understanding of how ASP.NET Core applica-
tions are put together, right from when your application is first run to when a
response is generated. Before we begin though, we’ll review how ASP.NET Core appli-
cations handle requests.
2.1 A brief overview of an ASP.NET Core application
In chapter 1, I described how a browser makes an HTTP request to a server and receives
a response, which it uses to render HTML on the page. ASP.NET Core allows you to
dynamically generate that HTML depending on the particulars of the request so that
you can, for example, display different data depending on the current logged-in user.
30 CHAPTER 2 Your first application
Say you want to create a web app to display information about your company. You
could create a simple ASP.NET Core app to achieve this, especially if you might later
want to add dynamic features to your app. Figure 2.1 shows how the application would
handle a request for a page in your application.
Much of this diagram should be familiar to you from figure 1.8 in chapter 1; the
request and response, the reverse proxy, and the ASP.NET Core web server are all still
there, but you’ll notice that I’ve expanded the ASP.NET Core application itself to
show the middleware pipeline and the MVC middleware. This is the main custom part
of your app that goes into generating the response from a request.
Middleware pipeline
Web host/reverse proxy
(IIS/NGINX/Apache)
ASP.NET Core
web server (Kestrel)
1. An HTTP request is
made to the server to
the homepage.
7. The HTML response
is sent to browser.
2. Request is forwarded by
IIS/NGINX/Apache to your
ASP
.NET Core app.
3. The ASP
.NET Core web server
receives the HTTP request and
passes it to middleware.
Request Response
6. Response passes through
the middleware back to the
web server.
4. The middleware processes
the request and passes it on
to the MVC middleware.
ASP.NET Core application
MVC middleware
5. MVC middleware generates
an HTML response.
Figure 2.1 An overview of an ASP.NET Core application. The ASP.NET Core application contains a
number of blocks that process an incoming request from the browser. Every request passes to the
middleware pipeline. It potentially modifies it, then passes it to the MVC middleware at the end of
the pipeline to generate a response. The response passes back through the middleware, to the
server, and finally, out to the browser.
31
A brief overview of an ASP.NET Core application
The first port of call after the reverse proxy forwards a request is the ASP.NET
Core web server, which is the default cross-platform Kestrel server. Kestrel takes the
raw incoming request and uses it to generate an HttpContext object the rest of the
application can use.
NOTE Kestrel isn’t the only HTTP server available in ASP.NET Core, but it’s
the most performant and is cross-platform. I’ll only refer to Kestrel through-
out the book. The main alternative, HTTP.sys, only runs on Windows and
can’t be used with IIS.1
Kestrel is responsible for receiving the request data and constructing a C# representa-
tion of the request, but it doesn’t attempt to handle the request directly. For that, Kes-
trel hands the HttpContext to the middleware pipeline found in every ASP.NET Core
application. This is a series of components that processes the incoming request to per-
form common operations such as logging, handling exceptions, or serving static files.
NOTE You’ll learn about the middleware pipeline in detail in the next chapter.
After the middleware pipeline comes the MVC block. This is responsible for generat-
ing the HTML that makes up the pages of a typical ASP.NET Core web app. It’s also
typically where you find most of the business logic of your app, by calling out to vari-
ous services in response to the data contained in the original request. Not every app
needs an MVC block, but it’s typically how you’ll build most apps that display HTML
to a user.
NOTE I’ll cover MVC controllers in chapter 4 and generating HTML in chap-
ters 7 and 8.
1
If you want to learn more about HTTP.sys, the documentation describes the server and how to use it:
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/httpsys.
The HttpContext object
The HttpContext constructed by the ASP.NET Core web server is used by the appli-
cation as a sort of storage box for a single request. Anything that’s specific to this
particular request and the subsequent response can be associated with it and stored
in it. This could include properties of the request, request-specific services, data
that’s been loaded, or errors that have occurred. The web server fills the initial Http-
Context with details of the original HTTP request and other configuration details and
passes it on to the rest of the application.
32 CHAPTER 2 Your first application
Most ASP.NET Core applications follow this basic architecture, and the example in
this chapter is no different. First, you’ll see how to create and run your application,
then we’ll look at how the code corresponds to the outline in figure 2.1. Without fur-
ther ado, let’s create an application!
2.2 Creating your first ASP.NET Core application
You can start building applications with ASP.NET Core in many different ways,
depending on the tools and operating system you’re using. Each set of tools will have
slightly different templates, but they have many similarities. The example used
throughout this chapter is based on a Visual Studio 2017 template, but you can easily
follow along with templates from the .NET CLI or Visual Studio for Mac.
REMINDER This chapter uses Visual Studio 2017 and ASP.NET Core 2.0.
Getting an application up and running typically follows four basic steps, which we’ll
work through in this chapter:
1 Generate—Create the base application from a template to get started.
2 Restore—Restore all the packages and dependencies to the local project folder
using NuGet.
3 Build—Compile the application and generate all the necessary assets.
4 Run—Run the compiled application.
2.2.1 Using a template to get started
Using a template can quickly get you up and running with an application, automati-
cally configuring many of the fundamental pieces for you. Both Visual Studio and the
.NET CLI come with a number of standard templates for building web applications,
console applications, and class libraries. To create your first web application, open
Visual Studio and perform the following steps:
1 Choose File > New > Project.
2 From the Templates node on the left, choose .NET Core, and then select
ASP.NET Core Web Application.
3 Enter a Name, Location, and a solution name (optional) and click OK, as
shown in figure 2.2.
4 On the following screen (figure 2.3):
 Select ASP.NET Core 2.0. The generated application will target ASP.NET
Core 2.0.
 Select Web Application (Model-View-Controller). This ensures you create a
traditional web application that generates HTML and is designed to be viewed
by users in a web browser directly. The other web application template uses the
33
Creating your first ASP.NET Core application
new Razor Pages2
functionality of ASP.NET Core 2.0. The Web API template
generates an application that returns data in a format that can be consumed
by single-page applications (SPAs) and APIs. The Angular, React.js, and
React.js and Redux templates create applications for specific SPAs.
 Ensure No Authentication is specified. You’ll learn how to add users to your
app in chapter 14.
 Ensure Enable Docker Support is unchecked.
 Click OK.
2
Razor Pages is a page-based alternative framework to the traditional MVC approach. It was introduced in ASP
.NET Core 2.0. You can read about it here: https://docs.microsoft.com/en-us/aspnet/core/mvc/razor-pages/.
Select ASP
.NET Core
Web Application.
Click OK to select the template
and create your project.
Choose .NET Core
from templates menu.
Enter a name and location
for your project and solution.
Figure 2.2 The new project dialog. To create a new .NET Core application, select ASP.NET Core Web Application
from the .NET Core templates. Enter a name, location, and a solution name (optional) and click OK.
34 CHAPTER 2 Your first application
5 Wait for Visual Studio to generate the application from the template. Once
Visual Studio has finished generating the application, you’ll be presented with
an introductory page about ASP.NET Core, and you should be able to see that
Visual Studio has created and added a number of files to your project, as shown
in figure 2.4.
NOTE If you’re developing using the .NET CLI, you can create a similar
application by running dotnet new mvc –o WebApplication2 from the com-
mand line. The -o argument ensures the CLI creates the template in a sub-
folder called WebApplication2.
Ensure the authentication
scheme is set to
No Authentication.
Select ASP
.NET Core 2.0.
Select Web Application
(Model-View-Controller).
Click OK to generate the
application from the
selected template.
Uncheck Enable
Docker Support.
Figure 2.3 The web application template screen. This screen follows on from the New Project dialog and lets
you customize the template that will generate your application. For this starter project, you’ll create an MVC
web application without authentication.
35
Creating your first ASP.NET Core application
2.2.2 Building the application
At this point, you have most of the files necessary to run your application, but you’ve
got two steps left. First, you need to ensure all the dependencies used by your project
are copied to your local directory, and second, you need to compile your application
so that it can be run.
The first of these steps isn’t strictly necessary, as both Visual Studio and the .NET
CLI automatically restore packages when they first create your project, but it’s good to
know what’s going on. In earlier versions of the .NET CLI, before 2.0, you needed to
manually restore packages using dotnet restore.
You can compile your application by choosing Build > Build Solution, by using the
shortcut Ctrl+Shift+B, or by running dotnet build from the command line. If you
build from Visual Studio, the output window that shows the progress of the build, and
assuming everything is hunky dory, will compile your application, ready for running.
You can also run the dotnet build console commands from the Package Manager
Console in Visual Studio.
TIP Visual Studio and the .NET CLI tools will automatically build your appli-
cation when you run it if they detect that a file has changed, so you generally
won’t need to explicitly perform this step yourself.
Solution Explorer
shows the files in
your project.
An introductary
page is shown
when your
project is
first created.
Figure 2.4 Visual Studio after creating a new ASP.NET Core application from a template.
36 CHAPTER 2 Your first application
2.3 Running the web application
You’re ready to run your first application and there are a number of different ways to go
about it. In Visual Studio, you can either click the green arrow on the toolbar next to IIS
Express, or press the F5 shortcut. Visual Studio will automatically open a web browser
window for you with the appropriate URL and, after a second or two, you should be pre-
sented with your brand new application, as shown in figure 2.5. Alternatively, you can
run the application from the command line with the .NET CLI tools using dotnet run
and open the URL in a web browser manually, using the address provided on the com-
mand line.
NuGet packages and the .NET Core command line interface
One of the foundational components of .NET Core cross-platform development is the
.NET Core command line interface (CLI). This provides a number of basic commands
for creating, building, and running .NET Core applications. Visual Studio effectively calls
these automatically, but you can also invoke them directly from the command line if
you’re using a different editor. The most common commands during development are
 dotnet restore
 dotnet build
 dotnet run
Each of these commands should be run inside your project folder and will act on that
project alone.
All ASP.NET Core applications have dependencies on a number of different external
libraries, which are managed through the NuGet package manager. These dependen-
cies are listed in the project, but the files of the libraries themselves aren’t included.
Before you can build and run your application, you need to ensure there are local cop-
ies of each dependency in your project folder. The first command, dotnet restore,
will ensure your application’s NuGet dependencies are copied to your project folder.
With the 2.0 version of the .NET CLI, you no longer need to explicitly run this com-
mand; later commands will implicitly restore packages for you, if necessary.
ASP.NET Core projects list their dependencies in the project’s csproj file. This is an
XML file that lists each dependency as a PackageReference node. When you run
dotnet restore, it uses this file to establish which NuGet packages to download
and copy to your project folder. Any dependencies listed are available for use in your
application.
You can compile your application using dotnet build. This will check for any errors
in your application and, if there are no issues, will produce an output that can be run
using dotnet run.
Each command contains a number of switches that can modify its behavior. To see
the full list of available commands, run
dotnet --help
or to see the options available for a particular command, new for example, run
dotnet new --help
37
Running the web application
By default, this page shows a variety of links to external resources and a big banner car-
ousel at the top of the page, which scrolls through several images. At the top of the page
are three links: Home, About, and Contact. The Home link is the page you’re currently
on. Clicking About or Contact will take you to a new page, as shown in figure 2.6. As
you’ll see shortly, you’ll use MVC in your application to define these three pages and to
build the HTML they display.
Figure 2.5 The homepage of your new ASP.NET Core application
Figure 2.6 The About page of your application. You can navigate between the three pages of the
application using the Home, About, and Contact links in the application’s header. The app generates
the content of the pages using MVC.
38 CHAPTER 2 Your first application
At this point, you need to notice a couple of things. First, the header containing the
links and the application title “WebApplication2” is the same on all three pages. Sec-
ond, the title of the page, as shown in the tab of the browser, changes to match the
current page. You’ll see how to achieve these features in chapter 7, when we discuss
the rendering of HTML using Razor templates.
There isn’t any more to the user experience of the application at this stage. Click
around a little and, once you’re happy with the behavior of the application, roll up
your sleeves—it’s time to look at some code!
2.4 Understanding the project layout
When you’re new to a framework, creating an application from a template like this can
be a mixed blessing. On the one hand, you can get an application up and running
quickly, with little input required on your part. Conversely, the number of files can
sometimes be overwhelming, leaving you scratching your head working out where to
start. The basic web application template doesn’t contain a huge number of files and
folders, as shown in figure 2.7, but I’ll run through the major ones to get you oriented.
The .sln and Git version control files
are found outside the project folder.
The root project folder is nested
in a top-level solution directory.
Solution Explorer from
Visual Studio
Folder view from Window
s
Explorer or Visual Studio Code
The wwwroot and Properties folders
are shown as special nodes in Visual
Studio, but they do exist on disk.
The Connected Services and
Dependencies nodes do not
exist on disk.
Program.cs and Startup.cs control
the startup and configuration of
your application at runtime.
The csproj file contains all the details
required to build your project, including
the NuGet packages used by your project.
Figure 2.7 The Solution Explorer and folder on disk for a new ASP.NET Core application. The Solution Explorer
also displays the Connected Services and Dependencies nodes, which list the NuGet and client-side
dependencies, though the folders themselves don’t exist on disk.
39
Understanding the project layout
The first thing to notice is that the main project, WebApplication2, is nested in a top-
level directory with the name of the solution, also WebApplication2 in this case.
Within this top-level folder, you’ll also find the solution (.sln) file for use by Visual Stu-
dio and files related to Git version control,3
though these are hidden in Visual
Studio’s Solution Explorer view.
NOTE Visual Studio uses the concept of a solution to work with multiple pro-
jects. The example solution only consists of a single project, which is listed in
the .sln file.
Inside the solution folder, you’ll find your project folder, which in turn contains five
subfolders—Models, Controllers, Views, Properties, and wwwroot. Models, Control-
lers, and Views (unsurprisingly) contain the MVC Model, Controller, and View files
you’ll use to build your application. The Properties folder contains a single file,
launchSettings.json, which controls how Visual Studio will run and debug the applica-
tion. The wwwroot folder is special, in that it’s the only folder in your application that
browsers are allowed to directly access when browsing your web app. You can store
your CSS, JavaScript, images, or static HTML files in here and browsers will be able to
access them. They won’t be able to access any file that lives outside of wwwroot.
Although the wwwroot and Properties folders exist on disk, you can see that Solu-
tion Explorer shows them as special nodes, out of alphabetical order, near the top of
your project. You’ve got two more special nodes in the project, Dependencies and
Connected Services, but they don’t have a corresponding folder on disk. Instead, they
show a collection of all the dependencies, such as NuGet packages, client-side depen-
dencies, and remote services that the project relies on.
In the root of your project folder, you’ll find several JSON files, such as appsettings
.json, bundleconfig.json, and bower.json. These provide various configuration set-
tings, some of which are used at runtime, and others which are used to build your app
at compile time.
NOTE Bower is a client-side asset management system for obtaining CSS and
JavaScript libraries. Bower is no longer supported, and so the ASP.NET team
are exploring alternatives to fulfill this role. The bower.json file will likely be
removed from the default templates and replaced in ASP.NET Core 2.1.
The most important file in your project is WebApplication2.csproj, as it describes how
to build your project. Visual Studio doesn’t explicitly show the csproj file in Solution
Explorer, but you can edit it if you right-click the project name and choose Edit
WebApplication2.csproj. We’ll have a closer look at this file in the next section.
3
The Git files will only be added if you select Create new Git repository from the New Project dialog. You don’t
have to use Git, but I strongly recommend using some sort of version control when you build applications. If
you’re somewhat familiar with Git, but still find it a bit daunting, and a rebase terrifying, I highly recommend
reading http://think-like-a-git.net/. It helped me achieve Git enlightenment.
40 CHAPTER 2 Your first application
Finally, Visual Studio shows two C# files in the project folder—Program.cs and
Startup.cs. In sections 2.6 and 2.7, you’ll see how these fundamental classes are
responsible for configuring and running your application.
2.5 The csproj project file: defining your dependencies
The csproj file is the project file for .NET applications and contains the details
required for the .NET tooling to build your project. It defines the type of project
being built (web app, console app, or library), which platform the project targets
(.NET Core, .NET Framework 4.5, Mono, and so on), and which NuGet packages the
project depends on.
The project file has been a mainstay of .NET applications, but in ASP.NET Core it
has had a facelift to make it easier to read and edit. These changes include:
 No GUIDs—Previously, Global Unique Identifiers (GUIDs) were used for many
things, now they’re rarely used in the project file.
 Implicit file includes—Previously, every file in the project had to be listed in the
csproj file for it to be included in the build. Now, files are automatically com-
piled.
 No paths to NuGet package dlls—Previously, you had to include the path to the dll
files contained in NuGet packages in the csproj, as well as listing the dependen-
cies in a packages.xml file. Now, you can reference the NuGet package directly
in your csproj, and don’t need to specify the path on disk.
All of these changes combine to make the project file far more compact than you’ll be
used to from previous .NET projects. The following listing shows the entire csproj file
for your sample app.
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All"
Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Version="2.0.0"
Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" />
</ItemGroup>
</Project>
Listing 2.1 The csproj project file, showing SDK, target framework, and references
The SDK attribute specifies the
type of project you’re building.
The TargetFramework
is the framework you’ll
run on, in this case,
.NET Core 2.0.
You reference NuGet
packages with the
PackageReference
element.
Additional tools used by Visual Studio to
generate controllers and views at design time
41
The Program class: building a web host
For simple applications, you probably won’t need to change the project file much. The
Sdk attribute on the Project element includes default settings that describe how to build
your project, whereas the TargetFramework element describes the framework your appli-
cation will run on. For .NET Core 2.0 projects, this will have the netcoreapp2.0 value; if
you’re running on the full .NET Framework, 4.6.1, this would be net461.
TIP With the new csproj style, Visual Studio users can right-click a project in
Solution Explorer and choose Edit <projectname>.csproj, without having to
close the project first.
The most common changes you’ll make to the project file are to add additional
NuGet packages using the PackageReference element. By default, your app refer-
ences a single NuGet package, Microsoft.AspNetCore.All. This is a metapackage that
includes all of the packages associated with ASP.NET Core 2.0. It’s only available when
you’re targeting .NET Core, but it means you don’t have to reference each individual
ASP.NET Core package.
DEFINITION A metapackage is a NuGet package that contains no code, refer-
encing one or more other NuGet packages instead. By adding the metapack-
age to your app, you can conveniently and implicitly add all of the packages it
references. In ASP.NET Core 2.1, the Microsoft.AspNetCore.App metapack-
age package is referenced by default instead. You can read about the differ-
ence between the App and All metapackages at https://github.com/aspnet/
Announcements/issues/287.
As well as NuGet package references, you can add command-line tools to your project
file. The default template includes a tool used under the covers by Visual Studio for
code generation. You’ll see how to add new tools in chapter 12.
The simplified project file format is much easier to edit by hand than previous ver-
sions, which is great if you’re developing cross-platform. But if you’re using Visual Stu-
dio, don’t feel like you have to take this route. You can still use the GUI to add project
references, exclude files, manage NuGet packages, and so on. Visual Studio will
update the project file itself, as it always has.
TIP For further details on the changes to the csproj format, see the docu-
mentation at https://docs.microsoft.com/en-us/dotnet/core/tools/csproj.
The project file defines everything Visual Studio and the .NET CLI need to build your
app. Everything, that is, except the code! In the next section, we’ll take a look at the
entry point for your ASP.NET Core application—the Program.cs class.
2.6 The Program class: building a web host
All ASP.NET Core applications start in the same way as .NET Console applications—
with a Program.cs file. This file contains a static void Main function, which is a stan-
dard characteristic of console apps. This method must exist and is called whenever
42 CHAPTER 2 Your first application
you start your web application. In ASP.NET Core applications, it’s used to build and
run an IWebHost instance, as shown in the following listing, which shows the default
Program.cs file. The IWebHost is the core of your ASP.NET Core application, contain-
ing the application configuration and the Kestrel server that listens for requests and
sends responses.
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args)
.Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
The Main function contains all the basic initialization code required to create a web
server and to start listening for requests. It uses a WebHostBuilder, created by the call
to CreateDefaultBuilder, to define how the IWebHost is configured, before instanti-
ating the IWebHost with a call to Build().
NOTE You’ll find this pattern of using a builder object to configure a com-
plex object repeated throughout the ASP.NET Core framework. It’s a useful
technique for allowing users to configure an object, delaying its creation until
all configuration has finished. It’s one of the patterns described in the “Gang
of Four” book, Design Patterns: Elements of Reusable Object-Oriented Software
(Addison Wesley, 1994).
Much of your app’s configuration takes place in the WebHostBuilder created by the
call to CreateDefaultBuilder, but it delegates some responsibility to a separate class,
Startup. The Startup class referenced in the generic UseStartup<> method is where
you configure your app’s services and define your middleware pipeline. In section 2.7,
we’ll spend a while delving into this crucial class.
At this point, you may be wondering why you need two classes for configuration:
Program and Startup. Why not include all of your app’s configuration in one class or
the other?
Figure 2.8 shows the typical split of configuration components between Program
and Startup. Generally speaking, Program is where you configure the infrastructure
Listing 2.2 The default Program.cs configures and runs an IWebHost
Create an IWebHost using
the BuildWebHost method.
Run the IWebHost,
start listening for
requests and
generating
responses.
Create a WebHostBuilder
using the default
configuration.
The Startup class defines
most of your application’s
configuration.
Build and return an
instance of IWebHost
from the WebHostBuilder.
43
The Program class: building a web host
of your application, such as the HTTP server, integration with IIS, and configuration
sources. In contrast, Startup is where you define which components and features
your application uses, and the middleware pipeline for your app.
The Program class for two different ASP.NET Core applications will generally be simi-
lar, but the Startup classes will often differ significantly (though they generally follow
a similar pattern, as you’ll see shortly). You’ll rarely find that you need to modify Pro-
gram as your application grows, whereas you’ll normally update Startup whenever you
add additional features. For example, if you add a new NuGet dependency to your
project, you’ll normally need to update Startup to make use of it.
The Program class is where a lot of app configuration takes place, but in the default
templates this is hidden inside the CreateDefaultBuilder method. The Create-
DefaultBuilder method is a static helper method, introduced in ASP.NET Core 2.0,
to simplify the bootstrapping of your app by creating a WebHostBuilder with some
common configuration. In chapter 11, we’ll peek inside this method and explore the
configuration system, but for now, it’s enough to keep figure 2.8 in mind, and to be
aware that you can completely change the IWebHost configuration if you need to.
Once the configuration of the WebHostBuilder is complete, the call to Build pro-
duces the IWebHost instance, but the application still isn’t handling HTTP requests
yet. It’s the call to Run that starts the HTTP server listening. At this point, your applica-
tion is fully operational and can respond to its first request from a remote browser.
Program Startup
HTTP server (Kestrel)
Content root
IIS integration
Dependency injection
Middleware pipeline
MVC configuration
Loads configuration settings
at runtime, such as connection
strings, usernames, and passwords
Program.cs is used to configure
infrastructure that rarely changes
over the lifetime of a project.
Startup is used to configure the
majority of your application’s
custom behavior.
The middleware pipeline
is defined in code
in Startup.
To correctly create classes
at runtime, dependencies
are registered with a
container.
Logging
Application settings
Figure 2.8 The difference in configuration scope for Program and Startup. Program is concerned
with infrastructure configuration that will typically remain stable throughout the lifetime of the project.
In contrast, you’ll often modify Startup to add new features and to update application behavior.
44 CHAPTER 2 Your first application
2.7 The Startup class: configuring your application
As you’ve seen, Program is responsible for configuring a lot of the infrastructure for
your app, but you configure some of your app’s behavior in Startup. The Startup
class is responsible for configuring two main aspects of your application:
 Service registration—Any classes that your application depends on for providing
functionality—both those used by the framework and those specific to your
application—must be registered so that they can be correctly instantiated at
runtime.
 Middleware and MVC—How your application handles and responds to requests.
You configure each of these aspects in its own method in Startup, service registration
in ConfigureServices, and middleware configuration in Configure. A typical outline
of Startup is shown in the following listing.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// method details
}
public void Configure(IApplicationBuilder app)
{
// method details
}
}
The WebHostBuilder created in Program calls ConfigureServices and then Configure,
as shown in figure 2.9. Each call configures a different part of your application, making
it available for subsequent method calls. Any services registered in the Configure-
Services method are available to the Configure method. Once configuration is com-
plete, an IWebHost is created by calling Build() on the WebHostBuilder.
An interesting point about the Startup class is that it doesn’t implement an inter-
face as such. Instead, the methods are invoked by using reflection to find methods with
the predefined names of Configure and ConfigureServices. This makes the class
more flexible and enables you to modify the signature of the method to accept addi-
tional parameters that are fulfilled automatically. I’ll cover how this works in detail in
chapter 10, for now it’s enough to know that anything that’s configured in
ConfigureServices can be accessed by the Configure method.
DEFINITION Reflection in .NET allows you to obtain information about types in
your application at runtime. You can use reflection to create instances of
classes at runtime, and to invoke and access them.
Listing 2.3 An outline of Startup.cs showing how each aspect is configured
Configure services by registering
services with the IServiceCollection.
Configure the middleware pipeline
for handling HTTP requests.
45
The Startup class: configuring your application
Given how fundamental the Startup class is to ASP.NET Core applications, the rest of
section 2.7 walks you through both ConfigureServices and Configure, to give you a
taste of how they’re used. I won’t explain them in detail (we have the rest of the book
for that!), but you should keep in mind how they follow on from each other and how
they contribute to the application configuration as a whole.
2.7.1 Adding and configuring services
ASP.NET Core uses small, modular components for each distinct feature. This allows
individual features to evolve separately, with only a loose coupling to others, and is
generally considered good design practice. The downside to this approach is that it
places the burden on the consumer of a feature to correctly instantiate it. Within your
application, these modular components are exposed as one or more services that are
used by the application.
DEFINITION Within the context of ASP.Net Core, service refers to any class that
provides functionality to an application and could be classes exposed by a
library or code you’ve written for your application.
For example, in an e-commerce app, you might have a TaxCalculator that calculates
the tax due on a particular product, taking into account the user’s location in the
Program
Startup
WebHostBuilder
ConfigureServices()
Configure()
IWebHost Once configuration is complete,
the IWebHost is created by calling
Build() on the WebHostBuilder.
The IWebHost is created in Program
using the Builder pattern, and the
CreateDefaultBuilder helper method.
Build()
The middleware pipeline
is defined in the Configure
method. It controls how
your application responds
to requests.
The WebHostBuilder calls out
to Startup to configure
your application.
To correctly create classes
at runtime, dependencies
are registered with a
container in the
ConfigureServices method.
Figure 2.9 The WebHostBuilder is created in Program.cs and calls methods on Startup to
configure the application’s services and middleware pipeline. Once configuration is complete, the
IWebHost is created by calling Build() on the WebHostBuilder.
46 CHAPTER 2 Your first application
world. Or you might have a ShippingCostService that calculates the cost of shipping
to a user’s location. A third service, OrderTotalCalculatorService, might use both
of these services to work out the total price the user must pay for an order. Each ser-
vice provides a small piece of independent functionality, but you can combine them to
create a complete application. This is known as the single responsibility principle.
DEFINITION The single responsibility principle (SRP) states that every class
should be responsible for only a single piece of functionality—it should only
need to change if that required functionality changes. It’s one of the five
main design principles promoted by Robert C. Martin in Agile Software Develop-
ment, Principles, Patterns, and Practices (Pearson, 2011).
The OrderTotalCalculatorService needs access to an instance of ShippingCost-
Service and TaxCalculator. A naïve approach to this problem is to use the new key-
word and create an instance of a service whenever you need it. Unfortunately, this
tightly couples your code to the specific implementation you’re using and can com-
pletely undo all the good work achieved by modularizing the features in the first
place. In some cases, it may break the SRP by making you perform initialization code
as well as using the service you created.
One solution to this problem is to make it somebody else’s problem. When writing
a service, you can declare your dependencies and let another class fill those depen-
dencies for you. Your service can then focus on the functionality for which it was
designed, instead of trying to work out how to build its dependencies.
This technique is called dependency injection or the inversion of control (IoC)
principle and is a well-recognized design pattern that is used extensively.
DEFINITION Design patterns are solutions to common software design problems.
Typically, you’ll register the dependencies of your application into a “container,”
which can then be used to create any service. This is true for both your own custom
application services and the framework services used by ASP.NET Core. You must reg-
ister each service with the container before it can be used in your application.
NOTE I’ll describe the dependency inversion principle and the IoC con-
tainer used in ASP.NET Core in detail in chapter 10.
In an ASP.NET Core application, this registration is performed in the Configure-
Services method. Whenever you use a new ASP.NET Core feature in your applica-
tion, you’ll need to come back to this method and add in the necessary services. This
is not as arduous as it sounds, as shown here, taken from the example application.
public class Startup
{
// This method gets called by the runtime.
// Use this method to add services to the container.
Listing 2.4 Startup.ConfigureServices: adding services to the IoC container
47
The Startup class: configuring your application
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
}
}
You may be surprised that a complete MVC application only includes a single call to
add the necessary services, but the AddMvc() method is an extension method that
encapsulates all the code required to set up the MVC services. Behind the scenes, it
adds various Razor services for rendering HTML, formatter services, routing services,
and many more!
As well as registering framework-related services, this method is where you’d register
any custom services you have in your application, such as the example TaxCalculator
discussed previously. The IServiceCollection is a list of every known service that your
application will need to use. By adding a new service to it, you ensure that whenever a
class declares a dependency on your service, the IoC container knows how to provide it.
With your services all configured, it’s time to move on to the final configuration
step, defining how your application responds to HTTP requests.
2.7.2 Defining how requests are handled with middleware
So far, in the WebHostBuilder and Startup class, you’ve defined the infrastructure of
the application and registered your services with the IoC container. In the final config-
uration method of Startup, Configure, you define the middleware pipeline for the
application, which is what defines the app’s behavior. Here’s the Configure method
for the template application.
public class Startup
{
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
Listing 2.5 Startup.Configure: defining the middleware pipeline
The IApplicationBuilder is used
to build the middleware pipeline.
Other services can be
accepted as parameters.
Different
behavior when
in development
or production
Only runs in a
development environment
Only runs in a
production environment
Adds the static file middleware
Adds the
MVC
middleware
48 CHAPTER 2 Your first application
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
As I described previously, middleware consists of small components that execute in
sequence when the application receives an HTTP request. They can perform a whole
host of functions, such as logging, identifying the current user for a request, serving
static files, and handling errors.
The IApplicationBuilder that’s passed to the Configure method is used to
define the order in which middleware executes. The order of the calls in this method
is important, as the order they’re added to the builder in is the order they’ll execute
in the final pipeline. Middleware can only use objects created by previous middleware
in the pipeline—it can’t access objects created by later middleware. If you’re perform-
ing authorization in middleware to restrict the users that may access your application,
you must ensure it comes after the authentication middleware that identifies the cur-
rent user.
WARNING It’s important that you consider the order of middleware when
adding it to the pipeline. Middleware can only use objects created by earlier
middleware in the pipeline.
You should also note that an IHostingEnvironment parameter is used to provide dif-
ferent behavior when you’re in a development environment. When you’re running in
development (when EnvironmentName is set to "Development"), the Configure
method adds one piece of exception-handling middleware to the pipeline; in produc-
tion, it adds a different one.
The IHostingEnvironment object contains details about the current environment,
as determined by the WebHostBuilder in Program. It exposes a number of properties:
 ContentRootPath—Location of the working directory for the app, typically the
folder in which the application is running
 WebRootPath—Location of the wwwroot folder that contains static files
 EnvironmentName—Whether the current environment is a development or pro-
duction environment
IHostingEnvironment is already set by the time Startup is invoked; you can’t change
these values using the application settings in Startup. EnvironmentName is typically set
externally by using an environment variable when your application starts.
NOTE You’ll learn about hosting environments and how to change the cur-
rent environment in chapter 11.
In development, DeveloperExceptionPageMiddleware (added by the UseDeveloper-
ExceptionPage() call) ensures that, if your application throws an exception that isn’t
49
The Startup class: configuring your application
caught, you’ll be presented with as much information as possible in the browser to
diagnose the problem, as shown in figure 2.10. It’s akin to the “yellow screen of death”
in the previous version of ASP.NET, but this time it’s white, not yellow!
NOTE The default templates also add BrowserLinkMiddleware in develop-
ment, which reloads your browser when it detects changes in your project.
But I personally find this unreliable, so tend to remove it from my projects.
When you’re running in a production environment, exposing this amount of data to
users would be a big security risk. Instead, ExceptionHandlerMiddleware is registered
so that, if users encounter an exception in your method, they will be presented with a
friendly error page that doesn’t reveal the source of the problems. If you run the
default template in production mode and trigger an error, then you’ll be presented
with the message shown in figure 2.11 instead. Obviously, you’d need to update this
page to be more visually appealing and more user-friendly, but at least it doesn’t reveal
the inner workings of your application!
Figure 2.10 The developer exception page contains many different sources of
information to help you diagnose a problem, including the exception stack trace
and details about the request that generated the exception.
50 CHAPTER 2 Your first application
The next piece of middleware added to the pipeline is StaticFileMiddleware, using
this statement:
app.UseStaticFiles();
This middleware is responsible for handling requests for static files such as CSS files,
JavaScript files, and images. When a request arrives at the middleware, it checks to see
if the request is for an existing file. If it is, then the middleware returns the file. If not,
the request is ignored and the next piece of middleware can attempt to handle the
request. Figure 2.12 shows how the request is processed when a static file is requested.
When the static-file middleware handles a request, other middleware that comes later
in the pipeline, such as the MVC middleware, won’t be called at all.
Which brings us to the final and most substantial piece of middleware in the pipe-
line: the MVC middleware. It’s responsible for interpreting the request to determine
which method to invoke, for reading parameters from the request, and for generating
the final HTML. Despite that, the only configuration required, aside from adding the
middleware to the pipeline, is to define a route that will be used to map requests to a
handler. For each request, the MVC middleware uses this route to determine the
appropriate controller and action method on the controller to invoke, and hence,
which HTML to generate.
Phew! You’ve finally finished configuring your application with all the settings, ser-
vices, and middleware it needs. Configuring your application touches on a wide range
of different topics that we’ll delve into further throughout the book, so don’t worry if
you don’t fully understand all the steps yet.
Figure 2.11 The default exception-handling page. In contrast to
the developer exception page, this doesn’t reveal any details
about your application to users. In reality, you’d update the
message to something more user-friendly.
51
The Startup class: configuring your application
Once the application is configured, it can start handling requests. But how does it han-
dle them? I’ve already touched on StaticFileMiddleware, which will serve the image
and CSS files to the user, but what about the requests that require an HTML response?
In the rest of this chapter, I’ll give you a glimpse into the MVC middleware and how it
generates HTML.
Error handler
middleware
Web host / reverse proxy
(IIS/NGINX/Apache)
ASP.NET Core
web server
1. HTTP request is made for a static file at
http://localhost:50714/css/site.css.
7. HTTP response containing the
site.css page is sent to browser.
2. Request is forwarded
by IIS/NGINX/Apache
to ASP
.NET Core.
3. ASP
.NET Core web server
receives the HTTP request
and passes it to the
middleware.
Request Response
6. Response passes through
the middleware back to the
web server.
4. The request passes through
the error handler middleware
unmodified and into the static
file middleware.
ASP.NET Core application
Static file
middleware
As the static file middleware
handled the request, the MVC
middleware is never run, and
never sees the request.
MVC
middleware
5. The static file middleware
handles the request by
returning the appropriate
site.css file, short-circuiting
the pipeline.
Figure 2.12 An overview of a request for a static file at /css/site.css for an ASP.NET Core
application. The request passes through the middleware pipeline until it’s handled by the static file
middleware. This returns the requested CSS file as the response, which passes back to the web
server. The MVC middleware is never invoked and never sees the request.
52 CHAPTER 2 Your first application
2.8 MVC middleware and the home controller
When an ASP.NET Core application receives a request, it progresses through the mid-
dleware pipeline until a middleware component can handle it, as you saw in figure
2.12. Normally, the final piece of middleware in a pipeline is the MVC middleware.
This middleware will attempt to match a request’s path to a configured route, which
defines which controller and action method to invoke. A path is the remainder of the
request URL, once the domain has been removed. For example, for a request to
www.microsoft.com/account/manage, the path is /account/manage.
DEFINITION An action is a method that runs in response to a request. A con-
troller is a class that contains a number of action methods that can be logi-
cally grouped.
Once the middleware has selected a controller and action for a given request, the
appropriate class will be instantiated and the action method invoked. Controllers are
ordinary classes, though they often inherit from the Controller base class to provide
access to a number of helper methods, as shown in the following listing. Similarly,
there’s nothing special about action methods, other than that they typically return an
IActionResult, which contains instructions for handling the request.
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
public IActionResult About()
{
ViewData["Message"] =
"Your application description page.";
return View();
}
public IActionResult Contact()
{
ViewData["Message"] = "Your contact page.";
return View();
}
public IActionResult Error()
{
Listing 2.6 The HomeController—an MVC controller
Action
methods
typically
return a
ViewResult
by calling
the View()
helper
method.
MVC controllers can inherit
from a helper base class but
don’t have to.
Data can be passed to
a view using the
ViewData dictionary.
Data can be passed to
a view using the
ViewData dictionary.
If not specified,
the name of the
view is taken
from the name
of the action
method.
53
MVC middleware and the home controller
return View(new ErrorViewModel
{
RequestId = Activity.Current?.Id ??
HttpContext.TraceIdentifier
});
}
}
This example shows the HomeController, which exposes four different action meth-
ods. You may remember from figures 2.5 and 2.6 that the application contains three
links: Home, About, and Contact. These links correspond to the Index, About, and
Contact action methods. ExceptionMiddlewareHandler calls the remaining action
method, Error, when an error occurs in production.
Each method calls the View function that’s defined on the Controller base class.
This method creates a ViewResult object, which implements IActionResult. By
returning a ViewResult, the action is asking the MVC middleware to invoke a view to
generate the HTML for a response. The name of the view is taken from the name of
the action. So, in the case of the first action method, the view will be called Index.
NOTE Action methods can use async and await if you need to use asynchro-
nous programming.4
In that case, the return value would be Task<IAction-
Result>.
As well as returning a ViewResult, the About and Contact action methods also set a
value in the ViewData dictionary. This dictionary only lasts for the duration of the
request and can be used to send arbitrary values to the view, which it can use when
generating the HTML response.
The Error method uses a different approach to pass data from the controller to
the view. A dedicated class, a view model, is created and passed to the View() method
with data to display in the view.
DEFINITION A view model is a simple object that contains data required by the
view to render a UI.
The action methods shown in listing 2.6 and discussed in paragraphs that follow are
simple, and you may be wondering why they’re worth having. If all they do is return a
view to generate, then why do we need controllers at all?
The key thing to remember here is that you now have a framework for performing
arbitrarily complex functions in response to a request. You could easily update the
action methods to load data from the database, send an email, add a product to a bas-
ket, or create an order—all in response to a simple HTTP request. This extensibility is
where a lot of the power in MVC lies.
4
For guidance on asynchronous programming, when to use it, and how to use the async and await keywords,
see https://docs.microsoft.com/en-us/dotnet/csharp/async.
Data can also be
passed to a view
using a view
model.
54 CHAPTER 2 Your first application
The other important point is that you’ve separated the execution of these meth-
ods from the generation of the HTML, as shown in figure 2.13. If the logic changes
and you need to add behavior to an action, you don’t need to touch the HTML gen-
eration code, so you’re less likely to introduce bugs. Conversely, if you need to
change the UI slightly, change the color of the title for example, then your action
method logic is safe.
With this in mind, all that’s left to demonstrate is how to generate the HTML that you
send to the client.
2.9 Generating HTML with Razor template views
When an action method returns a ViewResult, it’s signaling to the MVC middleware that
it should find a Razor template and generate the appropriate view. In the example, the
action method doesn’t specify the name of the view to find, so the middleware attempts
to find an appropriate file by using naming conventions, as shown in figure 2.14.
Razor view templates are stored in cshtml files (a portmanteau of cs and html)
within the Views folder of your project. Generally, they’re named according to their
associated actions, and reside in subfolders corresponding to the name of the control-
ler. When a controller returns a ViewResult, the Razor engine attempts to locate the
appropriate view template, as illustrated in figure 2.15. For example, considering the
HomeController you’ve already seen, the view template for the About action method
can be found at ViewsHomeAbout.cshtml, relative to the base project path.
Index action
MVC HomeController
ViewResult
View
HTML response
Request
1. HTTP request is made to
MVC page at /Home/Index.
2. The MVC HomeController
handles the request by
executing the Index action.
3. The Index action
returns a ViewResult. 4. The Razor view engine
executes the ViewResult
using a View template
to generate an HTML
response.
5. The HTML response is returned
to the middleware pipeline.
Figure 2.13 The execution of an MVC controller in response to a request. The HomeController
handles a request to /Home/Index by executing the Index action. This generates a ViewResult
that contains data that a view uses to generate the HTML response. The execution of the action
is independent of the HTML generation, so you can modify one without affecting the other.
55
Generating HTML with Razor template views
If the MVC middleware can’t find a view with the appropriate name in the expected
location, it will search in one other location. The Shared folder contains views that
any controller can access. For example, the previous HomeController contains an
Error action method, but there’s no corresponding cshtml template in the
ViewsHome folder. In the ViewsShared folder, however, there’s an appropriately
named view, so the template can be found and rendered without error.
TIP You can modify all of these conventions, including the algorithm shown
in figure 2.15, during initial configuration. In fact, you can replace the whole
Razor templating engine if required, but that’s beyond the scope of this book.
Razor view files
reside in the
Views folder.
Views for the
HomeController
will be found in
the Home folder
by default.
Views in the Shared
folder can be called
by any controller.
Views have the
same name as their
corresponding
action method.
Figure 2.14 View files are located at runtime based on naming conventions. Razor view files reside
in a folder based on the name of the associated MVC controller and are named with the name of the
action method that requested it. Views in the Shared folder can be used by any controller.
56 CHAPTER 2 Your first application
Has an absolute
view path been
provided?
Yes
No
Does a view exist in
views/controller/action?
Controller
name
Action
name
Yes Use provided
absolute path.
Use
views/controller/action.
Use
views/shared/action.
Does a view exist in
views/shared/action?
Yes
No
Throw exception:
InvalidOperationException
MVC action creates
a ViewResult.
Action
name
No
Does a view exist
at the provided path?
No
Throw exception:
InvalidOperationException
Yes
Figure 2.15 The algorithm used to locate view templates in ASP.NET Core when an
action creates a ViewResult. If the ViewResult has been created using a specific
path to a view, and a view exists at the path, then the view will be used. If not, the
controller name and action name will be used to try and find the view. If that doesn’t
exist, the Shared folder and action name will be used. If no view exists at any of the
locations, an InvalidOperationException will be thrown.
57
Generating HTML with Razor template views
Once the template has been located, it can be executed. Figure 2.16 shows the result
of executing the About action method and rendering the associated Razor template.
The following listing shows the contents of the About.cshtml file. You can see that
the file consists of a combination of some standalone C# code, some standalone
HTML, and some points where you’re writing C# values in the HTML.
@{
ViewData["Title"] = "About";
}
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>
<p>Use this area to provide additional information.</p>
This file, although small, demonstrates three features of Razor that you can use when
building your templates. The simplest and most obvious point is that standalone,
static HTML is always valid in a template and will be rendered as is in the response.
Second, you can write ordinary C# code in Razor templates by using this construct
@{ /* C# code here */ }
Any code between the curly braces will be executed but won’t be written to the
response. In the listing you’re setting the title of the page by writing a key to the View-
Data dictionary, but you aren’t writing anything to the response at this point:
@{
ViewData["Title"] = "About";
}
Listing 2.7 A simple Razor template—About.cshtml
Figure 2.16 Rendering a Razor template to HTML. The About
action is invoked and returns a ViewResult. The MVC
middleware searches for the appropriate Razor template and
executes it to generate the HTML displayed in the browser.
C# code that doesn’t write to
the response
HTML with dynamic C# values
written to the response
Standalone,
static HTML
58 CHAPTER 2 Your first application
Another feature shown in this template is dynamically writing C# variables to the
HTML stream using the @ symbol. This ability to combine dynamic and static markup
is what gives Razor templates their power. In the example, you’re fetching the "Title"
and "Message" values from the ViewData dictionary and writing the values to the
response inside the <h2> and <h3> tags, respectively:
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>
You may also remember that you previously set the value of the "Message" key in the
About action method, which you’re now retrieving and writing to the response. This is
one of the ways to pass data from an action method to a view—we’ll discuss others in
chapter 7.
At this point, you might be a little confused by the template from the listing when
compared to the output shown in figure 2.16. The title and the message values appear
in both the listing and figure, but some parts of the final web page don’t appear in the
template. How can that be?
Razor has the concept of layouts, which are base templates that define the common
elements of your application, such as headers and footers. These layouts define a
writeable section in which they will render the action method template, such as the
code in listing 2.7. The HTML of the layout combines with the action method tem-
plate to produce the final HTML that’s sent to the browser. This prevents you having
to duplicate code for the header and footer in every page and means that, if you need
to tweak something, you’ll only need to do it in one place.
NOTE I’ll cover Razor templates, including layouts, in detail in chapter 7.
And there you have it, a complete ASP.NET Core MVC application! Before I move on,
we’ll take one last look at how our application handles a request. Figure 2.17 shows a
request to the /Home/About path being handled by the sample application. You’ve
seen everything here already, so the process of handling a request should be familiar.
It shows how the request passes through the middleware pipeline before being han-
dled by the MVC middleware. A view is used to generate the HTML response, which
passes back through the middleware to the ASP.NET Core web server, before being
sent to the user’s browser.
It’s been a pretty intense trip, but you now have a good overview of how an entire
application is configured and how it handles a request using MVC. In the next chap-
ter, you’ll take a closer look at the middleware pipeline that exists in all ASP.NET Core
applications. You’ll learn how it’s composed, how you can use it to add functionality to
your application, and how you can use it to create simple HTTP services.
59
Generating HTML with Razor template views
Error handling
middleware
Web host / reverse proxy
(IIS/NGINX/Apache)
ASP.NET Core
web server
1. HTTP request is made
to the URL /Home/About.
8. HTTP response containing HTML
for the About page is sent to browser.
.
2. Request is forwarded by
IIS/NGINX/Apache to
ASP
.NET Core.
3. ASP
.NET Core web server
receives the HTTP request
and passes it to the
middleware.
Request Response
7. The HTML response passes
back through each middleware
to the ASP
.NET Core web server.
4. Request path /Home/About
is for an MVC web page, so it
passes through the middleware
pipeline unmodified.
ASP.NET Core application
MVC middleware
6. The About action returns a
ViewResult, which is used to
generate the HTML response.
Static file
middleware
HomeController
View
5. The HomeController handles
the request by executing the
About action.
Figure 2.17 An overview of a request to the /Home/About page for the sample ASP.NET Core
application. The request is received by the reverse proxy and passed to the ASP.NET Core web server.
It then passes through the middleware pipeline unchanged, until it’s handled by the MVC middleware.
The MVC middleware generates an HTML response by executing a view template. The response passes
back through the middleware, to the server, and finally, out to the browser.
60 CHAPTER 2 Your first application
Summary
 The csproj file contains details of how to build your project, including which
NuGet packages it depends on. It’s used by Visual Studio and the .NET CLI to
build your application.
 Restoring the NuGet packages for an ASP.NET Core application downloads all
your project’s dependencies so it can be built and run.
 Program.cs defines the static void Main entry point for your application.
This function is run when your app starts, the same as for console applications.
 Program.cs is where you build an IWebHost instance, using a WebHostBuilder.
The helper method, WebHost.CreateDefaultBuilder() can be used to create a
WebHostBuilder that uses the Kestrel HTTP server, loads configuration set-
tings, sets up logging, and adds IIS integration if necessary. Calling Build() cre-
ates the IWebHost instance.
 You can start the web server and begin accepting HTTP requests by calling Run
on the IWebHost.
 Startup is responsible for service configuration and defining the middleware
pipeline.
 All services, both framework and custom application services, must be regis-
tered in the call to ConfigureServices in order to be accessed later in your
application.
 Middleware is added to the application pipeline with IApplicationBuilder.
Middleware defines how your application responds to requests.
 The order in which middleware is registered defines the final order of the mid-
dleware pipeline for the application. Typically, MvcMiddleware is the last middle-
ware in the pipeline. Earlier middleware, such as StaticFileMiddleware, will
attempt to handle the request first. If the request is handled, MvcMiddleware will
never receive the request.
 An MVC controller consists of a group of actions that can be invoked in
response to a request, based on the request URL. An action can return a
ViewResult, which will cause a Razor view to be executed and HTML to be gen-
erated.
 Razor cshtml files are located, by convention, in a folder corresponding to the
associated controller, and are named the same as their associated action.
 Razor templates can contain standalone C#, standalone HTML, and dynamic
HTML generated from C# values. By combining all three, you can build highly
dynamic applications.
 Razor layouts define common elements of a web page, such as headers and
footers. They let you extract this code into a single file, so you don’t have to
duplicate it across every Razor template.
61
Handling requests with the
middleware pipeline
In the previous chapter, you had a whistle-stop tour of a complete ASP.NET Core
application to see how the components come together to create a web application.
In this chapter, we focus in on one small subsection: the middleware pipeline.
The middleware pipeline is one of the most important parts of configuration
for defining how your application behaves and how it responds to requests. Under-
standing how to build and compose middleware is key to adding functionality to
your applications.
In this chapter, you’ll learn what middleware is and how to use it to create a
pipeline. You’ll see how you can chain multiple middleware components together,
with each component adding a discrete piece of functionality. The examples in this
This chapter covers
 What middleware is
 Serving static files using middleware
 Adding functionality using middleware
 Combining middleware to form a pipeline
 Handling exceptions and errors with middleware
62 CHAPTER 3 Handling requests with the middleware pipeline
chapter are limited to using existing middleware components, showing how to
arrange them in the correct way for your application. In chapter 19, you’ll learn how
to build your own middleware components and incorporate them into the pipeline.
We’ll begin by looking at the concept of middleware, all the things you can achieve
with it, and how a middleware component often maps to a “cross-cutting concern.”
These are the functions of an application that cut across multiple different layers. Log-
ging, error handling, and security are classic cross-cutting concerns that are all
required by many different parts of your application. As all requests pass through the
middleware pipeline, it’s the preferred location to configure and handle these aspects.
In section 3.2, I’ll explain how you can compose individual middleware compo-
nents into a pipeline. You’ll start out small, with a web app that only displays a holding
page. From there, you’ll learn how to build a simple static-file server that returns
requested files from a folder on disk.
Next, you’ll move on to a more complex pipeline containing multiple middleware.
You’ll use this example to explore the importance of ordering in the middleware pipe-
line and to see how requests are handled when your pipeline contains more than one
middleware.
In section 3.3, you’ll learn how you can use middleware to deal with an important
aspect of any application: error handling. Errors are a fact of life for all applications,
so it’s important that you account for them when building your app. As well as ensur-
ing that your application doesn’t break when an exception is thrown or an error
occurs, it’s important that users of your application are informed about what went
wrong in a user-friendly way.
You can handle errors in a number of different ways, but as one of the classic cross-
cutting concerns, middleware is well placed to provide the required functionality. In
section 3.3, I’ll show how you can use middleware to handle exceptions and errors
using existing middleware provided by Microsoft. In particular, you’ll learn about
three different components:
 DeveloperExceptionPageMiddleware—Provides quick error feedback when
building an application
 ExceptionHandlerMiddleware—Provides a user-friendly generic error page in
production
 StatusCodePagesMiddleware—Converts raw error status codes into user-
friendly error pages
By combining these pieces of middleware, you can ensure that any errors that do
occur in your application won’t leak security details and won’t break your app. On top
of that, they will leave users in a better position to move on from the error, giving
them as friendly an experience as possible.
You won’t see how to build your own middleware in this chapter—instead, you’ll
see that you can go a long way using that provided as part of ASP.NET Core. Once you
understand the middleware pipeline and its behavior, it will be much easier to under-
stand when and why custom middleware is required. With that in mind, let’s dive in!
63
What is middleware?
3.1 What is middleware?
The word middleware is used in a variety of contexts in software development and IT,
but it’s not a particularly descriptive word—what is middleware?
In ASP.NET Core, middleware is C# classes that can handle an HTTP request or
response. Middleware can
 Handle an incoming HTTP request by generating an HTTP response.
 Process an incoming HTTP request, modify it, and pass it on to another piece of
middleware.
 Process an outgoing HTTP response, modify it, and pass it on to either another
piece of middleware, or the ASP.NET Core web server.
You can use middleware in a multitude of ways in your own applications. For example,
a piece of logging middleware might note when a request arrived and then pass it on
to another piece of middleware. Meanwhile, an image-resizing middleware compo-
nent might spot an incoming request for an image with a specified size, generate the
requested image, and send it back to the user without passing it on.
The most important piece of middleware in most ASP.NET Core applications is the
MvcMiddleware class. This class normally generates all your HTML pages and API
responses and is the focus of most of this book. Like the image-resizing middleware, it
typically receives a request, generates a response, and then sends it back to the user, as
shown in figure 3.1.
6. The response is returned
to the ASP.NET Core
.
web server
Logging
middleware
Image resizing
middleware
MVC middleware
1. ASP
.NET Core web server
passes the request to
the middleware pipeline.
2. The logging middleware
notes the time the request
arrived and passes the
request on to the next
.
middleware
3. If the request is for an
image of a specific size,
the image resize middleware
will handle it. If not, the
request is passed on to
.
the next middleware
5. The response passes back
through each middleware that
.
ran previously in the pipeline
Request
4. If the request makes it
through the pipeline to the
MVC middleware, it will
handle the request and
.
generate a response
Response
Figure 3.1 Example of a middleware pipeline. Each middleware handles the request and passes
it on to the next middleware in the pipeline. After a middleware generates a response, it passes
it back through the pipeline. When it reaches the ASP.NET Core web server, the response is sent
to the user’s browser.
64 CHAPTER 3 Handling requests with the middleware pipeline
DEFINITION This arrangement, where a piece of middleware can call another
piece of middleware, which in turn can call another, and so on, is referred to
as a pipeline. You can think of each piece of middleware as a section of pipe—
when you connect all the sections, a request flows through one piece and into
the next.
One of the most common use cases for middleware is for the cross-cutting concerns of
your application. These aspects of your application need to occur with every request,
regardless of the specific path in the request or the resource requested. These include
things like
 Logging each request
 Adding standard security headers to the response
 Associating a request with the relevant user
 Setting the language for the current request
In each of these examples, the middleware would receive a request, modify it, and then
pass the request on to the next piece of middleware in the pipeline. Subsequent middle-
ware could use the details added by the earlier middleware to handle the request in
some way. For example, in figure 3.2, the authentication middleware associates the
request with a user. The authorization middleware uses this detail to verify whether the
user has permission to make that specific request to the application or not.
If the user has permission, the authorization middleware will pass the request on
to the MVC middleware to allow it to generate a response. If the user doesn’t have
6. The response is returned
to the ASP
.NET Core web server.
.
The authorization middleware
handled the request, so the
MVC middleware is never run.
Authentication
middleware
Authorization
middleware
MVC middleware
1. ASP
.NET Core web server
passes the request to
middleware pipeline.
2. The authentication middleware
associates a user with the
current request.
3. The authorization middleware
uses the user associated with
the request to determine whether
the request is allowed to execute.
5. The response passes back
through each middleware that
ran previously in the pipeline.
Request
4. If the user is not allowed, the
authorization middleware will
short-circuit the pipeline.
Response
Figure 3.2 Example of a middleware component modifying the request for use later in the pipeline.
Middleware can also short-circuit the pipeline, returning a response before the request reaches later
middleware.
65
What is middleware?
permission, the authorization middleware can short-circuit the pipeline, generating a
response directly. It returns the response to the previous middleware before the MVC
middleware has even seen the request.
A key point to glean from this is that the pipeline is bidirectional. The request passes
through the pipeline in one direction until a piece of middleware generates a
response, at which point the response passes back through the pipeline, passing
through each piece of middleware for a second time, until it gets back to the first piece
of middleware. Finally, this first/last piece of middleware will pass the response back
to the ASP.NET Core web server.
As you saw in chapter 2, you define the middleware pipeline in code as part of your
initial application configuration in Startup. You can tailor the middleware pipeline
specifically to your needs—simple apps may need only a short pipeline, whereas large
apps with a variety of features may use much more middleware. Middleware is the fun-
damental source of behavior in your application—ultimately, the middleware pipeline
is responsible for responding to any HTTP requests it receives.
Requests are passed to the middleware pipeline as HttpContext objects. As you saw
in chapter 2, the ASP.NET Core web server builds an HttpContext object from an
incoming request, which passes up and down the middleware pipeline. When you’re
using existing middleware to build a pipeline, this is a detail you’ll rarely have to deal
with. But, as you’ll see in the final section of this chapter, its presence behind the
scenes provides a route to exerting extra control over your middleware pipeline.
The HttpContext object
We mentioned the HttpContext in chapter 2, and it’s sitting behind the scenes here
too. The ASP.NET Core web server constructs an HttpContext, which the ASP.NET
Core application uses as a sort of storage box for a single request. Anything that’s
specific to this particular request and the subsequent response can be associated
with and stored in it. This could include properties of the request, request-specific
services, data that’s been loaded, or errors that have occurred. The web server fills
the initial HttpContext with details of the original HTTP request and other configura-
tion details and passes it on to the rest of the application.
All middleware has access to the HttpContext for a request. It can use this, for
example, to determine whether the request contained any user credentials, which
page the request was attempting to access, and to fetch any posted data. It can then
use these details to determine how to handle the request.
Once the application has finished processing the request, it will update the Http-
Context with an appropriate response and return it through the middleware pipeline
to the web server. The ASP.NET Core web server will then convert the representation
into a raw HTTP response and send it back to the reverse proxy, which will forward it
to the user’s browser.
66 CHAPTER 3 Handling requests with the middleware pipeline
That’s pretty much all there is to the concept of middleware. In the next section, I’ll
discuss ways you can combine middleware components to create an application, and
how to use middleware to separate the concerns of your application.
3.2 Combining middleware in a pipeline
Generally speaking, each middleware component has a single primary concern. It will
handle one aspect of a request only. Logging middleware will only deal with logging the
request, authentication middleware is only concerned with identifying the current user,
and static-file middleware is only concerned with returning static files (when requested).
Each of these concerns is highly focused, which makes the components themselves
small and easy to reason about. It also gives your app added flexibility; adding a static-
file middleware doesn’t mean you’re forced into having image-resizing behavior or
authentication. Each of these features is an additional piece of middleware.
To build a complete application, you compose multiple middleware components
together into a pipeline, as shown in the previous section. Each middleware has access
to the original request, plus any changes made by previous middleware in the pipe-
line. Once a response has been generated, each middleware can inspect and/or mod-
ify the response as it passes back through the pipeline, before it’s sent to the user. This
allows you to build complex application behaviors from small, focused components.
In the rest of this section, you’ll see how to create a middleware pipeline by com-
posing small components together. Using standard middleware components, you’ll
learn to create a holding page and to serve static files from a folder on disk. Finally,
you’ll take another look at the default middleware pipeline you built previously, in
chapter 2, and decompose it to understand why it’s built like it is.
Middleware vs. HTTP modules and HTTP handlers
In the previous version of ASP.NET, the concept of a middleware pipeline isn’t used.
Instead, we have HTTP modules and HTTP handlers.
An HTTP handler is a process that runs in response to a request and generates the
response. For example, the ASP.NET page handler runs in response to requests for
.aspx pages. Alternatively, you could write a custom handler that returns resized
images when an image is requested.
HTTP modules handle the cross-cutting concerns of applications, such as security,
logging, or session management. They run in response to the lifecycle events that a
request progresses through when it’s received by the server. Examples of events
include BeginRequest, AcquireRequestState, and PostAcquireRequestState.
This approach works, but it’s sometimes tricky to reason about which modules will
run at which points. Implementing a module requires a relatively detailed understand-
ing of the state of the request at each individual lifecycle event.
The middleware pipeline makes understanding your application far simpler. The pipe-
line is completely defined in code, specifying which components should run, and in
which order.
67
Combining middleware in a pipeline
3.2.1 Simple pipeline scenario 1: a holding page
For your first app, and your first middleware pipeline, you’ll learn how to create an
app consisting of a holding page. This can be useful when you’re first setting up your
application, to ensure it’s processing requests without errors.
TIP Remember, you can view the application code for this book in the
GitHub repository at https://github.com/andrewlock/asp-dot-net-core-in-
action.
In previous chapters, I’ve mentioned that the ASP.NET Core framework is composed
of many small, individual libraries. You typically add a piece of middleware by refer-
encing a package in your application’s csproj project file and configuring the middle-
ware in the Configure method of your Startup class. Microsoft ships a few standard
middleware components with ASP.NET Core for you to choose from, though you can
obviously also use third-party components from NuGet and GitHub, or you can build
your own custom middleware.
NOTE I’ll discuss building custom middleware in chapter 19.
Unfortunately, there isn’t a definitive list of Microsoft middleware available anywhere.
With a bit of searching on https://nuget.org you can often find middleware with the
functionality you need. Alternatively, the ASP.NET Core GitHub repositories
(https://github.com/aspnet) contain source code for all the Microsoft middleware.
Regretabbly they’re split across multiple different repositories, so hunting them down
can be a bit of a chore. For example, the StaticFiles repository unsurprisingly contains
the StaticFileMiddleware, whereas the Security repository contains a variety of mid-
dleware for performing both authentication and authorization.
In this section, you’ll see how to create one of the simplest middleware pipelines,
consisting of WelcomePageMiddleware only. WelcomePageMiddleware is designed to
quickly provide a sample page when you’re first developing an application, as you can
see in figure 3.3. You wouldn’t use it in a production app, but it’s a single, self-contained
middleware component you can use to ensure your application is running correctly.
TIP WelcomePageMiddleware is contained in the Microsoft.AspNetCore
.Diagnostics NuGet package, which most new project templates include by
default as part of the Microsoft.AspNetCore.All metapackage. The metapack-
age includes all of the packages in ASP.NET Core, so you won’t need to
include other packages if the metapackage is referenced.1
If the metapackage
isn’t included, you’ll need to add the package using the PackageReference
element in your csproj file.
Even though this application is simple, the exact same process occurs when it receives
an HTTP request, as shown in figure 3.4.
1
For a description of the benefits of the Microsoft.AspNetCore.All metapackage and how it works, see my blog
post at http://mng.bz/bSw8.
68 CHAPTER 3 Handling requests with the middleware pipeline
The request passes to the ASP.NET Core web server, which builds a representation of
the request and passes it to the middleware pipeline. As it’s the first (only!) middle-
ware in the pipeline, WelcomePageMiddleware receives the request and must decide
how to handle it. The middleware responds by generating an HTML response, no
matter what request it receives. This response passes back to the ASP.NET Core web
server, which forwards it on to the user to display in their browser.
As with all ASP.NET Core applications, you define the middleware pipeline in the
Configure method of Startup by adding middleware to an IApplicationBuilder
object. To create your first middleware pipeline, consisting of a single middleware
component, you can add the middleware.
using Microsoft.AspNetCore.Builder;
namespace WebApplication3
{
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.UseWelcomePage();
}
}
}
Listing 3.1 Startup for a Welcome page middleware pipeline
Figure 3.3 The Welcome page middleware response. Every request to the application, at
any path, will return the same Welcome page response.
The Startup class is very simple
for this basic application.
The Configure
method is used to
define the
middleware pipeline.
The only middleware
in the pipeline
69
Combining middleware in a pipeline
As you can see, the Startup for this application is very simple. The application has no
configuration and no services, so Startup doesn’t have a constructor or a Configure-
Services method. The only required method is Configure, in which you call Use-
WelcomePage.
You build the middleware pipeline in ASP.NET Core by calling methods on
IApplicationBuilder, but this interface doesn’t define methods like UseWelcomePage
itself.Instead,theseareextension methods,definedineachmiddleware’sNuGetpackage.
Using extension methods allows these packages to effectively add functionality to
the IApplicationBuilder class, while keeping their functionality isolated from it.
Under the hood, the methods are typically calling another extension method to add
the middleware to the pipeline. For example, behind the scenes, the UseWelcomePage
method adds the WelcomePageMiddleware to the pipeline using
UseMiddleware<WelcomePageMiddleware>();
This convention of creating an extension method for each piece of middleware and
starting the method name with Use is designed to improve discoverability when add-
ing middleware to your application. You still have to remember to reference the
appropriate NuGet package in your csproj project file before the extension methods
will become available!
Welcome page
middleware
Web host / reverse proxy
(IIS/NGINX/Apache)
ASP.NET Core
web server
1. The browser makes an
HTTP request to the server.
6. The HTTP response containing
the HTML is sent to the browser.
2. Request is forwarded
by IIS/NGINX/Apache
to ASP
.NET Core.
3. ASP
.NET Core web server
receives the HTTP request,
builds an HttpContext
object, and passes it to
the middleware pipeline.
Request Response
4. The request is handled
by the Welcome page
middleware, which generates
an HTML response and
returns it to the pipeline.
ASP.NET Core application
5. The response is passed to
the ASP
.NET Core web server.
Figure 3.4 WelcomePageMiddleware handles a request. The request passes from the
reverse proxy to the ASP.NET Core web server and, finally, to the middleware pipeline, which
generates an HTML response.
70 CHAPTER 3 Handling requests with the middleware pipeline
Calling the UseWelcomePage method adds WelcomePageMiddleware as the next
middleware in the pipeline. Although you’re only using a single middleware compo-
nent here, it’s important to remember that the order in which you make calls to
IApplicationBuilder in Configure defines the order that the middleware will run in
the pipeline.
WARNING Always take care when adding middleware to the pipeline and con-
sider the order in which it will run. A component can’t access data created by
later middleware if it comes before it in the pipeline.
This is the most basic of applications, returning the same response no matter which
URL you navigate to, but it shows how easy it is to define your application behavior
using middleware. Now we’ll make things a little more interesting and return a differ-
ent response when you make requests to different paths.
3.2.2 Simple pipeline scenario 2: Handling static files
In this section, I’ll show how to create one of the simplest middleware pipelines you
can use for a full application: a static file application.
Most web applications, including those with dynamic content, serve a number of
pages using static files. Images, JavaScript, and CSS stylesheets are normally saved to
disk during development and are served up when requested, normally as part of a full
HTML page request.
For now, you’ll use StaticFileMiddleware to create an application that only
serves static files from the wwwroot folder when requested, as shown in figure 3.5. In
this example, a static HTML file called Example.html exists in the wwwroot folder.
When you request the file using the /Example.html path, it’s loaded and returned as
the response to the request.
Static file
middleware
Request FILE
FILE
1. The static file middleware
handles the request by
returning the file requested.
2. The file stream is sent back
through the middleware
pipeline and out to
the browser.
3. The browser displays the
file returned in the response.
9
9
Figure 3.5 Serving a static HTML file using the static file middleware
71
Combining middleware in a pipeline
If the user requests a file that doesn’t exist in the wwwroot folder, for example
Missing.html, then the static file middleware won’t serve a file. Instead, a 404 HTTP
error code response will be sent to the user’s browser, which will show its default “File
Not Found” page, as shown in figure 3.6.
NOTE How this page looks will depend on your browser. In some browsers,
for example Internet Explorer (IE), you might see a completely blank page.
Building the middleware pipeline for this application is easy, consisting of a single
piece of middleware, StaticFileMiddleware, as you can see in the following listing.
You don’t need any services, so configuring the middleware pipeline in Configure
with UseStaticFiles is all that’s required.
using Microsoft.AspNetCore.Builder;
namespace WebApplication3
{
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.UseStaticFiles();
}
}
Listing 3.2 Startup for a static file middleware pipeline
Static file
middleware
Request 404
404
!
!
1. The static file middleware
handles the request by trying
to return the requested file,
but as it doesn’t exist, it
returns a raw 404 response.
2. The 404 HTTP error code
is sent back through the middleware
pipeline and to the user.
3. The browser displays its
default “File Not Found”
error page.
Figure 3.6 Returning a 404 to the browser when a file doesn’t exist. The requested file did not
exist in the wwwroot folder, so the ASP.NET Core application returned a 404 response. The
browser, Microsoft Edge in this case, will then show the user a default “File Not Found” error.
The Startup class is very
simple for this basic
static file application. The Configure method
is used to define the
middleware pipeline.
The only middleware
in the pipeline
72 CHAPTER 3 Handling requests with the middleware pipeline
TIP Remember, you can view the application code for this book in the GitHub
repository at https://github.com/andrewlock/asp-dot-net-core-in-action.
When the application receives a request, the ASP.NET Core web server handles it and
passes it to the middleware pipeline. StaticFileMiddleware receives the request and
determines whether or not it can handle it. If the requested file exists, the middleware
handles the request and returns the file as the response, as shown in figure 3.7.
If the file doesn’t exist, then the request effectively passes through the static file middle-
ware unchanged. But wait, you only added one piece of middleware, right? Surely you
can’t pass the request through to the next middleware if there isn’t another one?
Luckily, ASP.NET Core effectively adds an automatic “dummy” piece of middleware
to the end of the pipeline. This middleware always returns a 404 response if it’s called.
TIP Remember, if no middleware generates a response for a request, the
pipeline will automatically return a simple 404 error response to the browser.
Static file
middleware
Web host/reverse proxy
(IIS/NGINX/Apache)
ASP.NET Core
web server
1. HTTP request is made for
the file Example.html.
7. The HTTP response containing
the file is sent to browser.
2. Request is forwarded
by IIS / NGINX / Apache
to ASP
.NET Core.
3. ASP
.NET Core web server
receives the HTTP request,
builds an HttpContext object,
and passes it to the middleware.
Request Response
5. As the Example.html file
exists, it is returned because
the response to the request.
4. The static file middleware
checks if the Example.html
file exists in the wwwroot
folder, and, if so, retrieves it.
.
ASP.NET Core application
6. The response is passed to
the ASP
.NET Core web server.
wwwroot/Example.html
?
9
Figure 3.7 StaticFileMiddleware handles a request for a file. The middleware checks the
wwwroot folder to see if the requested Example.html file exists. The file exists, so the middleware
retrieves it and returns it as the response to the web server and, ultimately, out to the browser.
73
Combining middleware in a pipeline
This basic ASP.NET Core application allows you to easily see the behavior of the
ASP.NET Core middleware pipeline and the static file middleware in particular, but
it’s unlikely your applications will be as simple as this. It’s more likely that static files
will form one part of your middleware pipeline. In the next section, we’ll look at how
to combine multiple middleware, looking at a simple MVC application.
3.2.3 Simple pipeline scenario 3: An MVC web application
By this point, you, I hope, have a decent grasp of the middleware pipeline, insofar as
understanding that it defines your application’s behavior. In this section, you’ll see
how to combine multiple middleware to form a pipeline, using a number of standard
middleware components. As before, this is performed in the Configure method of
Startup by adding middleware to an IApplicationBuilder object.
You’ll begin by creating a basic middleware pipeline that you’d find in a typical
ASP.NET Core MVC template and then extend it by adding middleware. The output
when you navigate to the homepage of the application is shown in figure 3.8—identi-
cal to the sample application shown in chapter 2.
HTTP response status codes
Every HTTP response contains a status code and, optionally, a reason phrase describ-
ing the status code. Status codes are fundamental to the HTTP protocol and are a
standardized way of indicating common results. A 200 response, for example, means
the request was successfully answered, whereas a 404 response indicates that the
resource requested couldn’t be found.
Status codes are always three digits long and are grouped into five different classes,
based on the first digit:
 1xx—Information. Not often used, provides a general acknowledgment.
 2xx—Success. The request was successfully handled and processed.
 3xx—Redirection. The browser must follow the provided link, to allow the user
to log in, for example.
 4xx—Client error. There was a problem with the request. For example, the
request sent invalid data, or the user isn’t authorized to perform the request.
 5xx—Server error. There was a problem on the server that caused the
request to fail.
These status codes typically drive the behavior of a user’s browser. For example, the
browser will handle a 301 response automatically, by redirecting to the provided new
link and making a second request, all without the user’s interaction.
Error codes are found in the 4xx and 5xx classes. Common codes include a 404
response when a file couldn’t be found, a 400 error when a client sends invalid data
(an invalid email address for example), and a 500 error when an error occurs on the
server. HTTP responses for error codes may or may not include a response body,
which is content to display when the client receives the response.
74 CHAPTER 3 Handling requests with the middleware pipeline
As you can see in the figure, creating this simple application requires only three
pieces of middleware: MVC middleware to generate the HTML, static file middleware
to serve the image files from the wwwroot folder, and an exception handler middle-
ware to handle any errors that might occur. The configuration of the middleware
pipeline for the application occurs in the Configure method of Startup, as always,
and is shown in the following listing. As well as the middleware configuration, this also
shows the call to AddMvc() in ConfigureServices, which is required when using MVC
middleware. You’ll learn more about service configuration in chapter 10.
public class Startup
{
public class ConfigureServices(IServiceCollection services
{
services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
app.UseExceptionHandler("/Home/Error");
app.UseStaticFiles();
app.UseMvc(routes =>
Listing 3.3 A basic middleware pipeline for an MVC application
Figure 3.8 A simple MVC application. The application uses only three pieces of middleware: an
MVC middleware to serve the HTML, a static file middleware to serve the image files, and an
exception handler middleware to capture any errors.
75
Combining middleware in a pipeline
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
The addition of middleware to IApplicationBuilder to form the pipeline should be
familiar to you now, but there are a couple of points worth noting in this example.
First, all of the methods for adding middleware start with Use. As I mentioned earlier,
this is thanks to the convention of using extension methods to extend the functional-
ity of IApplicationBuilder; by prefixing the methods with Use they should be easier
to discover.
Another important point about this listing is the order of the Use methods in the
Configure method. The order in which you add the middleware to the IApplication-
Builder object is the same order in which they’re added to the pipeline. This creates a
pipeline similar to that shown in figure 3.9.
The exception handler middleware is called first, which passes the request on to the
static file middleware. The static file handler will generate a response if the request cor-
responds to a file, otherwise it will pass the request on to the MVC middleware.
The impact of ordering can most obviously be seen when you have two pieces of
middleware that are both listening for the same path. For example, the MVC middle-
ware in the example pipeline currently responds to a request to the homepage of the
application (with the "/" path) by generating the HTML response shown in figure 3.9.
Error handling
middleware
Static file
middleware
MVC middleware
The error handling middleware
was added first, so it’s the first
(and last) middleware to
process the request.
The static file middleware is
the second middleware in the
pipeline. It handles requests for
static files before they get to
the MVC middleware.
The MVC middleware is the
last in the pipeline. If it can’t
handle the request, the pipeline
returns a 404 reponse.
Request Response
Figure 3.9 The middleware pipeline for the example application in listing 3.3.
The order in which you add the middleware to IApplicationBuilder
defines the order of the middleware in the pipeline.
76 CHAPTER 3 Handling requests with the middleware pipeline
Figure 3.10 shows what happens if you reintroduce a piece of middleware you saw pre-
viously, WelcomePageMiddleware, and configure it to respond to the "/" path as well.
As you saw in section 3.2.1, WelcomePageMiddleware is designed to return a fixed
HTML response, so you wouldn’t use it in a production app, but it illustrates the point
nicely. In the following listing, it’s added to the start of the middleware pipeline and
configured to respond only to the "/" path.
public class Startup
{
public class ConfigureServices(IServiceCollection services
{
services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
app.UseWelcomePage("/");
app.UseExceptionHandler("/Home/Error");
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
Listing 3.4 Adding WelcomePageMiddleware to the pipeline
Figure 3.10 The Welcome page middleware response. The Welcome page
middleware comes before the MVC middleware, so a request to the homepage
returns the Welcome page middleware instead of the MVC response.
WelcomePageMiddleware
handles all requests to the "/ "
path and returns a sample
HTML response.
Requests to "/ " will never
reach the MVC middleware.
77
Combining middleware in a pipeline
Even though you know the MVC middleware can also handle the "/" path, Welcome-
PageMiddleware is earlier in the pipeline, so it returns a response when it receives the
request to "/", short-circuiting the pipeline, as shown in figure 3.11. None of the
other middleware in the pipeline runs for the request, so none has an opportunity to
generate a response.
Figure 3.11 Overview of the application handling a request to the "/" path. The welcome page
middleware is first in the middleware pipeline, so it receives the request before any other middleware.
It generates an HTML response, short-circuiting the pipeline. No other middleware runs for the
request.
Welcome page
middleware
Web host/reverse proxy
(IIS/NGINX/Apache)
ASP.NET Core
web server
1. HTTP request is made to the URL
http://localhost:49392/.
6. HTTP response containing the
Welcome page is sent to browser.
.
2. Request is forwarded
by IIS/NGINX/Apache
to ASP
.NET Core.
3. ASP
.NET Core web server
receives the HTTP request
and passes it to the middleware.
Request Response
5. HTML response is passed
back to ASP
.NET Core
web server.
4. The Welcome page middleware
handles the request. It returns an
HTML response, short-circuiting
the pipeline.
ASP.NET Core application
Error handler
middleware
MVC
middleware
None of the other middleware
is run for the request, so the
MVC middleware doesn’t get
a chance to handle the request.
Static file
middleware
78 CHAPTER 3 Handling requests with the middleware pipeline
If you moved WelcomePageMiddleware to the end of the pipeline, after the call to Use-
Mvc, then you’d have the opposite situation. Any requests to "/" would be handled by
the MVC middleware and you’d never see the Welcome page.
TIP You should always consider the order of middleware when adding to the
Configure method. Middleware added earlier in the pipeline will run (and
potentially return a response) before middleware added later.
All of the examples shown so far attempt to handle an incoming request and generate a
response, but it’s important to remember that the middleware pipeline is bi-directional.
Each middleware component gets an opportunity to handle both the incoming request
and the outgoing response. The order of middleware is most important for those com-
ponents that create or modify the outgoing response.
In the previous example, I included ExceptionHandlerMiddleware at the start of
the application’s middleware pipeline, but it didn’t seem to do anything. Error hand-
ling middleware characteristically ignores the incoming request as it arrives in the
pipeline, and instead inspects the outgoing response, only modifying it when an error
has occurred. In the next section, I’ll detail the types of error handling middleware
that are available to use with your application and when to use them.
3.3 Handling errors using middleware
Errors are a fact of life when developing applications. Even if you write perfect code,
as soon as you release and deploy your application, users will find a way to break it,
whether by accident or intentionally! The important thing is that your application
handles these errors gracefully, providing a suitable response to the user, and doesn’t
cause your whole application to fail.
The design philosophy for ASP.NET Core is that every feature is opt-in. So, as error
handling is a feature, you need to explicitly enable it in your application. Many differ-
ent types of errors could occur in your application and there are many different ways
to handle them, but in this section I’ll focus on two: exceptions and error status codes.
Exceptions typically occur whenever you find an unexpected circumstance. A typi-
cal (and highly frustrating) exception you’ll no doubt have experienced before is
NullReferenceException, which is thrown when you attempt to access an object that
hasn’t been initialized. If an exception occurs in a middleware component, it propa-
gates up the pipeline, as shown in figure 3.12. If the pipeline doesn’t handle the
exception, the web server will return a 500 status code back to the user.
In some situations, an error won’t cause an exception. Instead, middleware might
generate an error status code. One such case you’ve already seen is when a requested
path isn’t handled. In that situation, the pipeline will return a 404 error, which results
in a generic, unfriendly page being shown to the user, as you saw in figure 3.6.
Although this behavior is “correct,” it doesn’t provide a great experience for users of
your application.
79
Handling errors using middleware
Error handling middleware attempts to address these problems by modifying the
response before the app returns it to the user. Typically, error handling middleware
either returns details of the error that occurred or it returns a generic, but friendly,
HTML page to the user. You should always place error handling middleware early in
the middleware pipeline to ensure it will catch any errors generated in subsequent
middleware, as shown in figure 3.13. Any responses generated by middleware earlier
in the pipeline than the error handling middleware can’t be intercepted.
The remainder of this section shows several types of error handling middleware
that are available for use in your application. You can use any of them by referencing
either the Microsoft.AspNetCore.All or the Microsoft.AspNetCore.Diagnostics NuGet
packages in your project’s csproj file.
5. If the exception is not handled
by the middleware, a raw 500
status code is sent to the browser.
.
3. The MVC middleware throws
an exception during execution.
Welcome page
middleware
Error handling
middleware
1. ASP
.NET Core web
server passes request to
the middleware pipeline.
2. Each middleware
component processes
the request in turn.
4. The exception propagates
back through the pipeline, giving
each middleware the opportunity
to handle it.
Request 500
Authorization
middleware
Figure 3.12 An exception in the MVC middleware propagates through the pipeline. If the
exception isn’t caught by middleware earlier in the pipeline, then a 500 “Server error” status
code will be sent to the user’s browser.
80 CHAPTER 3 Handling requests with the middleware pipeline
3.3.1 Viewing exceptions in development: DeveloperExceptionPage
When you’re developing an application, you typically want access to as much informa-
tion as possible when an error occurs somewhere in your app. For that reason, Micro-
soft provides DeveloperExceptionPageMiddleware, which can be added to your
middleware pipeline using
app.UseDeveloperExceptionPage();
Error handling
middleware
Static file
middleware
Request HTML
404
HTML
Static file
middleware
Error handling
middleware
Request
404
404
Error handling middleware first in the pipeline
Static file middleware first in the pipeline
!
2. For example, the static file
middleware will generate a raw
404 error response if a requested
file does not exist.
1. If middleware is placed after the
error handling static middleware
in the pipeline, then any
response it generates will
pass through the error
handling middleware.
3. If the static file middleware
generates a raw 404 status code,
it will be sent directly back to
the user unmodified.
2. The error handling middleware
processes the response of
middleware later in the pipeline,
but it never sees the response
generated by the static
file middleware.
1. If middleware is placed early
in the pipeline before the error
handling static middleware, then
any error codes returned by the
middleware will not be modified.
4. The error handling middleware
can modify the raw response to
a user-friendly HTML page.
3. This raw response passes through
the error handling middleware as it
passes back through the pipeline.
!
!
9
9
Figure 3.13 Error handling middleware should be placed early in the pipeline to catch raw status code
errors. In the first case, the error handling middleware is placed before the static file middleware, so it
can replace raw status code errors with a user-friendly error page. In the second case, the error handling
middleware is placed after the static file middleware, so raw error status codes can’t be modified.
81
Handling errors using middleware
When an exception is thrown and propagates up the pipeline to this middleware, it
will be captured. The middleware then generates a friendly HTML page, which it
returns with a 500 status code to the user, as shown in figure 3.14. This page contains a
variety of details about the request and the exception, including the exception stack
trace, the source code at the line the exception occurred, and details of the request,
such as any cookies or headers that had been sent.
Having these details available when an error occurs is invaluable for debugging a
problem, but they also represent a security risk if used incorrectly. You should never
return more details about your application to users than absolutely necessary, so you
should only ever use DeveloperExceptionPage when developing your application.
The clue is in the name!
WARNING Never use the developer exception page when running in produc-
tion. Doing so is a security risk as it could publicly reveal details about your
application’s code, making you an easy target for attackers.
If the developer exception page isn’t appropriate for production use, what should you
use instead? Luckily, there’s another general-purpose error handling middleware you
can use in production, one that you’ve already seen and used: ExceptionHandler-
Middleware.
Title indicating
the problem
Buttons to click that reveal
further details about the
request that caused
the exception
Detail of the
exception
that occurred
Full stack trace
for the exception
Location in the
code where
exception occurred
Code that caused
the exception
Figure 3.14 The developer exception page shows details about the exception when it occurs
during the process of a request. The location in the code that caused the exception, the source
code line itself, and the stack trace are all shown by default. You can also click the Query,
Cookies, or Headers buttons to reveal further details about the request that caused the
exception.
82 CHAPTER 3 Handling requests with the middleware pipeline
3.3.2 Handling exceptions in production: ExceptionHandlerMiddleware
The developer exception page is handy when developing your applications, but you
shouldn’t use it in production as it can leak information about your app to potential
attackers. You still want to catch errors though, otherwise users will see unfriendly
error pages or blank pages, depending on the browser they’re using.
You can solve this problem by using ExceptionHandlerMiddleware. If an error
occurs in your application, the user will be presented with a custom error page that’s
consistent with the rest of the application, but that only provides the necessary details
about the error. For example, a custom error page, such as the one shown in figure 3.15,
can keep the look and feel of the application by using the same header, displaying the
currently logged-in user, and only displaying an appropriate message to the user instead
of the full details of the exception.
If you were to peek at the Configure method of almost any ASP.NET Core applica-
tion, you’d almost certainly find the developer exception page used in combination
with ExceptionHandlerMiddleware, in a similar manner to that shown here.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
Listing 3.5 Configuring exception handling for development and production
Menu bar consistent
with the rest of your
application
Error page contains
appropriate details for the
user details for the user.
Dynamic details such as the
current user can be shown
on the error page.
Footer consistent
with the rest of
your application
The default error page reminds
you about the developer exception
page. You would change this text
in your application to something
more generic.
Figure 3.15 A custom error page created by ExceptionHandlerMiddleware. The custom error
page can keep the same look and feel as the rest of the application by reusing elements such as the
header and footer. More importantly, you can easily control the error details displayed to users.
Configure a different pipeline
when running in development.
83
Handling errors using middleware
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/home/error");
}
// additional middleware configuration
}
As well as demonstrating how to add ExceptionHandlerMiddleware to your middle-
ware pipeline, this listing shows that it’s perfectly acceptable to configure different
middleware pipelines depending on the environment when the application starts up.
You could also vary your pipeline based on other values, such as settings loaded from
configuration.
NOTE You’ll see how to use configuration values to customize the middle-
ware pipeline in chapter 11.
When adding ExceptionHandlerMiddleware to your application, you’ll typically
provide a path to the custom error page that will be displayed to the user. In the exam-
ple listing, you used an error handling path of /home/error:
app.UseExceptionHandler("/home/error");
ExceptionHandlerMiddleware will invoke this path after it captures an exception, in
order to generate the final response. The ability to dynamically generate a response is
a key feature of ExceptionHandlerMiddleware—it allows you to re-execute a middle-
ware pipeline in order to generate the response sent to the user.
Figure 3.16 shows what happens when ExceptionHandlerMiddleware handles an
exception. It shows the flow of events when the MVC middleware generates an exception
when a request is made to the /home path. The final response returns an error status
code but also provides an HTML response to display to the user, using the /error path.
The sequence of events when an exception occurs somewhere in the middleware
pipeline after ExceptionHandlerMiddleware is as follows:
1 A piece of middleware throws an exception.
2 ExceptionHandlerMiddleware catches the exception.
3 Any partial response that has been defined is cleared.
4 The middleware overwrites the request path with the provided error handling
path.
5 The middleware sends the request back down the pipeline, as though the origi-
nal request had been for the error handling path.
6 The middleware pipeline generates a new response as normal.
7 When the response gets back to ExceptionHandlerMiddleware, it modifies the
status code to a 500 error and continues to pass the response up the pipeline to
the web server.
The developer exception page
should only be used when
running in development mode.
When in production,
ExceptionHandlerMiddleware
is added to the pipeline.
84 CHAPTER 3 Handling requests with the middleware pipeline
The Exception HandlerMiddleware
uses the new response, but updates
the status code of the response to
a 500 status code. This indicates
to the browser that an error occurred,
but the user sees a friendly web page
indicating something went wrong.
Exception
handler
MVC middleware
Exception
handler
MVC middleware
MVC middleware
Exception
handler
MVC middleware
Error handling
middleware
HTML
HTML
HTML
HTML
The ExceptionHandlerMiddleware
ignores the request initially.
The MVC middleware throws
an exception for some reason
while handling the request.
The exception propagates up the pipeline and
is caught by the ExceptionHandlerMiddleware.
This changes the path of the request to the
error path /Error and sends the request
down the middleware pipeline again.
The middleware pipeline executes
the new error path and generates
a response as usual. In this case,
the MVC middleware generates
an HTML response.
A request is passed to the pipeline
for the URL path /Home.
/Home
/Error
/Home
/Error
!
!
9
9
9
Figure 3.16 ExceptionHandlerMiddleware handling an exception to generate an HTML
response. A request to the /Home path generates an exception, which is handled by the
middleware. The pipeline is re-executed using the /Error path to generate the HTML response.
85
Handling errors using middleware
The main advantage that re-executing the pipeline brings is the ability to have your
error messages integrated into your normal site layout, as shown in figure 3.15. It’s
certainly possible to return a fixed response when an error occurs, but you wouldn’t
be able to have a menu bar with dynamically generated links or display the current
user’s name in the menu. By re-executing the pipeline, you can ensure that all the
dynamic areas of your application are correctly integrated, as if the page was a stan-
dard page of your site.
NOTE You don’t need to do anything other than add ExceptionHandler-
Middleware to your application and configure a valid error handling path to
enable re-executing the pipeline. The middleware will catch the exception
and re-execute the pipeline for you. Subsequent middleware will treat the re-
execution as a new request, but previous middleware in the pipeline won’t be
aware anything unusual happened.
Re-executing the middleware pipeline is a great way to keep consistency in your web
application for error pages, but there are some gotchas to be aware of. First, middle-
ware can only modify a response generated further down the pipeline if the response
hasn’t yet been sent to the client. This can be a problem if, for example, an error
occurs while ASP.NET Core is sending a static file to a client. In that case, where bytes
have already begun to be sent to the client, the error handling middleware won’t be
able to run, as it can’t reset the response. Generally speaking, there’s not a lot you can
do about this issue, but it’s something to be aware of.
A more common problem occurs when the error handling path throws an error
during the re-execution of the pipeline. Imagine there’s a bug in the code that gener-
ates the menu at the top of the page:
1 When the user reaches your homepage, the code for generating the menu bar
throws an exception.
2 The exception propagates up the middleware pipeline.
3 When reached, ExceptionHandlerMiddleware captures it and the pipe is re-
executed using the error handling path.
4 When the error page executes, it attempts to generate the menu bar for your
app, which again throws an exception.
5 The exception propagates up the middleware pipeline.
6 ExceptionHandlerMiddleware has already tried to intercept a request, so it will
let the error propagate all the way to the top of the middleware pipeline.
7 The web server returns a raw 500 error, as though there was no error handling
middleware at all.
Thanks to this problem, it’s often good practice to make your error handling pages as
simple as possible, to reduce the possibility of errors occurring.
86 CHAPTER 3 Handling requests with the middleware pipeline
WARNING If your error handling path generates an error, the user will see a
generic browser error. It’s often better to use a static error page that will
always work, rather than a dynamic page that risks throwing more errors.
ExceptionHandlerMiddleware and DeveloperExceptionPageMiddleware are great
for catching exceptions in your application, but exceptions aren’t the only sort of
errors you’ll encounter. In some cases, your middleware pipeline will return an HTTP
error status code in the response. It’s important to handle both exceptions and error
status codes to provide a coherent user experience.
3.3.3 Handling other errors: StatusCodePagesMiddleware
Your application can return a wide range of HTTP status codes that indicate some sort
of error state. You’ve already seen that a 500 “server error” is sent when an exception
occurs and isn’t handled and that a 404 “file not found” error is sent when a URL isn’t
handled by any middleware. 404 errors, in particular, are common, often occurring
when a user enters an invalid URL.
TIP As well as indicating a completely unhandled URL, 404 errors are often
used to indicate that a specific requested object was not found. For example,
a request for the details of a product with an ID of 23 might return a 404 if no
such product exists.
Without handling these status codes, users will see a generic error page, such as in fig-
ure 3.17, which may leave many confused and thinking your application is broken. A
better approach would be to handle these error codes and return an error page that’s
in keeping with the rest of your application or, at the very least, doesn’t make your
application look broken.
Figure 3.17 A generic browser error page. If the middleware
pipeline can’t handle a request, it will return a 404 error to the
user. The message is of limited usefulness to users and may
leave many confused or thinking your web application is broken.
87
Handling errors using middleware
Microsoft provides StatusCodePagesMiddleware for handling this use case in the
Microsoft.AspNetCore.Diagnostics package. As with all error handling middleware,
you should add it early in your middleware pipeline, as it will only handle errors gen-
erated by later middleware components.
You can use the middleware a number of different ways in your application. The
simplest approach is to add the middleware to your pipeline without any additional
configuration, using
app.UseStatusCodePages();
With this method, the middleware will intercept any response that has an HTTP Sta-
tus code that starts with 4xx or 5xx and has no response body. For the simplest case,
where you don’t provide any additional configuration, the middleware will add a plain
text response body, indicating the type and name of the response, as shown in figure
3.18. This is arguably worse than the generic method at this point, but it is a starting
point for providing a more consistent experience to users!
A more typical approach to using StatusCodePageMiddleware in production is to re-
execute the pipeline when an error is captured, using a similar technique to
ExceptionHandlerMiddleware. This allows you to have dynamic error pages that fit
with the rest of your application. To use this technique, replace the call to UseStatus-
CodePages with the following extension method
app.UseStatusCodePagesWithReExecute("/error/{0}");
This extension method configures StatusCodePageMiddleware to re-execute the pipe-
line whenever a 4xx or 5xx response code is found, using the provided error handling
path. This is similar to the way ExceptionHandlerMiddleware re-executes the pipe-
line, as shown in figure 3.19.
Figure 3.18 Status code error pages for a 404 error. You generally won’t use this
version of the middleware in production as it doesn’t provide a great user experience,
but it demonstrates that the error codes are being correctly intercepted.
88 CHAPTER 3 Handling requests with the middleware pipeline
The StatusCodePageMiddleware
uses the new response, but updates
the status code of the response to
a 404 status code. This indicates
to the browser that an error
occurred, but the user sees a
friendly web page indicating
something went wrong.
Status code
middleware
MVC middleware
Status code
middleware
MVC middleware
MVC middleware
Status code
middleware
MVC middleware
Status code
middleware
HTML
HTML
HTML
HTML
The StatusCodePageMiddleware
ignores the request initially.
The MVC middleware returns
an error status code, in this
case, a 404 response.
The response propagates up the pipeline and
is intercepted by the StatusCodePageMiddleware.
This changes the path of the request to the
error path /Error/404 and sends the request
down the middleware pipeline again.
The middleware pipeline executes
the new error path and generates
a response as usual. In this case,
the MVC middleware generates
an HTML response.
A request is passed to the pipeline
for the URL path /Home.
/Home
/error/404
/Home
/error/404
!
!
404
!
9
9
Figure 3.19 StatusCodePagesMiddleware re-executing the pipeline to generate an
HTML body for a 404 response. A request to the /Home path returns a 404 response,
which is handled by the status code middleware. The pipeline is re-executed using the
/error/404 path to generate the HTML response.
89
Handling errors using middleware
Note that the error handling path "/error/{0}" contains a format string token, {0}.
When the path is re-executed, the middleware will replace this token with the status
code number. For example, a 404 error would re-execute the /error/404 path. The
handler for the path (typically an MVC route) has access to the status code and can
optionally tailor the response, depending on the status code. You can choose any
error handling path, as long as your application knows how to handle it.
NOTE You’ll learn about MVC routing in chapter 5.
With this approach in place, you can create different error pages for different error
codes, such as the 404-specific error page shown in figure 3.20. This technique
ensures your error pages are consistent with the rest of your application, including
any dynamically generated content, while also allowing you to tailor the message for
common errors.
WARNING As before, when re-executing the pipeline, you must be careful
your error handling path doesn’t generate any errors.
You can use StatusCodePagesMiddleware in combination with other exception hand-
ling middleware by adding both to the pipeline. StatusCodePagesMiddleware will
only modify the response if no response body has been written. So if another compo-
nent, for example ExceptionHandlerMiddleware, returns a message body along with
an error code, it won’t be modified.
NOTE StatusCodePageMiddleware has additional overloads that let you exe-
cute custom middleware when an error occurs, instead of re-executing an
MVC path.
Figure 3.20 An error status code page for a missing file. When
an error code is detected (in this case, a 404 error), the
middleware pipeline is re-executed to generate the response.
This allows dynamic portions of your web page to remain
consistent on error pages.
90 CHAPTER 3 Handling requests with the middleware pipeline
Error handling is essential when developing any web application; errors happen and
you need to handle them gracefully. But depending on your application, you may not
always need error handling middleware, and sometimes you may need to disable it for
a single request.
3.3.4 Disabling error handling middleware for Web APIs
ASP.NET Core isn’t only great for creating user-facing web applications, it’s also great
for creating HTTP services that can be accessed either from another server applica-
tion, or from a user’s browser when running a client-side single-page application. In
both of these cases, you probably won’t be returning HTML to the client, but rather
XML or JSON.
In that situation, if an error occurs, you probably don’t want to be sending back a
big HTML page saying, “Oops, something went wrong.” Returning an HTML page to
an application that’s expecting JSON could easily break it unexpectedly. Instead, the
HTTP 500 status code is more descriptive and useful to a consuming application.
Luckily, this is the default behavior when you don’t add error-handling middleware
to your application. In the simplest case, where your whole application serves as an
API to another application, you could probably get away without any error handling
middleware. In reality, you may want to make sure you log the errors using middle-
ware, but you certainly don’t need to change the response body in that case.
But what if you have an ASP.NET Core application that takes on both roles—it
serves HTML to users using standard MVC controllers and acts as an API in other
cases? You’ll probably want to ensure your error handling middleware only modifies
the requests to HTML endpoints and leaves the API requests to be returned as status
codes only.
StatusCodePagesMiddleware supports being disabled for a given request. When
the middleware runs as part of the middleware pipeline, it adds a feature to the collec-
tion of features on the HttpContext object called IStatusCodePagesFeature.
DEFINITION Features define which capabilities the ASP.NET Core web server
provides. Each feature is represented as an interface in the Features collec-
tion property on HttpContext. Middleware is free to add or replace features
in the collection as part of the request, thereby extending the features avail-
able to an application.
You may remember that the ASP.NET Core web server builds an HttpContext object
representing a request, which passes up and down the middleware pipeline. By add-
ing a feature to the HttpContext collection, StatusCodePagesMiddleware broadcasts
its presence to the rest of the application. This allows other parts of the application to
disable StatusCodePagesMiddleware if required. For example, a Web API controller
could disable the feature if it doesn’t want to have error responses replaced with
HTML pages, as shown in the following listing.
91
Summary
public class ValuesController : Controller
{
public string Index()
{
var statusCodePagesFeature = HttpContext.Features
.Get<IStatusCodePagesFeature>();
if (statusCodePagesFeature != null)
{
statusCodePagesFeature.Enabled = false;
}
return StatusCode(500);
}
}
When the Index method in this API controller is hit, it will attempt to retrieve
IStatusCodePagesFeature from the feature collection on HttpContext. If Status-
CodePagesMiddleware has already run in the request pipeline, then you’ll be able to
set Enabled = false. If it hasn’t run, then the feature will be null, so you need to
remember to check that before using the variable.
The final call to return StatusCode(500) will return an HTTP 500 status code to
the client. Ordinarily, StatusCodePagesMiddleware would intercept the response and
replace it with a friendly HTML page, but in this case the raw status code is sent as it is,
without a response body.
That brings us to the end of middleware in ASP.NET Core for now. You’ve seen
how to use and compose middleware to form a pipeline, as well as how to handle
errors in your application. This will get you a long way when you start building your
first ASP.NET Core applications. Later, you’ll learn how to build your own custom
middleware, as well as how to perform complex operations on the middleware pipe-
line, such as forking it in response to specific requests.
In the next chapter, you’ll learn about the MVC design pattern and how it applies
to ASP.NET Core. You’ve already seen several examples of MVC controllers but I’ll
expand on how and when you can make use of them in your application, both for
building dynamic web applications and for Web APIs.
Summary
 Middleware has a similar role to HTTP modules and handlers in ASP.NET but
is more easily reasoned about.
 Middleware is composed in a pipeline, with the output of one middleware pass-
ing to the input of the next.
Listing 3.6 Disabling StatusCodePagesMiddleware in a web API controller
Try to get
IStatusCodePagesFeature
from the HttpContext.
If StatusCodePagesMiddleware hasn’t
been added, the feature will be null.
Disable the feature
for this request.
Return a 500 error code—it
will pass through
StatusCodePagesMiddleware
untouched.
92 CHAPTER 3 Handling requests with the middleware pipeline
 The middleware pipeline is two-way: requests pass through each middleware on
the way in and responses pass back through in the reverse order on the way out.
 Middleware can short-circuit the pipeline by handling a request and returning
a response, or it can pass the request on to the next middleware in the pipeline.
 Middleware can modify a request by adding data to, or changing, the Http-
Context object.
 If an earlier middleware short-circuits the pipeline, not all middleware will exe-
cute for all requests.
 If a request isn’t handled, the middleware pipeline will return a 404 status code.
 The order in which middleware is added to IApplicationBuilder defines the
order in which middleware will execute in the pipeline.
 The middleware pipeline can be re-executed, as long as a response’s headers
haven’t been sent.
 When added to a middleware pipeline, StaticFileMiddleware will serve any
requested files found in the wwwroot folder of your application.
 DeveloperExceptionPageMiddleware provides a lot of information about
errors when developing an application but should never be used in production.
 ExceptionHandlerMiddleware lets you provide user-friendly custom error
handling messages when an exception occurs in the pipeline.
 StatusCodePagesMiddleware lets you provide user-friendly custom error hand-
ling messages when the pipeline returns a raw error response status code.
 StatusCodePagesMiddleware can be disabled by accessing the Features prop-
erty on HttpContext.
 Microsoft provides some common middleware and there are many third-party
options available on NuGet and GitHub.
93
Creating web pages
with MVC controllers
In chapter 3, you learned about the middleware pipeline, which defines how an
ASP.NET Core application responds to a request. Each piece of middleware can
modify or handle an incoming request, before passing the request to the next mid-
dleware in the pipeline.
In ASP.NET Core web applications, the final piece of middleware in the pipe-
line will normally be MvcMiddleware. This is typically where you write the bulk of
your application logic, by calling various other classes in your app. It also serves as
the main entry point for users to interact with your app. It typically takes one of
two forms:
 An HTML web application, designed for direct use by users. If the application is
consumed directly by users, as in a traditional web application, then the
This chapter covers
 Introducing the Model-View-Controller (MVC)
design pattern
 Using MVC in ASP.NET Core
 Creating MVC controllers for serving web pages
94 CHAPTER 4 Creating web pages with MVC controllers
MvcMiddleware is responsible for generating the web pages that the user inter-
acts with. It handles requests for URLs, it receives data posted using forms, and
it generates the HTML that users use to view and navigate your app.
 An API designed for consumption by another machine or in code. The other main pos-
sibility for a web application is to serve as an API, to backend server processes,
to a mobile app, or to a client framework for building single page applications
(SPAs). The same MvcMiddleware can fulfill this role by serving data in
machine-readable formats such as JSON or XML, instead of the human-focused
HTML output.
In this chapter, you’ll learn how ASP.NET Core uses the MvcMiddleware to serve these
two requirements. You’ll start by looking at the Model-View-Controller (MVC) design
pattern to see the benefits that can be achieved through its use and learn why it’s been
adopted by so many web frameworks as a model for building maintainable applications.
Next, you’ll learn how the MVC design pattern applies specifically to ASP.NET
Core. The MVC pattern is a broad concept that can be applied in a variety of situa-
tions, but the use case in ASP.NET Core is specifically as a UI abstraction. You’ll see
how to add the MvcMiddleware to your application, as well as how to customize it for
your needs.
Once you’ve installed the middleware in your app, I’ll show how to create your first
MVC controllers. You’ll learn how to define action methods to execute when your
application receives a request and how to generate a result that can be used to create
an HTTP response to return. For traditional MVC web applications, this will be a
ViewResult that can generate HTML.
I won’t cover how to create Web APIs in this chapter. Web APIs still use the Mvc-
Middleware but they’re used in a slightly different way. Instead of returning web pages
that are directly displayed on a user’s browser, they return data formatted for con-
sumption in code. Web APIs are often used for providing data to mobile and web
applications, or to other server applications. But they still follow the same general
MVC pattern. You’ll see how to create a Web API in chapter 9.
NOTE This chapter is the first of several on MVC in ASP.NET Core and the
MvcMiddleware. As I’ve already mentioned, this middleware is often responsi-
ble for handling all the business logic and UI code for your application, so,
perhaps unsurprisingly, it’s large and somewhat complicated. The next five
chapters all deal with a different aspect of the MVC pattern that makes up the
MVC middleware.
In this chapter, I’ll try to prepare you for each of the upcoming topics, but you may
find that some of the behavior feels a bit like magic at this stage. Try not to become
too concerned with exactly how all the pieces tie together; focus on the specific con-
cepts being addressed. It should all become clear as we cover the associated details in
the remainder of this first part of the book.
95
An introduction to MVC
4.1 An introduction to MVC
Depending on your background in software development, you may have previously
come across the MVC pattern in some form. In web development, MVC is a common
paradigm and is used in frameworks such as Django, Rails, and Spring MVC. But as it’s
such a broad concept, you can find MVC in everything from mobile apps to rich-client
desktop applications. Hopefully that is indicative of the benefits the pattern can bring
if used correctly!
In this section, I’ll look at the MVC pattern in general, how it applies to ASP.NET
Core, and how to add the MvcMiddleware to your application. By the end of this sec-
tion you should have a good understanding of the benefits of this approach and how
to get started.
4.1.1 The MVC design pattern
The MVC design pattern is a common pattern for designing apps that have UIs. The
original MVC pattern has many different interpretations, each of which focuses on a
slightly different aspect of the pattern. For example, the original MVC design pattern
was specified with rich-client graphical user interface (GUI) apps in mind, rather than
web applications, and so uses terminology and paradigms associated with a GUI envi-
ronment. Fundamentally, though, the pattern aims to separate the management and
manipulation of data from its visual representation.
Before I dive too far into the design pattern itself, let’s consider a typical request.
Imagine a user of your application requests a page that displays a to-do list. What hap-
pens when the MvcMiddleware gets this request? Figure 4.1 shows how the MVC pat-
tern is used to handle different aspects of that single page request, all of which
combine to generate the final response.
1. Request for ToDoList
is received from a user.
Model
Request
Controller
View
2. The ToDoList controller
handles the request.
3. The controller class requests the current items
on the list from the ToDoList model. The model
may retrieve them from memory, a file, or a
database, for instance.
Response
4. The controller class finds the correct HTML
template for the to-do list (also called the view)
and passes it the list items from the model.
5. The ToDoList view plugs the items into the
HTML template and sends the completed
HTML page back to the user.
List items
Figure 4.1 Requesting a to-do list page for an MVC application. A different component handles
each aspect of the request.
96 CHAPTER 4 Creating web pages with MVC controllers
In general, three components make up the MVC design pattern:
 Model—The data that needs to be displayed, the state of the application.
 View—The template that displays the data provided by the model.
 Controller—Updates the model and selects the appropriate view.
Each component in an MVC application is responsible for a single aspect of the over-
all system that, when combined, can be used to generate a UI. The to-do list example
considers MVC in terms of a web application, but a request could also be equivalent to
the click of a button in a desktop GUI application.
In general, the order of events when an application responds to a user interaction
or request is as follows:
1 The controller receives the request.
2 Depending on the request, the controller either fetches the requested data
from the application model, or it updates the data that makes up the model.
3 The controller selects a view to display and passes the model to it.
4 The view uses the data contained in the model to generate the UI.
When we describe MVC in this format, the controller serves as the entry point for the
interaction. The user communicates with the controller to instigate an interaction. In
web applications, this interaction takes the form of an HTTP request, so when a
request to a URL is received, the controller handles it.
Depending on the nature of the request, the controller may take a variety of
actions, but the key point is that the actions are undertaken using the model. The
model here contains all the business logic for the application, so it’s able to provide
requested data or perform actions.
NOTE In this description of MVC, the model is considered to be a complex
beast, containing all the logic for how to perform an action, as well as any
internal state.
Consider a request to view a product page for an e-commerce application, for exam-
ple. The controller would receive the request and would know how to contact some
product service that’s part of the application model. This might fetch the details of
the requested product from a database and return them to the controller.
Alternatively, imagine the controller receives a request to add a product to the
user’s shopping cart. The controller would receive the request, and most likely invoke
a method on the model to request that the product be added. The model would then
update its internal representation of the user’s cart, by adding, for example, a new row
to a database table holding the user’s data.
After the model has been updated, the controller needs to select a way to display
the data. One of the advantages of using the MVC design pattern is that the model
representing the data is decoupled from the final representation of that data, called
the view.
97
An introduction to MVC
This separation creates the possibility for the controller to choose to display the
model using a different view, based on where the original request originated, as shown
in figure 4.2. If the request came from a standard web application, then the controller
can display an HTML view. If the request came from another application, then the
controller can choose to return the model in a format the application understands,
such as JSON or XML.
The other advantage of the model being independent of the view is that it improves
testability. UI code is classically hard to test, as it’s dependent on the environment—
anyone who has written UI tests simulating a user clicking buttons and typing in
forms knows that it’s typically fragile. By keeping the model independent of the view,
you can ensure the model stays easily testable, without any dependencies on UI con-
structs. As the model often contains your application’s business logic, this is clearly a
good thing!
Once the controller has selected a view, it passes the model to it. The view can use
the data passed to it to generate an appropriate UI, an HTML web page, or a simple
JSON object. The view is only responsible for generating the final representation.
This is all there is to the MVC design pattern in relation to web applications. Much
of the confusion related to MVC seems to stem from slightly different uses of the term
for slightly different frameworks and types of application. In the next section, I’ll show
1. A request is received
from a user.
Model
Request
Controller
Standard
web app
6. In this case, the standard
web app is selected, so an HTML
response is generated.
2. The controller handles
the request.
3. The controller fetches data
from, or updates, the model to
perform the requested action.
4. The controller selects a view
based on the caller that made the
request and passes it the model.
HTML
Select a
view
JSON
Text-based
UI application
Plain text
5. The selected view uses the
provided model to generate
an appropriate response.
Model
SPA/mobile
application
Figure 4.2 Selecting a different view using MVC depending on the caller. The final representation
of the model, created by the view, is independent of the controller and business logic.
98 CHAPTER 4 Creating web pages with MVC controllers
how the ASP.NET Core framework uses the MVC pattern, along with more examples
of the pattern in action.
4.1.2 MVC in ASP.NET Core
As you’ve seen in previous chapters, ASP.NET Core implements MVC using a single
piece of middleware, which is normally placed at the end of the middleware pipeline,
as shown in figure 4.3. Once a request has been processed by each middleware (and
assuming none of them handle the request and short-circuit the pipeline), it will be
received by the MVC middleware.
Middleware often handles cross-cutting concerns or narrowly defined requests, such
as requests for files. For requirements that fall outside of these functions, or that have
many external dependencies, a more robust framework is required. The MvcMiddle-
ware in ASP.NET Core can provide this framework, allowing interaction with your
application’s core business logic, and the generation of a UI. It handles everything
from mapping the request to an appropriate controller to generating the HTML or
API response.
In the traditional description of the MVC design pattern, there’s only a single type
of model, which holds all the non-UI data and behavior. The controller updates this
model as appropriate and then passes it to the view, which uses it to generate a UI.
This simple, three-component pattern may be sufficient for some basic applications,
but for more complex applications, it often doesn’t scale.
Error-handling
middleware
Static-file
middleware
MVC middleware
The request passes
through each middleware
in the pipeline.
Each middleware gets
an opportunity to
handle the request.
The MVC middleware is typically
the last middleware in the
pipeline. It encompasses the
whole MVC design pattern.
Request Response
Figure 4.3 The middleware pipeline.
99
An introduction to MVC
One of the problems when discussing MVC is the vague and ambiguous terms that it
uses, such as “controller” and “model.” Model, in particular, is such an overloaded
term that it’s often difficult to be sure exactly what it refers to—is it an object, a collec-
tion of objects, an abstract concept? Even ASP.NET Core uses the word “model” to
describe several related, but different, components, as you’ll see shortly.
DIRECTING A REQUEST TO A CONTROLLER AND BUILDING A BINDING MODEL
The first step when the MvcMiddleware receives a request is routing the request to an
appropriate controller. Let’s think about another page in your to-do application. On
this page, you’re displaying a list of items marked with a given category, assigned to a
particular user. If you’re looking at the list of items assigned to the user “Andrew” with
a category of “Simple,” you’d make a request to the /todo/list/Simple/Andrew URL.
Routing takes the path of the request, /todo/list/Simple/Andrew, and maps it
against a preregistered list of patterns. These patterns match a path to a single con-
troller class and action method. You’ll learn more about routing in the next chapter.
DEFINITION An action (or action method) is a method that runs in response to a
request. A controller is a class that contains a number of logically grouped
action methods.
Once an action method is selected, the binding model (if applicable) is generated,
based on the incoming request and the method parameters required by the action
When not to use the MvcMiddleware
Typically, you’ll use MvcMiddleware to write most of your application logic for an app.
You’ll use it to define the APIs and pages in your application, and to define how they
interface with your business logic. The MvcMiddleware is an extensive framework (as
you’ll see over the next six chapters) that provides a great deal of functionality to help
build your apps quickly and efficiently. But it’s not suited to every app.
Providing so much functionality necessarily comes with a certain degree of perfor-
mance overhead. For typical apps, the productivity gains from using MVC strongly out-
weigh any performance impact. But if you’re building small, lightweight apps for the
cloud, then you might consider using custom middleware directly (see chapter 19).
You might want to also consider Microservices in .NET Core by Christian Horsdal Gam-
melgaard (Manning, 2017).
Alternatively, if you’re building an app with real-time functionality, you’ll probably want
to consider using WebSockets instead of traditional HTTP requests. The WebSockets
protocol allows high-performance two-way communication between a server and a cli-
ent, but they make some things more complicated. You can use WebSockets with
ASP.NET Core, but you’ll need to handle things such as connection dropouts, server
scaling issues, and old browsers yourself. For details, see the documentation at
http://mng.bz/Ol13. SignalR Core is in preview at the time of writing (https://github
.com/aspnet/SignalR), but will handle some of these considerations for you, so it’s
worth investigating.
100 CHAPTER 4 Creating web pages with MVC controllers
method, as shown in figure 4.4. A binding model is normally a standard class, with
properties that map to the requested data. We’ll look at binding models in detail in
chapter 6.
DEFINITION A binding model is an object that acts as a “container” for the data
provided in a request that’s required by an action method.
In this case, the binding model contains two properties: Category, which is “bound”
to the "Simple" value, and the User property, which is bound to the "Andrew" value.
These values are provided in the request URL’s path and are used to populate a bind-
ing model of the TodoModel type.
This binding model corresponds to the method parameter of the ListCategory
action method. This binding model is passed to the action method when it executes,
so it can be used to decide how to respond. For this example, the action method uses
it to decide which to-do items to display on the page.
EXECUTING AN ACTION USING THE APPLICATION MODEL
The role of an action method in the controller is to coordinate the generation of a
response to the request it’s handling. That means it should perform only a limited
number of actions. In particular, it should
 Validate that the data contained in the binding model provided is valid for the
request.
 Invoke the appropriate actions on the application model.
 Select an appropriate response to generate based on the response from the
application model.
1. A request is received and
handled by the middleware.
Request
Action
2. The routing module directs the
request to a specific controller
and action.
Binding model
Routing
3. A binding model is built
from the details provided
in the request.
Controller
4. The action is passed to the
binding model and a method
parameter and is executed.
URL mapped to action method
model.Category = "Simple"
model.User = "Andrew"
ListCategory(model)
TodoController.ListCategory
GET URL
/todo/list/Simple/Andrew
Action method is executed
Figure 4.4 Routing a request to a controller and building a binding model. A request to the
/todo/list/Simple/Andrew URL results in the ListCategory action being executed,
passing in a populated binding model.
101
An introduction to MVC
Figure 4.5 shows the action method invoking an appropriate method on the applica-
tion model. Here, you can see that the application model is a somewhat abstract con-
cept that encapsulates the remaining non-UI part of your application. It contains the
domain model, a number of services, and the database interaction.
DEFINITION The domain model encapsulates complex business logic in a series
of classes that don’t depend on any infrastructure and can be easily tested.
The action method typically calls into a single point in the application model. In our
example of viewing a product page, the application model might use a variety of ser-
vices to check whether the user is allowed to view the product, to calculate the display
price for the product, to load the details from the database, or to load a picture of the
product from a file.
Assuming the request is valid, the application model will return the required
details to the action method. It’s then up to the action method to choose a response
to generate.
GENERATING A RESPONSE USING A VIEW MODEL
Once the action method has called out to the application model that contains the
application business logic, it’s time to generate a response. A view model captures the
details necessary for the view to generate a response.
DEFINITION A view model is a simple object that contains data required by the
view to render a UI. It’s typically some transformation of the data contained
in the application model, plus extra information required to render the page,
for example the page’s title.
The action method selects an appropriate view template and passes the view model to
it. Each view is designed to work with a particular view model, which it uses to generate
Action
1. The action uses the category
and user provided in the binding
model to determine which method
to invoke in the application model.
Application model
Domain
model
Services
Database
interaction
2. The action method calls into
services that make up the application
model. This might use the domain
model to calculate the price of the
product, for example.
Controller
3. The services load the
details of the product from
the database and return
them to the action model.
Figure 4.5 When executed, an action will invoke the appropriate methods in the application model.
102 CHAPTER 4 Creating web pages with MVC controllers
the final HTML response. Finally, this is sent back through the middleware pipeline
and out to the user’s browser, as shown in figure 4.6.
It’s important to note that although the action method selects which view to display,
it doesn’t select what is generated. It’s the view itself that decides what the content of the
response will be.
PUTTING IT ALL TOGETHER: A COMPLETE MVC REQUEST
Now that you’ve seen each of the steps that goes into handling a request in ASP.NET
Core using MVC, let’s put it all together from request to response. Figure 4.7 shows
how all of the steps combine to handle the request to display the list of to-dos for user
“Andrew” and the “Simple” category. The traditional MVC pattern is still visible in
ASP.NET Core, made up of the action/controller, the view, and the application model.
By now, you might be thinking this whole process seems rather convoluted—so
many steps to display some HTML! Why not allow the application model to create the
view directly, rather than having to go on a dance back and forth with the control-
ler/action method?
The key benefit throughout this process is the separation of concerns:
 The view is responsible only for taking some data and generating HTML.
 The application model is responsible only for executing the required business
logic.
 The controller is responsible only for validating the incoming request and select-
ing the appropriate view to display, based on the output of the application model.
By having clearly defined boundaries, it’s easier to update and test each of the compo-
nents without depending on any of the others. If your UI logic changes, you won’t
necessarily have to modify any of your business logic classes, so you’re less likely to
introduce errors in unexpected places.
1. The ListCategory action
builds a view model from
the data provided by the
application model.
Action
View
2. The controller selects the
ListCategory view and passes
it the view model containing
the list of relevant to-dos.
3. The view uses the provided
view model to generate an HTML
response containing the details
of the to-dos to display
.
View model
Controller
HTML
4. The response is sent
back through the
middleware pipeline.
Figure 4.6 The action method builds a view model, selects which view to use to generate
the response, and passes it the view model. It’s the view that generates the response.
103
An introduction to MVC
1. A request is received for the
URL /todo/list/Simple/Andrew.
Request
Action
View
2. The routing module directs
the request to the ListCategory
action on the ToDoController
and builds a binding model.
4. The controller selects the
ListCategory view and passes
it the view model containing
the details about the product.
5. The view uses the provided view
model to generate an HTML response,
which is returned to the user.
Application model
Binding model
View model
Domain
model
Services
Database
interaction
Routing
3. The action method calls into
services that make up the application
model to fetch details about the
product and to build a view model.
Controller
HTML
Figure 4.7 A complete MVC request for the list of to-dos in the “Simple” category for user “Andrew”
The dangers of tight coupling
Generally speaking, it’s a good idea to reduce coupling between logically separate
parts of your application as much as possible. This makes it easier to update your
application without causing adverse effects or requiring modifications in seemingly
unrelated areas. Applying the MVC pattern is one way to help with this goal.
As an example of when coupling rears its head, I remember a case a few years ago
when I was working on a small web app. In our haste, we had not properly decoupled
our business logic from our HTML generation code, but initially there were no obvious
problems—the code worked, so we shipped it!
A few months later, someone new started working on the app, and immediately
“helped” by renaming an innocuous spelling error in a class in the business layer.
Unfortunately, the names of those classes had been used to generate our HTML
code, so renaming the class caused the whole website to break in users’ browsers!
Suffice it to say, we made a concerted effort to apply the MVC pattern after that, and
ensure we had a proper separation of concerns.
104 CHAPTER 4 Creating web pages with MVC controllers
The examples shown in this chapter demonstrate the vast majority of the MVC mid-
dleware functionality. It has additional features, such as the filter pipeline, that I’ll
cover later (chapter 13), and I’ll discuss binding models in greater depth in chapter 6,
but the overall behavior of the system is the same.
Similarly, in chapter 9, I’ll discuss how the MVC design pattern applies when
you’re generating machine-readable responses using Web API controllers. The pro-
cess is, for all intents and purposes, identical, with the exception of the final result
generated.
In the next section, you’ll see how to add the MVC middleware to your application.
Most templates in Visual Studio and the .NET CLI will include the MVC middleware
by default, but you’ll see how to add it to an existing application and explore the vari-
ous options available.
4.1.3 Adding the MvcMiddleware to your application
The MVC middleware is a foundational aspect of all but the simplest ASP.NET Core
applications, so virtually all templates include it configured by default. But to make
sure you’re comfortable with adding MVC to an existing project, I’ll show how to start
with a basic empty application and add the MVC middleware to it from scratch.
The result of your efforts won’t be exciting yet. We’ll display “Hello World” on a
web page, but it’ll show how simple it is to convert an ASP.NET Core application to
use MVC. It also emphasizes the pluggable nature of ASP.NET Core—if you don’t
need the functionality provided by the MVC middleware, then you don’t have to
include it. Here’s how you add the MvcMiddleware to your application:
1 In Visual Studio 2017, choose File > New > Project.
2 From the New Project dialog, choose .NET Core, then select ASP.NET Core
Web Application.
3 Enter a Name, Location, and optionally a solution name, and click OK.
4 Create a basic template without MVC by selecting the Empty Project template
in Visual Studio, as shown in figure 4.8. You can create a similar empty project
using the .NET CLI with the dotnet new web command.
105
An introduction to MVC
Ensure Enable Docker
Support is unchecked.
Select Empty
template.
Click OK to generate the
application from the
selected template.
Select ASP
.NET
Core 2.0.
Figure 4.8 Creating an empty ASP.NET Core template. The empty template will create a simple ASP.NET Core
application that contains a small middleware pipeline, but not the MvcMiddleware.
106 CHAPTER 4 Creating web pages with MVC controllers
5 Edit your project file by right-clicking the project and selecting Edit Project
.csproj, where Project is the name of your project, as shown in figure 4.9.
1. Right-Click on your
project’s name in
Solution Explorer.
2. Choose Edit csproj. The
csproj file has the same
name as your project.
Figure 4.9 You can edit the csproj file in Visual Studio while you have the project open.
Alternatively, edit the csproj file directly in a text editor.
107
An introduction to MVC
6 The default ASP.NET Core 2.0 template references the Microsoft.AspNetCore
.All metapackage in its csproj file:
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
This includes all of the packages in ASP.NET Core, so you won’t need to refer-
ence any others.1
If you’re targeting the full .NET Framework instead of .NET
Core, you can’t use the metapackage, so you’ll need to add the Microsoft.Asp-
NetCore.Mvc package to your project explicitly. You can add NuGet packages
using the graphical NuGet Package Manager in Visual Studio by editing the
csproj file to include the package, or by running the following command from
the project folder (not the solution folder):
dotnet add package Microsoft.AspNetCore.Mvc
This adds a <PackageReference> element to your project’s csproj file:
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0" />
7 Add the necessary MVC services (in bold) in your Startup.cs file’s Configure-
Services method:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
8 Add the MvcMiddleware to the end of your middleware pipeline with the Use-
Mvc extension method (in bold). For simplicity, remove any other middleware
from the Configure method of Startup.cs for now:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
9 Right-click your project in Solution Explorer and choose Add > Class, as shown
in figure 4.10.
1
In ASP.NET Core 2.1, a different metapackage, Microsoft.AspNetCore.App, is referenced, which includes
slightly fewer packages than the All metapackage. See https://github.com/aspnet/Announcements/issues/
287 for details.
108 CHAPTER 4 Creating web pages with MVC controllers
10 In the dialog box, name your class HomeController and click OK, as shown in
figure 4.11.
11 Add an action called Index (in bold) to the generated class:
public class HomeController
{
public string Index()
{
return "Hello world!";
}
}
3. Choose Class to add
a basic class to your project.
1. Right-click on your
project name to bring
up the context menu.
2. Click Add to open
the Add submenu.
Figure 4.10 Adding a new class to your project
109
An introduction to MVC
Once you’ve completed all these steps, you should be able to restore, build, and run
your application.
NOTE You can run your project by pressing F5 from within Visual Studio (or
by calling dotnet run at the command line from the project folder). This will
restore any referenced NuGet packages, build your project, and start your
application. Visual Studio will automatically open a browser window to access
your application’s homepage.
When you make a request to the "/" path, the application invokes the Index method
on HomeController due to the way you configured routing in the call to UseMvc. Don’t
worry about this for now; we’ll go into it in detail in the next chapter.
This returns the "Hello world!" string value, which is rendered in the browser as
plain text. You’re returning data rather than a view here, so it’s more of a Web API
controller, but you could’ve created a ViewResult to render HTML instead.
You access the MVC functionality by adding the Microsoft.AspNetCore.Mvc pack-
age (or more commonly the Microsoft.AspNetCore.All metapackage) to your project.
The MvcMiddleware relies on a number of internal services to perform its function,
which must be registered during application startup. This is achieved with the call to
AddMvc in the ConfigureServices method of Startup.cs. Without this, you’ll get
exceptions at runtime when the MvcMiddleware is invoked, reminding you that the
call is required.
Click Add to add the
class to your project.
Leave Class selected.
Enter a name for
the new controller.
Figure 4.11 Creating a new MVC controller class using the Add New Item dialog box
110 CHAPTER 4 Creating web pages with MVC controllers
The call to UseMvc in Configure registers the MvcMiddleware itself in the middle-
ware pipeline. As part of this call, the routes that are used to map URL paths to con-
trollers and actions are registered. We used the default convention here, but you can
easily customize these to match your requirements.
NOTE I’ll cover routing in detail in the next chapter.
As you might expect, the MvcMiddleware comes with a large number of options for
configuring how it behaves in your application. This can be useful when the default
conventions and configuration don’t meet your requirements. You can modify these
options by passing a configuration function to the AddMvc call that adds the MVC ser-
vices. This listing shows how you could use this method to customize the maximum
number of validation errors that the MVC middleware can handle.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.MaxModelValidationErrors = 100;
});
}
You can replace many parts of the MVC middleware internals this way, thanks to the
extensible design of the middleware. You won’t often need to touch the MVC options,
but it’s nice to be able to customize them when the need arises.
Listing 4.1 Configuring MVC options in Startup.cs
AddMvc has an
overload that
takes a lambda
function.
A number of properties are
available to customize the
MvcMiddleware behavior.
Customizing the MVC middleware internals
As I’ve hinted, the MvcMiddleware exposes a large amount of its internal configura-
tion through the AddMvc method, as shown in the following figure. The options
object contains many different properties that you can use to extend and modify the
default behavior of the middleware.
Some of the customizations
options available when
configuring the
MvcMiddleware
111
An introduction to MVC
The final part of adding MVC to your application is creating the controllers that are
invoked when a request arises. But what makes a class act as a controller?
4.1.4 What makes a controller a controller?
Controllers in ASP.NET Core are classes that contain a logical grouping of action
methods. How you define them is largely up to you, but there are a number of conven-
tions used by the runtime to identify controllers.
MVC or Web API controllers are discovered and used by the MvcMiddleware as long as
they are instantiable (they have a public constructor, aren’t static, and aren’t abstract)
and either:
 Have a name ending in “Controller,” for example HomeController
By manipulating these options, you can control things such as how data is read from
the request, how the data should be validated, and how the output data is formatted.
You can even modify the way actions function across your whole application. For details
on the options available, see the API reference on https://docs.microsoft.com.
Convention over configuration
Convention over configuration is a sometimes-controversial approach to building
applications, in which a framework makes certain assumptions about the structure
or naming of your code. Conforming to these assumptions reduces the amount of
boilerplate code a developer must write to configure a project—you typically only need
to specify the cases where your requirements don’t match the assumptions.
Imagine you have a number of provider classes that can load different types of files.
At runtime, you want to be able to let the user select a file and you’d automatically
select the correct provider. To do this, you could explicitly register all the providers in
some sort of central configuration service, or you could use a convention to rely on
the runtime to “find” your classes and do the wiring up for you. Typically, in .NET, this
is achieved by using reflection to search through all the types in an assembly and find
those with a particular name, that derive from a base class, or that contain specific
named methods.
ASP.NET Core takes this approach in a number of cases, perhaps most notably with
the Startup class, whose configuration methods are “discovered” at runtime, rather
than by explicitly implementing an interface.
This approach can sometimes result in an almost magical effect of things working for
no apparent reason, which some people find confusing. It can occasionally make
debugging problems tricky due to the additional level of indirection at play. On the
other hand, using conventions can result in terser code, as there’s no explicit wiring-
up necessary, and can provide additional flexibility, for example by allowing the sig-
nature of the methods in your Startup class to vary.
112 CHAPTER 4 Creating web pages with MVC controllers
 Inherit from the Controller or ControllerBase class (or a class that inherits
from these)
The MvcMiddleware will identify any class that meets these requirements at runtime
and make it available to handle requests as required. Although not required, the Mic-
rosoft.AspNetCore.Mvc package provides a base class, Controller, that your control-
lers can inherit from. It’s often a good idea to use this class given that it contains a
number of helper methods for returning results, as you’ll see later in this chapter.
TIP If you’re building a Web API, you can also inherit from ControllerBase.
This includes many of the same helper methods, but no helpers for creating
views.
Another common convention is to place all your controller files in a Controllers sub-
folder in your project, as shown in figure 4.12. This can be useful for organizing some
projects, but it isn’t required for the MvcMiddleware to discover them. You’re free to
place your controller files anywhere you like in your project folder.
Based on these requirements, it’s possible to come up with a variety of naming conven-
tions and hierarchies for your controllers, all of which will be discovered at runtime.
In general, it’s far better to stick to the common convention of naming your control-
lers by ending them with Controller, and optionally inheriting from the Controller
base class.
By convention, controllers
are placed in a Controllers
folder.
Controllers do not have to be
placed in the Controllers folder
—
they will still be discovered.
Figure 4.12 Controller location conventions in ASP.NET Core. MVC applications
often place controllers in a Controllers subfolder, but they can be located anywhere
in your project—the MvcMiddleware will still identify and make them available
to handle requests.
113
MVC controllers and action methods
public class HomeController: Controller
{
public ViewResult Index()
{
return View();
}
}
public class ValuesController
{
public string Get()
{
return "Hello world!";
}
}
It’s worth noting that although these examples have (implicit) parameterless con-
structors, it’s perfectly acceptable to have dependencies in your constructor. In fact,
this is one of the preferred mechanisms for accessing other classes and services from
your controllers. By requiring them to be passed during the construction of the con-
troller, you explicitly define the dependencies of your controller, which, among other
things, makes testing easier. The dependency injection container will automatically
populate any required dependencies when the controller is created.
NOTE See chapter 10 for details about configuring, and using, dependency
injection.
The controllers you’ve seen so far all contain a single action method, which will be
invoked when handling a request. In the next section, I’ll look at action methods, how
to define them, how to invoke them, and how to use them to return views.
4.2 MVC controllers and action methods
In the first section of this chapter, I described the MVC design pattern and how it
relates to ASP.NET Core. In the design pattern, the controller receives a request and is
the entry point for UI generation. In ASP.NET Core, the entry point is an action
method that resides in a controller. An action, or action method, is a method that runs in
response to a request.
MVC controllers can contain any number of action methods. Controllers provide a
mechanism to logically group actions and so apply a common set of rules to them. For
example, it’s simple to require a user to be logged in when accessing any action
method on a given controller by applying an attribute to the controller; you don’t
need to apply the attribute to every individual action method.
NOTE You’ll see how to apply authorization requirements to your actions and
controllers in chapter 15.
Listing 4.2 Common conventions for defining controllers
Suffix your controller
names with Controller
Inheriting from the Controller
base class allows access to
utility methods like View().
If you’re not using the
utility methods in your
controller, you don’t need
to inherit from Controller.
114 CHAPTER 4 Creating web pages with MVC controllers
Any public method on a controller acts as an action method, and so can be invoked by
a client (assuming the routing configuration allows it). The responsibility of an action
method is generally threefold:
 Confirm the incoming request is valid.
 Invoke the appropriate business logic corresponding to the incoming request.
 Choose the appropriate kind of response to return.
An action doesn’t need to perform all of these actions, but at the very least it must
choose the kind of response to return. For a traditional MVC application that’s return-
ing HTML to a browser, action methods will typically return either a ViewResult that
the MvcMiddleware will use to generate an HTML response, or a RedirectResult,
which indicates the user should be redirected to a different page in your application.
In Web API applications, action methods often return a variety of different results, as
you’ll see in chapter 9.
It’s important to realize that an action method doesn’t generate a response directly;
it selects the type of response and prepares the data for it. For example, returning a
ViewResult doesn’t generate any HTML at that point, it indicates which view template
to use and the view model it will have access to. This is in keeping with the MVC design
pattern in which it’s the view that generates the response, not the controller.
TIP The action method is responsible for choosing what sort of response to
send; the view engine in the MvcMiddleware uses the action result to generate
the response.
It’s also worth bearing in mind that action methods should generally not be perform-
ing business logic directly. Instead, they should call appropriate services in the appli-
cation model to handle requests. If an action method receives a request to add a
product to a user’s cart, it shouldn’t directly manipulate the database or recalculate
cart totals, for example. Instead, it should make a call to another class to handle the
details. This approach of separating concerns ensures your code stays testable and
manageable as it grows.
4.2.1 Accepting parameters to action methods
Some requests made to action methods will require additional values with details
about the request. If the request is for a search page, the request might contain details
of the search term and the page number they’re looking at. If the request is posting a
form to your application, for example a user logging in with their username and pass-
word, then those values must be contained in the request. In other cases, there will be
no such values, such as when a user requests the homepage for your application.
The request may contain additional values from a variety of different sources. They
could be part of the URL, the query string, headers, or in the body of the request
itself. The middleware will extract values from each of these sources and convert them
into .NET types.
115
MVC controllers and action methods
If an action method definition has method arguments, the additional values in the
request are used to create the required parameters. If the action has no arguments,
then the additional values will go unused. The method arguments can be simple
types, such as strings and integers, or they can be a complex type, as shown here.
public class HomeController: Controller
{
private SearchService _searchService;
public HomeController(SearchService searchService)
{
_searchService = searchService;
}
public ViewResult Index()
{
return View();
}
public IActionResult Search(SearchModel searchModel)
{
if(ModelState.IsValid)
{
var viewModel = _searchService.Search(searchModel);
return View(viewModel);
}
return Redirect("/")
}
}
In this example, the Index action method doesn’t require any parameters, and the
method is simple—it returns a view to the user. The Search action method, con-
versely, accepts a SearchModel object. This could contain multiple different proper-
ties that are obtained from the request and are set on the model in a process called
model binding. The SearchModel object is often described as a binding model.
NOTE I’ll discuss model binding in detail in chapter 6.
When an action method accepts parameters, it should always check that the model
provided is valid using ModelState.IsValid. The ModelState property is exposed
when you inherit from the base Controller or ControllerBase class and can be used
to check that the method parameters are valid. You’ll see how the process works in
chapter 6 when you learn about validation.
Once an action establishes that the method parameters provided to an action are
valid, it can execute the appropriate business logic and handle the request. In the case
Listing 4.3 Example action methods
The SearchService is provided to the
HomeController for use in action methods.
An action without parameters
requires no additional values
in the request.
The method doesn’t
need to check if the
model is valid, it just
returns a response.
If the model was not valid, the
method indicates the user should
be redirected to the path “/”.
If the model is valid, a view model is
created and passed to the view.
The action
method
requires the
request to
have values for
the properties
in
SearchModel.
116 CHAPTER 4 Creating web pages with MVC controllers
of the Search action, this involves calling the provided SearchService to obtain a view
model. This view model is then returned in a ViewResult by calling the base method
return View(viewModel);
If the model wasn’t valid, then you don’t have any results to display! In this example,
the action returns a RedirectResult using the Redirect helper method. When exe-
cuted, this result will send a 302 redirect response to the user, which will cause their
browser to navigate to the homepage.
Note that the Index method returns a ViewResult in the method signature,
whereas the Search method returns an IActionResult. This is required in the Search
method in order to allow the C# to compile (as the View and Redirect helper meth-
ods return different types of values), but it doesn’t change the final behavior of the
methods. You could just as easily have returned an IActionResult in the Index
method and the behavior would be identical.
TIP If you’re returning more than one type of result from an action method,
you’ll need to ensure your method returns an IActionResult.
4.2.2 Using ActionResult
In the previous section, I emphasized that action methods only decide what to gener-
ate, and don’t perform the generation of the response. It’s the IActionResult
returned by an action method which, when executed by the MvcMiddleware using the
view engine, will generate the response.
This approach is key to following the MVC design pattern. It separates the decision
of what sort of response to send from the generation of the response. This allows you
to easily test your action method logic to confirm the right sort of response is sent for
a given output. You can then separately test that a given IActionResult generates the
expected HTML, for example.
ASP.NET Core has many different types of IActionResult:
 ViewResult—Generates an HTML view.
 RedirectResult—Sends a 302 HTTP redirect response to automatically send a
user to a specified URL.
 RedirectToRouteResult—Sends a 302 HTTP redirect response to automati-
cally send a user to another page, where the URL is defined using routing.
 FileResult—Returns a file as the response.
 ContentResult—Returns a provided string as the response.
 StatusCodeResult—Sends a raw HTTP status code as the response, optionally
with associated response body content.
 NotFoundResult—Sends a raw 404 HTTP status code as the response.
Each of these, when executed by the MvcMiddleware, will generate a response to send
back through the middleware pipeline and out to the user.
117
MVC controllers and action methods
VIEWRESULT AND REDIRECTRESULT
When you’re building a traditional web application and generating HTML, most of
the time you’ll be using the ViewResult, which generates an HTML response using
Razor (by default). We’ll look at how this happens in detail in chapter 7.
You’ll also commonly use the various redirect-based results to send the user to a
new web page. For example, when you place an order on an e-commerce website you
typically navigate through multiple pages, as shown in figure 4.13. The web applica-
tion sends HTTP redirects whenever it needs you to move to a different page, such as
when a user submits a form. Your browser automatically follows the redirect requests,
creating a seamless flow through the checkout process.
Browser ASP.NET Core application
Checkout
Buy
Payment
Submit
Order Complete!
3. The user clicks Buy
on the checkout page,
which sends a POST to
the web application.
POST to /checkout
302 REDIRECT to /payment
GET to /payment
200 OK (HTML)
POST to /payment
302 REDIRECT to /order-complete
GET to /order-complete
200 OK (HTML)
4. The ASP
.NET Core application
begins the checkout process
and sends a 302 redirect
response to the payment page.
5. The user’s browser
automatically follows
the redirect to the
payment page. 6. The request for the payment
page is handled by the app,
generating an HTML page
and returning it to
the browser.
7. The user fills in the
the payment form and
clicks Submit which
sends a POST to the
web application.
8. The ASP
.NET Core application
processes the payment and
sends a 302 redirect response
to the order-complete page.
9. The user’s browser
automatically follows
the redirect to the
order-complete page. 10. The request for the order-
complete page is handled
by generating an HTML
page and returning it to
the browser.
11. The user views
the HTML order-
complete page.
GET to /checkout
200 OK (HTML)
1. The user begins by navigating
to the checkout page, which
sends a GET request to the
ASP
.NET Core application.
2. The request for the checkout
page is handled by the app,
generating an HTML page
and returning it to
the browser.
£
9
Figure 4.13 A typical POST, REDIRECT, GET flow through a website. A user sends their shopping basket to a
checkout page, which validates its contents and redirects to a payment page without the user having to manually
change the URL.
118 CHAPTER 4 Creating web pages with MVC controllers
NOTFOUNDRESULT AND STATUSCODERESULT
As well as HTML and redirect responses, you’ll occasionally need to send specific
HTTP status codes. If you request a page for viewing a product on an e-commerce
application, and that product doesn’t exist, a 404 HTTP status code is returned to the
browser and you’ll typically see a “Not found” web page. The MvcMiddleware can
achieve this behavior by returning a NotFoundResult, which will return a raw 404
HTTP status code. You could achieve a similar result using the StatusCodeResult and
setting the status code returned explicitly to 404.
Note that the NotFoundResult doesn’t generate any HTML; it only generates a raw
404 status code and returns it through the middleware pipeline. But, as discussed in
the previous chapter, you can use the StatusCodePagesMiddleware to intercept this
raw 404 status code after it’s been generated and provide a user-friendly HTML
response for it.
CREATING ACTIONRESULT CLASSES USING HELPER METHODS
ActionResult classes can be created and returned using the normal new syntax of C#:
return new ViewResult()
If your controller inherits from the base Controller class, then you can also use a num-
ber of helper methods for generating an appropriate response. It’s common to use the
View method to generate an appropriate ViewResult, the Redirect method to gener-
ate a RedirectResponse, or the NotFound method to generate a NotFoundResult.
TIP Most ActionResult classes have a helper method on the base Controller
class. They’re typically named Type, and the result generated is called Type-
Result. For example, the Content method returns a ContentResult instance.
As discussed earlier, the act of returning an IActionResult doesn’t immediately gen-
erate the response—it’s the execution of an IActionResult by the MvcMiddleware,
which occurs outside the action method. After producing the response, the Mvc-
Middleware returns it to the middleware pipeline. From there, it passes through all
the registered middleware in the pipeline, before the ASP.NET Core web server
finally sends it to the user.
By now, you should have an overall understanding of the MVC design pattern and
how it relates to ASP.NET Core. The action methods on a controller are invoked in
response to given requests and are used to select the type of response to generate by
returning an IActionResult.
In traditional web apps, the MvcMiddleware generates HTML web pages. These
can be served to a user who’s browsing your app with a web browser, as you’d see with
a traditional website. It’s also possible to use the MvcMiddleware to send data in a
machine-readable format, such as JSON, by returning data directly from action meth-
ods, as you’ll see in chapter 9. Controllers handle both of these use cases, the only tan-
gible difference being the data they return. These are typically known as MVC and
Web API controllers, respectively.
119
Summary
It’s important to remember that the whole MVC infrastructure in ASP.NET Core is
a piece of middleware that runs as part of the middleware pipeline, as you saw in the
previous chapter. Any response generated, whether a ViewResult or a Redirect-
Result, will pass back through the middleware pipeline, giving a potential opportu-
nity for middleware to modify the response before the web server sends it to the user.
An aspect I’ve only vaguely touched on is how the MvcMiddleware decides which
action method to invoke for a given request. This process is handled by the routing
infrastructure and is a key part of MVC in ASP.NET Core. In the next chapter, you’ll
see how to define routes, how to add constraints to your routes, and how they decon-
struct URLs to match a single action and controller.
Summary
 MVC allows for a separation of concerns between the business logic of your
application, the data that’s passed around, and the display of data in a response.
 Controllers contain a logical grouping of action methods.
 ASP.NET Core controllers inherit from either the Controller or Controller-
Base class or have a name that ends in controller.
 Action methods decide what sort of response to generate; action results handle
the generation.
 Action methods should generally delegate to services to handle the business
logic required by a request, instead of performing the changes themselves. This
ensures a clean separation of concerns that aids testing and improves applica-
tion structure.
 Action methods can have parameters whose values are taken from properties of
the incoming request.
 When building a traditional web application, you’ll generally use a ViewResult
to generate an HTML response.
 You can send users to a new URL using a RedirectResult.
 The Controller base class exposes many helper methods for creating an
ActionResult.
 The MVC and Web API infrastructure is unified in ASP.NET Core. The only
thing that differentiates a traditional MVC controller from a Web API control-
ler is the data it returns. MVC controllers normally return a ViewResult,
whereas Web API controllers typically return data or a StatusCodeResult.
120
Mapping URLs to methods
using conventional routing
In chapter 4, you learned about the MVC design pattern, and how ASP.NET Core
uses it to generate the UI for an application. Whether you’re building a traditional
HTML web application or creating a Web API for a mobile application, you can use
the MvcMiddleware to generate a response. This is typically placed at the end of the
middleware pipeline and handles requests after all the other middleware in the
pipeline have executed.
In ASP.NET Core, you build MVC applications by creating controller classes that
contain action methods. An action method executes in response to an appropriate
request. It’s responsible for invoking the required business logic in the application
model and determining what type of result to return. That might be a ViewResult
indicating the template to use for HTML generation, a RedirectResult to forward
the user to another URL, a StatusCodeResult if you’re writing a Web API, and so on.
This chapter covers
 Mapping URLs to action methods using conventions
 Using constraints and default values to match URLs
 Generating URLs from route parameters
121
Although not explicitly part of the classic MVC design pattern, one crucial part of
the MVC pattern in ASP.NET Core is selecting the correct action method to invoke in
response to a given request, as shown in figure 5.1. This process is called routing and is
the focus of this chapter.
This chapter begins by identifying the need for routing, why it’s useful, and the advan-
tages it has over traditional layout-based mapping. You’ll see several examples of rout-
ing techniques, and the separation routing can bring between the layout of your code
and the URLs you expose.
The bulk of this chapter focuses on how to define your routes so that the correct
action method executes in response to a request to a URL. I’ll show how to build pow-
erful route templates and give you a taste of the available options.
In section 5.5, I’ll describe how to use the routing system to generate URLs, which
you can use to create links and redirect requests for your application. One of the ben-
efits of using a routing system is that it decouples your action methods from the
underlying URLs that are used to execute them. This allows you to change the URLs
your app uses by tweaking the routing system, leaving your action methods
untouched. Using URL generation lets you avoid littering your code with hardcoded
URLs like /Product/View/3, and instead lets you generate them at runtime, based on
the current routing system.
By the end of this chapter, you should have a much clearer understanding of how
an ASP.NET Core application works. You can think of routing as the glue that ties the
middleware pipeline to the MVC design strategy used by the MvcMiddleware. With
middleware, MVC, and routing under your belt, you’ll be writing web apps in no time!
Router
Request
Each request is passed
to the MVC router, which
inspects the request’s URL.
/begin-checkout
Action
CheckoutController.Start()
Router
Request
/Product/View/3
Action
ProductController.View()
Router
Request
/
Action
HomeController.Index()
The router selects a single
action method to execute,
based on the incoming URL
and the app’s configuration.
Figure 5.1 The router is responsible for mapping incoming requests to an action method that
will be executed to handle the request.
122 CHAPTER 5 Mapping URLs to methods using conventional routing
5.1 What is routing?
In chapter 3, you saw that an ASP.NET Core application contains a middleware pipe-
line, which defines the behavior of your application. Middleware is well suited to
handling both cross-cutting concerns, such as logging and error handling, and nar-
rowly focused requests, such as requests for images and CSS files.
To handle more complex application logic, you’ll typically use the MvcMiddleware
at the end of your middleware pipeline, as you saw in chapter 4. This can handle an
appropriate request by invoking a method, known as an action method, and using the
result to generate a response.
One aspect that I glossed over was how to select an action to execute when you
receive a request. What makes a request “appropriate” for a given action? The process
of mapping a request to a given handler is called routing.
DEFINITION Routing in ASP.NET Core is the process of mapping an incoming
HTTP request to a specific handler. In MVC, the handler is an action
method.
When you use the MvcMiddleware in your application, you configure a router to map
any incoming requests to the MVC route handler. This takes in a URL and decon-
structs it to determine which controller and action method it corresponds to. A simple
routing pattern, for example, might determine that the /product/view URL maps to
the View action on the ProductController, as shown in figure 5.2.
You can define many different routing patterns, each of which can set how a number
of different URLs map to a variety of action methods. You can also have multiple pat-
terns that all point to the same action method from different URLs. Alternatively, you
could have a pattern that would only match a single URL, and maps to a single spe-
cific action.
start-checkout
user/{action}
{controller}/{action}/{id}
1. The request is passed to the
MvcMiddleware which passes
the URL to the router.
/Product/View
Request
Router Action
ProductController.View()
Route templates
2. The router consults the list
of configured route templates
and finds the first pattern
that matches.
3. The route template specifies
the controller and action that
should be executed to handle
the request.
Figure 5.2 The router compares the request URL against a list of configured route templates to
determine which action method to execute.
123
What is routing?
In the MvcMiddleware, the outcome of successfully routing a request will be a single
selected action and its associated controller. The middleware will then use this action
to generate an appropriate response.
Exactly what your URLs will look like and the action methods they map to will obvi-
ously be specific to your application, but you’ll see a number of typical conventions
used. These can be applied to any number of applications, as many web apps follow a
similar design.
In this chapter, imagine you’ve been asked to build an online currency converter
for a bank. The application will have traditional pages like a homepage and a contact
page, but the meat of the website will be a series of pages for viewing the exchange
rates of different currencies, and for converting from one exchange rate to the next.
As you’re going to be trying to attract as much traffic as possible to your web app,
it’s important that the URLs you use are easy to understand and make sense to both
the customer and search engines. We’re going to start with simple examples, and as
Why use routing?
The first version of ASP.NET MVC introduced routing, back when ASP.NET Web Forms
was the de facto Microsoft web framework. Routing was introduced as a way to
decouple the URLs exposed by an application from the handlers that would generate
the response.
Before routing, URLs typically mapped to a physical file on disk that would handle a
request. For example, a request to the /product.aspx URL would map to a
product.aspx file that would reside in the root folder of the application. This file would
contain the logic and HTML generation code required to service the request. This had
the advantage of being simple to reason about, and it was easy to see all the URLs
your application exposed by viewing the project directory structure.
Unfortunately, this approach has several issues. One common problem is the renam-
ing of files. If you needed to rename a file—maybe the filename had a typo in it—you
were intrinsically changing the public URLs exposed to your users. Any previous links
to the page would be broken, including any URLs within your own application that
pointed to that page.
Another common complaint is the “ugly” URLs that are often required. The typical
method of passing variable data when not using routing is to pass it in the query
string, resulting in URLs such as /product.aspx?id=3. Compare this to equivalents
using routing such as /product/3 or /product/apple-tv, and hopefully the advan-
tage here is clear. You can still use query string parameters in addition to routing,
but your application doesn’t have to use them to allow variable segments in the URL.
Fundamentally, routing enables you to explicitly define the URLs used to navigate
your application, without tying you to a particular file layout or structure for your appli-
cation. There are conventions that you’ll generally use in your application to make
defining the URLs easier, but you’re free to vary the two independently if required.
124 CHAPTER 5 Mapping URLs to methods using conventional routing
we make our way through the chapter, we’ll explore ways to improve the URLs your
app exposes.
As a starter, you might choose to expose the URLs shown in table 5.1, which would
map to the associated actions.
I hope you can see that the URLs are all quite similar in structure—this is a common
convention but is completely customizable, as you’ll see later. The first segment of the
URL maps to the name of the selected controller, and the second segment maps to the
name of the action (except in the case of List, which was not explicitly specified). Addi-
tionally, the id for the action method has been automatically assigned from the URL.
This approach to creating URLs, where you directly infer the controller and action
method from the segments of the URL is called convention-based. This approach is
often used when building traditional HTML web applications using MVC, as it makes
the structure easy to reason about for users and gives hackable URLs.
DEFINITION Hackable URLs refer to the ability to guess the URL for an action
you want to perform, based on previous URLs you’ve seen. For example,
based on the URLs in table 5.1, you might expect that the URL to view order
number 4 would be /orders/view/4. This property is generally seen as desir-
able when building web applications.
The ability to set default values for URL segments when they aren’t explicitly provided
can be useful for allowing multiple URLs that point to the same action method. You
saw it in table 5.1, where the List action was inferred from the /currencies URL. A
common convention used by default in most ASP.NET Core MVC applications is that
the homepage of your application is invoked using the HomeController.Index()
action method. With the default conventions, any of the following URL paths will
invoke this action:
 /
 /home
 /home/index
Table 5.1 Possible URLs and action method mappings for the customer section of a web application
Exposed URL Maps to action method Notes
/currencies CurrenciesController.List() Shows the list of all currencies
you support
/rates/view/1 RatesController.View(id) Shows the exchange rate for the
currency with id=1
/rates/edit/4 RatesController.Edit(id) Shows the form for editing the
exchange rate of the currency
with id=4
/orders/customer/3 OrdersController.Customer(id) Shows all the previous orders
for customer with id=1
125
Routing to MVC controllers and actions
These conventions make building web applications simpler to reason about and main-
tain, as it’s easy to infer which controller and action will be called.
TIP Where possible, it’s often a good idea to stick close to the default rout-
ing conventions. This will make your app easier to maintain in the long run,
especially for other people looking at your code.
Depending on your requirements, a purely convention-based approach may not be
sufficient to provide the URLs you need. What if you want a specific nonstandard URL
for your checkout page, or you’re only told the required URL after you’ve already
built the controllers? In table 5.1, the /currencies URL is used to execute the
CurrenciesController.List action; what if you want to change this so the action exe-
cutes when you navigate to the /view-currencies URL instead?
Luckily, the routing system is sufficiently flexible that you can easily create URLs
that map to arbitrary action methods. Here are two examples:
 /start-checkout—Could map to CheckoutController.BillingAddress()
 /view/rates—Could map to RatesController.Index()
Another use case for judicious use of routing is to make URLs more user friendly. This
can be useful on sites where having a readable URL is important for search engine
optimization, such as e-commerce sites, public websites, or blogs.
Take another look at table 5.1. The URL to view the current exchange rate for a
currency was /rates/view/1. This would work fine, but it doesn’t tell users much—
which currency will this show? Will it be a historical view or the current rate?
Luckily, with routing it’s easy to modify your exposed URLs without having to
change your controllers and actions at all. Depending on your routing configuration,
you could easily set the URL pointing to the RatesController.View action method to
any of the following:
 /rates/view/1
 /rates/USD
 /current-exchange-rate-for-USD
I know which of these I’d most like to see in the URL bar of my browser, and which
link I’d be most likely to click! I hope these examples have provided a glimpse of the
benefits of setting up sensible routes for your application. In the next section, you’ll
see how to define your routes in ASP.NET Core, and we’ll look at setting up the rout-
ing for the currency converter application in more detail.
5.2 Routing to MVC controllers and actions
Routing is a key part of the MVC design pattern in ASP.NET Core, as it connects the
incoming request to a specific controller action. Note that this only happens if the
request reaches the MvcMiddleware in the middleware pipeline. The request could be
short-circuited before reaching the MvcMiddleware, either due to a previous middleware
handling the request and generating a response (such as the StaticFileMiddleware,
126 CHAPTER 5 Mapping URLs to methods using conventional routing
for example) or a previous middleware generating an error. In these two cases, the Mvc-
Middleware won’t run, and so no routing will occur.
If the request does make it to the MvcMiddleware, then the first step is to route the
request to the required action method. You have two different ways to define these
mappings in your application:
 Using global, conventional routing
 Using attribute routing
The convention-based routes are defined globally for your application. You can use
convention-based routes to map all of the controllers and actions in your application,
as long as your code conforms to the conventions you define. This provides a succinct
and terse way to expose your action methods at easily understood URLs. Traditional
HTML-based MVC web applications typically use this approach to routing.
You can also use attribute-based routes to tie a given URL to a specific action
method by placing [Route] attributes on the action methods themselves. This pro-
vides a lot more flexibility as you can explicitly define what a URL for a given action
method should be. This approach is more verbose than the convention-based
approach, as it requires applying attributes to every action method in your applica-
tion. Despite this, the additional flexibility it provides can often be useful, especially
when building Web APIs.
Whichever technique you use, you’ll define your expected URLs using route tem-
plates. These define the pattern of the URL you’re expecting, with placeholders for
parts that may vary.
DEFINITION Route templates define the structure of known URLs in your appli-
cation. They’re strings with placeholders for variables that can contain
optional values and map to controllers and actions.
A single route template can match a number of different URLs. The /product/index
and /product URLs would both be matched by the product/{action=index} route
template, for example, and the /customer/1 and /customer/2 URLs would both be
matched by the customer/{id} route template. The route template syntax is power-
ful and contains many different features that are controlled by splitting a URL into
multiple segments.
DEFINITION A segment is a small contiguous section of a URL. It’s separated
from other URL segments by at least one character, often by the / character.
Routing involves matching the segments of a URL to a route template.
For a single-route template, you can define
 Specific, expected strings
 Variable segments of the URL
 Optional segments of a URL
 Default values when an optional segment isn’t provided
 Constraints on segments of a URL, for example, ensuring that it’s numeric
127
Routing to MVC controllers and actions
Your application will often define a number of different route templates, perhaps
defining different conventions for different sections of your application or providing
more hackable URLs for certain important pages. You’ll see how to define multiple
conventional routes in the next section.
A typical conventional route will contain placeholders for different segments of a
URL, where the controller and action are often inferred from the incoming URL. For
example, the {controller}/{action} single route template would map both of the
following URLs to their corresponding actions:
 /customer/list—To the CustomerController.List() action
 /product/view—To the ProductsController.View() action
These route templates are global, so you define them in Startup.cs when adding the
MvcMiddleware to your pipeline, as you’ll see in the next section. When routing an
incoming URL, the middleware uses the full collection of defined conventional routes
and attempts to determine the appropriate controller and action they correspond to.
For each request, the MvcMiddleware determines the action to execute by follow-
ing the process set out in figure 5.3. For each defined route template, the router
attempts to split the request’s URL into segments corresponding to the template.
If the URL pattern matches, the router infers the controller and action from the
URL. If this points to a valid action method, then routing is complete and the action
method is executed. If the route template doesn’t match the URL, or the specified
controller-action pair doesn’t exist, then the router moves on to the next defined tem-
plate and attempts to match that, until no more routes are left.
If none of the route templates match the incoming URL, then the MvcMiddleware
is unable to handle the request, so the request will continue along the middleware
pipeline. As the MvcMiddleware is typically the last middleware in the pipeline, this
normally means a 404 response will be generated.
The alternative to this conventional approach, in which routes are defined globally
for your application, is to define the specific URL that a given action maps to. Concep-
tually, this is kind of the reverse approach; instead of taking a URL and working out
which action it maps to, you take an action and generate the URL it maps to. When a
request arrives, the middleware checks if it corresponds to any of these URLs.
As this approach is tied so closely to the action methods themselves, it’s imple-
mented using attributes placed on the action methods themselves, and hence is
termed attribute routing.
You’re free to combine both conventional and attribute routing in your applica-
tions. Typically, you’ll use conventional routing for your MVC controllers serving
HTML and attribute routing for your Web API controllers, where the ability to pre-
cisely control an action’s associated route template can be useful.
TIP Use conventional routing for controllers returning HTML and attribute
routing for Web API controllers where possible.
128 CHAPTER 5 Mapping URLs to methods using conventional routing
In the next section, you’ll learn how to define and use conventional routing in your
application, but many of the concepts described apply equally when you’re using attri-
bute routing. I’ll cover attribute routing and how it interacts with conventional rout-
ing in chapter 9, when I discuss building a Web API.
No
Execute action
No Yes
URL
Split URL into
segments
Current
route
Move to
next route
Are there any
more routes?
Yes
Unable to handle URL:
request continues on
middleware pipeline
Does URL
match route?
Yes
Does action and
controller exist?
Route
values
Controller
Action
The controller and action
are extracted from the
route based on the URL.
The MvcMiddleware is normally
the last middleware in the
pipeline, so this will normally
result in a 404 response.
The action is executed
using route values extracted
from the URL.
If a route does not
match, the next
route is inspected.
Move to
first route
When a URL arrives, it
is split into segments
and compared to the
first defined route.
Figure 5.3 Flow chart showing how the MvcMiddleware matches a URL against multiple routes to
determine which action method to execute
129
Routing using conventions
5.3 Routing using conventions
You configure conventional routing when you add the MvcMiddleware to your middle-
ware pipeline with the UseMvc method. You’ve added this middleware several times so
far in this book, so the following listing should look familiar.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
When you call UseMvc, you also provide a lambda that defines all of the global conven-
tional routes for your application. Each call to MapRoute on the provided instance of
IRouteBuilder configures a new conventional route.
In the listing you can see you’ve added the default route template. This is a typical
default added by most MVC project templates. It was created with a name so that it
can be referenced from other parts of your program (as you’ll see later in the section
on URL generation), and a template, which is the route template used to match
against URLs.
The route template itself has a rich syntax that splits a URL into a number of seg-
ments. In the next section, you’ll learn about this syntax and see how it can be used to
match a variety of URLs.
This example only lists a single route so all the URLs in this application would have
to conform to the same general structure. To add more variety, and handle more spe-
cific requirements, it’s perfectly acceptable to configure multiple global convention-
based routes.
For the currency converter application, imagine a requirement exists to be able to
view details about a particular currency (which country uses it and so on) by navigating
to a URL that contains the currency code, such as /currency/USD or /currency/GBP.
In listing 5.2, you’ll add an additional route for viewing currencies by name. When
requests are made to the required URLs, this route will match and execute the Cur-
renciesController.View() action method.
Listing 5.1 Configuring the MvcMiddleware in the middleware pipeline
130 CHAPTER 5 Mapping URLs to methods using conventional routing
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
app.UseMvc(routes =>
{
routes.MapRoute(
name: "currency_by_code",
template: "currency/{code}",
defaults: new { controller="Currencies", action="View" });
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
NOTE You may see these hardcoded route templates and be tempted to
make them dynamic by loading them from a configuration file or similar.
Although possible, this is generally unnecessary—the routes are intimately
tied to both your app’s structure and its public API, so hardcoding the values
here is the norm.
The order in which you add the routes to the IRouteBuilder defines the order
against which the routing infrastructure will attempt to match an incoming request. It
will attempt to match the first route configured, and only if that isn’t a match will it
look at subsequent routes.
NOTE It’s important to consider the order of your conventional routes. You
should list the most specific routes first, in order of decreasing specificity,
until you get to the most broad/general route.
Figure 5.4 shows the consequence of not ordering your routes correctly. A set of route
templates has been defined in a social networking application:
 Photos/{action}/{id?}—Routes to actions on PhotoController with the
{id} ID
 {controller}/{action}—Default convention, will route to many actions
 Person/{name}/View—Routes to PersonController. View(), to view the
detailed profile of the user with the {name} name
Notice the ordering here—you’ve added the default conventional router before the
custom route for viewing the detailed profile information of a user.
Listing 5.2 Adding multiple routes to the MvcMiddleware
The name of the route can be
used during URL generation, as
you’ll see later.
The route template defines the
structure of the URL to match.
131
Routing using conventions
Imagine your app receives a request to the /Person/JoeBloggs/View URL. This
incoming URL is a perfect match for the final route, but because the route preceding
it is more general, the final route is never tested. Consequently, the JoeBloggs()
action method is executed instead of the method you might expect, View(string
name), with name = "JoeBloggs". To fix this problem, you can swap the order of the
last two routes. This is obviously a somewhat contrived example—it’s unlikely you’ll
have an action method called JoeBloggs!—but hopefully the principle is clear.
Every conventional route must define the controller and action to run when a
URL matches the route. This can be done either using the route template syntax, in
which the controller and action name are taken directly from the URL, or by using
default values, as you’ll see in the next section.
5.3.1 Understanding route templates
When you define a route during the configuration of the MvcMiddleware, you specify
a route template that defines the URL pattern the route will match. The route tem-
plate has a rich, flexible syntax, but a simple example is shown in figure 5.5.
A router parses a route template by splitting it into a number of segments. A seg-
ment is typically separated by the / character, but it can be any valid character. Each
segment is either
 A literal value—For example, api in figure 5.5
 A route parameter—For example, {controller} and {action} in figure 5.5
1. The request is passed to the
MvcMiddleware which tests
the URL against each route.
/Person/JoeBloggs/View
Router Action
PersonController.JoeBloggs()
Photos/{action}/{id?}
{controller}/{action}/{id?}
Route templates
2. The first route does not
match, but the second route
does, so it is selected.
4. The action selected corresponds
to the selected template, though it
is not the intended one (and in a
real app probably won’t exist).
Person/{name}/View
3. The final route template is
technically a better match for
the URL, but it is never tested
as it is listed last.
PersonController.View(string name)
PersonController.JoeBloggs()
Request
Figure 5.4 The order in which conventional routes are configured in your application controls the
order in which they’re matched to a URL during routing.
132 CHAPTER 5 Mapping URLs to methods using conventional routing
Literal values must be matched exactly (ignoring case) by the request URL. If you need
to match a particular URL exactly, you can use a template consisting only of literals.
Imagine your boss tells you the currency converter app must have a contact page,
which should be at the /about/contact URL. You could achieve this using the
about/contact route template. This route template consists of only literal values, and
so would only match this single URL. None of the following URLs would match this
route template:
 /about
 /about-us/contact
 /about/contact/email
WARNING Note that although the URL paths start with a / character when a
request is made to your application, the route templates don’t! If you include
the / character, you’ll get an error at runtime when routing runs.
Literal segments are often more useful when used in conjunction with route parame-
ters. Route parameters are sections of a URL that may vary but still be a match for the
template. Route parameters are defined by giving them a name, and placing them in
braces, such as {controller} or {action}. When used in this way, parameters are still
required, so there must be a segment in the request URL that they correspond to, but
the value can vary.
This ability to vary gives you great flexibility. For example, you could use the simple
{controller}/{action} route template in the currency converter application to map
the following request URLs:
 /currencies/list—Where controller=currencies and action=list
 /rates/view—Where controller=rates and action=view
But note that this template would not map the following URLs:
 /currencies/—No action parameter specified
 /rates/view/USD—Extra URL segment not found in route template
The literal segment and route parameters are the two cornerstones of the ASP.NET
Core router. With these two concepts, it’s possible to build all manner of URLs for
your application.
When a route template defines a route parameter, and the route matches a URL,
the value associated with the parameter is captured and stored in a dictionary of
api/{controller}/{action}
Literal segment
Required route parameter
Required route parameter
Figure 5.5 A simple route
template showing a literal segment
and required route parameters
133
Routing using conventions
values associated with the request. These route values typically drive other behavior in
the MvcMiddleware, such as model binding.
DEFINITION Route values are the values extracted from a URL based on a given
route template. Each route parameter in a template will have an associated
route value and is stored as a string pair in a dictionary. They can be used
during model binding, as you’ll see in chapter 6.
The most important use of route parameters in conventional routing is for determin-
ing the controller and action that a route is associated with. Specifically, a conven-
tional route must specify the {controller} and {action} route parameters so that the
router can map the URL to an action to execute. It does this by first attempting to find
a controller in your application named the same as the controller route value, which
has an action with the same name as the action route value (ignoring case sensitivity).
WARNING The controller and action route values must be able to be calcu-
lated for every route template so that the router knows which action to look for.
As well as the basic features of literals and variable route parameter segments, route
templates can use a number of additional features to build more powerful conven-
tions, and hence more varied URLs. These can let you have optional URL segments,
can provide default values when a segment isn’t specified, or can place additional con-
straints on the value that’s valid for a given route parameter. The next section takes a
look at some of these, and ways you can apply them.
5.3.2 Using optional and default values
In the previous section, you saw a simple route template, with a literal segment and
two required routing parameters. In figure 5.6, you can see a more complex route that
uses a number of additional features.
The literal api segment and the required {controller} parameter are the same as
you saw in figure 5.5. The {action} parameter looks similar, but it has a default value
specified for it, index. If the URL doesn’t contain a segment corresponding to the
action parameter, then the router will use the index value instead.
The final segment of figure 5.6, {id?}, defines an optional route parameter called
id. This segment of the URL is optional—if present, the router will capture the value
for the id parameter; if it isn’t there, then it won’t create a route value for id.
api/{controller}/{action=index}/{id?}
Literal segment
Required route parameter
Optional route parameter
with default value if not provided
Optional route parameter
Figure 5.6 A more complex route
template showing literal segments,
named route parameters, optional
parameters, and default values
134 CHAPTER 5 Mapping URLs to methods using conventional routing
You can specify any number of route parameters in your templates, and these val-
ues will be available to you when it comes to model binding. Remember that only the
{controller} and {action} parameters are compulsory, and that the router uses
only these values to decide which action to execute.
NOTE The router uses only the {controller} and {action} parameters to
determine which action to execute. Model binding can use any other route
parameter values, but these don’t affect action selection.
The complex route template of figure 5.6 allows you to match a greater variety of
URLs by making some parameters optional and providing a default for action. Table
5.2 shows some of the possible URLs this template would match, and the correspond-
ing route values the router would create.
Note that there’s no way to specify a value for the optional id parameter without also
specifying the action and controller parameters. You can’t put an optional parame-
ter before a required parameter, as there would be no way of specifying the required
parameter only. Imagine your route template had an optional controller parameter:
{controller?}/{action}
Now try to think of a URL that would specify the action parameter, but not the
controller. It can’t be done!
Using default values allows you to have multiple ways to call the same URL, which may
be desirable in some cases. Remember the default MVC route template from listing 5.1?
{controller=Home}/{action=Index}/{id?}
The meaning of this template should be a little more obvious now. It uses default val-
ues for the {controller} and {action} parameters, which means multiple URLs can
get you to the homepage of the application (typically the action HomeController
.Index()):
 /
 /Home
 /Home/Index
Table 5.2 URLs that would match the template of figure 5.6 and their corresponding route values
URL Route values
api/product/view/3 controller=product, action=view, id = 3
api/product/view controller=product, action=view
api/product controller=product, action=index
api/customer/list controller=customer, action=list
api/order controller=order, action=index
api/order/edit/O-123 controller=order, action=edit, id = O-123
135
Routing using conventions
Each of these URLs will execute the same action, as they will all match the default
route template.
By using default values and optional constraints, you can start to add some more
interesting URLs to your currency converter application. For example, you could add
a route to view currencies, where if you don’t specify a currency it assumes USD using
{controller}/{currency=USD}/{action=view}
I’ve added two default values here, so you could map any of the following URLs:
 /rates—controller=rates, action=view, currency=USD
 /rates/GBP—controller=rates, action=view, currency=GBP
 /rates/USD/edit—controller=rates, action=edit, currency=USD
 /currencies—controller=currencies, action=view, currency=USD
Adding default values allows you to use shorter and more memorable URLs in your
application for common URLs, but still have the flexibility to match a variety of other
routes.
5.3.3 Adding additional constraints to route parameters
By defining whether a route parameter is required or optional, and whether it has a
default value, you can match a broad range of URLs with a pretty terse template syn-
tax. Unfortunately, in some cases this can end up being a little too broad. Routing
only matches URL segments to route parameters, it doesn’t know anything about the
data that you’re expecting those route parameters to contain. Considering the
{controller=Home}/{action=Index}/{id?} default template, the following URLs
would all match:
 /Home/Edit/test
 /Home/Edit/123
 /1/2/3
These URLs are all perfectly valid given the template’s syntax, but some might cause
problems for your application. It might surprise you initially that all of those URLs
also match the currency route template you defined at the end of the last section:
{controller}/{currency=USD}/{action=view}
These URLs all have three segments, and so the router happily assigns route values
and matches the template when you probably don’t want it to!
 /Home/Edit/test—controller=Home, currency=Edit, action=test
 /Home/Edit/123—controller=Home, currency=Edit, action=123
 /1/2/3—controller=1, currency=2, action=3
Typically, the router passes route values other than controller and action as param-
eters to action methods through a process called model binding (which we’ll discuss
in detail in the next chapter). For example, an action method with the public
136 CHAPTER 5 Mapping URLs to methods using conventional routing
IActionResult Edit(int id) signature would obtain the id parameter from the id
route value. If the id route parameter ends up assigned a noninteger value from the
URL, then you’ll get an exception when it’s bound to the integer id action method
parameter.
To avoid this problem, it’s possible to add additional constraints to a route template
that must be satisfied for a URL to be considered a match. Constraints can be defined
in a route template for a given route parameter using : (a colon). For example,
{id:int} would add the IntRouteConstraint to the id parameter. For a given URL
to be considered a match, the value assigned to the id route value must be convertible
to an integer.
You can apply a large number of route constraints to route templates to ensure
that route values are convertible to appropriate types. You can also check more
advanced constraints, for example, that an integer value has a particular minimum
value, or that a string value has a maximum length. Table 5.3 describes a number of
the possible constraints available, but you can find a more complete list online at
http://mng.bz/U11Q.
TIP As you can see from table 5.3, you can also combine multiple con-
straints; place a colon between them or use an optional mark (?) at the end.
Using constraints allows you to narrow down the URLs that a given route template will
match. Figure 5.7 shows an enhanced version of the flow chart shown in figure 5.3.
After the router matches a URL to a route template, it interrogates the constraints to
check that they’re all valid. If they are, then the router will continue to attempt to find
an appropriate action method. If the constraints aren’t valid, then the router will
check the next defined route template instead.
Table 5.3 A few route constraints and their behavior when applied
Constraint Example Match examples Description
int {qty:int} 123, -123, 0 Matches any integer
Guid {id:guid} d071b70c-a812-
4b54-87d2-
7769528e2814
Matches any Guid
decimal {cost:decimal} 29.99, 52, -1.01 Matches any decimal value
min(value) {age:min(18)} 18, 20 Matches integer values of
18 or greater
length(value) {name:length(6)} andrew,123456 Matches string values
with a length of 6
optional int {qty:int?} 123, -123, 0,
null
Optionally matches any
integer
optional int
max(value)
{qty:int:max(10)?} 3, -123, 0, null Optionally matches any
integer of 10 or less
137
Routing using conventions
WARNING Don’t use route constraints to validate general input, for example
to check that an email address is valid. Doing so will result in 404 “Page not
found” errors, which will be confusing for the user.
You can use a simple constraint on the currency converter route to ensure that the cur-
rency route parameter only matches strings of length 3 using a simple length constraint:
{controller}/{currency=USD:length(3)}/{action=view}
No
Execute action
No Yes
URL
Split URL into
segments
Current
route
Move to
next route
Are there any
more routes?
Yes
Unable to handle URL:
request continues on
middleware pipeline
Does URL
match route?
Yes
Does action and
controller exist?
Route
Controller
Action
If a route does not
match, the next route
.
is inspected
Are constraints
satisfied?
Yes
Move to
first route
Figure 5.7 Flow chart indicating how the MvcMiddleware matches a URL against multiple routes
to determine the action method to execute when a route template uses constraints
138 CHAPTER 5 Mapping URLs to methods using conventional routing
Although not foolproof, this should help ensure the router only maps appropriate
URLs to this route.
5.3.4 Defining default values and constraints using anonymous objects
All of the examples shown in this section on route templates have defined default val-
ues and route constraints inline as part of the route template, using the {action=
view} and {id:int} syntax.
It’s also possible to specify constraints and defaults separately when creating your
routes, by using different overloads of MapRoute and anonymous objects. For exam-
ple, the definition of your currency converter route template, in which constraints
and defaults are defined inline, looks like the following at the moment:
routes.MapRoute(
name: "default",
template: "{controller}/}/{currency=USD:length(3)}/{action=view}");
This same route could also be defined using an alternative overload of MapRoute.
routes.MapRoute(
name: "default",
template: "{controller}/{currency}/{action}",
defaults: new {
currency="USD",
action="View"
},
constraints: new {
currency = new LengthRouteConstraint(3)
});
The inline approach to specifying constraints is normally sufficient for most route
templates and is generally preferable for readability. But some route templates can’t
be defined using the inline approach, and you must use the anonymous type
approach.
You might not want users to have to specify “currencies” at the start of the URL
(for example, /currencies/USD), so that the router knows which controller to use.
You can use anonymous objects to avoid this by removing controller from the tem-
plate and specifying a default value for it.
routes.MapRoute(
name: "currencies",
template: "{currency}/{action}",
defaults: new {
controller=currencies,
currency="USD",
action="View"
},
Listing 5.3 Defining route constraints and default values using anonymous types
Listing 5.4 Using anonymous types to set default values of missing parameters
Required. The raw route
template, without
defaults or constraints
Optional. The default values to
use when a parameter is missing
Optional. Constraints
to apply to route
parameters
The controller parameter is set as
required, but can’t be changed as it’s
no longer in the template.
139
Routing using conventions
constraints: new {
currency = new LengthRouteConstraint(3)
});
Another common case is when you want to have a highly customized URL for a partic-
ularly important action method. You might want the /checkout URL to point to the
PaymentController.StartProcess() action method. You could define a route to per-
form this mapping in a number of ways, one of which is shown here.
routes.MapRoute(
name: "start_checkout",
template: "checkout",
defaults: new { controller="Payment", action="StartProcess" });
As we’ve already discussed, you must always define the controller and action route
parameters for a given route, so the middleware can work out which action to exe-
cute. By using the defaults anonymous type, you can ensure the route is valid with-
out having to include the name of the controller or the action in your template
definition or URL.
We’re coming to the end of our look at conventional route templates, but before we
move on there’s one more type of parameter to think about, the catch-all parameter.
5.3.5 Matching arbitrary URLs with the catch-all parameter
You’ve already seen how route templates take URL segments and attempt to match
them to parameters or literal strings. These segments normally split around the slash
character, /, so the route parameters themselves won’t contain a slash. What do you
do if you need them to, or you don’t know how many segments you’re going to have?
Going back to the currency converter application—you’ve already defined a route
template that will match the /USD/view URL and will execute the action to view the
USD currency. But what if you also want the URL to contain all the currencies to show
exchange rates for? Here are some examples:
 /USD/convert/GBP—Show USD currency with exchange rates to GBP
 /USD/convert/GBP/EUR—Show USD currency with exchange rates to GBP and
EUR
 /USD/convert/GBP/EUR/CAD—Show USD with rates for GBP, EUR, and CAD
If you want to support showing any number of currencies, then you need a way of cap-
turing everything shown after the convert segment. Listing 5.6 shows how you can
capture all these segments in a single parameter called others by using a catch-all
parameter.
Listing 5.5 Using anonymous types to set the default values of missing parameters
The template will match a
single URL only: /checkout.
The controller and
action must be defined.
140 CHAPTER 5 Mapping URLs to methods using conventional routing
routes.MapRoute(
name: "convert_currencies",
template:
"{currency}/convert/{*others}",
defaults: new { controller="currencies", action="View" });
Catch-all parameters can be declared using an asterisk inside the parameter defini-
tion, like {*others}. This will match the remaining unmatched portion of a URL,
including any slashes or other characters that aren’t part of earlier parameters. They
can also match an empty string. For the USD/convert/GBP/EUR URL, the value of
others would be "GBP/EUR".
In the listing, I’ve specified a fixed literal segment, "convert", so that part of the
URL isn’t included in the catch-all title parameter. I’ve used the anonymous type
approach again for specifying the controller and action parameters.
An important point to bear in mind with catch-all parameters is that they’re
greedy, and more general than most other routes. This can result in a catch-all route
intercepting a URL intended for a more specific route. To get around this, define the
catch-all route after the more specific routes.
TIP Take particular care when ordering catch-all routes. They will capture
the whole unmatched portion of a URL. Counteract greedy routes by defin-
ing them later in your Startup.Configure method.
Convention-based routing is normally the standard approach taken when creating an
HTML-based web application. Conventional routing generally makes your applica-
tion’s controller structure easy to understand and browse, which is mirrored in the
URL surface for the application. Conveniently, they’re also succinct to define, with a
single route definition serving for many different controllers and action methods.
Sometimes however, and especially when building a Web API, you might find your-
self creating more and more route templates that map to a single action. In those
cases, attribute routing may be a better approach. I’ll show how to use attribute rout-
ing in chapter 9.
5.4 Handling multiple matching actions for a route
Whether you’re using conventional routing or attribute routing, you’ll often find that
you want to match a single URL to two different action methods. One of the most
common situations is when you’re submitting a form.
In standard web application forms, a browser makes an HTTP GET request to a
page to fetch the initial form from the server and display it to the user. A user might
request the login page for a website using /Account/Login. The user then fills in the
form, and submits the data using an HTTP POST request to the same URL.
Listing 5.6 Using catch-all parameters to include slashes in a route parameter
The template consists of a
parameter for currency, a literal
segment, and a catch-all parameter.
The template doesn’t specify controller or
action; you must define them here.
141
Handling multiple matching actions for a route
Up to this point, I’ve said that the router uses only the URL to determine the
selected controller and action, and that only a single action may be executed. If that’s
the case, how can this possibly work?! Well, without further work, it won’t. The Mvc-
Middleware would throw an exception when the URL is requested saying “Multiple
actions matched.”
Luckily, ASP.NET Core provides a number of attributes you can use to pick a win-
ning action method, when the router would normally end up selecting multiple
actions. Listing 5.7 shows an AccountController with the two action methods you
want to disambiguate. It’s typical for the two corresponding methods to have the same
name, where the POST action takes additional parameters corresponding to the form
values entered by the user.
public class AccountController : Controller
{
public IActionResult Login()
{
/* method implementation*/
}
[HttpPost]
public IActionResult Login(string username, string password)
{
/* method implementation*/
}
}
In order to select one action over the other for a given request, you’ve decorated the
second method with an HttpPostAttribute. This limits the type of requests an action
can match. In this case, the [HttpPost] attribute indicates that the second Login
method is a match for POST requests only, and not any other HTTP methods.
For the initial GET request, there’s now only one method that’s a match—the first
action in the controller—so this action executes to generate the response. The user
can then enter their details, press submit, and send a POST request.
Now, both methods can still handle POST requests—the first method can handle
any HTTP method and the second method can handle only POST requests. But an
action method with an IActionContraint attribute “trumps” an action method with-
out one. The second action method “wins” the contest and executes in response to
the POST request. All the common HTTP methods, such as GET, POST, PUT, and
DELETE, have IActionContraint HTTP methods.
NOTE Use types of IActionConstraint, such as [HttpPost] and [HttpGet],
to provide precedence to one action method over another where they would
match the same URL.
That brings us to the end of the discussion of mapping URLs to action methods when
the MvcMiddleware receives a request. One of the most important things to keep in mind
Listing 5.7 Selecting an action using the HttpPostAttribute
As the method has no Http attribute,
it can match GET or POST requests.
The method names are identical,
hence both methods match the
same /Account/Login URL.
Indicates the method is a match
for POST requests only.
142 CHAPTER 5 Mapping URLs to methods using conventional routing
when configuring your routing is to stick to a logical set of URLs. Doing so will make it
easier for you, as well as your clients, to reason about which action a URL will execute.
Mapping URLs to actions is only half of the responsibilities of the routing system.
It’s also used to generate URLs so that you can easily reference your actions from other
parts of your application.
5.5 Generating URLs from route parameters
One of the by-products of using the routing infrastructure in ASP.NET Core is that
your URLs can be somewhat fluid. If you rename a controller and you’re using
convention-based routing, then the URLs associated with the actions will also change.
For example, renaming the List action to Index on the CurrencyController would
cause the URL for the action to change from /Currency/List to /Currency/Index.
Trying to manually manage these links within your app would be a recipe for heart-
ache, broken links, and 404s. If your URLs were hardcoded, then you’d have to
remember to do a find-and-replace with every rename!
Luckily, you can use the routing infrastructure to generate appropriate URLs
dynamically at runtime instead, freeing you from the burden. Conceptually, this is
almost an exact reverse of the process of mapping a URL to an action method, as
shown in figure 5.8. Instead of taking a URL, finding the first route satisfied and split-
ting it into route values, the router takes in the route values and finds the first route it
can use to build a URL with them.
Routing takes in a URL,
uses the collection of
routes, and outputs
route values.
URL Router
Routes
Route
values
URL generation takes in a
collection of route values
and uses the collection of
routes to output a URL.
Route
values
Router
Routes
URL
Routing
URL generation
Figure 5.8 A comparison between routing and URL generation.
Routing takes in a URL and generates route values, but URL generation
uses a route value to generate a URL.
143
Generating URLs from route parameters
5.5.1 Generating URLs based on an action name
You might need to generate URLs in a number of places in your application, and one
common location is in your controllers. The following listing shows how you could
generate a link to the view page for a currency, using the Url helper from the
Controller base class.
public class CurrencyController : Controller
{
public IActionResult Index()
{
var url = Url.Action("View", "Currency", new { code = "USD" });
return Content(${"The URL is {url}");
}
public IActionResult View(string code)
{
/* method implementation*/
}
}
The Url property is an instance of IUrlHelper that allows you to easily generate URLs
for your application by referencing other action methods. It exposes an Action
method to which you pass the name of the action method, the name of the controller,
and any additional route data. The helper will then generate a URL based on the col-
lection of registered conventional routes.
TIP Instead of using strings for the name of the action method, use the C# 6
nameof operator to make the value refactor-safe, for example, nameof(View).
The IUrlHelper has a number of different overloads of the Action method, some of
which don’t require you to specify all the routing values. If you’re routing to an action
in the same controller, you can omit the controller name when generating the URL.
The helper uses ambient values from the current request and overrides these with any
specific values you provide.
DEFINITION Ambient values are the route values for the current request. They
always include controller and action but can also include any additional
route values set when the action was initially located using routing.
In listing 5.8, as well as providing the controller and action name, I passed in an anon-
ymous object, new { code = "USD" }. This object provides additional route values when
generating the URL, in this case setting the code parameter to "USD". The IUrlHelper
will use these values when finding the correct route to use for generation. It will pick
the first route for which it has all the required route parameters.
Listing 5.8 Generating a URL using IUrlHelper and the action name
Deriving from Controller gives
access to the Url property.
With the default
convention, this will
return "The URL is
/Currency/View/USD".
You provide the
controller and action
name, along with any
additional route values.
The URL generated
will route to the
View action method.
144 CHAPTER 5 Mapping URLs to methods using conventional routing
If a selected route explicitly includes the defined route value in its definition, such
as in the "{controller}/{action}/{code}" route template, then the route value will
be used in the URL path, such as in the example, /currency/view/GBP.
If a route doesn’t contain the route value explicitly, such as in the "{controller}/
{action}" template, then the route value is appended as additional data as part of the
query string, for example /currency/view?code=GBP.
DEFINITION The query string is part of a URL that contains additional data that
doesn’t fit in the path. It isn’t used by the routing infrastructure for identify-
ing which action to execute, but can be used for model binding, as you’ll see
in chapter 6.
Generating URLs based on the action you want to execute is convenient, and the
usual approach taken in most cases. Unfortunately, sometimes the nature of conven-
tional routing means that generating the correct URL based on route parameters can
be tricky. This is often the case when you have multiple conventional routes that over-
lap in the URLs they cover. In these cases, routing based on a specific route’s name
can sometimes be easier.
5.5.2 Generating URLs based on a route name
The IUrlHelper class exposes the RouteUrl method for generating URLs based on a
specific named route. This takes the name of a route and uses the provided route val-
ues to generate a URL specifically for that route. For example, if you had defined the
name "view_currency" when declaring the routes in Startup.cs
routes.MapRoute(
name: "view_currency",
template: "{controller=currency}/{action=view}/{code}");
then you could use this during URL generation.
public class CurrencyController : Controller
{
public IActionResult Index()
{
var url = Url.RouteUrl("view_currency", new { code = "GBP" });
return Content("The URL is {url}");
}
public IActionResult View (string code)
{
/* method implementation*/
}
}
Listing 5.9 Generating a URL using a named route
You provide the name of the
route to use, along with any
additional route values.
This will use the specified
route, returning "The URL
is /Currency/View/GBP".
The URL generated will
route to the View action
method.
145
Generating URLs from route parameters
Using named routes can sometimes simplify the process of generating URLs, as it
avoids issues introduced by overlapping URLs in your route templates. The URL gen-
eration process uses your defined list of routes in order to determine which URL to
generate—if the route you need is at the end of registered routes, then referencing it
by name can often be easier.
5.5.3 Generating URLs with ActionResults
Generating a URL from within your controllers is less common than you might
think—it’s more usual to generate a URL as part of the action result returned by an
action method. This is normally the case when you want to redirect a user’s browser to
a particular action or route. Conceptually, it’s the same as generating a URL using the
Url property and sending the URL back as a redirect response to the user.
Listing 5.13 shows two ways to generate URLs from an ActionResult. The
RedirectToAction method takes in action and controller names, as well as route
parameters, and generates a URL in the same way as the Url.Action method. Simi-
larly, the RedirectToRoute is equivalent to Url.RouteUrl.
public class CurrencyController : Controller
{
public IActionResult RedirectingToAnActionMethod()
{
return RedirectToAction("View", "Currency", new { id = 5 });
}
public IActionResult RedirectingToARoute()
{
return RedirectToRoute("view_currency", new { code = "GBP" });
}
public IActionResult RedirectingToAnActionInTheSameController()
{
return RedirectToAction("Index");
}
public IActionResult Index()
{
/* method implementation */
}
}
As with the IUrlHelper, you can use a number of different overloads to generate the
correct URL. The listing shows an example of providing the action name to
the RedirectToAction method. This will use the ambient value of the controller
Listing 5.10 Generating redirect URLs from an ActionResult
The RedirectToAction method generates a
RedirectToActionResult with the generated URL.
The RedirectToRoute
method generates a
RedirectToRouteResult
with the generated URL.
Only the action name is specified,
so the current controller will be
used to generate the URL.
146 CHAPTER 5 Mapping URLs to methods using conventional routing
parameter, "Currency" in this case, when generating URLs. Consequently, it will cre-
ate a URL pointing to the CurrencyController.Index action.
As well as generating URLs from your controllers, you’ll find you need to generate
URLs when building HTML in your views. This is necessary in order to provide naviga-
tion links in your web application. You’ll see how to achieve this when we look at
Razor Tag Helpers in chapter 8, but the approach is nearly the same as you’ve already
seen here.
Congratulations, you’ve made it all the way through this detailed discussion on
routing! Routing is one of those topics that people often get stuck on when they come
to building an application, which can be frustrating. You’ll revisit the other approach
to routing, attribute routing, when I describe how to create Web APIs in chapter 9, but
rest assured, you’ve already covered all the tricky details in this chapter!
The most important thing to focus on is keeping your routes simple. If you do,
then you’ll find it much easier to reason about the URLs in your application and avoid
giving yourself a headache trying to work out why the wrong action methods are
executing!
In chapter 6, we’ll dive into the M of MVC—the various models of ASP.NET Core.
You’ll see how the route values generated during routing are bound to your action
method parameters, and perhaps more importantly, how to validate the values you’re
provided.
Summary
 Routing is the process of mapping an incoming request URL to an action
method that will execute to generate a response.
 Route templates define the structure of known URLs in your application.
They’re strings with placeholders for variables that can contain optional values
and map to controllers and actions.
 Routes can be defined either globally using conventional routing or can be
mapped explicitly to an action using attribute routing.
 An application can have many different routes. The router will attempt to find
the first route where the route template matches the incoming URL.
 It’s important to consider the order of your conventional routes. You should list
the most specific routes first, in order of decreasing specificity, until you get to
the most broad/general route.
 Route parameters are variable values extracted from a request’s URL.
 Route parameters can be optional and can have default values used when
they’re missing.
 Route parameters can have constraints that restrict the possible values allowed.
If a route parameter doesn’t match its constraints, the route isn’t considered a
match.
147
Summary
 The controller and action route values must be able to be calculated for
every route template. They can either be matched from the URL or using
default values.
 Don’t use route constraints as general input validators. Use them to disambigu-
ate between two similar routes.
 Use a catch-all parameter to capture the remainder of a URL into a route value.
 The [HttpPost] and [HttpGet] attributes allow choosing between actions
based on the request’s HTTP method when two actions correspond to the same
URL.
 You can use the routing infrastructure to generate internal URLs for your
application.
 The IUrlHelper can be used to generate URLs as a string based on an action
name or on the name of a specific route.
 You can use the RedirectToAction and RedirectToRoute methods to generate
URLs while also generating a redirect response.
148
The binding model:
retrieving and validating
user input
In chapter 5, I showed you how to define a route with parameters—perhaps for the
day in a calendar or the unique ID for a product page. But say a user requests a
given product page—what then? Similarly, what if the request includes data from a
form, to change the name of the product, for example? How do you handle that
request and access the values the user provided?
In the first half of this chapter, we’ll look at using a binding model to retrieve
those parameters from the request so that you can use them in your action meth-
ods. You’ll see how to take the data posted in the form or in the URL and bind them
This chapter covers
 Using request values to create a binding model
 Customizing the model binding process
 Validating user input using DataAnnotations
attributes
149
Understanding the M in MVC
to C# objects. These objects are passed to your action methods as method parameters
so you can do something useful with them—return the correct diary entry or change a
product’s name, for instance.
Once your code is executing in an action method, you might be forgiven for think-
ing that you can happily use the binding model without any further thought. Hold on
now, where did that data come from? From a user—you know they can’t be trusted!
The second half of the chapter focuses on how to make sure that the values provided
by the user are valid and make sense for your app.
You can think of the binding model as the input model to an action method, taking
the user’s raw HTTP request and making it available to your code by populating a
“plain old CLR object” (POCO). Once your action method has run, you’re all set up
to use the output models in ASP.NET Core’s implementation of MVC—the view mod-
els and API models. These are used to generate a response to the user’s request. We’ll
cover them in chapters 7 and 9.
Before we go any further, let’s recap the MVC design pattern and how binding
models fit into ASP.NET Core.
6.1 Understanding the M in MVC
MVC is all about the separation of concerns. The premise is that by isolating each
aspect of your application to focus on a single responsibility, it reduces the interde-
pendencies in your system. This makes it easier to make changes without affecting
other parts of your application.
The classic MVC design pattern has three independent components:
 Controller—Calls methods on the model and selects a view.
 View—Displays a representation of data that makes up the model.
 Model—The data to display and the methods for updating itself.
In this representation, there’s only one model, which represents all the business logic
for the application as well as how to update and modify its internal state. ASP.NET
Core has multiple models, which takes the single responsibility principle one step fur-
ther than some views of MVC.
In chapter 4, we looked at an example of a to-do list application that can show all
the to-do items for a given category and username. With this application, you make a
request to a URL that’s routed using todo/list/{category}/{username}. This will
then return a response showing all the relevant to-do items, as shown in figure 6.1.
The application uses the same MVC constructs you’ve already seen, such as routing
to an action method, as well as a number of different models. Figure 6.2 shows how a
request to this application maps to the MVC design pattern and how it generates the
final response, including additional details around the model binding and validation
of the request.
ASP.NET Core MVC has a number of different models, most of which are POCOs,
and the application model, which is more of a concept around a collection of services.
150 CHAPTER 6 The binding model: retrieving and validating user input
Each of the models in ASP.NET Core MVC is responsible for handling a different
aspect of the overall request:
 Binding model—This includes information that’s explicitly provided by the user
when making a request, as well as additional contextual data. This includes
things like route parameters parsed from the URL, the query string, and form
or JSON data in the request body. The binding model itself is one or more
.NET objects that you define. They’re passed to a controller’s action method as
parameters when it’s executed.
For this example, the binding model would include the name of the cate-
gory, open, and the username, Andrew. The MvcMiddleware inspects the binding
model before the action method executes to check whether the provided values
are valid, though the method will execute even if they’re not.
 Application model—This is typically a whole group of different services and
classes—anything needed to perform some sort of business action in your appli-
cation. It may include the domain model (which represents the thing your app
is trying to describe) and database models (which represent the data stored in a
database), as well as any other additional services.
In the to-do list application, the application model would contain the com-
plete list of to-do items, probably stored in a database, and would know how to
find only those to-do items in the open category assigned to Andrew.
Modeling your domain is a huge topic, with many different possible
approaches, so it’s outside the scope of this book, but we’ll touch briefly on cre-
ating database models in chapter 11.
The category and username
are provided in the URL.
The category and username
are used to filter the list of
to-do task items.
The category and
username can also be
shown in the view model.
Figure 6.1 A basic to-do list application that displays to-do list items. A user can filter
the list of items by changing the category and username parameters in the URL.
151
Understanding the M in MVC
 View model—This contains the data needed by the view to generate a response,
such as the list of to-dos in the open category assigned to Andrew. It often also
contains extra data, such as the total number of to-dos in all categories.
 API model—A variation on the view model, in which the whole model represents
the data to return. Web API controllers use this to generate the final response
to return to the caller, normally in the form of a JSON or XML response. You’ll
see how to create and use API models in chapter 9.
The main difference between API models and view models is that API
models contain only the data to return, whereas view models often contain
1. A request is received for the
URL /todo/list/open/Andrew.
Request
Action
View
2. The routing module matches
the request to the ListCategory
action and derives the
parameters category=open
and username=Andrew.
7. The controller selects the ListCategory
view and passes it the view model
containing the list of to-do items, as well
as other details, such as the category
and username.
8. The view uses the provided
view model to generate an HTML
response, which is returned to the user.
Application model
View model
Domain
model
Services
Database
interaction
Routing
6. The action method calls into services
that make up the application model to
fetch the list of to-do items and to build
a view model.
Controller
HTML
Binding model
Model binding
Model validation
Binding model Model state
3. The parameters parsed from the request
are used to build a binding model, setting
the Username and Category properties on
the ToDoBindingModel.
4. After binding, the model is validated to
check that it has acceptable values. The
result of the validation is stored in
ModelState.
5. The model (ToDoBindingModel) is
passed to the ListCategory action as
a method parameter, while the ModelState
is available as a property on the controller.
Figure 6.2 MVC in ASP.NET Core handling a request to view a subset of items in a to-do list application
152 CHAPTER 6 The binding model: retrieving and validating user input
additional information related to generating the view. You define both types of
model, and both can be POCOs.
These four types of models make up the bulk of any MVC application, handling the
input, business logic, and output of each controller action. Imagine you have an e-
commerce application that allows users to search for clothes by sending requests to
the URL /search/{query} URL, where {query} holds their search term:
 Binding model—Would take the {query} route parameter from the URL and any
values posted in the request body (maybe a sort order, or number of items to
show), and bind them to a C# class, which acts as a throwaway data transport
class. This would be passed as a method parameter to the action method when
it’s invoked.
 Application model—The services and classes that perform the logic. When
invoked by the action method, this would load all the clothes that match the
query, applying the necessary sorting and filters, and return the results back to
the controller.
 View model—The values provided by the application model would be added to
the view model, along with other metadata, such as the total number of items
available, or whether the user can currently check out.
The important point about all these models is that their responsibilities are well-
defined and distinct. Keeping them separate and avoiding reuse helps to ensure your
application stays agile and easy to update.
Now that you’ve been properly introduced to the various models in ASP.NET Core
MVC, it’s time to focus on how to use them. This chapter looks at the binding models
that are built from incoming requests—how are they created, and where do the values
come from?
6.2 From request to model: making the request useful
By now, you should be familiar with the MvcMiddleware handling a request by execut-
ing an action method on a controller. You’ve also already seen a number of action
methods, such as
public IActionResult EditProduct(ProductModel product)
Action methods are normal C# methods (there’s nothing particularly special about
them), so the ASP.NET Core framework needs to be able to call them in the usual way.
When action methods accept parameters as part of their method signature, such as
the preceding product, the framework needs a way to generate those objects. Where
exactly do they come from, and how are they created?
I’ve already hinted that in most cases, these values come from the request itself.
But the HTTP request that the server receives is a series of strings—how does the Mvc-
Middleware turn that into a .NET object? This is where model binding comes in.
153
From request to model: making the request useful
DEFINITION Model binding extracts values from a request and uses them to cre-
ate .NET objects that are passed as method parameters to the action being
executed.
The model binder is responsible for looking through the request that comes in and
finding values to use. It then creates objects of the appropriate type and assigns these
values to your model in a process called binding. These objects are then provided to
your action methods as method parameters.
This binding is a one-way population of an object from the request, not the two-
way data-binding that desktop or mobile development sometimes uses.
NOTE Some method parameters can also be created using dependency injec-
tion, instead of from the request. For more details on dependency injection,
see chapter 10.
The MvcMiddleware can automatically populate your binding models for you using
properties of the request, such as the URL they used, any headers sent in the HTTP
request, any data explicitly POSTed in the request body, and so on. These models are
then passed to your action methods as method parameters.
By default, MVC will use three different binding sources when creating your binding
models. It will look through each of these in order and will take the first value it finds
(if any) that matches the name of the parameter:
 Form values—Sent in the body of an HTTP request when a form is sent to the
server using a POST.
 Route values—Obtained from URL segments or through default values after
matching a route.
 Query string values—Passed at the end of the URL, not used during routing.
The model binding process is shown in figure 6.3. The model binder checks each
binding source to see if it contains a value that could be set on the model. Alterna-
tively, the model can also choose the specific source the value should come from, as
you’ll see later in this section. Once each property is bound, the model is validated
and is passed to the action method to execute it. You’ll learn about the validation pro-
cess in the second half of this chapter.
Figure 6.4 shows an example of a request creating the ProductModel method argu-
ment using model binding for the initial example, the EditProduct action method:
public IActionResult EditProduct(ProductModel product)
The Id property has been bound from a URL route parameter, but the Name and
SellPrice have been bound from the request body. The big advantage of using
model binding is that you don’t have to write the code to parse requests and map the
data yourself. This sort of code is typically repetitive and error-prone, so using the
built-in conventional approach lets you focus on the important aspects of your appli-
cation: the business requirements.
154 CHAPTER 6 The binding model: retrieving and validating user input
TIP Model binding is great for reducing repetitive code. Take advantage of it
whenever possible and you’ll rarely find yourself having to access the Request
object directly.
If you need to, the capabilities are there to let you completely customize the way
model binding works, but it’s relatively rare that you’ll find yourself needing to dig too
deep into this. For the vast majority of cases it works as is, as you’ll see in the remain-
der of this section.
class ProductModel
{
public int Id {get; set;}
public string Name {get; set;}
}
Form values
Route values
Query string values
Form values
Route values
Query string values
Each of the properties on the
binding model tries to bind to
a value from a binding source.
The binding sources are
checked in order, and the
first value that matches is
used. In this case, the Id
property is bound to a
route value.
Because there is a form
value that matches the name
property, the route values
and query string values
are not checked in this case.
Figure 6.3 Model binding involves mapping values from binding sources, which correspond to
different parts of a request.
An HTTP request is received
and directed to the EditProduct
action by routing. This method
requires a ProductModel object.
The model binder builds a new
ProductModel object using values
taken from the request. This
includes route values from the
URL, as well as data sent in the
body of the request.
Figure 6.4 Using model binding to create an instance of a model that’s used to execute an action method
155
From request to model: making the request useful
6.2.1 Binding simple types
You’ll start your journey into model binding by considering a simple action method.
The next listing shows a simple calculator action method that takes one number as a
method parameter and squares it by multiplying it by itself.
public CalculatorController : Controller
{
public IActionResult Square(int value)
{
var result = value * value;
var viewModel = new ResultViewModel(result);
return View(viewModel);
}
}
In the last chapter, you learned about routing and how this selects which action
method to execute. The number of route templates that would match the action
method you’ve defined is endless, but use the following simple template:
{controller}/{action}/{value}
When a client requests a URL, for example /Calculator/Square/5, the MvcMiddleware
uses routing to parse it for route parameters. With the preceding template, this would
produce the following route values:
 controller=Calculator
 action=Square
 value=5
The router only uses the controller and action parameters for routing, but all the values
are stored in a collection as name-value pairs. The router will look for the Calculator-
Controller.Square action method, it will find the method defined in the previous list-
ing and will then attempt to execute it.
This action method contains a single parameter—an integer called value—which
is your binding model. When the MvcMiddleware executes this action method, it will
spot the expected parameter, flick through the route values associated with the
request, and find the value=5 pair. It can then bind the value parameter to this route
value and execute the method. The action method itself doesn’t care about where this
value came from; it goes along its merry way, creating a view model and returning a
ViewResult.
The key thing to appreciate is that you didn’t have to write any extra code to try to
extract the value from the URL when the method executed. All you needed to do was
create a method parameter with the right name and let model binding do its magic.
Listing 6.1 An action method accepting a simple parameter
The method parameter
is the binding model.
A more complex example
would do this work in an
external service, in the
application model.
The result is passed to the view to
generate a response using a view model.
156 CHAPTER 6 The binding model: retrieving and validating user input
Route values aren’t the only values the model binder can use to create your action
method parameters. As you saw previously, MVC will look through three default bind-
ing sources to find a match for your action parameters:
 Form values
 Route values
 Query string values
Each of these binding sources store values as name-value pairs. If none of the binding
sources contain the required value, then the method parameter receives a new
instance of the type instead. The exact value the method parameter will have in this
case depends on the type of the variable:
 For value types, the value will be default(T). For an int parameter this would
be 0, and for a bool it would be false.
 For reference types, the type is created using the default constructor. For cus-
tom types like ProductModel, that will create a new object. For nullable types
like int? or bool?, the value will be null.
 For string types, the value will be null.
WARNING It’s important to consider the behavior of your action method if
model binding fails to bind your method parameters. If none of the binding
sources contain the value, the value passed to the method could be null, or
have an unexpected default value.
Listing 6.1 shows how to bind a single method parameter. Let’s take the next logical
step and look at how you’d bind multiple method parameters.
In the previous chapter, we looked at configuring routing while building a currency
converter application. As the next step in your development, your boss asks you to cre-
ate a method in which the user provides a value in one currency and you must convert
it to another. You first create an action method that accepts the three values you need,
as shown next, and configure a specific route template using "{currencyIn}/
{currencyOut}".
public ConverterController
{
public IActionResult ConvertCurrency(
string currencyIn,
string currencyOut,
int qty)
{
/* method implementation */
}
}
As you can see, there are three different parameters to bind. The question is, where will
the values come from and what will they be set to? The answer is, it depends! Table 6.1
Listing 6.2 An action method accepting multiple binding parameters
157
From request to model: making the request useful
shows a whole variety of possibilities. All these examples use the same route and action
method, but depending on the data sent, different values can be bound from what you
might expect, as the available binding sources offer conflicting values.
For each example, be sure you understand why the bound values have the values that
they do. In the first example, the qty value isn’t found in the form data, in the route
values, or in the query string, so it has the default value of 0. In each of the other
examples, the request contains one or more duplicated values; in these cases, it’s
important to bear in mind the order in which the model binder consults the binding
sources. By default, form values will take precedence over other binding sources,
including route values!
NOTE The default model binder isn’t case sensitive, so a binding value of
QTY=50 will happily bind to the qty parameter.
Although this may seem a little overwhelming, it’s relatively unusual to be binding
from all these different sources at once. It’s more common to have your values all
come from the request body as form values, maybe with an ID from URL route values.
This scenario serves as more of a cautionary tale about the knots you can twist yourself
into if you’re not sure how things work under the hood!
In these examples, you happily bound the qty integer property to incoming val-
ues, but as I mentioned earlier, the values stored in binding sources are all strings.
What types can you convert a string to? The model binder will convert pretty much
any primitive .NET type such as int, float, decimal (and string obviously), plus any-
thing that has a TypeConverter.1
There are a few other special cases that can be con-
verted from a string, such as Type, but thinking of it as primitives only will get you a
long way there!
Table 6.1 Binding request data to action method parameters from two binding sources
URL (route values) HTTP body data (form values) Parameter values bound
/GBP/USD currencyIn=GBP,
currencyOut=USD
qty=0
/GBP/USD?currencyIn=CAD QTY=50 currencyIn=GBP,
currencyOut=USD
qty=50
/GBP/USD?qty=100 qty=50 currencyIn=GBP,
currencyOut=USD
qty=50
/GBP/USD?qty=100 currencyIn=CAD&
currencyOut=EUR&
qty=50
currencyIn=CAD,
currencyOut=EUR
qty=50
1
TypeConverters can be found in the System.ComponentModel.TypeConverter package. You can view the
source code at http://mng.bz/CShQ.
158 CHAPTER 6 The binding model: retrieving and validating user input
6.2.2 Binding complex types
If it seems like only being able to bind simple primitive types is a bit limiting, then
you’re right! Luckily, that’s not the case for the model binder. Although it can only
convert strings directly to those primitive types, it’s also able to bind complex types by
traversing any properties your binding model exposes.
In case this doesn’t make you happy straight off the bat, let’s look at how you’d
have to bind your actions if simple types were your only option. Imagine a user of your
currency converter application has reached a checkout page and is going to exchange
some currency. Great! All you need now is to collect their name, email, and phone
number. Unfortunately, your action method would have to look something like this:
public IActionResult Checkout(string firstName, string lastName, string
phoneNumber, string email)
Yuck! Four parameters might not seem that bad right now, but what happens when
the requirements change and you need to collect other details? The method signature
will keep growing. The model binder will bind the values quite happily, but it’s not
exactly clean code.
SIMPLIFYING METHOD PARAMETERS BY BINDING TO COMPLEX OBJECTS
A common pattern when you have many method parameters is to extract a class that
encapsulates the data the method requires. If extra parameters need to be added, you
can add a new property to this class. This class becomes your binding model and
might look something like this.
public UserBindingModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
}
With this model, you can now update your action’s method signature to
public IActionResult Checkout(UserBindingModel model)
Functionally, the model binder treats this new complex type a little differently. Rather
than looking for parameters with a value that matches the parameter name (model), it
attempts to create a new instance of the model using new UserBindingModel().
NOTE You don’t have to use custom classes for your methods; it depends on
your requirements. If your action method needs only a single integer, then it
makes more sense to bind to the simple parameter.
Next, the model binder loops through all the properties your model exposes, such as
FirstName and LastName. For each of these properties, it consults the collection of
Listing 6.3 A binding model for capturing a user’s details
159
From request to model: making the request useful
binding sources and attempts to find a name-value pair that matches. If it finds one, it
sets the value on the property and moves on to the next.
TIP Although the name of the model isn’t necessary in this example, the
model binder will also look for properties prefixed with the name of the
model, such as model.FirstName and model.LastName. This can be useful if you
have multiple complex arguments to an action method, as it ensures the cor-
rect properties are bound on each type.
Once all the properties that can be bound on the binding model are set, the model is
passed to the action method, which is executed as usual. The behavior from this point
is identical to when you have lots of individual parameters—you’ll end up with the
same values set on your binding model—the code is cleaner and easier to work with.
TIP For a class to be model-bound, it must have a default public constructor.
You can only bind properties which are public and settable.
With this technique you can bind complex hierarchical models, whose properties are
themselves complex models. As long as each property exposes a type that can be
model-bound, the binder can traverse it with ease.
BINDING COLLECTIONS AND DICTIONARIES
As well as ordinary custom classes and primitives, you can bind to collections, lists, and
dictionaries. Imagine you had a page in which a user selected all the currencies they
were interested in; you’d display the rates for all those selected, as shown in figure 6.5.
To achieve this, you could create an action method that accepted a List<string>
type such as
IActionResult ShowRates(List<string> currencies);
Figure 6.5 The select list in the currency converter application
will send a list of selected currencies to the application. Model
binding can bind the selected currencies and customize the view
for the user to show the equivalent cost in the selected currencies.
160 CHAPTER 6 The binding model: retrieving and validating user input
You could then POST data to this method by providing values in a number of differ-
ent formats:
 currencies[index]—Where currencies is the name of the parameter to bind
and index is the index of the item to bind, for example, currencies[0]=
GBR&currencies[1]=USD.
 [index]—If there’s only a single list, you can omit the name of the parameter,
for example, [0]=GBR&[1]=USD.
 currencies—Alternatively, you can omit the index and send currencies as the
key value for every value, for example, currencies=GBR&currencies=USD.
The key values can come from route values and query values, but it’s far more com-
mon to POST them in a form. Dictionaries can use similar binding, where the dictio-
nary key replaces the index both where the parameter is named and where it’s
omitted.
If this all seems a bit confusing, don’t feel too alarmed. If you’re building a tradi-
tional web application, and using Razor views to generate HTML, then it will take care
of most of the naming for you. As you’ll see in chapter 8, the view will ensure that any
form data you POST will be generated in the correct format.
BINDING FILE UPLOADS WITH IFORMFILE
A common feature of many websites is the ability to upload files. This could be a rela-
tively infrequent activity, for example if a user uploads a profile picture for their Stack
Overflow profile, or it may be integral to the application, like uploading photos to
Facebook.
ASP.NET Core supports uploading files by exposing the IFormFile interface. You can
use this interface as a method parameter to your action method and it will be popu-
lated with the details of the file upload:
public IActionResult UploadFile(IFormFile file);
Letting users upload files to your application
Uploading files to websites is a pretty common activity, but you should carefully con-
sider whether your application needs that ability. Whenever files can be uploaded by
users the road is fraught with danger.
You should be careful to treat the incoming files as potentially malicious, don’t trust
the filename provided, take care of large files being uploaded, and don’t allow the
files to be executed on your server.
Files also raise questions as to where the data should be stored—should they go in
a database, in the filesystem, or some other storage? None of these questions has
a straightforward answer and you should think hard about the implications of choos-
ing one over the other. Better yet, if you can avoid it, don’t let users upload files!
161
From request to model: making the request useful
You can also use an IEnumerable<IFormFile> if your action method accepts multiple
files:
public IActionResult UploadFiles(IEnumerable<IFormFile> file);
The IFormFile object exposes several properties and utility methods for reading the
contents of the uploaded file, some of which are shown here:
public interface IFormFile
{
string ContentType { get; }
long Length { get; }
string FileName { get; }
Stream OpenReadStream();
}
As you can see, this interface exposes a FileName property, which returns the filename
that the file was uploaded with. But you know not to trust users, right? You should
never use the filename directly in your code—always generate a new filename for the
file before you save it anywhere.
WARNING Never use posted filenames in your code. Users can use them to
attack your website and access files they shouldn’t be able to.
The IFormFile approach is fine if users are only going to be uploading small files.
When your method accepts an IFormFile instance, the whole content of the file is
buffered in memory and on disk before you receive it. You can then use the Open-
ReadStream method to read the data out.
If users post large files to your website, you may find you start to run out of space in
memory or on disk, as it buffers each of the files. In that case, you may need to stream
the files directly to avoid saving all the data at once. Unfortunately, unlike the model
binding approach, streaming large files can be complex and error-prone, so it’s outside
the scope of this book. For details, see the documentation at http://mng.bz/SH7X.
TIP Don’t use the IFormFile interface to handle large file uploads as you
may see performance issues.
For the vast majority of your action methods, the default configuration of model bind-
ing for simple and complex types works perfectly well, but you may find some situa-
tions where you need to take a bit more control. Luckily, that’s perfectly possible, and
you can completely override the process if necessary by replacing the ModelBinders
used in the guts of the MvcMiddleware.
It’s rare to need that level of customization—a far more common requirement is to
be able to specify which binding source to use to bind an action method parameter.
162 CHAPTER 6 The binding model: retrieving and validating user input
6.2.3 Choosing a binding source
As you’ve already seen, by default the ASP.NET Core model binder will attempt to
bind all action method parameters from three different binding sources: form data,
route data, and the query string.
Occasionally, you may find it necessary to specifically declare which binding source
to bind to, but in other cases, these three sources won’t be sufficient. The most com-
mon scenarios are when you want to bind a method parameter to a request header
value, or when the body of a request contains JSON-formatted data that you want to
bind to a parameter. In these cases, you can decorate your action method parameters
(or binding model class properties) with attributes that say where to bind from, as
shown here.
public class PhotosController
{
public IActionResult TagPhotosWithUser(
[FromHeader] string userId,
[FromBody] List<Photo> photos)
{
/* method implementation */
}
}
In this example, an action method updates a collection of photos with a user. You’ve
got method parameters for the ID of the user to tag in the photos, userId, and a list of
Photo objects to tag, photos.
Rather than binding these action methods using the standard binding sources,
you’ve added attributes to each parameter, indicating the binding source to use. The
[FromHeader] attribute has been applied to the userId parameter. This tells the
model binder to bind the value to an HTTP request header value called userId.
You’re also binding a list of photos to the body of the HTTP request by using the
[FromBody] attribute. This will read JSON from the body of the request and will bind
it to the List<Photo> method parameter.
WARNING Developers familiar with the previous version of ASP.NET should
take note that the [FromBody] attribute is explicitly required when binding to
JSON requests. This differs from previous ASP.NET behavior, in which no
attribute was required.2
You aren’t limited to binding JSON data from the request body—you can use other
formats too, depending on which InputFormatters you configure the MvcMiddleware
Listing 6.4 Choosing a binding source for model binding
2
ASP.NET Core 2.1 introduces the [ApiController] attribute, which allows you to add model binding con-
ventions designed for Web API controllers. This means [FromBody] is automatically used for complex
parameters. For details see http://mng.bz/FYH1.
The userId will be bound from an
HTTP header in the request.
The list of photos will be bound to
the body of the request, typically
in JSON format.
163
From request to model: making the request useful
to use. By default, only a JSON input formatter is configured. You’ll see how to add an
XML formatter in chapter 9, when I discuss Web APIs.
You can use a few different attributes to override the defaults and to specify a bind-
ing source for each method parameter:
 [FromHeader]—Bind to a header value
 [FromQuery]—Bind to a query string value
 [FromRoute]—Bind to route parameters
 [FromForm]—Bind to form data posted in the body of the request
 [FromBody]—Bind to the request’s body content
You can apply each of these to any number of method parameters, as you saw in listing
6.4, with the exception of the [FromBody] attribute—only one parameter may be dec-
orated with the [FromBody] attribute. Also, as form data is sent in the body of a
request, the [FromBody] and [FromForm] attributes are effectively mutually exclusive.
TIP Only one parameter may use the [FromBody] attribute. This attribute
will consume the incoming request as HTTP request bodies can only be safely
read once.
As well as these attributes for specifying binding sources, there are a few other attri-
butes for customizing the binding process even further:
 [BindNever]—The model binder will skip this parameter completely.3
 [BindRequired]—If the parameter was not provided, or was empty, the binder
will add a validation error.
 [FromServices]—Used to indicate the parameter should be provided using
dependency injection (see chapter 10 for details).
In addition, you’ve got the [ModelBinder] attribute, which puts you into “God mode”
with respect to model binding. With this attribute, you can specify the exact binding
source, override the name of the parameter to bind to, and specify the type of binding
to perform. It’ll be rare that you need this one, but when you do, at least it’s there!
By combining all these attributes, you should find you’re able to configure the
model binder to bind to pretty much any data your action method wants to receive. If
you’re building a traditional web app, then you’ll probably find you rarely need to use
them; the defaults should work well for you in most cases.
This brings us to the end of this section on model binding. If all has gone well, you
should have a populated binding model, and the middleware can execute the action
method. It’s time to handle the request, right? Nothing to worry about?
Not so fast! How do you know that the data you received was valid? That you
haven’t been sent malicious data attempting a SQL injection attack, or a phone num-
ber full of letters?
3
You can use the [BindNever] attribute to prevent mass assignment, as discussed in this post:
http://mng.bz/QvfG.
164 CHAPTER 6 The binding model: retrieving and validating user input
The binder is relatively blindly assigning values sent in a request, which you’re hap-
pily going to plug into your own methods? What’s to stop nefarious little Jimmy from
sending malicious values to your application?
Except for basic safeguards, there’s nothing stopping him, which is why it’s import-
ant that you always validate the input coming in. ASP.NET Core provides a way to do
this in a declarative manner out of the box, which is the focus of the second half of
this chapter.
6.3 Handling user input with model validation
Validation in general is a pretty big topic, and one that you’ll need to consider in
every app you build. ASP.NET Core makes it relatively easy to add validation to your
applications by making it an integral part of the framework.
6.3.1 The need for validation
Data can come from many different sources in your web application—you could load
it from files, read it from a database, or you could accept values that a user typed into
a form in requests. Although you might be inclined to trust that the data already on
your server is valid (though this is sometimes a dangerous assumption!), you definitely
shouldn’t trust the data sent as part of a request.
Validation occurs in the MvcMiddleware after model binding, but before the action
executes, as you saw in figure 6.2. Figure 6.6 shows a more compact view of where
model validation fits in this process, demonstrating how a request to a checkout page
that requests a user’s personal details is bound and validated.
1. A request is received for
the URL /checkout/saveuser
..
Request
Action
2. The routing module directs the
request to the SaveUser action on
the CheckoutController.
Binding model
Routing
3. Once an action method has been selected,
a UserBindingModel is built from the details
provided in the request.
Controller
The UserBindingModel and the
validation model state resul
t
are passed to the SaveUser
action as arguments when
the action is executed.
Model binding
Model validation
Binding model
4. The UserBindingModel is validated
according to the DataAnnotation
attributes on its properties.
Model state
Figure 6.6 Validation occurs after model binding but before the action method
executes. The action executes whether or not validation is successful.
165
Handling user input with model validation
You should always validate data provided by users before you use it in your methods. You have
no idea what the browser may have sent you. The classic example of “little Bobby
Tables” (https://xkcd.com/327/) highlights the need to always validate any data sent
by a user.
The need for validation isn’t only to check for security threats, though; it’s also
needed to check for nonmalicious errors, such as:
 Data should be formatted correctly (email fields have a valid email format).
 Numbers might need to be in a particular range (you can’t buy -1 copies of this
book!).
 Some values may be required but others are optional (name may be required
for a profile but phone number is optional).
 Values must conform to your business requirements (you can’t convert a cur-
rency to itself, it needs to be converted to a difference currency).
It might seem like some of these can be dealt with easily enough in the browser—for
example, if a user is selecting a currency to convert to, don’t let them pick the same
currency; and we’ve all seen the “please enter a valid email address” messages.
Unfortunately, although this client-side validation is useful for users, as it gives them
instant feedback, you can never rely on it, as it will always be possible to bypass these
browser protections. It’s always necessary to validate the data as it arrives at your web
application, using server-side validation.
WARNING In your application, always validate user input on the server side.
If that feels a little redundant, like you’ll be duplicating logic and code, then I’m
afraid you’re right. It’s one of the unfortunate aspects of web development; the dupli-
cation is a necessary evil. Thankfully, ASP.NET Core provides several features to try to
reduce this burden.
If you had to write this validation code fresh for every app, it would be tedious and
likely error-prone. Luckily, you can simplify your validation code significantly using a
set of attributes provided by .NET Core and the .NET Framework.
6.3.2 Using DataAnnotations attributes for validation
Validation attributes, or more precisely DataAnnotations attributes, allow you to spec-
ify rules that the properties in your model should conform to. They provide metadata
about your model by describing the sort of data the model should contain, as opposed
to the data itself.
DEFINITION Metadata describes other data, specifying the rules and character-
istics the data should adhere to.
You can apply DataAnnotations attributes directly to your binding models to indicate
the type of data that’s acceptable. This allows you to, for example, check that required
fields have been provided, that numbers are in the correct range, and that email fields
are valid email addresses.
166 CHAPTER 6 The binding model: retrieving and validating user input
As an example, let’s consider the checkout page for your currency converter appli-
cation. You need to collect details about the user before you can continue, so you ask
them to provide their name, email and, optionally, a phone number. The following list-
ing shows the UserBindingModel decorated with validation attributes that represent
the validation rules for the model. This expands on the example you saw in listing 6.3.
Public class UserBindingModel
{
[Required]
[StringLength(100)]
[Display(Name = "Your name")]
public string FirstName { get; set; }
[Required]
[StringLength(100)]
[Display(Name = "Last name")]
public string LastName { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
[Phone]
[Display(Name = "Phone number")]
public string PhoneNumber { get; set; }
}
Suddenly your binding model contains a whole wealth of information where previ-
ously it was pretty sparse on details. For example, you’ve specified that the FirstName
property should always be provided, that it should have a maximum length of 100
characters, and that when it’s referred to (in error messages, for example) it should
be called "Your name" instead of "FirstName".
The great thing about these attributes is that they clearly declare the expected state
of the model. By looking at these attributes, you know what the properties will/should
contain. They also provide hooks for the ASP.NET Core framework to validate that the
data set on the model during model binding is valid, as you’ll see shortly.
You’ve got a plethora of attributes to choose from when applying DataAnnotations
to your models. I’ve listed some of the common ones here, but you can find more in
the System.ComponentModel.DataAnnotations namespace. For a more complete list,
I recommend using IntelliSense in Visual Studio/Visual Studio Code, or you can
always look at the source code directly on GitHub (http://mng.bz/7N7s).
 [CreditCard]—Validates that a property has a valid credit card format
 [EmailAddress]—Validates that a property has a valid email address format
Listing 6.5 Adding DataAnnotations to a binding model to provide metadata
Values marked Required
must be provided.
Customizes the
name used to
describe the
property
The StringLengthAttribute sets the
maximum length for the property.
The StringLengthAttribute sets the
maximum length for the property.
Validates the value of Email
is a valid email address
167
Handling user input with model validation
 [StringLength(max)]—Validates that a string has at most the max amount of
characters
 [MinLength(min)]—Validates that a collection has at least the min amount of
items
 [Phone]—Validates that a property has a valid phone number format
 [Range(min, max)]—Validates that a property has a value between min and max
 [RegularExpression(regex)]—Validates that a property conforms to the
regex regular expression pattern
 [Url]—Validates that a property has a valid URL format
 [Required]—Indicates the property that must be provided
 [Compare]—Allows you to confirm that two properties have the same value (for
example, Email and ConfirmEmail)
WARNING The [EmailAddress] and other attributes only validate that the
format of the value is correct. They don’t validate that the email address exists.
The DataAnnotations attributes aren’t a new feature—they have been part of the
.NET Framework since version 3.5—and their usage in ASP.NET Core is almost the
same as in the previous version of ASP.NET.
They’re also used for other purposes, in addition to validation. Entity Framework
Core (among others) uses DataAnnotations to define the types of column and rule to
use when creating database tables from C# classes. You can read more about Entity
Framework Core in chapter 12, and in Jon P Smith’s Entity Framework Core in Action
(Manning, 2018).
If the DataAnnotation attributes provided out of the box don’t cover everything
you need, then it’s also possible to write custom attributes by deriving from the base
ValidationAttribute. You’ll see how to create a custom attribute for your currency
converter application in chapter 19.
Alternatively, if you’re not a fan of the attribute-based approach, the MvcMiddleware
is flexible enough that you can replace the validation infrastructure with your
preferred technique. For example, you could use the popular FluentValidation
library (https://github.com/JeremySkinner/FluentValidation) in place of the Data-
Annotations attributes if you prefer.
TIP DataAnnotations are good for input validation of properties in isola-
tion, but not so good for validating business rules. You’ll most likely need to
perform this validation outside the DataAnnotations framework.
Whichever validation approach you use, it’s important to remember that these tech-
niques don’t protect your application by themselves. The MvcMiddleware will ensure
the validation occurs, but it doesn’t automatically do anything if validation fails. In the
next section, we’ll look at how to check the validation result on the server and handle
the case where validation has failed.
168 CHAPTER 6 The binding model: retrieving and validating user input
6.3.3 Validating on the server for safety
Validation of the binding model occurs before the action executes, but note that the
action always executes, whether the validation failed or succeeded. It’s the responsibil-
ity of the action method to handle the result of the validation.
NOTE Validation happens automatically but handling validation failures is
the responsibility of the action method.
The MvcMiddleware stores the output of the validation attempt in a property on the
ControllerBase base class called ModelState. This object is a ModelStateDictionary,
which contains a list of all the validation errors that occurred after model binding, as
well as some utility properties for working with it.
As an example, listing 6.6 shows the SaveUser action method being invoked with
the UserBindingModel shown in the previous section. This action doesn’t do anything
with the data currently, but the pattern of checking ModelState early in the method is
the key takeaway here.
public class CheckoutController : Controller
{
public IActionResult SaveUser(UserBindingModel model)
{
if(!ModelState.IsValid)
{
return View(model);
}
/* Save to the database, update user, return success *
return RedirectToAction("Success");
}
}
If the ModelState indicates an error occurred, the method immediately calls the View
helper method, passing in the model. This will return a ViewResult that will ultimately
generate HTML to return to the user, as you saw in chapter 4. By passing in the
model, the view can repopulate the values the user provided in the form when it’s dis-
played, as shown in figure 6.7. Also, helpful messages to the user can be added using
the validation errors in the ModelState.
If the request is successful, the action method returns a RedirectToAction result
that will redirect the user to the Success action on the CheckoutController. This pat-
tern of returning a redirect response after a successful POST is called the POST-
REDIRECT-GET pattern.
Listing 6.6 Checking model state to view the validation result
Deriving from Controller makes
the ModelState property available.
The model parameter
contains the model-
bound data.
If there were
validation errors,
IsValid will be false.
Validation failed, so redisplay the form
with errors, and finish the method early.
Validation passed, so
it’s safe to use the data
provided in model.
169
Handling user input with model validation
NOTE The error messages displayed on the form are the default values for
each validation attribute. You can customize the message by setting the
ErrorMessage property on any of the validation attributes. For example, you
could customize a [Required] attribute using [Required(ErrorMessage=
"Required")].
Figure 6.7 When validation fails, you can
redisplay the form to display ModelState
validation errors to the user. Note the “Your
name” field has no associated validation errors,
unlike the other fields.
POST-REDIRECT-GET
The POST-REDIRECT-GET design pattern is a web development pattern that prevents
users from accidently submitting the same form multiple times. Users typically sub-
mit a form using the standard browser POST mechanism, sending data to the server.
This is the normal way by which you might take a payment, for example.
If a server takes the naive approach and responds with a 200 OK response and some
HTML to display, then the user will still be on the same URL. If the user refreshes
their browser, they will be making an additional POST to the server, potentially making
another payment! Browsers have some mechanisms to avoid this, such as in the fol-
lowing figure, but the user experience isn’t desirable.
170 CHAPTER 6 The binding model: retrieving and validating user input
You might be wondering why validation isn’t handled automatically—if validation has
occurred, and you have the result, why does the action method get executed at all?
Isn’t there a risk that you might forget to check the validation result?
This is true, and in some cases the best thing is to make the generation of the vali-
dation check and response automatic—this is often the case for Web APIs, and can be
made automatic using filters, which we’ll cover in chapters 9 and 13.
For traditional web apps however, you typically still want to generate an HTML
response, even when validation failed. This allows the user to see the problem, and
potentially correct it. This is often harder to make automatic, especially as the view
model you use to generate your view and the binding model that’s validated are often
different classes.
By including the IsValid check explicitly in your action methods, it’s easier to
control what happens when other validation fails. If the user tries to update a product,
then you won’t know whether the requested product ID exists as part of the Data-
Annotations validation, only whether the ID has the correct format. By moving the
validation to the action method, you can treat data and business rule validation fail-
ures similarly.
(continued)
The POST-REDIRECT-GET pattern says that in response to a successful POST, you
should return a REDIRECT response to a new URL, which will be followed by the
browser making a GET to the URL. If the user refreshes their browser now, then they’ll
be refreshing the final GET call. No additional POST is made, so no additional pay-
ments or side effects should occur.
This pattern is easy to achieve in ASP.NET Core MVC applications using the pattern
shown in listing 6.6. By returning a RedirectToActionResult after a successful
POST, your application will be safe if the user refreshes the page in their browser.
Refreshing a browser window
after a POST causes warnings
to be shown to the user
171
Handling user input with model validation
You might find you need to load additional data into the view model before you
can regenerate the view—a list of available currencies, for example. That all becomes
simpler and more explicit with the IsValid pattern.
I hope I’ve hammered home how important it is to validate user input in ASP.NET
Core, but just in case: VALIDATE! There, we’re good. Having said that, just perform-
ing validation on the server can leave users with a slightly poor experience. How many
times have you filled out a form online, submitted it, gone to get a snack, and come
back to find out you mistyped something and have to redo it. Wouldn’t it be nicer to
have that feedback immediately?
6.3.4 Validating on the client for user experience
You can add client-side validation to your application in a couple of different ways.
HTML5 has several built-in validation behaviors that many browsers will use. If you
display an email address field on a page and use the “email” HMTL input type, then
the browser will automatically stop you from submitting an invalid format, as shown in
figure 6.8.
Your application doesn’t control this validation, it’s built into modern HTML5 brows-
ers.4
The alternative approach is to perform client-side validation by running Java-
Script on the page and checking the values the user has entered before submitting the
form. This is the default approach used in ASP.NET Core MVC when you’re building
a traditional web application.
I’ll go into detail on how to generate the client-side validation helpers in the next
chapter, where you’ll see the DataAnnotations attributes come to the fore once again.
By decorating a view model with these attributes, you provide the necessary metadata
to the Razor engine for it to generate the appropriate HTML.
With this approach, the user sees any errors with their form immediately, even
before the request is sent to the server, as shown in figure 6.9. This gives a much
shorter feedback cycle, providing a much better user experience.
If you’re building an SPA, then the onus is on the client-side framework to validate
the data on the client side before posting it to the Web API. The Web API will still val-
idate the data when it arrives at the server, but the client-side framework is responsible
for providing the smooth user experience.
4
HTML5 constraint validation support varies by browser. For details on the available constraints, see
http://mng.bz/daX3.
Figure 6.8 By default, modern browsers
will automatically validate fields of the
email type before a form is submitted.
172 CHAPTER 6 The binding model: retrieving and validating user input
When you use the Razor templating engine to generate your HTML, you get much of
this validation for free. It automatically configures the validation for the built-in attri-
butes without requiring any additional work. Unfortunately, if you’ve used custom
ValidationAttributes, then these will only run on the server by default; you need to
do some wiring up of the attribute to make it work on the client side too. Despite this,
custom validation attributes can be useful for handling common scenarios in your
application, as you’ll see in chapter 19.
That concludes this look at the binding models of MVC. You saw how the ASP.NET
Core framework uses model binding to simplify the process of extracting values from
a request and turning them into normal .NET objects you can quickly work with. The
most important aspect of this chapter is the focus on validation—this is a common
concern for all web applications, and the use of DataAnnotations can make it easy to
add validation to your models.
In the next chapter, we continue our journey through MVC by looking at how to
create views. In particular, you’ll learn how to generate HTML in response to a
request using the Razor templating engine.
Figure 6.9 With client-side validation, clicking Submit will trigger validation to be shown in the
browser before the request is sent to the server. As shown in the right-hand pane, no request is sent.
173
Summary
Summary
 ASP.NET Core MVC has three distinct models, each responsible for a different
aspect of a request. The binding model encapsulates data sent as part of a
request, the application model represents the state of the application, and the
view model contains data used by the view to generate a response.
 Model binding extracts values from a request and uses them to create .NET
objects to pass as method parameters to the action being executed.
 By default, there are three binding sources: POSTed form values, route values,
and the query string. The binder will interrogate these in order when trying to
bind an action method parameter.
 When binding values to parameters, the names of the parameters aren’t case
sensitive.
 You can bind to simple method parameter types or to the properties of complex
types.
 To bind complex types, they must have a default constructor and public, setta-
ble properties.
 Simple types must be convertible to strings to be bound automatically, for
example numbers, dates, and Boolean values.
 Collections and dictionaries can be bound using the [index]=value and
[key]=value syntax, respectively.
 You can customize the binding source for an action parameter using [From*]
attributes applied to the method, such as [FromHeader] and [FromBody]. These
can be used to bind to nondefault binding sources, such as headers or JSON
body content.
 In contrast to the previous version of ASP.NET, the [FromBody] attribute is
required when binding JSON properties (previously it was not required).
 Validation is necessary to check for security threats. Check that data is format-
ted correctly, confirm it conforms to expected values and that it meets your
business rules.
 ASP.NET Core provides DataAnnotations attributes to allow you to declara-
tively define the expected values.
 Validation occurs automatically after model binding, but you must manually
check the result of the validation and act accordingly in your action method by
interrogating the ModelState property.
 Client-side validation provides a better user experience than server-side valida-
tion alone, but you should always use server-side validation.
 Client-side validation uses JavaScript and attributes applied to your HTML ele-
ments to validate form values.
174
Rendering HTML
using Razor views
In the previous four chapters, I’ve covered a whole cross-section of MVC, including
the MVC design pattern itself, controllers and actions, routing, and binding mod-
els. This chapter shows how to create your views—the UI to your application. For
desktop apps, that might be WinForms or WPF. For Web APIs, that might be a non-
visual view, such as a JSON or XML response, which is subsequently used to gener-
ate HTML in the client’s browser. For traditional web applications, the UI is the
HTML, CSS, and JavaScript that’s delivered to the user’s browser directly, and that
they interact with.
This chapter covers
 Creating views to display HTML to a user
 Using C# and the Razor markup syntax to
generate HTML dynamically
 Reusing common code with layouts and partial
views
175
Views: rendering the user interface in MVC
In ASP.NET Core, views are normally created using the Razor markup syntax
(sometimes described as a templating language), which uses a mixture of HTML and
C# to generate the final result. This chapter covers some of the features of Razor and
how to use it to build the view templates for your application.
Generally speaking, users will have two sorts of interactions with your app: they
read data that your app displays, and they send data or commands back to it. The
Razor language contains a number of constructs that make it simple to build both
types of applications.
When displaying data, you can use the Razor language to easily combine static
HTML with values from your view model. Razor can use C# as a control mechanism,
so adding conditional elements and loops is simple, something you couldn’t achieve
with HTML alone.
The normal approach to sending data to web applications is with HTML forms.
Virtually every dynamic app you build will use forms; some applications will be pretty
much nothing but forms! ASP.NET Core and the Razor templating language include a
number of helpers that make generating HTML forms easy, called Tag Helpers.
NOTE You’ll get a brief glimpse of Tag Helpers in the next section, but I’ll
explore them in detail in the next chapter.
In this chapter, we’ll be focusing primarily on displaying data and generating HTML
using Razor, rather than creating forms. You’ll see how to render values from your
view model to the HTML, and how to use C# to control the generated output. Finally,
you’ll learn how to extract the common elements of your views into sub-views called
layouts and partial views, and how to compose them to create the final HTML page.
7.1 Views: rendering the user interface in MVC
As you know from earlier chapters on the MVC design pattern, it’s the job of the con-
troller and action method to choose which view to return to the client. For example, if
you’re developing a to-do list application, imagine a request to view a particular to-do
item, as shown in figure 7.1.
A typical request follows the steps shown in figure 7.1:
1 The request is received by Kestrel and uses routing to determine the action to
invoke—the ViewToDo method on ToDoController.
2 The model binder uses the request to build the action method’s binding
model, as you saw in the last chapter. This is passed to the action method as a
method parameter when it’s executed. The action method checks that you have
passed a valid id for the to-do item, making the request valid.
3 Assuming all looks good, the action method calls out to the various services that
make up the application model. This might load the details about the to-do
from a database, or from the filesystem, returning them to the controller. As
part of this process, either the application model or the controller itself gener-
ates a view model.
176 CHAPTER 7 Rendering HTML using Razor views
The view model is a custom class, typically a POCO, that contains all the data
required to render a view. In this example, it contains details about the to-do
itself, but it might also contain other data: how many to-dos you have left,
whether you have any to-dos scheduled for today, your username, and so on,
anything that controls how to generate the end UI for the request.
4 The final task for the action method is to choose a view template to render. In
most cases, an action method will only need to render a single type of template,
but there are situations when you might like to render a different one depend-
ing on the data you get back from the application model. For example, you may
have one template for to-do items that contain a list of tasks, and a different
template when the to-do item contains a picture, as in figure 7.2.
5 The selected view template uses the view model to generate the final response,
and returns it back to the user via the middleware pipeline.
A common thread throughout this discussion of MVC is the separation of concerns it
brings, and this is no different when it comes to your views. It would be easy enough to
directly generate the HTML in your application model or in your controller actions,
but instead you’ll delegate that responsibility to a single component, the view.
1. A request is received to
the URL /todo/View/3.
Request
Action
View
2. The routing module directs
the request to the ViewToDo
action on the ToDoController
and builds a binding model.
4. The controller selects an
appropriate view and passes
it the view model containing
the details about the to-do item.
5. The view uses the provided view
model to generate an HTML response
that is returned to the user.
Application model
Binding model
View model
Domain
model
Services
Database
interaction
Routing 3. The action method calls into
services that make up the application
model to fetch details about the to-do
item and to build a view model.
Controller
HTML
id = 3
Figure 7.1 Handling a request for a to-do list item using ASP.NET Core MVC
177
Views: rendering the user interface in MVC
But even more than that, you’ll also separate the data required to build the view from
the process of building it, by using a view model. The view model should contain all the
dynamic data needed by the view to generate the final output.
An action method selects a template and requests that a view be rendered by
returning a ViewResult object that contains the view model. You saw this type in chap-
ter 4, but we’ll look closer at how you they’re typically used in the following sections.
When a ViewResult executes, it locates the requested Razor template and exe-
cutes the content. The use of C# in the template means you can dynamically generate
the final HTML sent to the browser. This allows you to, for example, display the name
of the current user in the page, hide links the current user doesn’t have access to, or
render a button for every item in a list.
Imagine your boss asks you to add a page to your application that displays a list of
the application’s users. You should also be able to view a user from the page, or create
a new one, as shown in figure 7.3.
With Razor templates, generating this sort of dynamic content is simple. For exam-
ple, the following listing shows the template that could be used to generate the inter-
face in figure 7.3. It combines standard HTML with C# statements, and uses Tag
Helpers to generate the form elements.
Figure 7.2 The controller is responsible for selecting the appropriate
template to render the view model. For example, a list of to-do items
can be displayed using a different template to a to-do item consisting
of a picture.
178 CHAPTER 7 Rendering HTML using Razor views
@model IndexViewModel
<div class="row">
<div class="col-md-12">
<form asp-action="Index">
<div class="form-group">
<label asp-for="NewUser"></label>
<input class="form-control" asp-for="NewUser" />
<span asp-validation-for="NewUser"></span>
</div>
<div class="form-group">
<button type="submit"
class="btn btn-success">Add</button>
</div>
</form>
</div>
</div>
<h4>Number of users: @Model.ExistingUsers.Count</h4>
<div class="row">
<div class="col-md-12">
<ul>
@foreach (var user in Model.ExistingUsers)
Listing 7.1 A Razor template to list users and a form for adding a new user
Form elements can be used
to send values back to the
application.
The view model contains the data
you wish to display on the page.
Razor markup describes how to display this
data using a mixture of HTML and C#.
By combining the data in your view
model with the Razor markup, HTML
can be generated dynamically, instead
of being fixed at compile time.
@foreach(var user in Model.ExistingUsers)
{
<li>
<button>View</button>
<span>@user</span>
</li>
}
Model.ExistingUsers = new[]
{
'Andrew',
'Robbie',
'Jimmy',
'Bart'
};
Figure 7.3 The use of C# in Razor lets you easily generate dynamic HTML that varies at runtime.
Normal HTML is sent to
the browser unchanged.
Tag Helpers attach to
HTML elements to
create forms.
Values can be written
from C# objects to
the HTML.
C# constructs like for
loops can be used in Razor.
179
Creating Razor views
{
<li>
<a class="btn btn-default"
asp-action="ViewUser"
asp-route-userName="@user">View</a>
<span>@user</span>
</li>
}
</ul>
</div>
</div>
This example demonstrates a variety of Razor features. There’s a mixture of HTML
that’s written unmodified to the response output, and various C# constructs that are
used to dynamically generate HTML. In addition, you can see a number of Tag Help-
ers. These look like normal HTML attributes that start asp-, but they’re part of the
Razor language. They can customize the HTML element they’re attached to, chang-
ing how it’s rendered. They make building HTML forms much simpler than they
would be otherwise. Don’t worry if this template is a bit overwhelming at the moment;
we’ll break it all down as you progress through this chapter and the next.
You add Razor templates to your project as cshtml files. They’re sometimes copied
directly to the server when you deploy your application and are compiled at runtime
by your application. If you modify one of these files, then your app will automatically
recompile it on the fly. If you don’t need that capability and would rather take the
one-time performance hit at compile time, then you can also precompile these files
when you build your app. Precompilation is the default in ASP.NET Core 2.0.
NOTE Like most things in ASP.NET Core, it’s possible to swap out the Razor
templating engine and replace it with your own server-side rendering engine.
You can’t replace Razor with a client-side framework like AngularJS or React.
If you want to take this approach, you’d use Web APIs instead. I’ll discuss Web
APIs in detail in chapter 9.
In the next section, you’ll learn how to choose a view template to invoke from your
controller and how to pass your view model to it to build the HTML response.
7.2 Creating Razor views
With ASP.NET Core, whenever you need to display an HTML response to the user,
you should use a view to generate it. Although it’s possible to directly return a string
from your action methods that would be rendered as HTML in the browser, this
approach doesn’t adhere to the MVC separation of concerns and will quickly leave
you tearing your hair out.
NOTE Some middleware, such as the WelcomePageMiddleware you saw in chap-
ter 3, may generate HTML responses without using a view, which can make
sense in some situations. But your MVC controllers should always generate
HTML using views.
Tag Helpers can also be
used outside of forms to
help in other HTML
generation.
180 CHAPTER 7 Rendering HTML using Razor views
This chapter focuses on the final part of the ASP.NET Core MVC pattern, the view
generation. Figure 7.4 shows a zoomed-in view of this process, right after the action
has invoked the application model and received some data back.
Some of this figure should be familiar—it’s similar to the lower half of figure 7.1. It
shows that the action method uses a ViewResult object to indicate that a view should
be created. This ViewResult contains the view model and the name of the view tem-
plate to render.
At this point, the control flow passes back to the MVC middleware, which uses a
series of heuristics (as you’ll see shortly) to locate the view, based on the template
name provided. Once a template has been located, the Razor engine passes it the view
model from the ViewResult and invokes it to generate the final HTML.
You saw how to create controllers in chapter 4, and in this section, you’ll see how
to create views and ViewResult objects and how to specify the template to render. You
can add a new view template to your application in Visual Studio by right-clicking in
your application in Solution Explorer and choosing Add > New Item, and selecting
MVC View Page from the dialog, as shown in figure 7.5. If you aren’t using Visual Stu-
dio, create a blank new file with the cshtml file extension.
Action
View
1. The final step taken by
MVC action method is to
generate a view model
and select the name of
the template to render.
4. Once located, the View template
is passed the view model and invoked
to generate the final HTML output.
View model
Controller
HTML
Template name
ViewResult
2. These values are encapsulated
in a ViewResult object, which is
returned from the action method.
Locate template
View model
3. The MVC middleware uses the
template name to find the specific
view template to render.
5. The generated HTML is passed
back through the middleware pipeline
and back to the user’s browser.
Figure 7.4 The process of generating HTML from an MVC controller using a view
181
Creating Razor views
With your view template created, you now need to invoke it. In the remainder of this
section you’ll see how to create a ViewResult and provide it a view model so that your
view can render the data it contains.
7.2.1 Selecting a view from a controller
In most cases, you won’t create a ViewResult directly in your action methods. Instead,
you’ll use one of the View helper methods on the Controller base class. These helper
methods simplify passing in a view model and selecting an action method, but there’s
nothing magic about them—all they do is simplify creating ViewResult objects.
In the simplest case, you can call the View method without any arguments, as
shown next. This helper method returns a ViewResult that will use conventions to
find the view template to render.
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
}
Listing 7.2 Returning ViewResult from an action method using default conventions
Figure 7.5 The Add New Item dialog. Choosing MVC View Page will add a new Razor template view
file to your application.
Inheriting from the Controller
base class makes the View helper
methods available.
The View helper method
returns a ViewResult.
182 CHAPTER 7 Rendering HTML using Razor views
In this example, the View helper method returns a ViewResult without specifying the
name of a template to run. Instead, the name of the template to use is based on the
name of the action method. Given that the controller is called HomeController and
the method is called Index, by default the Razor template engine will look for a tem-
plate at the Views/Home/Index.cshtml location.
This is another case of using conventions in MVC to reduce the amount of boiler-
plate you have to write. As always, the conventions are optional. You can also explicitly
pass the name of the template to run as a string to the View method. For example, if
the Index method instead returned View("ListView");, then the templating engine
would look for a template called ListView.cshtml instead. You can even specify the
complete path to the view file, relative to your application’s root folder, such as View
("Views/global.cshtml");,which would look for the template at the Views/global
.chtml location.
NOTE When specifying the absolute path to a view, you must include both
the top-level Views folder and the cshtml file extension in the path.
Figure 7.6 shows the complete process used by the default Razor templating engine to
try to locate the correct View template to execute. It’s possible for more than one tem-
plate to be eligible, for example if an Index.chstml file exists in both the Home and
Shared folders. In a manner analogous to routing, the engine will use the first tem-
plate it finds.
You may find it tempting to be explicit by default and provide the name of the view
file you want to render in your controller; if so, I’d encourage you to fight that urge.
You’ll have a much simpler time if you embrace the conventions as they are and go
with the flow. That extends to anyone else who looks at your code; if you stick to the
standard conventions, then there’ll be a comforting familiarity when they look at your
app. That can only be a good thing!
Now you know where to place your view templates, and how to direct the templat-
ing engine to them. It’s about time to take a closer look at a template, so you can see
what you’re working with!
7.2.2 Introducing Razor templates
Razor templates contain a mixture of HTML and C# code interspersed with one
another. The HTML markup lets you easily describe exactly what should be sent to the
browser, whereas the C# code can be used to dynamically change what is rendered.
For example, the listing 7.3 shows an example of Razor rendering a list of strings, rep-
resenting to-do items.
183
Creating Razor views
@{
var tasks = new List<string>
{ "Buy milk", "Buy eggs", "Buy bread" };
}
Listing 7.3 Razor template for rendering a list of strings
Has an absolute
view path been
provided?
Yes
No
Does a view exist in
views/controller/action?
Controller
name
Action
name
Yes Use provided
absolute path
Use
views/controller/action
Use
views/shared/action
Does a view exist in
views/shared/action?
Yes
No
Throw exception:
InvalidOperationException
MVC action creates
a ViewResult
Action
name
No
Does a view exist
at the provided path?
No
Throw exception:
InvalidOperationException
Yes
Figure 7.6 A flow chart describing how the Razor templating engine locates the
correct view template to execute
Arbitrary C# can be
executed in a template.
Variables remain in scope
throughout the page.
184 CHAPTER 7 Rendering HTML using Razor views
<h1>Tasks to complete</h1>
<ul>
@for(var i=0; i< tasks.Count; i++)
{
var task = tasks[i];
<li>@i - @task</li>
}
</ul>
The pure HTML sections in this template are the angle brackets. The Razor engine
copies this HTML directly to the output, unchanged, as though you were writing a
normal HTML file.
As well as HTML, you can also see a number of C# statements in there. The advan-
tage of being able to, for example, use a for loop rather than having to explicitly write
out each <li> element should be self-evident. I’ll dive a little deeper into more of the
C# features of Razor in the next section. When rendered, this template would pro-
duce the HTML shown here.
<h1>Tasks to complete</h1>
<ul>
<li>0 - Buy milk</li>
<li>1 - Buy eggs</li>
<li>2 - Buy bread</li>
</ul>
As you can see, the final output of a Razor template after it’s been rendered is simple
HTML. There’s nothing complicated left, just straight markup that can be sent to the
browser and rendered. Figure 7.7 shows how a browser would render it.
Listing 7.4 HTML output produced by rendering a Razor template
Standard HTML markup will be
rendered to the output unchanged.
Mixing C# and HTML
allows you to dynamically
create HTML at runtime.
HTML from the Razor template is
written directly to the output.
The <li> elements are generated
dynamically based on the data.
HTML from the Razor template is
written directly to the output.
The data to display is defined in C# .
Razor markup describes how to display this
data using a mixture of HTML and C#.
By combining the C# object data
with the Razor markup, HTML can
be generated dynamically, instead
of being fixed at compile time.
<h1>Tasks to complete</h1>
<ul>
@for(var i=0; i<tasks.Count; i++)
{
var task = tasks[i];
<li>@i - @task</li>
}
</ul>
var tasks = new List<string>
{
'Buy milk',
'Buy eggs',
'Buy bread'
}
Figure 7.7 Razor templates can be used to generate the HTML dynamically at runtime from C# objects.
185
Creating Razor views
In this example, I hardcoded the list values for simplicity—there was no dynamic data
provided. This is often the case on simple action methods like you might have on your
homepage—you need to display an almost static page. For the rest of your applica-
tion, it will be far more common to have some sort of data you need to display, encap-
sulated by a view model.
7.2.3 Passing data to views
In ASP.NET Core, you have a number of ways of passing data from an action method
in a controller to a view. Which approach is best will depend on the data you’re trying
to pass through, but in general you should use the mechanisms in the following order:
 View model—The view model should be used to encapsulate any data that needs
to be displayed, which is specific to that view. It’s a strongly typed class that can
be any sort of object you like. The view model is available in the view when it’s
rendered, as you’ll see shortly.
 ViewData—This is a dictionary of objects with string keys that can be used to
pass arbitrary data from the controller to the view.
 ViewBag—A version of the ViewData that uses C# dynamic objects instead of
string keys. It’s a wrapper around the ViewData object, so you can use it in
place of ViewData if you prefer dynamic to Dictionary. I’ll only refer to View-
Data in this book, as they’re almost the same thing.
 HttpContext—Technically the HttpContext object is available in both the con-
troller and view, so you could use it to transfer data between them. But don’t—
there’s no need for it with the other methods and abstractions available to you.
Far and away the best approach for passing data from a controller to a view is to use a
view model. There’s nothing special about a view model; it’s a custom class that you
create to hold the data you require.
NOTE Many frameworks have the concept of a data context for binding UI
components. The view model is a similar concept, in that it contains values to
display in the UI, but the binding is only one-directional; the view model pro-
vides values to the UI, and once the UI is built and sent as a response, the view
model is destroyed.
You can make a view model available by passing it to the View helper method from
your controller, as shown in the following listing. In this example, I’ve created a
ToDoItemViewModel to hold the values I want to display in the view—the Title and its
Tasks.
public class ToDoController : Controller
{
public IActionResult ViewTodo(int id)
{
Listing 7.5 Passing a view model to a view for a controller
Inheriting from the Controller-based
class provides the helper View methods
186 CHAPTER 7 Rendering HTML using Razor views
var viewModel = new ToDoItemViewModel
{
Title = "Tasks for today",
Tasks = new List<string>{
{
"Get fuel",
"Check oil",
"Check tyre pressure"
}
};
return View(viewModel);
}
}
By passing it to the View method, the viewModel instance will be available in the tem-
plate, allowing you to access the values it contains from the view template. All that’s
required is to add an @model directive at the top of your template so that it knows the
Type of the view model it should expect:
@model ToDoItemViewModel
DEFINITION A directive is a statement in a Razor file that changes the way the
template is parsed or compiled. Another common directive is the @using
newNamespace directive, which would make objects in the newNamespace name-
space available.
Once you’ve added this directive, you can access any of the data on the todoModel you
provided, using the Model property. For example, to display the Title property from
the ToDoItemViewModel, you’d use <h1>@Model.Title</h1>. This would render the
string provided on the original viewModel object, producing the <h1>Tasks for
today</h1> HTML.
TIP Note that the @model directive should be at the top of your view, and has
a lowercase m. The Model property can be accessed anywhere in the view and
has an uppercase M.
In the vast majority of cases, using a view model is the way to go; it’s the standard mech-
anism for passing data between the controller and the view. But in some circumstances,
a view model may not be the best fit. This is often the case when you want to pass data
between view layouts (you’ll see how this works in the last section of this chapter).
A common example is the title of the page. You need to provide a title for every
page, so you could create a base class with a Title property and make all your view
models inherit from it. But that’s a little cumbersome, so a common approach for this
situation is to use the ViewData collection to pass data around.
In fact, the standard MVC templates use this approach by default rather than using
view models by setting values in the ViewData dictionary from within the action methods:
public IActionResult Contact()
{
ViewData["Message"] = "Your contact page.";
Building a view model: this would
normally call out to a database or
filesystem to load the data.
Creates a ViewResult that looks for a view
called ViewTodo, and passes it the viewModel
187
Creating dynamic web pages with Razor
return View();
}
They’re then displayed in the template using
<h3>@ViewData["Message"]</h3>
NOTE Personally, I don’t agree with the default approach in the templates—
the message being presented is integral to the page, so it should probably be
part of a view model.
You can also set values on the ViewData dictionary from within the view itself:
@{
ViewData["Title"] = "About";
}
<h2>@ViewData["Title"].</h2>
This template sets the value of the "Title" key in the ViewData dictionary to "About"
and then fetches the key to render in the template. This might seem superfluous, but
as the ViewData dictionary is shared throughout the request, it makes the title of the
page available in layouts, as you’ll see later. When rendered, this would produce the
following output:
<h2>About.</h2>
TIP Create a set of global, static constants for any ViewData keys, and refer-
ence those instead of typing "Title" repeatedly. You’ll get IntelliSense for
the values, they’ll be refactor-safe, and you’ll avoid hard-to-spot typos.
As I mentioned previously, there are other mechanisms besides view models and
ViewData that you can use to pass data around, but these two are the only ones I use
personally, as you can do everything you need with them.
You’ve had a small taste of the power available to you in Razor templates, but in the
next section, I’d like to dive a little deeper into some of the available C# capabilities.
7.3 Creating dynamic web pages with Razor
You might be glad to know that pretty much anything you can do in C# is possible in
Razor syntax. Under the covers, the cshtml files are compiled into normal C# code
(with string for the raw HTML sections), so whatever weird and wonderful behavior
you need can be created.
Having said that, just because you can do something doesn’t mean you should.
You’ll find it much easier to work with, and maintain, your files if you keep them as
simple as possible. This is true of pretty much all programming, but I find especially
so with Razor templates.
This section covers some of the more common C# constructs you can use. If you
find you need to achieve something a more exotic, refer to the Razor syntax docu-
mentation at https://docs.microsoft.com/en-us/aspnet/core/mvc/views/razor.
188 CHAPTER 7 Rendering HTML using Razor views
7.3.1 Using C# in Razor templates
One of the most common requirements when working with Razor templates is to ren-
der a value you’ve calculated in C# to the HTML. For example, you might want to
print the current year to use with a copyright statement in your HTML, to give
<p>Copyright 2018 ©</p>
or you might want to print the result of a calculation, for example
<p>The sum of 1 and 2 is <i>3</i><p>
You can do this in two ways, depending on the exact C# code you need to execute. If
the code is a single statement, then you can use the @ symbol to indicate you want to
write the result to the HTML output, as shown in figure 7.8. You’ve already seen this
used to write out values from the view model or from ViewData.
If the C# you want to execute is something that needs a space, then you need to use
parentheses to demarcate the C#, as shown in figure 7.9.
These two approaches, in which C# is evaluated and written directly to the HTML out-
put, are called Razor expressions. Sometimes you want to execute some C#, but you
don’t need to output the values. I used this technique when we were setting values in
ViewData:
@{
ViewData["Title"] = "About";
}
<p>Copyright @DateTime.Now.Year &copy;</p>
@ indicates start of
C# expression
Whitespace indicates
end of C# expression
HTML HTML
C# expression. The result
will be written to the
HTML output.
Figure 7.8 Writing the result of a C#
expression to HTML. The @ symbol
indicates where the C# code begins and
the expression ends at the end of the
statement, in this case at the space.
<p>The sum of 1 and 2 is <i>@(1 + 2)</i></p>
HTML
The whole C# expression within @() is
evaluated and written to the HTML output.
HTML
Figure 7.9 When a C# expression
contains whitespace, you must wrap
it in parentheses using @() so the
Razor engine knows where the C#
stops and HTML begins.
189
Creating dynamic web pages with Razor
This example demonstrates a Razor code block, which is normal C# code, identified by
the @{} structure. Nothing is written to the HTML output here, it’s all compiled as
though you’d written it in any other normal C# file.
TIP When you execute code within code blocks, it must be valid C#, so you
need to add semicolons. Conversely, when you’re writing values directly to the
response using Razor expressions, you don’t need them. If your output HTML
breaks unexpectedly, keep an eye out for missing or rogue extra semicolons!
Razor expressions are one of the most common ways of writing data from your view
model to the HTML output. You’ll see the other approach, using Tag Helpers, in the
next chapter. Razor’s capabilities extend far further than this, however, as you’ll see in
the next section where you’ll learn how to include traditional C# structures in your
templates.
7.3.2 Adding loops and conditionals to Razor templates
One of the biggest advantages of using Razor templates over static HTML is the ability
to dynamically generate the output. Being able to write values from your view model
to the HTML using Razor expressions is a key part of that, but another common use is
loops and conditionals. With these, you could hide sections of the UI, or produce
HTML for every item in a list, for example.
Loops and conditionals include constructs such as if and for loops, and using
them in Razor templates is almost identical to C#, but you need to prefix their usage
with the @ symbol. In case you’re not getting the hang of Razor yet, when in doubt,
throw in another @!
One of the big advantages of Razor in the context of ASP.NET Core is that it uses lan-
guages you’re already familiar with: C# and HTML. There’s no need to learn a whole
new set of primitives for some other templating language: it’s the same if, foreach, and
while constructs you already know. And when you don’t need them, you’re writing raw
HTML, so you can see exactly what the user will be getting in their browser.
In listing 7.6 , I've applied a number of these different techniques in the template
for displaying a to-do item. You can see the @model directive indicates I’ve provided a
ToDoItemViewModel view model, which is used to generate the final HTML. The view
model has a bool IsComplete property, as well as a List<string> property called
Tasks, which contains any outstanding tasks.
@model ToDoItemViewModel
<div>
@if (Model.IsComplete)
{
<strong>Well done, you’re all done!</strong>
}
else
{
Listing 7.6 Razor template for rendering a ToDoItemViewModel
The @model directive indicates
the type of view model in Model.
The if control structure
checks the value of the
view model’s IsComplete
property at runtime.
190 CHAPTER 7 Rendering HTML using Razor views
<strong>The following tasks remain:</strong>
<ul>
@foreach (var task in Model.Tasks)
{
<li>@task</li>
}
</ul>
}
</div>
This code definitely lives up to the promise of mixing C# and HTML! There are tradi-
tional C# control structures, like if and foreach, that you’d expect in any normal pro-
gram, interspersed with the HTML markup that I want to send to the browser. As you
can see, the @ symbol is used to indicate when I’m starting a control statement, but I
generally let the Razor template infer when I’m switching back and forth between
HTML and C#.
The template shows how to generate dynamic HTML at runtime, depending on
the exact data provided. If the view model has outstanding Tasks, then the HTML will
generate a list item for each task, producing output something like that shown in fig-
ure 7.10.
The foreach structure
will generate the <li>
elements once for each
task in Tasks.
A razor expression is
used to write the task
to the HTML output.
@if (Model.IsComplete)
{
<p>Well done, you're all done!</p>
} else {
<p>The following tasks remain:</p>
<ul>
@foreach(var task in Model.Tasks)
{
<li>@task</li>
}
</ul>
}
The data to display is defined in the model.
Razor markup can include C# constructs such
as if statements and for loops.
Only the relevant if block is rendered to the
HTML, and the content within a foreach loop
is rendered once for every item.
Model.IsComplete = false;
Model.Tasks = new List<string>
{
“Get gas”,
“Check oil”,
“Check tire pressure”
};
Figure 7.10 The Razor template generates a <li> item for each remaining task, depending on the
view model passed to the view at runtime.
191
Creating dynamic web pages with Razor
A common trope of the ASP.NET Core team is that they try to ensure you “fall into the
pit of success” when building an application. This refers to the idea that, by default,
the easiest way to do something should be the correct way of doing it. This is a great phi-
losophy, as it means you shouldn’t get burned by, for example, security problems if
you follow the standard approaches. Occasionally, however, you may need to step
beyond the safety rails; a common use case is when you need to render some HTML
contained in a C# object to the output, as you’ll see in the next section.
7.3.3 Rendering HTML with Raw
In the previous example, I rendered the list of tasks to HTML by writing the string
task using the @task Razor expression. But what if the task variable contains HTML
you want to display, so instead of "Check oil" it contains "<strong>Check
oil</strong>"? If you use a Razor expression to output this as you did previously,
then you might hope to get this:
<li><strong>Check oil</strong></li>
IntelliSense and tooling support
The mixture of C# and HTML might seem hard to read in the book, and that’s a rea-
sonable complaint. It’s also another valid argument for trying to keep your Razor tem-
plates as simple as possible.
Luckily, if you’re using Visual Studio or Visual Studio Code, then the tooling can help
somewhat. As you can see in this figure, the C# portions of the code are shaded to
help distinguish them from the surrounding HTML.
Although the ability to use loops and conditionals is powerful—they’re one of the
advantages of Razor over static HTML—they also add to the complexity of your view.
Try to limit the amount of logic in your views to make them as easy to understand and
maintain as possible.
Visual Studio shades the C# regions of
code and highlights @ symbols where
C# transitions to HTML. This makes
the Razor templates easier to read.
192 CHAPTER 7 Rendering HTML using Razor views
But that’s not the case. The HTML generated comes out like this:
<li>&lt;strong&gt;Check oil&lt;/strong&gt;</li>
Hmm, looks odd, right? What’s happened here? Why did the template not write your
variable to the HTML, like it has in previous examples? If you look at how a browser
displays this HTML, like in figure 7.11, then hopefully it makes more sense.
Razor templates encode C# expressions before they’re written to the output stream.
This is primarily for security reasons; writing out arbitrary strings to your HTML could
allow users to inject malicious data and JavaScript into your website. Consequently, the
C# variables you print in your Razor template get written as HTML-encoded values.
In some cases, you might need to directly write out HTML contained in a string
to the response. If you find yourself in this situation, first, stop. Do you really need to
do this? If the values you’re writing have been entered by a user, or were created based
on values provided by users, then there’s a serious risk of creating a security hole in
your website.
If you really need to write the variable out to the HTML stream, then you can do so
using the Html property on the view page and calling the Raw method:
<li>@Html.Raw(task)</li>
With this approach, the string in task will be directly written to the output stream, pro-
ducing the HTML you originally wanted, <li><strong>Check oil</strong></li>,
which renders as shown in figure 7.12.
Figure 7.11 The second item,
<strong>Check oil<strong> has been
HTML-encoded, so the <strong>
elements are visible to the user as part
of the task.
Figure 7.12 The second item,
"<strong>Check oil<strong>"
has been output using Html.Raw(),
so it hasn’t been HTML encoded. The
<strong> elements result in the
second item being shown in bold instead.
193
Layouts, partial views, and _ViewStart
WARNING Using Html.Raw on user input creates a security risk that users
could use to inject malicious code into your website. Avoid using Html.Raw if
possible!
The C# constructs shown in this section can be useful, but they can make your tem-
plates harder to read. It’s generally easier to understand the intention of Razor
templates that are predominantly HTML markup rather than C#.
In the previous version of ASP.NET, these constructs, and in particular the Html
helper property, were the standard way to generate dynamic markup. You can still use
this approach in ASP.NET Core by using the various HtmlHelper1
methods on the
Html property, but these have largely been superseded by a cleaner technique: Tag
Helpers.
NOTE I’ll discuss Tag Helpers, and how to use them to build HTML forms, in
the next chapter.
Tag Helpers are a useful feature that’s new to Razor in ASP.NET Core, but a number
of other features have been carried through from the previous version of ASP.NET. In
the final section of this chapter, you’ll see how you can create nested Razor templates
and use partial views to reduce the amount of duplication in your views.
7.4 Layouts, partial views, and _ViewStart
Every HTML document has a certain number of elements that are required: <html>,
<head>, and <body>. As well, there are often common sections that are repeated on
every page of your application, such as the header and footer, as shown in figure 7.13.
Each page on your application will also probably reference the same CSS and Java-
Script files.
All of these different elements add up to a maintenance nightmare. If you had to
manually include these in every view, then making any changes would be a laborious,
error-proneprocess.Instead, Razor letsyouextractthese common elementsinto layouts.
DEFINITION A layout in Razor is a template that includes common code. It
can’t be rendered directly, but it can be rendered in conjunction with normal
Razor views.
By extracting your common markup into layouts, you can reduce the duplication in
your app. This makes changes easier, makes your views easier to manage and main-
tain, and is generally good practice! In this section, you’ll see how to create a layout
and reference it from your normal Razor views.
1
HTML Helpers are almost obsolete, though they’re still available if you prefer to use them.
194 CHAPTER 7 Rendering HTML using Razor views
7.4.1 Using layouts for shared markup
Layout files are, for the most part, normal Razor templates that contain markup com-
mon to more than one page. An ASP.NET Core app can have multiple layouts, and
layouts can reference other layouts. A common use for this is to have different layouts
for different sections of your application. For example, an e-commerce website might
use a three-column view for most pages, but a single-column layout when you come to
the checkout pages, as shown in figure 7.14.
You’ll often use layouts across many different action methods, so they’re typically
placed in the Views/Shared folder. You can name them anything you like, but there’s
a common convention to use _Layout.cshtml as the filename for the base layout in
your application. This is the default name used by the MVC templates in Visual Studio
and the .NET CLI.
TIP A common convention is to prefix your layout files with an underscore
(_) to distinguish them from standard Razor templates in your Views folder.
Header common to every
page in the app
Sidebar and sub-header
common to some views
in the app
Body content specific
to this view only
Figure 7.13 A typical web application has a block-based layout, where some blocks are common to
every page of your application. The header block will likely be identical across your whole application,
but the sub-header and sidebar are probably identical for all the pages in one section. The body content
will differ for every page in your application.
195
Layouts, partial views, and _ViewStart
A layout file looks similar to a normal Razor template, with one exception: every lay-
out must call the @RenderBody() function. This tells the templating engine where to
insert the content from the child views. A simple layout is shown in the following list-
ing. Typically, your application will reference all your CSS and JavaScript files in the
layout, as well as include all the common elements such as headers and footers, but
this example includes pretty much the bare minimum HTML.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>@ViewData["Title"]</title>
<link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
@RenderBody()
</body>
</html>
As you can see, the layout file includes the required elements, such as <html> and
<head>, as well as elements you need on every page, such as <title> and <link>. This
example also shows the benefit of storing the page title in ViewData; the layout can
render it in the <title> element so that it shows in the browser’s tab, as shown in fig-
ure 7.15.
Listing 7.7 A basic Layout.cshtml file calling RenderBody
Three-column layout Single-column layout
Figure 7.14 The https://manning.com website uses different layouts for different parts of the web application.
The product pages use a three-column layout, but the cart page uses a single-column layout.
ViewData is the standard
mechanism for passing data
to a layout from a view.
Elements common to
every page, such as
your CSS, are typically
found in the layout.
Tells the templating engine where
to insert the child view’s content
196 CHAPTER 7 Rendering HTML using Razor views
Views can specify a layout file to use by setting the Layout property inside a Razor
code block.
@{
Layout = "_Layout";
ViewData["Title"] = "Home Page";
}
<h1>@ViewData[“Title”]</h1>
<p>This is the home page</p>
Any contents in the view will be rendered inside the layout, where the call to @Render-
Body() occurs. Combining the two previous listings will result in the following HTML
being generated and sent to the user.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Home Page</title>
<link rel="stylesheet" href="/css/site.css" />
</head>
<body>
<h1>Home Page</h1>
<p>This is the home page</p>
</body>
<html>
Judicious use of layouts can be extremely useful in reducing the duplication on a
page. By default, layouts only provide a single location where you can render content
from the view, at the call to @RenderBody. In cases where this is too restrictive, you can
render content using sections.
7.4.2 Overriding parent layouts using sections
A common requirement when you start using multiple layouts in your application is to
be able to render content from child views in more than one place in your layout.
Consider the case of a layout that uses two columns. The view needs a mechanism for
Listing 7.8 Setting the Layout property from a view
Listing 7.9 Rendered output from combining a view with its layout
Figure 7.15 The contents of the <title>
element is used to name the tab in the
user’s browser, in this case Home Page.
Set the layout for the page
to _Layout.cshtml.
ViewData is a convenient way of passing
data from a view to the layout.
The content in the view to
render inside the layout
ViewData set in the
view is used to render
the layout.
The RenderBody call renders
the contents of the view.
197
Layouts, partial views, and _ViewStart
saying “render this content in the left column” and “render this other content in the
right column”. This is achieved using sections.
NOTE Remember, all of the features outlined in this chapter are specific to
Razor, which is a server-side rendering engine. If you’re using a client-side
SPA framework to build your application, you’ll likely handle these require-
ments in other ways, either within the client code or by making multiple
requests to a Web API endpoint.
Sections provide a way of organizing where view elements should be placed within a
layout. They’re defined in the view using an @section definition, as shown in the fol-
lowing listing, which defines the HTML content for a sidebar separate from the main
content, in a section called Sidebar. The @section can be placed anywhere in the file,
top or bottom, wherever is convenient.
@{
Layout = "_TwoColumn";
}
@section Sidebar {
<p>This is the sidebar content</p>
}
<p>This is the main content </p>
The section is rendered in the parent layout with a call to @RenderSection(). This will
render the content contained in the child section into the layout. Sections can be
either required or optional. If they’re required, then a view must declare the given
@section; if they’re optional then they can be omitted, and the layout will skip it.
Skipped sections won’t appear in the rendered HTML. This listing shows a layout that
has a required section called Sidebar, and an optional section called Scripts.
@{
Layout = "_Layout";
}
<div class="main-content">
@RenderBody()
</div>
<div class="side-bar">
@RenderSection("Sidebar", required: true)
</div>
@RenderSection("Scripts", required: false)
Listing 7.10 Defining a section in a view template
Listing 7.11 Rendering a section in a layout file, _TwoColumn.cshtml
All content inside the braces
is part of the Sidebar section,
not the main body content.
Any content not inside an
@section will be rendered
by the @RenderBody call.
This layout is nested
inside a layout itself.
Renders all the content from a
view that isn’t part of a section
Renders the Sidebar section; if
the Sidebar section isn’t defined
in the view, throws an error
Renders the Scripts section;
if the Scripts section isn’t
defined in the view, ignore it.
198 CHAPTER 7 Rendering HTML using Razor views
TIP It’s common to have an optional section called Scripts in your layout
pages. This can be used to render additional JavaScript that’s required by
some views, but that isn’t needed on every view. A common example is the
jQuery Unobtrusive Validation scripts for client-side validation. If a view
requires the scripts, it adds the appropriate @section Scripts to the Razor
markup.
You may notice that the previous listing defines a Layout property, even though it’s a
layout itself, not a view. This is perfectly acceptable, and lets you create nested hierar-
chies of layouts, as shown in figure 7.16.
Layout files and sections provide a lot of flexibility to build sophisticated UIs, but one
of their most important uses is in reducing the duplication of code in your applica-
tion. They’re perfect for avoiding duplication of content that you’d need to write for
every view. But what about those times when you find you want to reuse part of a view
somewhere else? For those cases, you have partial views.
7.4.3 Using partial views to encapsulate markup
Partial views are exactly what they sound like—they’re part of a view. They provide a
means of breaking up a larger view into smaller, reusable chunks. This can be useful
for both reducing the complexity in a large view by splitting it into multiple partial
views, or for allowing you to reuse part of a view inside another.
_Layout.cshtml defines the HTML
in the header and footer.
The main content of the view is
rendered in _TwoColumn.cshtml
by RenderBody.
_TwoColumn.cshtml is rendered
inside _Layout.cshtml.
The sidebar content of the view is
rendered in _TwoColumn.cshtml
by RenderSection(Sidebar).
Figure 7.16 Multiple layouts can be nested to create complex hierarchies. This allows you
to keep the elements common to all views in your base layout and extract layout common to
multiple views into sub-layouts.
199
Layouts, partial views, and _ViewStart
Most web frameworks that use server-side rendering have this capability—Ruby on
Rails has partial views, Django has inclusion tags, and Zend has partials. All of these
work in the same way, extracting common code into small, reusable templates. Even
client-side templating engines such as Mustache and Handlebars used by client-side
frameworks like AngularJs and Ember have similar “partial view” concepts.
Consider a to-do list application. You might find you have an action method that
displays a single to-do with a given id using a view called ViewToDo.cshtml. Later on,
you create a new action method that displays the five most recent to-do items using a
view called RecentToDos.cshtml. Instead of copying and pasting the code from one
view to the other, you could create a partial view, called _ToDo.cshtml.
@model ToDoItemViewModel
<h2>@Model.Title</h2>
<ul>
@foreach (var task in Model.Tasks)
{
<li>@task</li>
}
</ul>
Both the ViewToDo.cshtml and RecentToDos.cshtml views can now render the _ToDo
.cshtml partial view. Partial views are rendered using the @await Html.PartialAsync
method and can be passed a view model to render. For example, the RecentTo-
Dos.cshtml view could achieve this using
@model List<ToDoItemViewModel>
@foreach(var todo in Model)
{
@await Html.PartialAsync("_ToDo", todo)
}
You can call the partial in a number of other ways, such as Html.Partial and
Html.RenderPartial, but Html.PartialAsync is recommended.2
NOTE Like layouts, partial views are typically named with a leading under-
score.
The Razor code contained in a partial view is almost identical to a standard view. The
main difference is the fact that partial views are typically called from other views
rather than as the result of an action method. The only other difference is that partial
views don’t run _ViewStart.cshtml when they execute, which you’ll see shortly.
Listing 7.12 Partial view _ToDo.cshtml for displaying a ToDoItemViewModel
2
You should use the asynchronous partial methods to render partials where possible, as the synchronous meth-
ods can lead to subtle performance issues.
Partial views can use a view
model, just like normal views.
The content of the partial
view, which previously existed
in the ViewToDo.cshtml file
200 CHAPTER 7 Rendering HTML using Razor views
Partial views aren’t the only way to reduce duplication in your view templates. Razor
also allows you to pull common elements such as namespace declarations and layout
configuration into centralized files. In the final section of this chapter, you’ll see how
to wield these files to clean up your templates.
7.4.4 Running code on every view with _ViewStart and _ViewImports
Due to the nature of views, you’ll inevitably find yourself writing certain things repeat-
edly. If all of your views use the same layout, then adding the following code to the top
of every page feels a little redundant:
@{
Layout = "_Layout";
}
Similarly, if you’ve declared your view models in a different namespace to your app’s
default, for example the WebApplication1.Models namespace, then having to add
@using WebApplication1.Models to the top of the page can get to be a chore.
Thankfully, ASP.NET Core includes two mechanisms for handling these common
tasks: _ViewImports.cshtml and _ViewStart.cshtml.
IMPORTING COMMON DIRECTIVES WITH _VIEWIMPORTS
The _ViewImports.cshtml file contains directives that will be inserted at the top of
every view. This includes things like the @using and @model statements that you’ve
already seen—basically any Razor directive. To avoid adding a using statement to
every view, you can include it in here instead.
Child actions in ASP.NET Core
In the previous version of ASP.NET MVC, there was the concept of a child action. This
was an action method that could be invoked from inside a view. This was the main
mechanism for rendering discrete sections of a complex layout that had nothing to
do with the main action method. For example, a child action method might render the
shopping cart on an e-commerce site.
This approach meant you didn’t have to pollute every page’s view model with the view
model items required to render the shopping cart, but it fundamentally broke the MVC
design pattern, by referencing controllers from a view.
In ASP.NET Core, child actions are no more. View components have replaced them.
These are conceptually quite similar in that they allow both the execution of arbitrary
code and the rendering of HTML, but they don’t directly invoke controller actions. You
can think of them as a more powerful partial view that you should use anywhere a
partial view needs to contain significant code or business logic. You’ll see how to
build a small view component in chapter 19.
201
Layouts, partial views, and _ViewStart
@using WebApplication1
@using WebApplication1.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
The _ViewImports.cshtml file can be placed in any folder, and it will apply to all views
and sub-folders in that folder. Typically, it’s placed in the root Views folder so that it
applies to every view in your app.
It’s important to note that you should only put Razor directives in _ViewImports
.cshtml—you can’t put any old C# in there. As you can see in the previous listing, this
is limited to things like @using or the @addTagHelper directive that you’ll learn about
in the next chapter. If you want to run some arbitrary C# at the start of every view in
your application, for example to set the Layout property, then you should use the
_ViewStart.cshtml file instead.
RUNNING CODE FOR EVERY VIEW WITH _VIEWSTART
You can easily run common code at the start of every view by adding a _ViewStart file
to the Views folder in your application. This file can contain any Razor code, but it’s
typically used to set the Layout for all the views in your application, as shown in the
following listing. You can then omit the Layout statement from all views that use the
default layout. If a view needs to use a nondefault layout then you can override it by
setting the value in the view itself.
@{
Layout = "_Layout";
}
Any code in the _ViewStart.cshtml file runs before the view executes. Note that _View-
Start.cshtml only runs for full views—it doesn’t run for layouts or partials views. Also,
note that the names for these special Razor files are enforced rather than conventions.
WARNING You must use the names _ViewStart.cshtml and _ViewImports
.cshtml for the Razor engine to locate and execute them correctly. To apply
them to all your app’s views, add them to the root of the Views folder, not to
the Shared subfolder.
If you only want to run _ViewStart.cshtml or _ViewImports.cshtml for some of your
views, you can include additional _ViewStart.cshtml or _ViewImports.cshtml files in a
controller-related subfolder. The controller-specific files will run after the root Views
folder files.
Listing 7.13 A typical _ViewImports.cshtml file importing additional namespaces
Listing 7.14 A typical _ViewStart.cshtml file setting the default layout
The default namespace of your application
Add this directive to avoid placing in every view
Makes Tag Helpers
available in your views
202 CHAPTER 7 Rendering HTML using Razor views
That concludes our first look at rendering HTML using the Razor templating engine.
You saw how to select a view, pass a view model to it, and render the data to the
HTML. Finally, you saw how to build complex layouts and reduce duplication in your
views using partial views.
In the next chapter, you’ll learn about Tag Helpers and how to use them to build
HTML forms, a staple of modern web applications. Tag Helpers are one of the biggest
improvements to Razor in ASP.NET Core, so getting to grips with them will make edit-
ing your views an overall more pleasant experience!
Summary
 In MVC, views are responsible for generating the UI for your application.
 Razor is a templating language that allows you to generate dynamic HTML
using a mixture of HTML and C#.
 HTML forms are the standard approach for sending data from the browser to
the server. You can use Tag Helpers to easily generate these forms.
 By convention, views are named the same as the action that invokes them, and
reside either in a folder with the same name as the action method’s controller
or in the Shared folder.
 Controllers can override the default view template by passing the template
name when returning a ViewResult using the View helper method on the
Controller base class.
Partial views, layouts, and AJAX
This chapter describes using Razor to render full HTML pages server-side, which are
then sent to the user’s browser in traditional web apps. A common alternative
approach when building web apps is to use a JavaScript client-side framework to build
a Single Page Application (SPA), which renders the HTML client-side in the browser.
One of the technologies SPAs typically use is AJAX (Asychronous JavaScript and
XML), in which the browser sends requests to your ASP.NET Core app without reload-
ing a whole new page. It’s also possible to use AJAX requests with apps that use
server-side rendering. To do so, you’d use JavaScript to request an update for part of
a page.
If you want to use AJAX with an app that uses Razor, you should consider making
extensive use of partial views. You can then expose these via additional actions.
Using AJAX can reduce the overall amount of data that needs to be sent back and
forth between the browser and your app, and it can make your app feel smoother and
more responsive, as it requires fewer full-page loads. But using AJAX with Razor can
add complexity, especially for larger apps. If you foresee yourself making extensive
use of AJAX to build a highly dynamic web app, you might want to consider using Web
API controllers and a client-side framework (see chapter 9) instead.
203
Summary
 Controllers can pass strongly typed data to a view using a view model. To access
the properties on the view model, the view should declare the model type using
the @model directive.
 Controllers can pass key-value pairs to the view using the ViewData dictionary.
 Razor expressions render C# values to the HTML output using @ or @(). You
don’t need to include a semicolon after the statement when using Razor
expressions.
 Razor code blocks, defined using @{}, execute C# without outputting HTML.
The C# in Razor code blocks must be complete statements, so it must include
semicolons.
 Loops and conditionals can be used to easily generate dynamic HTML in tem-
plates, but it’s a good idea to limit the number of if statements in particular, to
keep your views easy to read.
 If you need to render a string as raw HTML you can use Html.Raw, but do so
sparingly—rendering raw user input can create a security vulnerability in your
application.
 Tag Helpers allow you to bind your data model to HTML elements, making it
easier to generate dynamic HTML while staying editor friendly.
 You can place HTML common to multiple views in a layout. The layout will ren-
der any content from the child view at the location @RenderBody is called.
 Encapsulate commonly used snippets of Razor code in a partial view. A partial
view can be rendered using @await Html.PartialAsync().
 _ViewImports.cshtml can be used to include common directives, such as @using
statements, in every view.
 _ViewStart.cshtml is called before the execution of each main view and can be
used to execute code common to all views, such as a default layout page. It
doesn’t execute for layouts or partial views.
 _ViewImports.cshtml and _ViewStart.cshtml are hierarchical—files in the root
folder execute first, followed by files in controller-specific view folders.
204
Building forms
with Tag Helpers
In chapter 7, you learned about Razor templates and how to use them to generate
the views for your MVC application. By mixing HTML and C#, you can create
dynamic applications that can display different data based on the request, the
logged-in user, or any other data you can access.
Displaying dynamic data is an important aspect of many web applications, but
it’s typically only one half of the story. As well as displaying data to the user, you
often need the user to be able to submit data back to your application. You can use
data to customize the view, or to update the application model by saving it to a data-
base, for example. For traditional web applications, this data is usually submitted
using an HTML form.
This chapter covers
 Building forms easily with Tag Helpers
 Generating URLs with the Anchor Tag Helper
 Using Tag Helpers to add functionality to Razor
205
In chapter 6, you learned about model binding, which is how you accept the data
sent by a user in a request and convert it into a C# object that you can use in your
action methods. You also learned about validation, and how important it is to validate
the data sent in a request. You used DataAnnotations to define the rules associated
with your models, as well as other associated metadata like the display name for a
property.
The final aspect we haven’t yet looked at is how to build the HTML forms that users
use to send this data in a request. Forms are one of the key ways users will interact with
your application in the browser, so it’s important they’re both correctly defined for
your application and also user friendly. ASP.NET Core provides a feature to achieve
this, called Tag Helpers.
Tag Helpers are new to ASP.NET Core. They’re Razor components that you can
use to customize the HTML generated in your templates. Tag Helpers can be added
to an otherwise standard HTML element, such as an <input>, to customize its attri-
butes based on your view model, saving you from having to write boilerplate code.
They can also be standalone elements and can be used to generate completely cus-
tomized HTML.
NOTE Remember, Razor, and therefore Tag Helpers, are for server-side
HTML rendering. You can’t use Tag Helpers directly in frontend frameworks
like Angular or React.
If you’ve used the previous version of ASP.NET, then Tag Helpers may sound reminis-
cent of HTML Helpers, which could also be used to generate HTML based on your
view model. Tag Helpers are the logical successor to HTML Helpers, as they provide a
more streamlined syntax than the previous, C#-focused helpers. HTML Helpers are
still available in ASP.NET Core, so if you’re converting some old templates to ASP.NET
Core, or want to, you can use them in your templates, but I won’t be covering them in
this book.
In this chapter, you’ll primarily learn how to use Tag Helpers when building forms.
They simplify the process of generating correct element names and IDs so that model
binding can occur seamlessly when the form is sent back to your application. To put
them into context, you’re going to carry on building the currency converter applica-
tion that you’ve seen in previous chapters. You’ll add the ability to submit currency
exchange requests to it, validate the data, and redisplay errors on the form using Tag
Helpers to do the leg work for you, as shown in figure 8.1.
As you develop the application, you’ll meet the most common Tag Helpers you’ll
encounter when working with forms. You’ll also see how you can use Tag Helpers to
simplify other common tasks, such as generating links, conditionally displaying data in
your application, and ensuring users see the latest version of an image file when they
refresh their browser.
To start, I’ll talk a little about why you need Tag Helpers when Razor can already
generate any HTML you like by combining C# and HTML in a file.
206 CHAPTER 8 Building forms with Tag Helpers
8.1 Catering to editors with Tag Helpers
One of the common complaints about the mixture of C# and HTML in Razor tem-
plates is that you can’t easily use standard HTML editing tools with them; all the @ and
{} symbols in the C# code tend to confuse the editors. Reading the templates can be
similarly difficult for people; switching paradigms between C# and HTML can be a bit
jarring sometimes.
This arguably wasn’t such a problem when Visual Studio was the only supported
way to build ASP.NET websites, as it could obviously understand the templates without
any issues, and helpfully colorize the editor. But with ASP.NET Core going cross-
platform, the desire to play nicely with other editors reared its head again.
This was one of the biggest motivations for Tag Helpers. They integrate seamlessly
into the standard HTML syntax by adding what look to be attributes, typically starting
with asp-*. They’re most often used to generate HTML forms, as shown in the follow-
ing listing. This listing shows a view from the first iteration of the currency converter
application, in which you choose the currencies to convert and the quantity.
Figure 8.1 The currency converter application forms, built using Tag Helpers. The labels,
dropdowns, input elements, and validation messages are all generated using Tag Helpers.
207
Catering to editors with Tag Helpers
@model CurrencyConverterModel
<form asp-action="Convert" asp-controller="Currency">
<div class="form-group">
<label asp-for="CurrencyFrom"></label>
<input class="form-control" asp-for="CurrencyFrom" />
<span asp-validation-for="CurrencyFrom"></span>
</div>
<div class="form-group">
<label asp-for="Quantity"></label>
<input class="form-control" asp-for="Quantity" />
<span asp-validation-for="Quantity"></span>
</div>
<div class="form-group">
<label asp-for="CurrencyTo"></label>
<input class="form-control" asp-for="CurrencyTo" />
<span asp-validation-for="CurrencyTo"></span>
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
At first glance, you might not even spot the Tag Helpers, they blend in so well with the
HTML! This makes it easy to edit the files with any standard HTML text editor. But
don’t be concerned that you’ve sacrificed readability in Visual Studio—as you can see
in figure 8.2, elements with Tag Helpers are clearly distinguishable from the standard
HTML <div> element and the standard HTML class attribute on the <input> ele-
ment. The C# properties of the view model being referenced (CurrencyFrom, in this
case) are also still shaded, as with other C# code in Razor files. And of course, you get
IntelliSense, as you’d expect.
Listing 8.1 User registration form using Tag Helpers
asp-for on Labels generates the
caption for labels based on the
view model.
Use @model to describe
the view model type for
Tag Helpers. Tag Helpers on Forms
are used to generate
the action URL.
asp-for on Inputs generate the
correct type, value, and validation
attributes for the model.
Validation messages
are written to a span
using Tag Helpers.
Validation messages
are written to a span
using Tag Helpers.
Validation messages are written
to a span using Tag Helpers.
Figure 8.2 In Visual Studio, Tag Helpers are distinguishable from normal elements
by being bold and a different color, C# is shaded, and IntelliSense is available.
208 CHAPTER 8 Building forms with Tag Helpers
TIP Visual Studio 2017 currently doesn’t have native IntelliSense support for
the asp-* Tag Helpers themselves, though it will be enabled in an update. In
the meantime, install the Razor Language Services extension.1
Unfortunately,
IntelliSense for Tag Helpers isn’t available in Visual Studio Code.
Tag Helpers are extra attributes on standard HTML elements (or new elements
entirely) that work by modifying the HTML element they’re attached to. They let you
easily integrate your server-side values, such as those in your view model, with the gen-
erated HTML.
Notice that listing 8.1 didn’t specify the captions to display in the labels. Instead, you
declaratively used asp-for="CurrencyFrom" to say, “for this <label>, use the Currency-
From property to work out what caption to use.” Similarly, for the <input> elements, Tag
Helpers are used to
 Automatically populate the values from the view model
 Choose the correct input type to display (for example, a number input for the
Quantity property)
 Display any validation errors, as shown in figure 8.32
1
You can install Visual Studio extensions by clicking Tools > Extensions and Updates, or by viewing on the
Visual Studio Marketplace at http://mng.bz/LcNX.
2
To learn more about the internals of Tag Helpers, read the documentation at http://mng.bz/Idb0.
Figure 8.3 Tag Helpers hook into the metadata provided by DataAnnotations,
as well as the property types themselves. The Validation Tag Helper can even
populate error messages based on the ModelState, as you saw in the last
chapter on validation.
Label caption calculated
from [Display] attribute
Validation error message
populated from ModelState
Input types determined
from DataAnnotations and
property type
209
Creating forms using Tag Helpers
Tag Helpers can perform a variety of functions by modifying the HTML elements
they’re applied to. This chapter introduces a number of the common Tag Helpers
and how to use them, but it’s not an exhaustive list. I don’t cover all of the helpers that
come out of the box in ASP.NET Core (there are more coming with every release!),
and you can easily create your own, as you’ll see in chapter 18. Alternatively, you could
use those published by others on NuGet or GitHub.3
As with all of ASP.NET Core,
Microsoft is developing Tag Helpers in the open on GitHub, so you can always take a
look at the source to see how they’re implemented.
8.2 Creating forms using Tag Helpers
The Tag Helpers for working with forms are some of the most useful and are the ones
you’ll probably use most often. You can use them to generate HTML markup based on
properties of your view model, creating the correct id and name attributes and setting
the value of the element to the model property’s value (among other things). This
capability significantly reduces the amount of markup you need to write manually.
Imagine you’re building the checkout page for the currency converter application,
and you need to capture the user’s details on the checkout page. In chapter 6, you built
a UserBindingModel binding model (shown in listing 8.2), added DataAnnotation
3
I’ve published a few custom Tag Helpers on GitHub at http://mng.bz/FYH1
WebForms flashbacks
For those who remember ASP.NET back in the day of WebForms, before the advent
of the MVC pattern for web development, Tag Helpers may be triggering your PTSD.
Although the asp- prefix is somewhat reminiscent of ASP.NET Web Server control
definitions, never fear—the two are different beasts.
Web Server controls were directly added to a page’s backing C# class, and had a
broad scope that could modify seemingly unrelated parts of the page. Coupled with
that, they had a complex lifecycle that was hard to understand and debug when things
weren’t working. The perils of trying to work with that level of complexity haven’t been
forgotten, and Tag Helpers aren’t the same.
Tag Helpers don’t have a lifecycle—they participate in the rendering of the element
to which they’re attached, and that’s it. They can modify the HTML element they’re
attached to, but they can’t modify anything else on your page, making them concep-
tually much simpler. An additional capability they bring is the ability to have multiple
Tag Helpers acting on a single element—something Web Server controls couldn’t
easily achieve.
Overall, if you’re writing Razor templates, you’ll have a much more enjoyable experi-
ence if you embrace Tag Helpers as integral to its syntax. They bring a lot of benefits
without obvious downsides, and your cross-platform-editor friends will thank you!
210 CHAPTER 8 Building forms with Tag Helpers
attributes for validation, and saw how to model bind it in your action method. In this
chapter, you’ll see how to create the view for it, using the UserBindingModel as a view
model.
WARNING For simplicity, I’m using the same object for both my binding
model and view model, but in practice you should use two separate objects to
avoid mass-assignment attacks on your app.4
public class UserBindingModel
{
[Required]
[StringLength(100, ErrorMessage = "Maximum length is {1}")]
[Display(Name = "Your name")]
public string FirstName { get; set; }
[Required]
[StringLength(100, ErrorMessage = "Maximum length is {1}")]
[Display(Name = "Last name")]
public string LastName { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
[Phone(ErrorMessage = "Not a valid phone number.")]
[Display(Name = "Phone number")]
public string PhoneNumber { get; set; }
}
The UserBindingModel is decorated with a number of DataAnnotations attributes. In
the previous chapter, you saw that these attributes are used during model validation
when your binding model is bound, before the action method is executed. These
attributes are also used by the Razor templating language to provide the metadata
required to generate the correct HTML when you use Tag Helpers.
With the help of the UserBindingModel, Tag Helpers, and a little HTML, you can
create a Razor view that lets the user enter their details, as shown in figure 8.4.
The Razor template to generate this page is shown in listing 8.3. This code uses a
variety of tag helpers, including
 A Form Tag Helper on the <form> element
 Label Tag Helpers on the <label>
 Input Tag Helpers on the <input>
 Validation Message Tag Helpers on <span> validation elements for each prop-
erty in the UserBindingModel
Listing 8.2 UserBindingModel for creating a user on a checkout page
4
You can read about techniques for working with separate binding and view models at http://mng.bz/QvfG.
211
Creating forms using Tag Helpers
@model UserBindingModel
@{
ViewData["Title"] = "Checkout";
}
<h1>@ViewData["Title"]</h1>
<form asp-action="Index" asp-controller="Checkout">
<div class="form-group">
<label asp-for="FirstName"></label>
<input class="form-control" asp-for="FirstName" />
<span asp-validation-for="FirstName"></span>
</div>
<div class="form-group">
<label asp-for="LastName"></label>
<input class="form-control" asp-for="LastName" />
<span asp-validation-for="LastName"></span>
</div>
<div class="form-group">
<label asp-for="Email"></label>
<input class="form-control" asp-for="Email" />
<span asp-validation-for="Email"></span>
</div>
Listing 8.3 Razor template for binding to UserBindingModel on the checkout page
Figure 8.4 The checkout page for an
application. The HTML is generated
based on a UserBindingModel,
using Tag Helpers to render the
required element values, input types,
and validation messages.
The @model directive describes
the view model for the page.
Form Tag Helpers use
routing to determine
the URL the form will
be posted to.
The Label Tag Helper
uses DataAnnotations
on a property to
determine the caption
to display.
The Input Tag Helper
uses DataAnnotations
to determine the type
of input to generate.
212 CHAPTER 8 Building forms with Tag Helpers
<div class="form-group">
<label asp-for="PhoneNumber"></label>
<input class="form-control" asp-for="PhoneNumber" />
<span asp-validation-for="PhoneNumber"></span>
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
You can see the HTML markup that this template produces in listing 8.4. This Razor
markup and the resulting HTML produces the results you saw in figure 8.4. You can
see that each of the HTML elements with a Tag Helper have been customized in the
output: the <form> element has an action attribute, the <input> elements have an id
and name based on the name of the referenced property, and both the <input> and
<span> have data-* elements for validation.
<form action="/Checkout" method="post">
<div class="form-group">
<label for="FirstName">Your name</label>
<input class="form-control" type="text"
id="FirstName" name="FirstName" value=""
data-val="true" data-val-length-max="100"
data-val-length="Maximum length is 100."
data-val-required="The Your name field is required." />
<span class="field-validation-valid"
data-valmsg-for="FirstName" data-valmsg-replace="true"></span>
</div>
<div class="form-group">
<label for="LastName">Last name</label>
<input class="form-control" type="text"
id="LastName" name="LastName" value=""
data-val="true" data-val-length-max="100"
data-val-length="Maximum length is 100."
data-val-required="The Last name field is required." />
<span class="field-validation-valid"
data-valmsg-for="LastName" data-valmsg-replace="true"></span>
</div>
<div class="form-group">
<label for="Email">Email</label>
<input class="form-control" type="email"
id="Email" name="Email" value=""
data-val="true" data-val-length-max="100"
data-val-email="The Email field is not a valid e-mail address."
data-val-required="The Email field is required." />
<span class="field-validation-valid"
data-valmsg-for="Email" data-valmsg-replace="true"></span>
</div>
<div class="form-group">
<label for="PhoneNumber">Phone number</label>
<input class="form-control" type="tel"
id="PhoneNumber" name="PhoneNumber" value=""
Listing 8.4 HTML generated by the Razor template on the checkout page
The Validation Tag Helper displays error
messages associated with the given property.
213
Creating forms using Tag Helpers
data-val="true" data-val-length-max="100"
data-val-phone="Not a valid phone number." />
<span class="field-validation-valid"
data-valmsg-for="PhoneNumber" data-valmsg-replace="true"></span>
</div>
<button type="submit" class="btn btn-default">Submit</button>
<input name="__RequestVerificationToken" type="hidden"
value="CfDJ8PkYhAINFx1JmYUVIDWbpPyy_TRUNCATED" />
</form>
Wow, that’s a lot of markup! If you’re new to working with HTML, this might all seem
a little overwhelming, but the important thing to notice is that you didn’t have to write
most of it! The Tag Helpers took care of the vast majority of the plumbing for you.
That’s basically Tag Helpers in a nutshell; they simplify the fiddly mechanics of build-
ing HTML forms, leaving you to concentrate on the overall design of your application
instead of writing boilerplate markup.
NOTE If you’re using Razor to build your views, Tag Helpers will make your
life easier, but they’re entirely optional. You’re free to write raw HTML with-
out them, or to use the previous HTML Helpers.
Tag Helpers simplify and abstract the process of HTML generation, but they generally
try to do so without getting in your way. If you need the final generated HTML to have
a particular attribute, then you can add it to your markup. You can see that in the pre-
vious listings where class attributes are defined on <input> elements, such as <input
class="form-control" asp-for="FirstName" />, they pass untouched through from
Razor to the HTML output.
TIP This is different from the way HTML Helpers worked in the previous ver-
sion of ASP.NET; they often required jumping through hoops to set attributes
in the generated markup.
Even better than this, you can also set attributes that are normally generated by a Tag
Helper, like the type attribute on an <input> element. For example, if the Favorite-
Color property on your view model was a string, then by default, Tag Helpers would
generate an <input> element with type="text". Updating your markup to use the
HTML5 color picker type is trivial; set the type in the markup:
<input type="color" asp-for="FavoriteColor" />
TIP HTML5 adds a huge number of features, including lots of form ele-
ments that you may not have come across before, such as range inputs
and color pickers. We’re not going to cover them in this book, but you can
read about them on the Mozilla Developer Network website at
http://mng.bz/qOc1.
In this section, you’ll build the currency calculator Razor templates from scratch, add-
ing Tag Helpers as you find you need them. You’ll probably find you use most of the
214 CHAPTER 8 Building forms with Tag Helpers
common form Tag Helpers in every application you build, even if it’s on a simple
login page.
8.2.1 The Form Tag Helper
The first thing you need to start building your HTML form is, unsurprisingly, the
<form> element. In the previous example, the <form> element was augmented with
two Tag Helper attributes: asp-action and asp-controller:
<form asp-action="Index" asp-controller="Checkout">
This resulted in the addition of action and method attributes to the final HTML, indi-
cating the URL that the form should be sent to when submitted:
<form action="/Checkout" method="post">
Setting the asp-action and asp-controller attributes allows you to specify the action
method in your application that the form will be posted to when it’s submitted. The
asp-action and asp-controller attributes are added by a FormTagHelper. This Tag
Helper uses the values provided to generate a URL for the action attribute using the
URL generation features of routing that I described at the end of chapter 5.
NOTE Tag Helpers can make multiple attributes available on an element.
Think of them like properties on a Tag Helper configuration object. Adding
a single asp- attribute activates the Tag Helper on the element. Adding addi-
tional attributes lets you override further default values of its implementation.
The Form Tag Helper makes a number of other attributes available on the <form> ele-
ment that you can use to customize the generated URL. You’ve already seen the asp-
action and asp-controller attributes for setting the controller and action route
parameters. Hopefully you’ll remember from chapter 5 that you can set other route
parameters too, all of which will be used to generate the final URL. For example, the
default route template looks something like {controller}/{action}/{id?}.
As you can see, there’s an optional id route parameter that will be used during
URL generation. How can you set this value for use during URL generation?
The Form Tag Helper defines an asp-route-* wildcard attribute that you can use
to set arbitrary route parameters. Set the * in the attribute to the route parameter
name. For the example, to set the id route parameter, you’d set the asp-route-id
value, so
<form asp-action="View" asp-controller="Product" asp-route-id="5">
would generate the following markup, using the default route template to generate
the URL:
<form action="/Product/View/5" method="post">
215
Creating forms using Tag Helpers
You can add as many asp-route-* attributes as necessary to your <form> to generate
the correct action URL, but sometimes that’s still not enough. In chapter 5, I
described using a route name when generating URLs as being another option. You
can achieve this with the Form Tag Helper by using the asp-route attribute.
NOTE Use the asp-route attribute to specify the route name to use, and
the asp-route-* attributes to specify the route parameters to use during URL
generation.
Imagine you’re building a social networking application, and you’ve defined a custom
route called view_posts for viewing the posts of a user with a given username:
routes.MapRoute(
name: "view_posts",
template: "{username}/posts",
defaults: new {controller="User", action="ViewPosts");
You can ensure the URL generated for a <form> uses this route by employing the fol-
lowing combination of asp-route and asp-route-username, where the current user-
name is stored in the Username property on the view model.
<form asp-route="view_posts" asp-route-username="@Model.Username">
TIP You can use values from your view model (and C# in general) in Tag
Helpers like you would in normal Razor. See chapter 7 for details.
The main job of the Form Tag Helper is to generate the action attribute, but it per-
forms one additional, important function: generating a hidden <input> field needed
to prevent cross-site request forgery (CSRF) attacks.
DEFINITION Cross-site request forgery (CSRF) attacks are a website exploit that
can be used to execute actions on your website by an unrelated malicious
website. You’ll learn about them in detail in chapter 18.
You can see the generated hidden <input> at the bottom of the generated <form> in
listing 8.4; it’s named __RequestVerificationToken and contains a seemingly ran-
dom string of characters. This field won’t protect you on its own, but I’ll describe in
chapter 18 how it’s used to protect your website. The Form Tag Helper generates it by
default, so generally speaking you won’t need to worry about it, but if you need to dis-
able it, you can do so by adding asp-antiforgery="false" to your <form> element.
The Form Tag Helper is obviously useful for generating the action URL, but it’s
time to move on to more interesting elements, those that you can see in your browser!
8.2.2 The Label Tag Helper
Every <input> field in your currency converter application needs to have an associ-
ated label so the user knows what the <input> is for. You could easily create those
yourself, manually typing the name of the field and setting the for attribute as appro-
priate, but luckily there’s a Tag Helper to do that for us.
216 CHAPTER 8 Building forms with Tag Helpers
The Label Tag Helper is used to generate the caption (the visible text) and the for
attribute for a <label> element, based on the properties in the view model. It’s used
by providing the name of the property in the asp-for attribute:
<label asp-for="FirstName"></label>
The Label Tag Helper uses the [Display] DataAnnotations attribute that you saw in
chapter 6 to determine the appropriate value to display. If the property you’re gener-
ating a label for doesn’t have a [Display] attribute, the Label Tag Helper will use the
name of the property instead. So, for the model
public class UserBindingModel
{
[Display(Name = "Your name")]
public string FirstName { get; set; }
public string Email { get; set; }
}
in which the FirstName property has an [Display] attribute, but the Email property
doesn’t; the following Razor
<label asp-for="FirstName"></label>
<label asp-for="Email"></label>
would generate the HTML
<label for="FirstName">Your Name</label>
<label for="Email">Email</label>
The caption text inside the <label> element uses the value set in the [Display] attri-
bute, or the property name in the case of the Email property. Also note that the for
attribute has been generated with the name of the property. This is a key bonus of
using Tag Helpers—it hooks in with the element IDs generated by other Tag Helpers,
as you’ll see shortly.
NOTE The for attribute is important for accessibility. It specifies the ID of
the element to which the label refers.
As is typical with Tag Helpers, the Label Tag Helper won’t override values you set your-
self. If, for example, you don’t want to use the caption generated by the helper, you
could insert your own manually. The following code
<label asp-for="Email">Please enter your Email</label>
would generate the HTML
<label for="Email">Please enter your Email</label>
As ever, you’ll generally have an easier time with maintenance if you stick to the stan-
dard conventions and don’t override values like this, but the option is there. Right,
next up is a biggie: the Input and Textarea Tag Helpers.
217
Creating forms using Tag Helpers
8.2.3 The Input and Textarea Tag Helpers
Now you’re getting into the meat of your form—the <input> elements that handle
user input. Given that there’s such a wide array of possible input types, there’s a vari-
ety of different ways they can be displayed in the browser. For example, Boolean val-
ues are typically represented by a checkbox type <input> element, whereas integer
values would use a number type <input> element, and a date would use the date type,
shown in figure 8.5.
To handle this diversity, the Input Tag Helper is one of the most powerful Tag Help-
ers. It uses information based on both the type of the property (bool, string, int,
and so on) and any DataAnnotations attributes applied to it ([EmailAddress] and
[Phone], among others) to determine the type of the input element to generate. The
DataAnnotations are also used to add data-val-* client-side validation attributes to
the generated HTML.
Consider the Email property from listing 8.2 that was decorated with the [Email-
Address] attribute. Adding an <input> is as simple as using the asp-for attribute:
<input asp-for="Email" />
The property is a string, so ordinarily, the Input Tag Helper would generate an
<input> with type="text". But the addition of the [EmailAddress] attribute provides
additional metadata about the property. Consequently, the Tag Helper generates an
HTML5 <input> with type="email":
Figure 8.5 Various input element
types. The exact way in which each
type is displayed varies by browser.
218 CHAPTER 8 Building forms with Tag Helpers
<input type="email" id="Email" name="Email"
value="test@example.com" data-val="true"
data-val-email="The Email Address field is not a valid e-mail address."
data-val-required="The Email Address field is required."
/>
You can take a whole host of things away from this example. First, the id and name attri-
butes of the HTML element have been generated from the name of the property.
These are the same as the value referenced by the Label Tag Helper in its for attribute.
Also, the initial value of the field has been set to the value currently stored in the
property ("test@example.com", in this case). The type of the element has also been
set to the HTML5 email type, instead of using the default text type.
Perhaps the most striking addition is the swath of data-val-* attributes. These can
be used by client-side JavaScript libraries such as jQuery to provide client-side valida-
tion of your DataAnnotations constraints. Client-side validation provides instant feed-
back to users when the values they enter are invalid, providing a smoother user
experience than can be achieved with server-side validation alone, as I described in
chapter 6.
Client-side validation
In order to enable client-side validation in your application, you need to add more
jQuery libraries to your HTML pages. In particular, you need to include the jQuery,
jQuery-validation, and jQuery-validation-unobtrusive JavaScript libraries. You can do
this in a number of ways, but the simplest is to include the script files at the bottom
of your view using
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.
➥min.js"></script>
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/
➥jquery.validate.min.js"></script>
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/
➥3.2.6/jquery.validate.unobtrusive.min.js"></script>
The default MVC templates include these scripts for you, in a handy partial template
that you can add to your page in a Scripts section. If you’re using the default layout
and need to add client-side validation to your view, add the following section some-
where on your view:
@section Scripts{
@Html.Partial("_ValidationScriptsPartial")
}
This will ensure the appropriate scripts are added using a best-practice approach, by
serving from a content delivery network (CDN) for speed and using your application
as a fallback if there are network issues.
219
Creating forms using Tag Helpers
The Input Tag Helper tries to pick the most appropriate template for a given property
based on DataAnnotation attributes or the type of the property. Whether this gener-
ates the exact <input> type you need may depend, to an extent, on your application.
As always, you can override the generated type by adding your own type attribute to
the Razor. Table 8.1 shows how some of the common data types are mapped to
<input> types, and how the data types themselves can be specified.
The Input Tag Helper has one additional attribute that can be used to customize the
way data is displayed: asp-format. HTML forms are entirely string-based, so when the
value of an <input> is set, the Input Tag Helper must take the value stored in
the property and convert it to a string. Under the covers, this performs a string
.Format() on the property’s value, passing in a format string.
The Input Tag Helper uses a default format string for each different data type, but
with the asp-format attribute, you can set the specific format string to use. For exam-
ple, you could ensure a decimal property, Dec, is formatted to three decimal places
with the following code:
<input asp-for="Dec" asp-format="0.000" />
If the Dec property had a value of 1.2, this would generate HTML similar to
<input type="text" id="Dec" name="Dec" value="1.200">
Table 8.1 Common data types, how to specify them, and the input element type they map to
Data type How it’s specified Input element type
byte, int, short, long, uint Property type number
decimal, double, float Property type text
string Property type,
[DataType(DataType.Text)]
attribute
text
HiddenInput [HiddenInput] attribute hidden
Password [Password] attribute password
PhoneAttribute [Phone] attribute tel
EmailAddress [EmailAddress] attribute email
Url [Url] attribute url
Date DateTime property type,
[DataType(DataType.Date)]
attribute
date
220 CHAPTER 8 Building forms with Tag Helpers
NOTE You may be surprised that decimal and double types are rendered as
text fields and not as number fields. This is due to a number of technical rea-
sons, predominantly related to the way some cultures render numbers with
commas and spaces. Rendering as text avoids errors that would only appear in
certain browser-culture combinations.
In addition to the Input Tag Helper, ASP.NET Core provides the Textarea Tag Helper.
This works in a similar way, using the asp-for attribute, but is attached to a <textarea>
element instead:
<textarea asp-for="Multiline"></textarea>
This would generate HTML similar to the following. Note that the property value is
rendered inside the Tag, and data-val-* validation elements are attached as usual:
<textarea data-val="true" id="Multiline" name="Multiline"
data-val-length="Maximum length 200." data-val-length-max="200"
data-val-required="The Multiline field is required." >This is some text,
I'm going to display it
in a text area</textarea>
Hopefully, this section has hammered home how much typing Tag Helpers can cut
down on, especially when using them in conjunction with DataAnnotations for gener-
ating validation attributes. But this is more than reducing the number of keystrokes
required; Tag Helpers ensure that the markup generated is correct, and has the correct
name, id, and format to automatically bind your binding models when they’re sent to
the server.
With <form>, <label>, and <input> under your belt, you’re able to build the major-
ity of your currency converter forms. Before we look at displaying validation messages,
there’s one element I’d like to take a look at: the <select>, or drop-down, input.
8.2.4 The Select Tag Helper
As well as <input> fields, a common element you’ll see on web forms is the <select>
element, or dropdowns and list boxes. Your currency converter application, for exam-
ple, could use a <select> element to let you pick which currency to convert from a list.
By default, this element shows a list of items and lets you select one, but there are a
number of variations, as shown in figure 8.6. As well as the normal select box, you
could show a list box, add multiselection, and display your list items in groups.
To use <select> elements in your Razor code, you’ll need to include two proper-
ties in your view model: one property for the list of options to display and one to hold
the value (or values) selected. For example, listing 8.5 shows the view model used to
create the three left-most select lists you saw in figure 8.6. Displaying groups requires a
slightly different setup, as you’ll see shortly.
221
Creating forms using Tag Helpers
public class SelectListsViewModel
{
public string SelectedValue1 { get; set; }
public string SelectedValue2 { get; set; }
public IEnumerable<string> MultiValues { get; set; }
public IEnumerable<SelectListItem> Items { get; set; }
= new List<SelectListItem>
{
new SelectListItem{Value= "csharp", Text="C#"},
new SelectListItem{Value= "python", Text= "Python"},
new SelectListItem{Value= "cpp", Text="C++"},
new SelectListItem{Value= "java", Text="Java"},
new SelectListItem{Value= "js", Text="JavaScript"},
new SelectListItem{Value= "ruby", Text="Ruby"},
};
}
This listing demonstrates a number of aspects of working with <select> lists:
 SelectedValue1/SelectedValue2—Used to hold the values selected by the
user. They’re model-bound to the selected values and used to preselect the cor-
rect item when rendering the form.
 MultiValues—Used to hold the selected values for a multiselect list. It’s an
IEnumerable, so it can hold more than one selection per <select> element.
 Items—Provides the list of options to display in the <select> elements. Note
that the element type must be SelectListItem, which exposes the Value and
Text properties, to work with the Select Tag Helper.
Listing 8.5 View model for displaying select element dropdowns and list boxes
Figure 8.6 Some of the many ways to display <select> elements using the Select Tag Helper.
These properties will hold the
values selected by the single-
selection select boxes.
To create a
multiselect list
box, use an
IEnumerable<>.
The list of
items to
display in
the select
boxes
222 CHAPTER 8 Building forms with Tag Helpers
NOTE The Select Tag Helper only works with SelectListItem elements.
That means you’ll normally have to convert from an application-specific list
set of items (for example, a List<string> or List<MyClass>) to the UI-
centric List<SelectListItem>. But hey, that’s why we have view models!
The Select Tag Helper exposes the asp-for and asp-items attributes that you can
add to <select> elements. As for the Input Tag Helper, the asp-for attribute speci-
fies the property in your view model to bind to. The asp-items attribute is provided
the IEnumerable<SelectListItem> to display the available <option>.
TIP It’s common to want to display a list of enum options in a <select> list.
This is so common that ASP.NET Core ships with a helper for generating a
SelectListItem for any enum. If you have an enum of the TEnum type, you
can generate the available options in your View using asp-items="Html
.GetEnumSelectList<TEnum>()".
The following listing shows how to display a drop-down list, a single-selection list box,
and a multiselection list box. It uses the view model from the previous listing, binding
each <select> list to a different property, but reusing the same Items list for all of
them.
@model SelectListsViewModel
<select asp-for="SelectedValue1"
asp-items="Model.Items"></select>
<select asp-for="SelectedValue2"
asp-items="Model.Items"
size="@Model.Items.Count()"></select>
<select asp-for="MultiValues"
asp-items="Model.Items"></select>
Hopefully, you can see that the Razor for generating a drop-down <select> list is
almost identical to the Razor for generating a multiselect <select> list. The Select
Tag Helper takes care of adding the multiple HTML attribute to the generated out-
put, if the property it’s binding to is an IEnumerable.
WARNING The asp-for attribute must not include the Model. prefix. The asp-
items attribute, on the other hand, must include it if referencing a property
on the view model. The asp-items attribute can also reference other C#
items, such as objects stored in ViewData, but using the view model is the best
approach.
You’ve seen how to bind three different types of select list so far, but the one I haven’t
yet covered from figure 8.6 is how to display groups in your list boxes using <optgroup>
Listing 8.6 Razor template to display a select element in three different ways
Creates a standard drop-down
select list by binding to a
standard property in asp-for
Creates a single-select list box
by providing the standard
HTML size attribute
Creates a multiselect list box
by binding to an IEnumerable
property in asp-for
223
Creating forms using Tag Helpers
elements. Luckily, nothing needs to change in your Razor code, you just have to update
how you define your SelectListItems.
The SelectListItem object defines a Group property that specifies the Select-
ListGroup the item belongs to. The following listing shows how you could create two
groups and assign each list item to either a “dynamic” or “static” group, using a view
model similar to that shown in listing 8.5. The final list item, C#, isn’t assigned to a
group, so it will be displayed as normal, outside of the grouping.
public class SelectListsViewModel
{
public IEnumerable<string> SelectedValues { get; set; }
public IEnumerable<SelectListItem> Items { get; set; }
public SelectListsViewModel()
{
var dynamic = new SelectListGroup { Name = "Dynamic" };
var stat = new SelectListGroup { Name = "Static" };
ItemsWithGroups = new List<SelectListItem>
{
new SelectListItem {
Value= "js",
Text="Javascript",
Group = dynamic
},
new SelectListItem {
Value= "cpp",
Text="C++",
Group = stat
},
new SelectListItem {
Value= "python",
Text="Python",
Group = dynamic
},
new SelectListItem {
Value= "csharp",
Text="C#",
},
}
}
With this in place, the Select Tag Helper will generate <optgroup> elements as neces-
sary when rendering the Razor to HTML. The preceding view model would be ren-
dered to HTML as
<select id="SelectedValues" name="SelectedValues" multiple="multiple">
<optgroup label="Dynamic">
<option value="js">JavaScript</option>
<option value="python">Python</option>
Listing 8.7 Adding Groups to SelectListItems to create optgroup elements
Initializes the list
items in the
constructor
Creates single instance
of each group to pass to
SelectListItems
Sets the appropriate
group for each
SelectListItem
If a SelectListItem doesn’t
have a Group, it won’t be
added to an optgroup.
224 CHAPTER 8 Building forms with Tag Helpers
</optgroup>
<optgroup label="Static Languages">
<option value="cpp">C++</option>
</optgroup>
<option value="csharp">C#</option>
</select>
Another common requirement when working with <select> elements is to include an
option in the list that indicates “no value selected,” as shown in figure 8.7. Without
this extra option, the default <select> dropdown will always have a value and default
to the first item in the list.
You can achieve this in one of two ways: you could either add the “not selected” option
to the available SelectListItems, or you could manually add the option to the Razor,
for example by using
<select asp-for="Model.SelectedValue" asp-items="Model.Items">
<option Value = "">**Not selected**</option>
</select>
This will add an extra <option> at the top of your <select> element, with a blank
Value attribute, allowing you to provide a “no selection” option for the user.
TIP Adding a “no selection” option to a <select> element is so common,
you might want to create a partial view to encapsulate this logic, as you saw in
the previous chapter. I’ll leave it as an exercise for the reader, but you’d need
to create a common interface as the view model for your partial view.
With the Input Tag Helper and Select Tag Helper under your belt, you should be able
to create most of the forms that you’ll need. You have all the pieces you need to create
the currency converter application now, with one exception.
Remember, whenever you accept input from a user, you should always validate the
data. The Validation Tag Helpers provide a way to display model validation errors to
the user on your form, without having to write a lot of boilerplate markup.
Figure 8.7 Without a “no selection” option, the <select> element will always have
a value. This may not be the behavior you desire if you don’t want an <option> to be
selected by default.
225
Creating forms using Tag Helpers
8.2.5 The Validation Message and Validation Summary Tag Helpers
In section 8.2.3 you saw that the Input Tag Helper generates the necessary data-val-
* validation attributes on form input elements themselves. But you also need some-
where to display the validation messages. This can be achieved for each property in
your view model using the Validation Message Tag Helper applied to a <span> by
using the asp-validation-for attribute:
<span asp-validation-for="Email"></span>
When an error occurs during client-side
validation, the appropriate error message
for the referenced property will be dis-
played in the <span>, as shown in figure
8.8. This <span> element will also be used
to show appropriate validation messages if
server-side validation fails, and the form is
being redisplayed.
Any errors associated with the Email property stored in ModelState will be ren-
dered in the element body, and the element will have appropriate attributes to hook
into jQuery validation:
<span class="field-validation-valid" data-valmsg-for="Email"
data-valmsg-replace="true">The Email Address field is required.</span>
The validation error shown in the element will be replaced when the user updates the
Email <input> field and client-side validation is performed.
NOTE For further details on model validation, see chapter 6.
As well as displaying validation messages for individual properties, you can also display
a summary of all the validation messages in a <div> by using the Validation Summary
Tag Helper, shown in figure 8.9. This renders a <ul> containing a list of the Model-
State errors.
The Validation Summary Tag Helper is applied to a <div> using the asp-validation-
summary attribute and providing a ValidationSummary enum value, such as
<div asp-validation-summary="All"></div>
The ValidationSummary enum controls which values are displayed, and has three pos-
sible values:
 None—Don’t display a summary. (I don’t know why you’d use this.)
 ModelOnly—Only display errors that are not associated with a property.
 All—Display errors either associated with a property or with the model.
The Validation Summary Tag Helper is particularly useful if you have errors associated
with your view model that aren’t specific to a property. These can be added to the model
state by using a blank key, as shown in listing 8.8. In this example, the property validation
Figure 8.8 Validation messages can be shown
in an associated <span> by using the
Validation Message Tag Helper.
226 CHAPTER 8 Building forms with Tag Helpers
passed, but we provide additional model-level validation that we aren’t trying to convert
a currency to itself.
public class CurrencyController : Controller
{
[HttpPost]
public IActionResult Convert(
CurrencyConverterModel model)
{
if(model.CurrencyFrom == model.CurrencyTo)
{
ModelState.AddModelError(
string.Empty,
"Cannot convert currency to itself");
}
if (!ModelState.IsValid)
{
return View(model);
}
Listing 8.8 Adding model-level validation errors to the ModelState
Figure 8.9 Form showing validation errors. The Validation Message Tag Helper
is applied to <span>, close to the associated input. The Validation Summary
Tag Helper is applied to a <div>, normally at the top or bottom of the form.
Validation Message
Tag Helpers
Validation Summary
Tag Helper
Can’t convert
currency to itself
Adds model-level error
by using empty key
If there are any property-
level or model-level errors,
display them.
227
Creating forms using Tag Helpers
//store the valid values somewhere etc
return RedirectToAction("Index", "Checkout");
}
}
Without the Validation Summary Tag Helper, the model-level error would still be
added if the user used the same currency twice, and the form would be redisplayed.
Unfortunately, there would have been no visual cue to the user indicating why the
form did not submit—obviously that’s a problem! By adding the Validation Summary
Tag Helper, the model-level errors are shown to the user so they can correct the prob-
lem, as shown in figure 8.10.
NOTE For simplicity, I added the validation check to the controller action. A
better approach might be to create a custom validation attribute to achieve
this instead. That way, your controller stays lean and sticks to the single
responsibility principle. You’ll see how to achieve this in chapter 19.
Identical currencies
cause model-level
validation error
.
Without Tag Helper
,
the user has no idea
why the form has been
redisplayed.
Identical currencies
cause model-level
validation error
.
Tag Helper shows
model-level errors.
Figure 8.10 Model-level errors are only displayed by the Validation Summary Tag Helper. Without one, users
won’t have any indication that there were errors on the form, and so won’t be able to correct them.
228 CHAPTER 8 Building forms with Tag Helpers
This section has covered most of the common Tag Helpers available for working with
forms, including all the pieces you need to build the currency converter forms. They
should also be enough to get you going on your own applications, building out your
Razor views using your view models. But forms aren’t the only area in which Tag Help-
ers are useful; they’re generally applicable any time you need to mix server-side logic
with HTML generation.
One such example is generating links to other pages in your application using
routing-based URL generation. Given that routing is designed to be fluid as you refac-
tor your application, keeping track of the exact URLs the links should point to would
be a bit of a maintenance nightmare if you had to do it “by hand.” As you might
expect, there’s a Tag Helper for that: the Anchor Tag Helper.
8.3 Generating links with the Anchor Tag Helper
At the end of chapter 5, I showed how you could generate URLs for links to other
pages in your application from inside your controllers and by using ActionResults.
Views are the other common place where you often need to link to other actions in
your application, normally by way of an <a> element with an href attribute pointing
to the appropriate URL.
The Anchor Tag Helper can be used to generate the URL for a given action using
routing. Conceptually, this is almost identical to the way the Form Tag Helper gener-
ates the action URL, as you saw in section 8.2.1. For the most part, using the Anchor
Tag Helper is identical too; you provide asp-controller and asp-action attributes,
along with asp-route-* attributes as necessary. The default MVC templates use the
Anchor Tag Helper to generate the links shown in the navigation bar using the code.
<ul class="nav navbar-nav">
<li><a asp-area="" asp-controller="Home"
asp-action="Index">Home</a></li>
<li><a asp-area="" asp-controller="Home"
asp-action="About">About</a></li>
<li><a asp-area="" asp-controller="Home"
asp-action="Contact">Contact</a></li>
</ul>
As you can see, each <a> element has an asp-action and asp-controller attribute.
These use the routing system to generate an appropriate URL for the <a>, resulting in
the following markup:
<ul class="nav navbar-nav">
<li><a href="/">Home</a></li>
<li><a href="/Home/About">About</a></li>
<li><a href="/Home/Contact">Contact</a></li>
</ul>
Listing 8.9 Using the Anchor Tag Helper to generate URLs in _Layout.cshtml
229
Cache-busting with the Append Version Tag Helper
The URLs use default values where possible, so the Index action on the HomeController
generates the simple "/" URL instead of "/Home/Index".
If you need more control over the URL generated, the Anchor Tag Helper exposes
a number of additional properties you can set, which will be used during URL genera-
tion. The most commonly used are
 asp-action—Sets the action route parameter.
 asp-controller—Sets the controller route parameter.
 asp-area—Sets the area route parameter to use. Areas can be used to provide
an additional layer of organization to your application.5
 asp-host—If set, the link will point to the provided host and will generate an
absolute URL instead of a relative URL.
 asp-protocol—Sets whether to generate an http or https link. If set, it will gen-
erate an absolute URL instead of a relative URL.
 asp-route—Uses the named route to generate the URL.
 asp-route-*—Sets the route parameters to use during generation. Can be
added multiple times for different route parameters.
By using the Anchor Tag Helper and its attributes, you generate your URLs using the
routing system as described in chapter 5, ensuring that you won’t break your applica-
tion if you rename something. This reduces the duplication in your code by removing
the hardcoded URLs you’d otherwise need to embed in all your views.
If you find yourself writing repetitive code in your markup, chances are someone
has written a Tag Helper to help with it. The Append Version Tag Helper in the fol-
lowing section is a great example of using Tag Helpers to reduce the amount of fiddly
code required.
8.4 Cache-busting with the Append Version Tag Helper
A common problem with web development, both when developing and when an
application goes into production, is ensuring that browsers are all using the latest
files. For performance reasons, browsers often cache files locally and reuse them for
subsequent requests, rather than calling your application every time a file is
requested.
Normally, this is great—most of the static assets in your site rarely change, so cach-
ing them significantly reduces the burden on your server. Think of an image of your
company logo—how often does that change? If every page shows your logo, then
caching the image in the browser makes a lot of sense.
But what happens if it does change? You want to make sure users get the updated
assets as soon as they’re available. A more critical requirement might be if the Java-
Script files associated with your site change. If users end up using cached versions of
5
I won’t cover areas in this book. They’re an optional aspect of MVC that are often only used on large projects.
You can read about them here: http://mng.bz/3X64.
230 CHAPTER 8 Building forms with Tag Helpers
your JavaScript then they might see strange errors, or your application might appear
broken to them.
This conundrum is a common one in web development, and one of the most com-
mon ways for handling it is to use a cache-busting query string.
DEFINITION A cache-busting query string adds a query parameter to a URL,
such as ?v=1. Browsers will cache the response and use it for subsequent
requests to the URL. When the resource changes, the query string is also
changed, for example to ?v=2. Browsers will see this is a request for a new
resource, and will make a fresh request.
The biggest problem with this approach is that it requires you to update a URL every
time an image, CSS, or JavaScript file changes. This is a manual step that requires
updating every place the resource is referenced, so it’s inevitable that mistakes are
made. Tag Helpers to the rescue! When you add a <script>, <img>, or <link> ele-
ment to your application, you can use Tag Helpers to automatically generate a cache-
busting query string:
<script src="~/js/site.js" asp-append-version="true"></script>
The asp-append-version attribute will load the file being referenced and generate a
unique hash based on its contents. This is then appended as a unique query string to
the resource URL:
<script src="/js/site.js?v=EWaMeWsJBYWmL2g_KkgXZQ5nPe"></script>
As this value is a hash of the file contents, it will remain unchanged as long as the file
isn’t modified, so the file will be cached in users’ browsers. But if the file is modified,
then the hash of the contents will change and so will the query string. This ensures
browsers are always served the most up-to-date files for your application without your
having to worry about manually updating every URL whenever you change a file.
The default MVC templates in Visual Studio and the .NET CLI make use of Tag
Helpers for both forms and cache busting. They also use Tag Helpers to condition-
ally render different markup depending on the current environment using a tech-
nique you haven’t seen yet, where the Tag Helper is declared as a completely
separate element.
8.5 Using conditional markup
with the Environment Tag Helper
In many cases, you want to render different HTML in your Razor templates depend-
ing if your website is running in a development or production environment. For
example, in development, you typically want your JavaScript and CSS assets to be ver-
bose and easy to read, but in production you’d process these files to make them as
small as possible. Another example might be the desire to apply a banner to the appli-
cation when it’s running in a testing environment, which is removed when you move
to production, shown in figure 8.11.
231
Using conditional markup with the Environment Tag Helper
NOTE You’ll learn about configuring your application for multiple environ-
ments in chapter 11.
You’ve already seen how to use C# to add if statements to your markup, so it would be
perfectly possible to use this technique to add an extra div to your markup when the
current environment has a given value. If we assume that the env variable contains the
current environment, then you could use something like
@if(env == "Testing" || env == "Staging")
{
<div class="warning">You are currently on a testing environment</div>
}
There’s nothing wrong with this, but a better approach would be to use the Tag
Helper paradigm to keep your markup clean and easy to read. Luckily, ASP.NET Core
comes with the EnvironmentTagHelper, which can be used to achieve the same result
in a slightly clearer way:
<environment include="Testing,Staging">
<div class="warning">You are currently on a testing environment</div>
</environment>
This Tag Helper is a little different from the others you’ve seen before. Instead of aug-
menting an existing HTML element using an asp- attribute, the whole element is the Tag
Helper. This Tag Helper is completely responsible for generating the markup, and it
uses an attribute to configure it.
Functionally, this Tag Helper is identical to the C# markup (although for now I’ve
glossed over how the env variable could be found), but it’s more declarative in its func-
tion than the C# alternative. You’re obviously free to use either approach, but person-
ally I like the HTML-like nature of Tag Helpers. Either way, the EnvironmentTagHelper
is used in the default MVC templates, so at least you’ll know what it’s up to now!
We’ve reached the end of this chapter on Tag Helpers, and with it, our first look at
building traditional web applications that display HTML to users. In the last part of
the book, we’ll revisit Razor templates, and you’ll learn how to build custom compo-
nents like custom Tag Helpers and View Components. For now, you have everything
Figure 8.11 The warning banner will
be shown whenever you’re running in a
testing environment, to make it easy to
distinguish from production.
232 CHAPTER 8 Building forms with Tag Helpers
you need to build complex Razor layouts—the custom components can help tidy up
your code down the line.
This chapter, along with the previous four, has been a whistle-stop tour of how to
build MVC applications with ASP.NET Core. You now have the basic building blocks to
start making simple ASP.NET Core applications. In the second part of this book, I’ll
show you some of the additional features you’ll need to understand to build complete
applications. But before we get to that, I’ll take a chapter to discuss building Web APIs.
I’ve mentioned the Web API approach previously, in which your application serves
data using the MVC framework, but instead of returning user-friendly HTML, it
returns machine-friendly JSON or XML. In the next chapter, you’ll see why and how
to build a Web API, take a look at an alternative routing system designed for APIs, and
learn how to generate JSON responses to requests.
Summary
 Tag Helpers let you bind your data model to HTML elements, making it easier
to generate dynamic HTML while remaining editor friendly.
 As with Razor in general, Tag Helpers are for server-side rendering of HTML
only. You can’t use them directly in frontend frameworks, such as Angular or
React.
 Tag Helpers can be standalone elements or can attach to existing HTML using
attributes.
 Tag Helpers can customize the elements they’re attached to, add additional
attributes, and customize how they’re rendered to HTML. This can greatly
reduce the amount of markup you need to write.
 Tag Helpers can expose multiple attributes on a single element.
 You can add the asp-action and asp-controller attributes to the <form> ele-
ment to set the action URL using the URL generation feature of the MVC
middleware’s router.
 You specify route values to use during routing with the Form Tag Helper using
asp-route-* attributes.
 The Form Tag Helper also generates a hidden field that you can use to prevent
CSRF attacks.
 You can attach the Label Tag Helper to a <label> using asp-for. It generates an
appropriate for attribute and caption based on the [Display] DataAnnotations
attribute and the view model property name.
 The Input Tag Helper sets the type attribute of an <input> element to the
appropriate value based on a bound property’s Type and any DataAnnotations
applied to it. It also generates the data-val-* attributes required for client-side
validation.
 To enable client-side validation, you must add the necessary JavaScript files to
your view for jQuery validation and unobtrusive validation.
233
Summary
 The Select Tag Helper can generate drop-down <select> elements as well as
list boxes, using the asp-for and asp-items attributes.
 To generate a multiselect <select> element, bind the element to an IEnumerable
property on the view model.
 The items supplied in asp-for must be an IEnumerable<SelectListItem>.
 You can generate an IEnumerable<SelectListItem> for an enum TEnum using
the Html.GetEnumSelectList<TEnum>() helper method.
 The Select Tag Helper will generate <optgroup> elements if the items supplied
in asp-for have an associated SelectListGroup on the Group property.
 Any extra additional <option> elements added to the Razor markup will be
passed through to the final HTML. You can use these additional elements to
easily add a “no selection” option to the <select> element.
 The Validation Message Tag Helper is used to render the client- and server-side
validation error messages for a given property. Use the asp-validation-for
attribute to attach the Validation Message Tag Helper to a <span>.
 The Validation Summary Tag Helper is used to display validation errors for the
model, as well as for individual properties. Use the asp-validation-summary
attribute to attach the Validation Summary Tag Helper to a <div>.
 You can generate <a> URLs using the Anchor Tag Helper. This Helper uses
routing to generate the href URL using asp-action, asp-controller, and
asp-route-* attributes.
 You can add the asp-append-version attribute to <link>, <script>, and <img>
elements to provide cache-busting capabilities based on the file’s contents.
 You can use the Environment Tag Helper to conditionally render different
HTML based on the app’s current execution environment.
234
Creating a Web API
for mobile and client
applications using MVC
In the previous five chapters, you’ve worked through each layer of a traditional
ASP.NET Core MVC application, using Razor views to render HTML to the
browser. In this chapter, you’ll see a slightly different take on an MVC application.
We’ll explore Web APIs, which serve as the backend for client-side SPAs and
mobile apps.
You can apply much of what you’ve learned to Web APIs; they use the same
MVC design pattern, and the concepts of routing, model binding, and validation
This chapter covers
 Creating a Web API controller to return JSON to
clients
 Using attribute routing to customize your URLs
 Generating a response using content negotiation
 Enabling XML formatting
235
What is a Web API and when should you use one?
all carry through. The differentiation from traditional web applications is entirely in
the view part of MVC. Instead of returning HTML, they return data as JSON or XML,
which client applications use to control their behavior or update the UI.
In this chapter, you’ll learn how to define controllers and actions and see how sim-
ilar they are to the controllers you already know. You’ll learn how to create an API
model to return data and HTTP status codes in response to a request, in a way that cli-
ent apps can understand.
The subsequent section looks at an alternative approach to routing often used with
Web APIs, called attribute routing. This approach uses the same route templates con-
cept from chapter 5 but applies them to your action methods in a way that’s more
suited to the customization needs of Web API applications.
You’ll also learn how to format the API models returned by your action methods
using content negotiation, to ensure you generate a response that the calling client
can understand. As part of this, you’ll learn how to add support for additional format
types, such as XML, so that you can both generate XML responses and receive XML
data POSTed to your app.
One of the great aspects of ASP.NET Core is the variety of applications you can cre-
ate with it. The ability to easily build a generalized HTTP Web API presents the possi-
bility of using ASP.NET Core in a greater range of situations than can be achieved with
traditional web apps alone. But should you build a Web API and why? In the first sec-
tion of this chapter, I’ll go over some of the reasons why you might or might not want
to create a Web API.
9.1 What is a Web API and when should you use one?
Traditional web applications handle requests by returning HTML to the user, which is
displayed in a web browser. You can easily build applications of this nature using Mvc-
Middleware to generate HTML with Razor templates, as you’ve seen in recent chap-
ters. This approach is common and well understood, but the modern application
developer also has a number of other possibilities to consider, as shown in figure 9.1.
Client-side single-page applications (SPAs) have become popular in recent years
with the development of frameworks such as Angular, React, and Ember. These frame-
works use JavaScript that runs in a user’s web browser to generate the HTML they see
and interact with. The server sends this initial JavaScript to the browser when the user
first reaches the app. The user’s browser loads the JavaScript and initializes the SPA,
before loading any application data from the server.
Once the SPA is loaded, communication with a server still occurs over HTTP, but
instead of sending HTML directly to the browser in response to requests, the server-
side application sends data (normally in a format such as JSON or XML) to the client-
side application. The SPA then parses the data and generates the appropriate HTML
to show to a user, as shown in figure 9.2. The server-side application endpoint that the
client communicates with is sometimes called a Web API.
236 CHAPTER 9 Creating a Web API for mobile and client applications using MVC
DEFINITION A Web API exposes a number of URLs that can be used to access
or change data on a server. It’s typically accessed using HTTP.
These days, mobile applications are common and are, from the server application’s
point of view, similar to client-side SPAs. A mobile application will typically communi-
cate with a server application using an HTTP Web API, receiving data in a common
format, such as JSON, just like an SPA. It then modifies the application’s UI depend-
ing on the data it receives.
One final use case for a Web API is where your application is designed to be par-
tially or solely consumed by other services. Imagine you’ve built a web application to
send emails. By creating a Web API, you can allow other application developers to use
your email service by sending you an email address and a message. Virtually all lan-
guages and platforms have access to an HTTP library they could use to access your ser-
vice from code.
This is all there is to a Web API. It exposes a number of endpoints (URLs) that cli-
ent applications can send requests to and retrieve data from. These are used to power
the behavior of the client apps, as well as to provide all the data they need to display
the correct interface to a user.
Browser
Traditional
web application
Server
Client
Synchronous
request via HTTP
Response: HTML web page
SPA web application REST API
Asynchronous
request via HTTP
Response: partial page
data as JSON or XML
Client application RPC service
Synchronous or asynchronous
request via HTTP
Response: data as JSON,
XML or binary
Figure 9.1 Modern developers have to consider a number of different
consumers of their applications. As well as traditional users with web
browsers, these could be SPAs, mobile applications, or other apps.
237
What is a Web API and when should you use one?
Whether you need or want to create a Web API for your ASP.NET Core application
depends on the type of application you want to build. If you’re familiar with client-
side frameworks, will need to develop a mobile application, or already have an SPA
build-pipeline configured, then you’ll most likely want to add Web APIs for them to be
able to access your application.
One of the selling points of using a Web API is that it can serve as a generalized
backend for all of your applications. For example, you could start by building a client-
side application that uses a Web API. Later, you could add a mobile app that uses the
same Web API, with little or no modification required to your ASP.NET Core code.
If you’re new to web development, have no need to call your application from out-
side a web browser, or don’t want/need the effort involved in configuring a client-side
application, then you probably won’t need Web APIs initially. You can stick to generat-
ing your UI using Razor and will no doubt be highly productive!
NOTE Although there has definitely been a shift toward client-side frame-
works, server-side rendering using Razor is still relevant. Which approach you
choose will depend largely on your preference for building HTML applica-
tions in the traditional manner versus using JavaScript on the client.
Having said that, adding Web APIs to your application isn’t something you have to
worry about ahead of time. Adding them later is simple, so you can always ignore
them initially and add them in as the need arises. In many cases, this will be the best
approach.
Initial requests fetch client-side
JavaScript application.
Subsequent requests fetch
data in JSON format.
Figure 9.2 A sample client-side SPA using Angular. The initial requests load the SPA JavaScript into the browser,
and subsequent requests fetch data from a Web API, formatted as JSON.
238 CHAPTER 9 Creating a Web API for mobile and client applications using MVC
Once you’ve established that you need a Web API for your application, creating one is
easy, as it’s built into ASP.NET Core. In the next section, you’ll see how to create a
Web API controller in an existing MVC application.
9.2 Creating your first Web API Controller
The MVC design pattern is sometimes only thought of in relation to applications that
directly render their UI, like the Razor views you’ve seen in previous chapters. In
ASP.NET Core, the MVC pattern applies equally well when building a Web API; but
the view part of MVC involves generating a machine-friendly response rather than a
user -friendly response.
As a parallel to this, you create Web API controllers in ASP.NET Core in the very
same way you create traditional MVC controllers. The only thing that differentiates
them from a code perspective is the type of data they return—MVC controllers typi-
cally return a ViewResult; Web API controllers generally return raw .NET objects
from the action methods, or an IActionResult such as StatusCodeResult, as you saw
in chapter 4.
NOTE This is different from the previous version of ASP.NET, where the
MVC and Web API stacks were completely independent. ASP.NET Core uni-
fies the two stacks into a single approach, which makes using both in a project
painless!
To give you an initial taste of what you’re working with, figure 9.3 shows the result of
calling a Web API endpoint from your browser. Instead of a friendly HTML UI, you
SPAs with ASP.NET Core
The cross-platform and lightweight design of ASP.NET Core means it lends itself well
to acting as a backend for your SPA framework of choice. Given the focus of this book
and the broad scope of SPAs in general, I won’t be looking at Angular, React, or other
SPAs here. Instead, I suggest checking out the resources appropriate to your chosen
SPA. Books are available from Manning for all the common client-side frameworks:
 React in Action by Mark Tielens Thomas (Manning, 2018) https://livebook
.manning.com/#!/book/react-in-action/.
 Angular in Action by Jeremy Wilken (Manning, 2018) https://livebook.manning
.com/#!/book/angular-in-action/.
 Vue.js in Action by Erik Hanchett with Benjamin Listwon (Manning, 2018)
https://livebook.manning.com/#!/book/vue-js-in-action/.
If you want to get started using ASP.NET Core with an SPA, Microsoft provides a num-
ber of templates you can install for use with the .NET CLI. See https://docs
.microsoft.com/en-us/ aspnet/core/spa/ for details. Additionally, these use Micro-
soft JavaScriptServices to provide features such as server-side pre-rendering and hot-
module replacement. See http://mng.bz/O4bd for details on integrating JavaScript-
Services into your own apps.
239
Creating your first Web API Controller
receive data that can be easily consumed in code. In this example, the Web API
returns a list of string fruit names as JSON when you request the URL /Fruit/Index.
TIP Web APIs are normally accessed from code by SPAs or mobile apps, but
by accessing the URL in your web browser directly, you can view the data the
API is returning.
Listing 9.1 shows the code that was used to create the Web API demonstrated in figure
9.3. This is obviously a trivial example, but it highlights the similarity to traditional
MVC controllers. You can add a Web API controller to your project in exactly the
same way as you saw in chapter 4, using the New Item dialog in Visual Studio, or by
creating a new .cs file when you use the .NET CLI or another IDE.
public class FruitController : Controller
{
List<string> _fruit = new List<string>
{
"Pear",
"Lemon",
"Peach"
};
public IEnumerable<string> Index()
{
return _fruit;
}
}
There’s nothing particularly special about this controller; it returns a list of strings
when the action method executes. The only real difference from the equivalent MVC
controller in this case is the return type of the action. Instead of returning a View-
Result or an IActionResult, it directly returns the list of strings.
When you return data directly from an action method, you’re providing the API
model for the request. The client will receive this data. It’s formatted into an appropriate
Listing 9.1 A simple Web API controller
Figure 9.3 Testing a Web API by accessing the URL in the browser. A GET
request is made to the /Fruit/Index URL, which returns a List<string> that
has been JSON-encoded into an array of strings.
The Web API controller
inherits from the Controller
base class, as per conventions
The data returned would
typically be fetched from the
application model in a real app.
The controller exposes a
single action method, Index,
which returns the list of fruit.
240 CHAPTER 9 Creating a Web API for mobile and client applications using MVC
response, in this case a JSON representation of the list, and sent back to the browser
with a 200 (OK) status code.
TIP ASP.NET Core will format returned data as JSON by default. You’ll see
how to format the returned data in other ways later in this chapter.
The URL at which a Web API controller is exposed is handled in the same way as for
traditional MVC controllers—using routing. The routing module directs a request to
a particular controller and action, which is then invoked and returns a result. This
result is then used to generate an appropriate response.
Web API controllers don’t have to return data directly. You’re free to return an
IActionResult instead, and often this is required. Depending on the desired behav-
ior of your API, you may sometimes want to return data, and other times you may want
to return a raw HTTP status code, indicating whether the request was successful. If an
API call is made requesting details of a product that does not exist, you might want to
return a 404 (Not Found) status code, as you’ll see shortly.1
Although the primary goal of a Web API is to return data, typically only the most
trivial of cases will be able to return data directly from the action method in the man-
ner you’ve seen. For example, if the request represented a command, such as “Delete
the user with ID 9,” then there might not be any data to return, other than a success/
failure status code.
Listing 9.2 shows an example of a case where you’d want to return an IAction-
Result instead of returning the data directly. It shows another method on the same
FruitController as before. This method exposes a way for clients to fetch a specific
fruit by an id, which we’ll assume is its index in the list of _fruit you defined in the
previous listing. Model binding is used to set the value of the id parameter from the
request.
public IActionResult View(int id)
{
if (id >= 0 && id < _fruit.Count)
{
return Ok(_fruit[id]);
}
return NotFound();
}
1
ASP.NET Core 2.1 introduces IActionResult<T>, which can simplify generating OpenAPI specifications
for your Controllers. See http://mng.bz/FYH1 for details.
Listing 9.2 A Web API action returning IActionResult to handle error conditions
The action method returns an
IActionResult as it can no longer
always return List<string>.
An element can only be
returned if the id value is a
valid _fruit element index.
Using Ok to return data will
format the data passed to it
and send a 200 status code.
NotFound returns a
NotFoundResult, which will
send a 404 status code.
241
Creating your first Web API Controller
In the successful path for the action method, the id parameter has a value greater
than zero and less than the number of elements in _fruit. When that’s true, the value
of the element can be returned to the caller. This is achieved by creating an OkResult,
using the Ok helper method1
on the Controller base class. This generates a 200 status
code and returns the element in the response body, as shown in figure 9.4, as if you
had returned the _fruit[id] object from the method directly.
If the id is outside the bounds of the _fruit list, then the method calls NotFound to
create a NotFoundResult. When executed, this method generates a raw 404 HTTP sta-
tus code response, which will show the default “not found” page for your browser, as
shown in figure 9.5. The fact that you’re returning two different types depending on
the code path taken means that you must mark the action method as returning an
IActionResult in order to make the code compile.
1
Some people get uneasy when they see the phrase “helper method,” but there’s nothing magic about the
Controller helpers—they’re shorthand for creating a new IActionResult of a given type. You don’t have
to take my word for it though, you can always view the source code for the base class on GitHub at
http://mng.bz/NZDt.
Figure 9.4 Returning data using the Ok helper method. The Ok method uses the
data passed to it to create the response body, then sets the status code to 200.
Figure 9.5 A request that generates a raw 404 response without any
content, as is typical for a Web API, will show the default “not found”
page in a browser.
242 CHAPTER 9 Creating a Web API for mobile and client applications using MVC
You’re free to return any type of IActionResult from your Web API controllers, but
you’ll commonly return StatusCodeResult instances, which return a specific status
code with or without associated data. NotFoundResult and OkResult both derive
from StatusCodeResult, for example. Another commonly used response is a 400
(bad request), which is normally returned when the data provided in the request fails
validation. This can be generated using a BadRequestResult, often using the Bad-
Request helper method.
TIP You learned about various IActionResults in chapter 4. BadRequest-
Result, OkResult, and NotFoundResult all inherit from StatusCodeResult,
and set the appropriate status code for their type (200, 404, and 400, respec-
tively). Using these wrapper classes makes the intention of your code clearer
than relying on other developers to understand the significance of the various
status code numbers.
Once you’ve returned an IActionResult (or other object) from your controller, it’s
serialized to an appropriate response. This works in several ways, depending on
 The formatters that your app supports
 The data you return from your method
 The data formats the requesting client can handle
You’ll learn more about formatters and serializing data at the end of this chapter, but
before we go any further, it’s worth zooming out a little, and exploring the parallels
between traditional MVC applications and Web API endpoints. The two are similar, so
it’s important to establish the patterns that they share and where they differ.
9.3 Applying the MVC design pattern to a Web API
In the previous version of ASP.NET, Microsoft commandeered the generic term “Web
API” to create the ASP.NET Web API framework. This framework, as you might
expect, was used to create HTTP endpoints that could return formatted JSON or
XML in response to requests.
The ASP.NET Web API framework was completely separate from the MVC frame-
work, even though it used similar objects and paradigms. The underlying web stacks
for them were completely different beasts and couldn’t interoperate.
In ASP.NET Core, that has all changed. You now have a single framework, unified
in MvcMiddleware, which you can use to build both traditional web applications and
Web APIs. The names MVC and Web API are often still used to differentiate between
the different ways the MvcMiddleware can be applied, but there are few differences in
the framework itself. You’ve already seen this yourself; the Web API FruitController
you created in the last section derived from the same Controller base class as the
examples you’ve seen in previous chapters.
Consequently, even if you’re building an application that consists entirely of Web
APIs, using no server-side rendering of HTML with Razor templates, the MVC design
243
Applying the MVC design pattern to a Web API
pattern still applies. Whether you’re building traditional web applications or Web
APIs, you can structure your application virtually identically.
After five chapters of it, you’re, I hope, nice and familiar with how ASP.NET Core
handles an MVC request. But just in case you’re not, figure 9.6 shows how Mvc-
Middleware handles a typical request after passing through the middleware pipeline.
This example shows how a request to view the available fruit on a traditional grocery
store website might look.
The router routes the request to view all the fruit listed in the apples category to
the View action method on the FruitController. The middleware then constructs a
binding model, validates it, and passes it to the action. The action method interacts
with the application model by calling into services, talking to a database, and return-
ing the necessary data to the action.
1. A request is received to
the URL /fruit/apples.
Request
Action
View
2. The routing module matches
the request to FruitController.View
action and derives the route
parameter category=apples.
5. The controller selects the
view template and passes it
the view model containing the
list of apples, as well as other
details, such as suggested items,
favorites, or previously
purchased apples.
6. The view uses the provided
view model to generate an HTML
response which is returned to the
user via the middleware pipeline.
Application model
View model
Domain
model
Services
Database
interaction
Routing
4. The action method calls into
services that make up the
application model to fetch
the apples and to
build a view model.
MVC controller
HTML
Binding model
Model binding
Model validation
Binding model Model state
3. The method parameters
for the View action method
are bound and validated, and
the action method is executed.
Figure 9.6 Handling a request to a traditional MVC application, in which the view generates an
HTML response that’s sent back to the user.
244 CHAPTER 9 Creating a Web API for mobile and client applications using MVC
Finally, the action method selects a View to render, creates a view model, and executes
the view to generate the HTML response. The MvcMiddleware returns the response to
the middleware pipeline and back to the user’s browser.
How would this change if the request came from a client-side or mobile applica-
tion? If you want to serve machine-readable JSON instead of HTML, how does the
MVC process change? As shown in figure 9.7, the answer is “very little.” This shows how
a Web API endpoint handles a similar request.
As before, routing handles the request by selecting a controller and action to
invoke. You’ll normally use a slightly different routing system when building a Web
API, as you’ll see later, but that’s completely optional. You can use the same conven-
tional routing as with traditional MVC apps if you prefer.
1. A request is received to
the URL /fruit/apples.
Request
JSON formatter
2. The routing module matches
the request to the View action
on the ApiFruitController and
derives the route parameter
category=apples.
5. The controller selects the
JSON formatter and passes it
the API model containing the
list of apples.
6. The formatter uses the provided
API model to generate a JSON response
which is returned to the SPA / mobile
app via the middleware pipeline.
Application model
API model
Database
interaction
Routing
4. The action method calls into
services that make up the application
model to fetch the apples and to build
a view model.
Web API controller
JSON
Binding model
Model binding
Model validation
Binding model Model state
3. The method parameters
for the View action method
are bound and validated, and
the action method is executed.
Services
Action
Domain
model
Figure 9.7 A call to a Web API endpoint in an e-commerce ASP.NET Core web application. The ghosted
portion of the diagram is identical to figure 9.6.
245
Applying the MVC design pattern to a Web API
In this example, routing directs the request to a controller described as a Web API
controller, but there’s nothing special about this class compared to the MVC control-
ler from figure 9.6. The only difference is that the Web API controller will return data
in a machine-readable format, rather than as HTML.
TIP In ASP.NET Core, there’s no difference between MVC controllers and
Web API controllers. The naming refers only to the difference in the data
returned by their methods and the purpose for which they’re used.1
After routing comes model binding, in which the binder creates a binding model and
populates it with values from the request, exactly as in the MVC request. Validation
occurs in the same way.
The action executes in the same way as for the MVC controller, by interacting with
the application model—the very same application model as is used by the MVC con-
troller. This is an important point; by separating the behavior of your app into an
application model, instead of incorporating it into the controllers themselves, you’re
able to reuse the business logic of your application.
After the application model has returned the data necessary to service the
request—the fruit objects in the apples category—you see the first difference to the
traditional HTML app. Instead of building a view model, the action method creates an
API model. This is analogous to the view model used previously, but rather than con-
taining data used to generate an HTML view, it contains the data that will be sent back
in the response.
DEFINITION View models contain both data required to build a response and
metadata about how to build the response. API Models only contain the data
to be returned in the response.
When we looked at the traditional MVC app, we used the view model in conjunction
with a Razor view template to build the final response. With the Web API app, we use
the API model in conjunction with a formatter. A formatter, as the name suggests, seri-
alizes the API model into a machine-readable response, such as JSON or XML. The
formatter forms the “V” in the Web API version of MVC, by choosing an appropriate
representation for the data to return.
Finally, as with the traditional web app, the generated response is then sent back
through the middleware pipeline, passing through each of the configured middle-
ware components, and back to the original caller.
Hopefully, the parallels between traditional web applications and Web APIs are
clear; the majority of behavior is identical, only the response varies. Everything from
when the request comes in to the interaction with the application model is similar
between the paradigms.
1
ASP.NET Core 2.1 introduces the [ApiController] attribute. You can use this attribute to opt-in to specific
Web API conventions. To learn more about this attribute, see http://mng.bz/FYH1.
246 CHAPTER 9 Creating a Web API for mobile and client applications using MVC
Some of the minor differences are down to convention in how Web APIs are used
rather than hard and fast rules. These differences aren’t specific to Web APIs—you’re
free to use them with MVC controllers too. You’ll tend to use them with Web APIs, pri-
marily due to the differing use cases for traditional MVC apps and Web API apps.
The first of these differences relates to the routing infrastructure and how the
router selects action methods for execution based on an incoming request. You’ve
already seen that MVC controllers typically use conventional routing, which selects an
action method based on a handful of globally defined route templates. In contrast,
Web API controllers tend to use attribute routing, where each action method is tied
directly to one or more specific URLs.
9.4 Attribute routing: taking fine-grained control
of your URLs
Chapter 5 described the conventional routing scheme, used by default in most tradi-
tional web applications. This involves defining a number of global routes that Mvc-
Middleware compares to an incoming request’s URL. A route consists of a name, a route
template, and, optionally, defaults and constraints to control whether the middleware con-
siders a given request URL to be a match for the route. This listing shows an example
of adding the MVC middleware to an application using the default MVC route.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
With the configuration shown in the listing, the routes are global; they’re applied to
every request and they can select any action on a controller to be executed, depend-
ing on the incoming URL. These routes are terse and defined in one place, so they’re
often a good choice to use for your MVC applications. Where they fall down is when
you need more control over the exact URLs you expose.
When building a Web API application, you sometimes find you want, or need, to
make minor changes to the URL a particular action exposes. These URLs form the
Listing 9.3 Defining conventional routing when configuring MvcMiddleware
247
Attribute routing: taking fine-grained control of your URLs
public API of your application, so it’s important that they’re easy to consume. Imagine
you have a commonly called action on the CategoriesController that returns a list
of all the current categories on your e-commerce site that contain available products:
public IActionResult ListAllCurrentCategoriesWithProducts();
Using conventional routing, that would map to an excessively long URL,
/Categories/ListAllCurrentCategoriesWithProducts. The team decides to shorten
it to /CurrentCategories, so users can easily understand which section of your app
they’re in.
With conventional routing you’d need to add a new, highly specific, route in the
global configuration, to handle this outlier method. Over time, you might add other
similarly specific routes, giving the potential for clashes and breaking other routes in
your application. Conventional routing is not as useful when you need to have this
level of fine-grained control over your routes.
Instead, a more common approach is to use attribute routing for your Web APIs.
Attribute routing, as the name suggests, involves applying attributes to your action
methods to specify the URL that they should match.
public class HomeController: Controller
{
[Route("")]
public IActionResult Index()
{
/* method implementation*/
}
[Route("contact")]
public IActionResult Contact()
{
/* method implementation*/
}
}
By applying [Route] attributes to your controller, you bypass conventional routing for
that action method. Each [Route] attribute defines a specific URL that corresponds
to the associated action method. In the example provided, the "/" URL maps directly
to the Index method and the "/contact" URL maps to the Contact method.
TIP If you use [Route] attributes on all your action methods, then you don’t
need to set up any conventional routes when you call UseMvc() in
Startup.Configure().
Conceptually, attribute routing takes the opposite approach to conventional routing.
The MVC-style conventional routing looks at the incoming URL, checks it against
your route templates to fill in the blanks for the controller and action, and then
checks to see if the requested controller and action exist in your application.
Listing 9.4 Attribute routing example
The Index action will be
executed when the "/"
URL is requested.
The Contact action will be
executed when the
"/home/contact" URL is
requested.
248 CHAPTER 9 Creating a Web API for mobile and client applications using MVC
Attribute routing, on the other hand, starts by looking for all the controllers and
action methods in your application that have [Route] attributes. It uses these to calcu-
late the URL that needs to be called to invoke a given action, building a dictionary
where the key is the URL and the value is the action method. When a request arrives,
the router can check if the key is in the dictionary—if it is, then it knows which action
to execute, if it isn’t, then the action isn’t attribute-routed.
Attribute routing maps URLs to a specific action method, but a single action
method can still have multiple URLs. Each URL must be declared with its own Route-
Attribute, as shown in this listing, which shows the skeleton of the Web API for a car-
racing game.
public class CarController
{
[Route("car/start")]
[Route("car/ignition")]
[Route("start-car")]
public IActionResult Start()
{
/* method implementation*/
}
[Route("car/speed/{speed}")]
[Route("set-speed/{speed}")]
public IActionResult SetCarSpeed(int speed)
{
/* method implementation*/
}
}
The listing shows two different action methods, both of which can be accessed from
multiple URLs. For example, the Start method will be executed when any of the fol-
lowing URLS are requested:
 /car/start
 /car/ignition
 /start-car
These URLs are completely independent of the controller and action method names;
only the value in the RouteAttribute matters.
NOTE The controller and action name have no bearing on the URLs or
route templates when RouteAttributes are used.
The templates used by the route attributes are standard route templates, the same as
you used in chapter 5. You can use literal segments and you’re free to define route
parameters that will extract values from the URL, as shown by the SetCarSpeed
Listing 9.5 Attribute routing with multiple attributes
The Start method will be
executed when any of these
URLs are reached.
The name of the action
method has no effect on
the RouteAttribute or URL.
The RouteAttributes can
contain route parameters, in
this case speed.
249
Attribute routing: taking fine-grained control of your URLs
method in the previous listing. That method defines two route templates, both of
which define a route parameter, {speed}.
TIP I’ve used multiple [Route] attributes on each action in this example, but
it’s best practice to expose your action at a single URL. This will make your
API easier to understand and consume by other applications.
Route parameters are handled in the very same way as for conventional routing—they
represent a segment of the URL that can vary. The only difference with attribute rout-
ing is that the controller and action name are already known, so you’re not allowed to
use the {controller} and {action} parameters—these are already set to the control-
ler and action that corresponds to the decorated action method.
As before, when defining conventional routes, the route parameters in your Route-
Attribute templates can
 Be optional
 Have default values
 Use route constraints
For example, you could update the SetCarSpeed method in the previous listing to
constrain {speed} to an integer and to default to 20 like so:
[Route("car/speed/{speed=20:int}")]
[Route("set-speed/{speed=20:int}")]
public IActionResult SetCarSpeed(int speed)
It’s also possible to give your route attributes a name by providing one to Route-
Attribute, for example:
[Route("set-speed/{speed}", Name = "set_speed")]
public IActionResult SetCarSpeed(int speed)
Route names are optional, and aren’t used when matching URLs, but they can be use-
ful for URL generation. You saw in chapter 5 how to create URLs in your action meth-
ods using the Url helper and the RedirectToRoute method, and how to use them
with Tag Helpers in chapter 8. Although you may only have half a dozen conventional
routes, you effectively have a route for every attribute-routed action method that has
been given a name. Each route name must be unique in the application, so be sure to
keep track of them if you do use them!
If you managed to get your head around routing in chapter 5, then attribute rout-
ing shouldn’t hold any dangers for you. You’re writing a custom route template for
every action method in your application. When combining both conventional and
attribute routing in your application, you need to bear in mind how these two
approaches interact and what it means for your URLs.
9.4.1 Ordering of conventional and attribute routes
In chapter 5, you saw that the order in which you define conventional routes in
Startup.cs controls the order in which those routes are checked when MvcMiddleware
250 CHAPTER 9 Creating a Web API for mobile and client applications using MVC
receives a request. A route template defined earlier will be tested first, so gets first dibs
on handling the request. Because of that, it’s important to define the most specific
routes first, and the most general routes at the end.
With attribute routing, ordering doesn’t happen in quite the same way. On startup,
MvcMiddleware scans your app using reflection and locates all the routes applied
using RouteAttributes. It then automatically orders the routes from most specific to
least specific, as though you had defined the routes in the most optimum way.
Having said that, RouteAttributes do have an Order property. You can use this to
explicitly ensure a particular route attribute match is attempted before other routes,
but I’d strongly advise against using it. Doing so adds another layer of complexity that
you need to manage and could be indicative of excessive complexity in the URLs your
application exposes.
WARNING If you find yourself relying on a route order, then your URLs are
probably confusing. This will make it difficult for clients to consume your
API. Instead of using the Order property, look at ways to reduce the overlap
between your routes.
What does ordering routes in the most optimum way look like? Well, it obviously
depends on your application, and if you’ve defined your [Route] attributes using a
confusing URL space, then there may not be a truly optimum order. In those cases,
falling back to route naming can sometimes be an easier solution than trying to
coerce the correct order.
An aspect I haven’t touched on yet is what happens when you have an application
with both conventional and attribute routing. For example, the standard conventional
routing for the Index action method
public class HomeController
{
[Route("view”)]
public IActionResult Index() { }
}
would suggest that the /home/index URL would invoke the action. On the other
hand, attribute routing suggests the /view URL would invoke the action. Who wins?
Either? Both?
Well, applying attribute routing to an action means that it can never be matched
using conventional routing. Even though the action theoretically matches the
/home/index route, the conventional router can’t even see it, so it will never match.
You’re free to use both conventional routing and attribute routing in a single
application, but you need to bear this conflict between the two approaches in mind.
Even if a convention seems like it should match a particular action, the presence of an
attribute route will mean the action won’t be matched by the convention.
TIP Attribute routing on an action (or a controller, as you’ll see) will make
the action unreachable by conventional routing.
251
Attribute routing: taking fine-grained control of your URLs
This fact lends credence to the convention of sticking to conventional routing for tra-
ditional MVC controllers that return HTML using Razor templates and using attribute
routing for your Web API controllers. Doing so will make your life a lot simpler and
can help avoid conflicts if you’re consistent with your naming.1
TIP Use conventional routing for MVC controllers, and attribute routing for
Web API controllers. Adding a prefix such as "api" to your Web API route
templates helps to separate the Web API URL space from the MVC URL
space.
One thing you might begin noticing when you start using attribute routing is how
much more verbose it is than conventional routing. Where with conventional routing
you had only a handful of routes, you now have one for every single action method!
This is largely a symptom of the greater control attribute routing provides, but
there are a few features available to make your life a little easier. In particular, combin-
ing route attributes and token replacement can help reduce duplication in your code.
9.4.2 Combining route attributes to keep your route templates DRY
Adding route attributes to all of your API controllers can get a bit tedious, especially if
you’re mostly following conventions where your routes have a standard prefix such as
"api" or the controller name. Generally, you’ll want to ensure you don’t repeat your-
self (DRY) when it comes to these strings. The following listing shows two action meth-
ods with a number of [Route] attributes. (This is for demonstration purposes only.
Stick to one per action if you can!)
public class CarController
{
[Route("api/car/start")]
[Route("api/car/ignition")]
[Route("/start-car")]
public IActionResult Start()
{
/* method implementation*/
}
[Route("api/car/speed/{speed}")]
[Route("/set-speed/{speed}")]
public IActionResult SetCarSpeed(int speed)
{
/* method implementation*/
}
}
1
ASP.NET Core 2.1 introduces the [ApiController] attribute. Applying this attribute to your controller
means it must only use [Route] attributes; it will never match conventional routes.
Listing 9.6 Duplication in RouteAttribute templates
252 CHAPTER 9 Creating a Web API for mobile and client applications using MVC
There’s quite a lot of duplication here—you’re adding "api/car" to most of your
routes. Presumably, if you decided to change this to "api/vehicles", you’d have to go
through each attribute and update it. Code like that is asking for a typo to creep in!
To alleviate this pain, it’s also possible to apply RouteAttributes to controllers, in
addition to action methods. When a controller and an action method both have a
route attribute, the overall route template for the method is calculated by combining
the two templates.
[Route("api/car")]
public class CarController
{
[Route("start")]
[Route("ignition")]
[Route("/start-car")]
public IActionResult Start()
{
/* method implementation*/
}
[Route("speed/{speed}")]
[Route("/set-speed/{speed}")]
public IActionResult SetCarSpeed(int speed)
{
/* method implementation*/
}
}
Combining attributes in this way can reduce some of the duplication in your route
templates and makes it easier to add or change the prefixes (such as switching "car"
to "vehicle") for multiple action methods. To ignore the RouteAttribute on the
controller and create an absolute route template, start your action method route tem-
plate with a slash (/).
You can combine multiple RouteAttribute on controllers too, if you use inheri-
tance. If all your controllers inherit from a base class, you can use this approach to
add a global prefix to all your attribute routes.
[Route("api")]
public class BaseController : Controller { }
[Route("car")]
public class CarController : BaseController
{
[Route("start")]
[Route("ignition")]
Listing 9.7 Combining RouteAttribute templates
Listing 9.8 Using a base class to add a global prefix to RouteAttribute templates
Combines to give
the"api/car/start"
template
Combines to give
the "api/car/ignition"
template
Does not combine as
starts with /, gives the
"start-car" template
Combines to give the
"api/car/speed/{speed}"
template
Does not combine as starts
with /, gives the "set-
speed/{speed}" template
The BaseController defines
the "api" global prefix
The CarController inherits the
RouteAttribute from the BaseController
and adds its own prefix
Combines all attributes
to give the
"api/car/start" template
Combines all attributes
to give the
"api/car/ignition" template
253
Attribute routing: taking fine-grained control of your URLs
[Route("/start-car")]
public IActionResult Start()
{
/* method implementation*/
}
}
As you can see, the [Route] attribute from the “most-base” controller is used first, fol-
lowed by the action controller, and finally, the action method, combining to give a sin-
gle route for the action method. Once again, specifying a leading "/" to the route will
prevent route combination and will use only the final [Route] attribute to define the
route. This has reduced a lot of the duplication, but you can do one better by using
token replacement.
9.4.3 Using token replacement to reduce duplication
in attribute routing
The ability to combine attribute routes is handy, but you’re still left with some duplica-
tion if you’re prefixing your routes with the name of the controller, or your route tem-
plates use the action name. Luckily, you can simplify even further!
Attribute routes support the automatic replacement of the [action] and
[controller] tokens in your attribute routes. These will be replaced with the name of
the action and the controller (without the “Controller” suffix), respectively. The
tokens are replaced after all attributes have been combined, so this is useful when you
have controller inheritance hierarchies. This listing shows how you can simplify the
previous route attribute hierarchy.
[Route("api/[controller]")]
public abstract class BaseController { }
public class CarController : BaseController
{
[Route("[action]")]
[Route("ignition")]
[Route("/start-car")]
public IActionResult Start()
{
/* method implementation*/
}
}
The router will also replace tokens in the Name property of RouteAttributes, which
can make generating unique route names easier. If you use a RouteAttribute on a
base controller with a tokenized name
[Route("api/[controller]", Name = "[controller]_[action]")]
Listing 9.9 Token replacement in RouteAttributes
Does not combine with base
attributes as it starts with /, so
remains as "start-car"
Token replacement happens
last, so [controller] is replaced
with "car" not "base"
Combines and replaces
tokens to give the
"api/car/start" template
Combines and replaces
tokens to give the
"api/car/ignition" template
Does not combine with base attributes as
it starts with /, so remains as "start-car"
254 CHAPTER 9 Creating a Web API for mobile and client applications using MVC
then a unique route name will be generated for each action. You need to be careful if
you have multiple RouteAttributes applied to a single action, as in the previous list-
ing, as then the route names will no longer be unique!
TIP Avoid using multiple [Route] attributes on your Web API action meth-
ods. Having multiple URLs invoke the same action can be confusing for con-
sumers of your app and complicates routing.
We’ve covered pretty much everything there is to know about attribute routing now,
with one exception: handling different HTTP request types like GET and POST.
9.4.4 Handling multiple matching actions with attribute routing
In chapter 5, you saw that you could discriminate between two action methods that
match the same URL by using HTTP verb attributes such as [HttpPost]. With Web
API controllers, the need to distinguish between identical URLs becomes more preva-
lent, due to their typical design.
Imagine you’re building an API to manage your calendar. You want to be able to
list and create appointments. Well, a traditional HTTP REST service might define the
following URLs and HTTP verbs to achieve this:
 GET /appointments—List all your appointments
 POST /appointments—Create a new appointment
Note that these two endpoints define the same URL, only the HTTP verb differs. This
is common when building Web APIs and, luckily, is easy to model in ASP.NET Core. As
with conventional routing, you use attributes such as [HttpPost] to define the HTTP
verb an action method corresponds to.
public class AppointmentController
{
[HttpGet("/appointments")]
public IActionResult ListAppointments()
{
/* method implementation */
}
[HttpPost("/appointments")]
public IActionResult CreateAppointment()
{
/* method implementation */
}
}
These HTTP verb attributes are the same ones you saw used in chapter 5 with conven-
tional routing, but in this case, they also contain the route template themselves.
[HttpGet("/appointments")] effectively combines [Route("/appointments")] and
Listing 9.10 Using HTTP verb attributes with attribute routing
Only executed in response
to GET /appointments
Only executed in response
to POST /appointments
255
Enabling additional input formatters: binding to XML data
[HttpGet] into a more compact representation. For that reason, this is the preferred
approach for defining your attribute routes.
TIP Define routes using attributes such as [HttpGet], [HttpPost], and
[HttpDelete]. This makes the action methods more specific and easier to
reason about.
And with that, you’ll probably be glad to hear we’re finished with routing for Web
APIs and, in fact, for the whole book! With the route template details you have in
chapter 5 and in this section, you have everything you need to customize the URLs in
your application, whether you’re using conventional routes or attribute routing.
Conceptually, people often find attribute routing a bit easier to grok, as you’re nor-
mally mapping a URL one-to-one with an action method. Although having the free-
dom to define the exact URL for a particular action method is useful, try not to get
too power-crazed; always try to think about the users and clients consuming your API.
Having a simple and well-defined set of URLs for your application should be your
main goal.
With routing checked off the list, it’s time to consider the next step in an MVC
request—model binding—and how you can customize it for Web APIs. The process is
identical for both MVC and Web API, but Web APIs often run into an additional
requirement, especially when interoperating with legacy systems—the ability to post
XML to a Web API action method. By default, this isn’t possible but, luckily, enabling
it is easy.
9.5 Enabling additional input formatters:
binding to XML data
In chapter 6, I introduced model binding as a way of mapping an incoming request to
the method parameters of an action. After the router selects an action method, the
model binder is responsible for taking the data posted in a request and mapping it to
the method parameters of the action method.
In particular, you saw that by using the [FromBody] attribute on a method parame-
ter, you could bind a parameter to the body of a request.
These days, with mobile applications and SPAs becoming increasingly popular, the
vast majority of data posted to Web APIs is JSON. The default model binder supports
this out of the box, so all that’s required to have the JSON data bound to your method
parameter is to add the [FromBody] attribute.1
There was a time, however, when XML was king. It’s still used for configuration
(good old MSBuild and .csproj files) and many communication protocols (SOAP,
RSS). Consequently, it’s quite likely you’ll need to be able to accept XML data to a
Web API action at some point.
1
ASP.NET Core 2.1 introduces the [ApiController] attribute. If you decorate your Web API controller with
this attribute, you don’t need to decorate your binding models with [FromBody]. The MVC middleware will
infer that complex types should be bound using [FromBody] automatically.
256 CHAPTER 9 Creating a Web API for mobile and client applications using MVC
By default, MvcMiddleware only accepts POST data in JSON format. In order to be
able to accept XML data and have it bind automatically to your models, you’ll need
additional services.
Before you dig into how to add the formatters, it’s worth considering what happens if
you try to send XML to an ASP.NET Core application that you haven’t yet configured
to accept it.
In this example, I’ve created a simple Web API Controller that parses an object
form the request body and returns a 200 OK response:
[HttpPost]
public IActionResult Add([FromBody] Car car)
{
return Ok();
}
Figure 9.8 shows a screenshot of Postman (www.getpostman.com), which you can use
to create requests for testing your application. I created a request with XML in the
body and POSTed it to the preceding action. As you can see, the application returns a
415 response code, which means “Unsupported Media Type”, indicating that Mvc-
Middleware was unable to parse the XML in the body of the request.
Clients include the content-type header as part of POST requests to tell the server
what sort of data it’s sending. In this case, the request is sent with a content-type of
text/xml. When the request arrives, it checks this header and looks for an input for-
matter that can deserialize the request.
TIP The input formatter is selected based on the content-type header of
the request.
Customizable by default
The ability to customize each aspect of ASP.NET Core is one of the features that sets
it apart from the previous version of ASP.NET. ASP.NET Core configures the vast
majority of its internal components using one of two mechanisms—dependency injec-
tion or by configuring an Options object when you add the service to your application,
as you’ll see in chapters 10 (dependency injection) and 11 (Options object).
As an adjunct to this, much of ASP.NET Core starts from the assumption that you
want nothing in your application, and lets you add in the things you need. This means
you can easily create small, stripped-back web applications, when compared to the
monolithic System.Web-based previous version of ASP.NET. Your application has only
the features you need.
In counterpoint to this, this philosophy means you’ll run into many more situations
where the default settings won’t fit your requirements. Luckily, the ability to easily
customize all the components of ASP.NET Core means you’ll normally be able to add
additional features without too much difficulty.
257
Enabling additional input formatters: binding to XML data
You’ve already seen what happens when you make a request with a content-type your
app can’t deserialize. ASP.NET Core doesn’t include an XML input formatter by
default when you add MVC to your project, but you can add a NuGet package to pro-
vide the functionality.
1 If you’re not using the ASP.NET Core 2.0 metapackage, add <PackageReference>
for the Microsoft.AspNetCore.Mvc.Formatters.Xml package to your csproj file:
<PackageReference Include="Microsoft.AspNetCore.Mvc.Formatters.Xml"
Version="2.0.0" />
You can add the preceding line directly, use the Visual Studio package manager,
or run the following command from inside your project’s folder:
dotnet add package Microsoft.AspNetCore.Mvc.Formatters.Xml
2 Update the ConfigureServices method in Startup to add the formatters to
MVC
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddXmlSerializerFormatters();
}
That’s it! That simple change registers the XML input formatter with MvcMiddleware.
Whenever your app receives a request with a content-type of text/xml or application/
A request is sent to the web API with
a content-type of text/xml.
The body of the request
contains XML.
The ASP
.NET Core app is not
configured to handle text/xml so
it returns 4 5 Unsupported Media Type.
1
.
Figure 9.8 When an application can’t handle the format of the data sent to a Web API action,
it returns a 415 Unsupported Media Type response.
258 CHAPTER 9 Creating a Web API for mobile and client applications using MVC
xml, the input formatter will handle it, parse the request, and allow the model binder
to bind your data, so the application can return a 200 response, shown in figure 9.9.
Accepting XML is a common requirement and, luckily, it’s easy to enable in your
Web API (and MVC) controllers with the XML formatters package. Similarly, you
often need to customize the format of the data returned by your Web API control-
lers—whether that’s JSON, XML, or a different, custom format. In the next section,
you’ll see how you can control this both at the application level and for individual
action methods.
9.6 Generating a response from a model
This brings us to the final section in this chapter: formatting a response. You’ve seen
how to tweak your application for a Web API using attribute routing and adding dif-
ferent input formatters, but the output formatting is where Web APIs differ from tradi-
tional MVC controllers.
Consider this scenario: You’ve created a Web API action method for returning a
list of cars you’ve owned, as in the following listing. It invokes a method on your appli-
cation model, which hands back the list of data to the controller. Now you need to for-
mat the response and return it to the caller.
A request is sent to the
web API with a content-type
of text/xml.
The body of the request
contains XML.
The XML formatter package has been
added, so the request is accepted,
and the action method returns
a 200 OK response.
Figure 9.9 Once the XML formatters have been configured, the application can parse the
text/xml content-type and returns a 200 OK response code.
259
Generating a response from a model
public class CarsController : Controller
{
[HttpGet("api/cars")]
public IEnumerable<string> ListCars()
{
return new string[]
{ "Nissan Micra", "FordFocus" };
}
}
You’ve already seen that it’s possible to return data directly from an action method, in
which case, the middleware formats it and returns the formatted data to the caller. But
how does the middleware know which format to use? After all, you could serialize it as
JSON, as XML, even with a simple ToString() call.
The process of determining the format of data to send to clients is known gener-
ally as content negotiation (conneg). At a high level, the client sends a header indicating
the types of content it can understand—the Accept header—and the server picks one
of these, formats the response, and sends a content-type header in the response,
indicating which it chose.
You’re not forced into only sending a content-type the client expects and, in some
cases, you may not even be able to handle the types it requests. What if a request stipu-
lates it can only accept Excel spreadsheets? It’s unlikely you’d support that, even if
that’s the only content-type the request contains!
When you return an API model from an action method, whether directly (as in the
previous listing) or via an OkResult or other StatusCodeResult, ASP.NET Core will
Listing 9.11 A Web API controller to return a list of cars
The action is executed with
a request to GET /api/cars.
The API Model containing
the data is an
IEnumerable<string>.
This data would normally be
fetched from the application model.
The accept and content-type headers
The accept header is sent by a client as part of a request to indicate the type of con-
tent that the client can handle. It consists of a number of MIME types,a
with optional
weightings (from 0 to 1) to indicate which type would be preferred. For example, the
application/json,text/xml;q=0.9,text/plain;q=0.6 header indicates that
the client can accept JSON, XML, and plain text, with weightings of 1.0, 0.9, and 0.6,
respectively. JSON has a weighting of 1.0, as no explicit weighting was provided. The
weightings can be used during content negotiation to choose an optimal representa-
tion for both parties.
The content-type header describes the data sent in a request or response. It con-
tains the MIME type of the data, with an optional character encoding. For example,
the application/json; charset=utf-8 header would indicate that the body of the
request or response is JSON, encoded using UTF-8.
a
For more on MIME types see http://mng.bz/D3UB.
260 CHAPTER 9 Creating a Web API for mobile and client applications using MVC
always return something. If it can’t honor any of the types stipulated in the Accept
header, it will fall back to returning JSON by default. Figure 9.10 shows that even
though XML was requested, MvcMiddleware formatted the response as JSON.
NOTE In the previous version of ASP.NET, objects were serialized to JSON
using PascalCase, where properties start with a capital. In ASP.NET Core,
objects are serialized using camelCase by default, where properties start with a
lowercase letter.
Whichever way the data is sent, it’s serialized by an IOutputFormatter implementa-
tion. ASP.NET Core ships with a limited number of output formatters out of the box,
but as always, it’s easy to add additional ones, or change the way the defaults work.
9.6.1 Customizing the default formatters: adding XML support
As with most of ASP.NET Core, the Web API formatters are completely customizable.
By default, only formatters for plain text (text/plain), HTML (text/html), and
JSON (application/json) are configured.
A request is sent to the
web API with an accept
header type of text/xml.
The ASP
.NET Core app is not configured
to return text/xml, so it returns JSON by
default instead.
Figure 9.10 Even though the request was made with an Accept header of text/xml,
the response returned was JSON, as the server was not configured to return XML.
261
Generating a response from a model
Given the common use case of SPAs and mobile applications, this will get you a
long way. But as you saw in section 9.5, you often need to be able to return data in
another format, such as XML.
You’ve already seen how to add XML output-formatting support—it’s the same
process as adding input-formatter support in section 9.5! Technically, you can add the
output and input formatters independently, but it’s unlikely you’ll need one and not
the other. Not many clients are going to send you JSON but demand an XML
response!
As you saw in section 9.5, add the Microsoft.AspNetCore.Mvc.Formatters.Xml
package, and update the call to AddMvc()to add the output formatters:
services.AddMvc()
.AddXmlSerializerFormatters();
With this simple change, the middleware can now format responses as XML. Running
the same request as shown in figure 9.10 with XML support enabled means the app
will respect the text/xml accept header. The formatter serializes the string array to
XML, as shown in figure 9.11, instead of the JSON returned previously.
A request is sent to the
web API with an accept
header type of text/xml.
With the XML formatters added, the accept
header can be honored, so text/xml is returned
instead of the default JSON response.
Figure 9.11 With the XML output formatters added, the text/xml Accept header
is respected and the response can be serialized to XML.
262 CHAPTER 9 Creating a Web API for mobile and client applications using MVC
This is an example of content negotiation, where the client has specified what formats
it can handle and the server selects one of those, based on what it can produce. This
approach is part of the HTTP protocol, but there are some quirks to be aware of when
relying on it in ASP.NET Core. You won’t often run into these, but if you’re not aware
of them when they hit you, they could have you scratching your head for hours!
9.6.2 Choosing a response format with content negotiation
Content negotiation is where a client says which types of data it can accept using
the accept header and the server picks the best one it can handle. Generally speak-
ing, this works as you’d hope: the server formats the data using a type the client can
understand.
The ASP.NET Core implementation has some special cases that are worth bearing
in mind:
 By default, the middleware will only return application/json, text/plain and
text/html MIME types. You can add additional IOutputFormatters to make
other types available, as you saw in the previous section for text/xml.
 By default, if you return null as your API model, whether from an action
method, or by passing null in StatusCodeResult, the middleware will return a
204 No Content response.
 When you return a string as your API model, if no Accept header is set, the
middleware will format the response as text/plain.
 When you use any other class as your API model and there’s either no Accept
header or none of the supported formats were requested, the first formatter
that can generate a response will be used (typically JSON by default).
 If the middleware detects that the request is probably from a browser (the
accept header contains */*), then it will not use conneg. Instead, it will format
the response as though no accept header was provided, using the default for-
matter (typically JSON).
These defaults are relatively sane, but they can certainly bite you if you’re not aware of
them. The last point in particular, where the response to a request from a browser is
virtually always formatted as JSON has certainly caught me out when trying to test
XML requests locally!
As you should expect by now, all of these rules are configurable; you can easily
change the default behavior in your application if it doesn’t fit your requirements. In
chapter 4, I showed how to customize MVC by modifying the maximum number of
validation errors in your application. The following listing shows a similar approach,
in which you can force the middleware to respect the browser’s Accept header when
adding the MVC services in Startup.cs.
263
Summary
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.RespectBrowserAcceptHeader = true;
});
}
In most cases, conneg should work well for you out of the box, whether you’re build-
ing a SPA or a mobile application. In some cases, you may find you need to bypass the
usual conneg mechanisms for certain actions, and there are a number of ways to
achieve this, but I won’t cover them in this book as I’ve found I rarely need to use
them. For details, see the documentation at http://mng.bz/tnu9.
That brings us to the end of this chapter on Web APIs and, with it, part 1 of this
book! It’s been a pretty intense tour of ASP.NET Core, with a heavy focus on the MVC
middleware. By making it this far, you now have all the knowledge you need to start
building simple MVC applications using Razor view templates, or to create a Web API
server for your SPA or mobile app.
In part 2, you’ll get into some juicy topics, in which you’ll learn the details needed
to build complete apps, like adding users to your application, saving data to a data-
base, and how to deploy your application.
In chapter 10, we’ll look at dependency injection in ASP.NET Core and how it
helps create loosely coupled applications. You’ll learn how to register the ASP.NET
Core framework services with a container and set up your own classes as dependency-
injected services. Finally, you’ll see how to replace the built-in container with a third-
party alternative.
Summary
 A Web API exposes a number of methods or endpoints that can be used to
access or change data on a server. It’s typically accessed using HTTP.
 Web API action methods can return data directly or can use IActionResult to
generate an arbitrary response.
 Web APIs follow the same MVC design pattern as traditional web applications.
The formatters which generate the final response form the view.
 Unlike the previous version of ASP.NET, there’s no difference between MVC
controllers and Web API controllers in ASP.NET Core. The naming refers only
to the difference in the data returned by their methods and the purpose for
which they’re used.
 The data returned by a Web API action is called an API model. It contains the
data the middleware will serialize and send back to the client. This differs from
Listing 9.12 Customizing MVC to respect the browser Accept header in Web APIs
AddMvc has an
overload that takes
a lambda function
False by default, a number
of other properties are also
available to be set
264 CHAPTER 9 Creating a Web API for mobile and client applications using MVC
view models, as view models contain both data and metadata about how to gen-
erate the response.
 Attribute routing is an alternative way of defining the routes in your application
by applying RouteAttributes to your action methods. Web API controllers
often use this approach, as it allows tighter control over the URL generated for
each action method.
 The controller and action name have no bearing on the URLs or route tem-
plates when you use attribute routing.
 Route attributes applied to a controller combine with attributes on action
methods to form the final template. These are also combined with attributes on
inherited base classes.
 If an action or controller uses attribute routing, it can no longer be reached via
conventional routing.
 Use the "[controller]" and "[action]" tokens in your route templates to
reduce repetition. They’ll be replaced with the current controller and action
name.
 The [HttpPost] and [HttpGet] attributes allow choosing between actions
based on the request’s HTTP method when two actions correspond to the same
URL.
 You can model bind requests sent in the XML format using the Microsoft
.AspNetCore.Mvc.Formatters.Xml package. Add the XML formatters by calling
services.AddMvc().AddXmlSerializerFormatters() in your Startup class.
This will also enable XML responses.
 By default, ASP.NET Core will format the API model returned from a Web API
controller as JSON.
 If you return more than one type of result from an action method, the method
signature must return IActionResult.
 In contrast to the previous version of ASP.NET, JSON data is serialized using
camelCase rather than PascalCase.
 Content negotiation occurs when the client specifies the type of data it can han-
dle and the server chooses a return format based on this.
 By default, ASP.NET Core can return text/plain, text/html, and application/
json, but you can add additional formatters.
 Content negotiation isn’t used when the accept header contains */*, such as in
most browsers. You can disable this option by modifying the RespectBrowser-
AcceptHeader option when adding MVC services in Startup.cs.
Part 2
Building complete applications
We covered a lot of ground in part 1. You saw how an ASP.NET Core appli-
cation is composed of middleware and we focused heavily on the MVC middle-
ware. You saw how to use it to build traditional server-side-rendered apps using
Razor and how to build web APIs for mobile and client-side apps.
In part 2, we dive deeper into the framework and look at a variety of compo-
nents that you’ll inevitably need to build more complex apps. By the end of this
part, you’ll be able to build dynamic applications, customized to specific users, that
can be deployed to multiple environments, each with a different configuration.
ASP.NET Core uses dependency injection (DI) throughout its libraries, so it’s
important that you understand how this design pattern works. In chapter 10, I
introduce DI, why it is used, and how to configure the services in your applica-
tions to use DI.
Chapter 11 looks at the ASP.NET Core configuration system, which lets you
pass configuration values to your app from a range of sources—JSON files, envi-
ronment variables, and many more. You’ll learn how to configure your app to
use different values depending on the environment in which it is running, and
how to bind strongly typed objects to your configuration to help reduce runtime
errors.
Most web applications require some sort of data storage, so in chapter 12, I
introduce Entity Framework Core (EF Core). This is a new, cross-platform
library that makes it easier to connect your app to a database. EF Core is worthy
of a book in and of itself, so I only provide a brief introduction. I show you how
to create a database and how to insert, update, and query simple data.
266 CHAPTER
In chapters 13 through 15, we look at how to build more complex applications.
You’ll see how you can add ASP.NET Core Identity to your apps so that users can log
in and enjoy a customized experience. You’ll learn how to protect your app using
authorization to ensure only certain users can access certain action methods, and
you’ll see how to refactor your app to extract common code out of your action meth-
ods in filters.
In the final chapter of this part, I cover the steps required to make an app live,
including how to publish your app to IIS, how to configure the URLs your app listens
on, and how to optimize your client-side assets for improved performance.
267
Service configuration with
dependency injection
In part 1 of this book, you saw the bare bones of how to build applications with
ASP.NET Core. You learned how to compose middleware to create your application
and how to use the MVC pattern to build traditional web applications and web
APIs. This gave you the tools to start building simple applications.
In this chapter, you’ll see how to use dependency injection (DI) in your ASP.NET
Core applications. DI is a design pattern that helps you develop loosely coupled
code. ASP.NET Core uses the pattern extensively, both internally in the framework
This chapter covers
 Understanding the benefits of dependency
injection
 How ASP.NET Core uses dependency injection
 Configuring your services to work with
dependency injection
 Choosing the correct lifetime for your services
268 CHAPTER 10 Service configuration with dependency injection
and in the applications you build, so you’ll need to use it in all but the most trivial of
applications.
You may have heard of DI before, and possibly even used it in your own applica-
tions. If so, this chapter shouldn’t hold many surprises for you. If you haven’t used DI
before, never fear, I’ll make sure you’re up to speed by the time the chapter is done!
This chapter will start by introducing DI in general, the principles it drives, and
why you should care about it. You’ll see how ASP.NET Core has embraced DI
throughout its implementation and why you should do the same when writing your
own applications.
Once you have a solid understanding of the concept, you’ll see how to apply DI to
your own classes. You’ll learn how to configure your app so that the ASP.NET Core
framework can create your classes for you, removing the pain of having to create new
objects manually in your code. Toward the end of the chapter, you’ll learn how to con-
trol how long your objects are used for and some of the pitfalls to be aware of when
you come to write your own applications.
In chapter 19, we’ll revisit some of the more advanced ways to use DI, including
how to wire up a third-party DI container. For now though, let’s get back to basics:
what is DI and why should you care about it?
10.1 Introduction to dependency injection
The ASP.NET Core framework has been designed from the ground up to be modular
and to adhere to “good” software engineering practices. As with anything in software,
what is considered best practice varies over time, but for object-oriented program-
ming, the SOLID1
principles have stood the test of time.
On that basis, ASP.NET Core has dependency injection (sometimes called dependency
inversion, DI, or inversion of control2
) baked in to the heart of the framework. Whether
or not you want to use it within your own application code, the framework libraries
themselves depend on it as a concept.
This section aims to give you a basic understanding of what dependency injection
is, why you should care about it, and how ASP.NET Core uses it. The topic itself
extends far beyond the reach of this single chapter. If you want a deeper background,
I highly recommend checking out Martin Fowler’s articles online.3
TIP For a more directly applicable read with many examples in C#, I recom-
mend picking up Dependency Injection in .NET by Steven van Deursen and
Mark Seemann (Manning, 2017).
1
SOLID is a mnemonic for Single responsibility, Open-closed, Liskov substitution, Interface segregation, and
Dependency inversion: https://en.wikipedia.org/wiki/SOLID_(object-oriented_design).
2
Although related, dependency injection and dependency inversion are two different things. I cover both in a
general sense in this chapter, but for a good explanation of the differences, see this post by Derick Bailey:
http://mng.bz/vU7N.
3
Martin Fowler’s website at https://martinfowler.com is a gold mine of best-practice goodness. One of the most
applicable articles to this chapter can be found at www.martinfowler.com/articles/injection.html.
269
Introduction to dependency injection
I’ll begin this section by starting with a common scenario: a class in your application
depends on a different class, which in turn depends on another. You’ll see how depen-
dency injection can help alleviate this chaining of dependencies for you and provide a
number of extra benefits.
10.1.1 Understanding the benefits of dependency injection
When you first started programming, the chances are you didn’t immediately use a DI
framework. That’s not surprising, or even a bad thing; DI adds a certain amount of
extra wiring that’s not warranted in simple applications or when you’re getting
started. But when things start to get more complex, DI comes into its own as a great
tool to help keep that complexity in check.
Let’s consider a simple example, written without any sort of DI. Imagine a user has
registered on your web app and you want to send them an email. This listing shows
how you might approach this initially.
public class UserController : Controller
{
public IActionResult RegisterUser(string username)
{
var emailSender = new EmailSender();
emailSender.SendEmail(username);
return View();
}
}
In this example, the RegisterUser action on UserController executes when a new
user registers on your app. This creates a new instance of an EmailSender class, and
calls SendEmail() to send the email. The EmailSender class is the one that does the
sending of the email. For the purposes of this example, you can imagine it will look
something like this:
public class EmailSender
{
public void SendEmail(string username)
{
Console.WriteLine($"Email sent to {username}!");
}
}
Console.Writeline will perform the sending of the email.
NOTE Although I’m using sending email as a simple example, in practice you
might want to move this code out of your Controller classes entirely. This
sort of task is well suited to using message queues and a background process,
but that’s outside the scope of this book.
Listing 10.1 Sending an email without DI when there are no dependencies
The action method
is called when a
new user is created.
Creates a new instance
of EmailSender
Uses the new instance
to send the email
270 CHAPTER 10 Service configuration with dependency injection
If the EmailSender class is as simple as the previous example and it has no dependen-
cies, then you might not see any need to adopt a different approach to creating objects.
And to an extent, you’d be right. But what if you later update your implementation of
EmailSender so that it doesn’t implement the whole email-sending logic itself?
In practice, EmailSender would need to do a number of things to send an email. It
would need to
 Create an email message
 Configure the settings of the email server
 Send the email to the email server
Doing all of that in one class would go against the single responsibility principle (SRP),
so you’d likely end up with EmailSender depending on other services. Figure 10.1 shows
how this web of dependencies might look. UserController wants to send an email
using EmailSender, but in order to do so, it also needs to create the MessageFactory,
NetworkClient, and EmailServerSettings objects that EmailSender depends on.
Each class has a number of dependencies, so the “root” class, in this case User-
Controller, needs to know how to create every class it depends on, as well as every
class its dependencies depend on. This is sometimes called the dependency graph.
DEFINITION The dependency graph is the set of objects that must be created in
order to create a specific requested “root” object.
EmailSender
MessageFactory EmailServerSettings
UserController
NetworkClient
EmailSender depends on
MessageFactory and
NetworkClient.
UserController depends
on EmailSender.
NetworkClient depends
on EmailServerSettings.
To use EmailSender,
UserController
must create all of
the dependencies.
Figure 10.1 Dependency diagram without dependency injection. UserController indirectly
depends on all the other classes; it also creates them all.
271
Introduction to dependency injection
EmailSender depends on the MessageFactory and NetworkClient objects, so they’re
provided via the constructor, as shown here.
public class EmailSender
{
private readonly NetworkClient _client;
private readonly MessageFactory _factory;
public EmailSender(MessageFactory factory, NetworkClient client)
{
_factory = factory;
_client = client;
}
public void SendEmail(string username)
{
var email = _factory.Create(username);
_client.SendEmail(email);
Console.WriteLine($"Email sent to {username}!");
}
}
On top of that, the NetworkClient class that EmailSender depends on also has a
dependency on an EmailServerSettings object:
public class NetworkClient
{
private readonly EmailServerSettings _settings;
public NetworkClient(EmailServerSettings settings)
{
_settings = settings;
}
}
This might feel a little contrived, but it’s common to find this sort of chain of depen-
dencies. In fact, if you don’t have this in your code, it’s probably a sign that your classes
are too big and aren’t following the single responsibility principle.
So, how does this affect the code in UserController? The following listing shows
how you now have to send an email, if you stick to new-ing up objects in the controller.
public IActionResult RegisterUser(string username)
{
var emailSender = new EmailSender(
new MessageFactory(),
new NetworkClient(
new EmailServerSettings
(
host: "smtp.server.com",
port: 25
))
Listing 10.2 A service with multiple dependencies
Listing 10.3 Sending email without DI when you manually create dependencies
The EmailSender now depends
on two other classes.
Instances of the
dependencies are provided
in the constructor.
The EmailSender coordinates
the dependencies to create
and send an email.
To create EmailSender, you also have
to create all of its dependencies.
You need a new MessageFactory.
The
NetworkClient
also has
dependencies.
You’re already two layers
deep, but there could
feasibly be more.
272 CHAPTER 10 Service configuration with dependency injection
);
emailSender.SendEmail(username);
return View();
}
This is turning into some gnarly code. Improving the design of EmailSender to sepa-
rate out the different responsibilities has made calling it from UserController a real
chore. This code has a number of issues, among them:
 Not obeying the single responsibility principle—Our code is now responsible for both
creating an EmailSender object and using it to send an email.
 Considerable ceremony—Of the 11 lines of code in the RegisterUser method,
only the last two are doing anything useful. This makes it harder to read and
harder to understand the intent of the method.
 Tied to the implementation—If you decide to refactor EmailSender and add
another dependency, you’d need to update every place it’s used.
UserController has an implicit dependency on the EmailSender class, as it manually
creates the object itself as part of the RegisterUser method. The only way to know
that UserController uses EmailSender is to look at its source code. In contrast,
EmailSender has explicit dependencies on NetworkClient and MessageFactory, which
must be provided in the constructor. Similarly, NetworkClient has an explicit depen-
dency on the EmailServerSettings class.
TIP Generally speaking, any dependencies in your code should be explicit,
not implicit. Implicit dependencies are hard to reason about and difficult to
test, so you should avoid them wherever you can. DI is useful for guiding you
along this path.
Dependency injection aims to solve the problem of building a dependency graph by
inverting the chain of dependencies. Instead of the UserController creating its
dependencies manually, deep inside the implementation details of the code, an
already-created instance of EmailSender is injected via the constructor.
Now, obviously something needs to create the object, so the code to do that has to
live somewhere. The service responsible for creating an object is called a DI container
or an IoC container, as shown in figure 10.2.
DEFINITION The DI container or IoC container is responsible for creating
instances of services. It knows how to construct an instance of a service by cre-
ating all of its dependencies and passing these to the constructor. I’ll refer to
it as a DI container throughout this book.
The term dependency injection is often used interchangeably with inversion of control
(IoC). IoC describes the way that the control of how EmailSender is created has been
reversed: instead of the UserController controlling how to create an instance, it’s
provided one instead.
Finally, you can
send the email.
273
Introduction to dependency injection
NOTE There are many existing DI containers available for .NET: Autofac,
StructureMap, Unity, Ninject, Simple Injector . . . The list goes on! In chap-
ter 19, you’ll see how to replace the default ASP.NET Core container with one
of these alternatives.
The advantage of adopting this pattern becomes apparent when you see how much it
simplifies using dependencies. The following listing shows how UserController
would look if you used DI to create EmailSender instead of doing it manually. All of
the new cruft has gone and you can focus purely on what the controller is doing—call-
ing EmailSender and returning a View.
public class UserController : Controller
{
private readonly EmailSender _emailSender;
public UserController(EmailSender emailSender)
Listing 10.4 Sending an email using DI to inject dependencies
EmailSender
MessageFactory EmailServerSettings
UserController
NetworkClient
The EmailSender depends
on the MessageFactory and
the NetworkClient.
The UserController depends
on the EmailSender.
The NetworkClient depends
on the EmailServerSettings.
Dependency Injection Container
The DI Container creates
the complete dependency
graph and provides an
instance of the EmailSender
to the User Controller.
Figure 10.2 Dependency diagram using dependency injection. UserController
indirectly depends on all the other classes but doesn’t need to know how to create them.
UserController declares that it requires EmailSender and the container provides it.
Instead of creating the
dependencies implicitly,
they’re injected via the
constructor.
274 CHAPTER 10 Service configuration with dependency injection
{
_emailSender = emailSender;
}
public IActionResult RegisterUser(string username)
{
_emailSender.SendEmail(username);
return View();
}
}
One of the advantages of a DI container is that it has a single responsibility: creating
objects or services. You ask a container for an instance of a service and it takes care of
figuring out how to create the dependency graph, based on how you configure it.
NOTE It’s common to refer to services when talking about DI containers,
which is slightly unfortunate as it’s one of the most overloaded terms in soft-
ware engineering! In this context, a service refers to any class or interface that
the DI container creates when required.
The beauty of this approach is that by using explicit dependencies, you never have to
write the mess of code you saw in listing 10.3. The DI container can inspect your ser-
vice’s constructor and work out how to write the majority of the code itself. DI con-
tainers are always configurable, so if you want to describe how to manually create an
instance of a service you can, but by default you shouldn’t need to.
TIP You can inject dependencies into a service in other ways; for example, by
using property injection. But constructor injection is the most common and is
the only one supported out of the box in ASP.NET Core, so I’ll only be using
that in this book.
Hopefully, the advantages of using DI in your code are apparent from this quick
example, but DI provides additional benefits that you get for free. In particular, it
helps keep your code loosely coupled by coding to interfaces.
10.1.2 Creating loosely coupled code
Coupling is an important concept in object-oriented programming. It refers to how a
given class depends on other classes to perform its function. Loosely coupled code
doesn’t need to know a lot of details about a particular component to use it.
The initial example of UserController and EmailSender was an example of tight
coupling; you were creating the EmailSender object directly and needed to know
exactly how to wire it up. On top of that, the code was difficult to test. Any attempts to
test UserController would result in an email being sent. If you were testing the con-
troller with a suite of unit tests, that seems like a surefire way to get your email server
blacklisted for spam!
Taking EmailSender as a constructor parameter and removing the responsibility of
creating the object helps reduce the coupling in the system. If the EmailSender
Instead of creating the dependencies implicitly,
they’re injected via the constructor.
The action method
is easy to read and
understand again.
275
Introduction to dependency injection
implementation changes so that it has another dependency, you no longer have to
update UserController at the same time.
One issue that remains is that UserController is still tied to an implementation
rather than an interface. Coding to interfaces is a common design pattern that helps fur-
ther reduce the coupling of a system, as you’re not tied to a single implementation.
This is particularly useful in making classes testable, as you can create “stub” or “mock”
implementations of your dependencies for testing purposes, as shown in figure 10.3.
TIP You can choose from many different mocking frameworks. My favorite is
Moq, but NSubstitute and FakeItEasy are also popular options.
As an example, you might create an IEmailSender interface, which EmailSender
would implement:
public interface IEmailSender
{
public void SendEmail(string username);
}
UserController could then depend on this interface instead of the specific
EmailSender implementation, as shown here. That would allow you to use a different
implementation during unit tests, a DummyEmailSender for example.
public class UserController : Controller
{
private readonly IEmailSender _emailSender;
public UserController(IEmailSender emailSender)
{
_emailSender = emailSender;
}
Listing 10.5 Using interfaces with dependency injection
IEmailSender EmailSender
UserController
Instead of depending on a
specific implementation, the
UserController depends on the
interface IEmailSender.
MockEmailSender
HtmlEmailSender
At runtime, we can choose a
specific implementation to use.
We can even use “stub” or “mock”
implementations for unit tests.
Figure 10.3 By coding to interfaces instead of an explicit implementation, you can use
different IEmailSender implementations in different scenarios, for example a
MockEmailSender in unit tests.
You now depend on
IEmailSender instead of
the specific EmailSender
implementation.
276 CHAPTER 10 Service configuration with dependency injection
public IActionResult RegisterUser(string username)
{
_emailSender.SendEmail(username);
return View();
}
}
The key point here is that the consuming code, UserController, doesn’t care how
the dependency is implemented, only that it implements the IEmailSender interface
and exposes a SendEmail method. The application code is now independent of the
implementation.
Hopefully, the principles behind DI seem sound—by having loosely coupled code,
it’s easy to change or swap out implementations completely. But this still leaves you
with a question: how does the application know to use EmailSender in production
instead of DummyEmailSender? The process of telling your DI container, “when you
need IEmailSender, use EmailSender” is called registration.
DEFINITION You register services with a DI container so that it knows which
implementation to use for each requested service. This typically takes the
form of, “for interface X, use implementation Y.”
Exactly how you register your interfaces and types with a DI container can vary
depending on the specific DI container implementation, but the principles are gener-
ally all the same. ASP.NET Core includes a simple DI container out of the box, so let’s
look at how it’s used during a typical request.
10.1.3 Dependency injection in ASP.NET Core
ASP.NET Core was designed from the outset to be modular and composable, with an
almost plugin-style architecture, which is generally complemented by DI. Conse-
quently, ASP.NET Core includes a simple DI container that all the framework libraries
use to register themselves and their dependencies.
This container is used, for example, to register all of the MVC infrastructure—the
formatters, the view engine, the validation system, and so on. It’s only a basic con-
tainer, so it only exposes a few methods for registering services, but you can also
replace it with a third-party DI container. This can give you extra capabilities, such as
auto-registration or setter injection. The DI container is built into the ASP.NET Core
hosting model, as shown in figure 10.4.
The hosting model pulls dependencies from the DI container when they’re
needed. If the framework determines that UserController is required due to the
incoming URL/route, the controller activator responsible for creating a Controller
instance will ask the DI container for an IEmailSender implementation.
You don’t care what the
implementation is, as
long as it implements
IEmailSender.
277
Introduction to dependency injection
NOTE This approach, where a class calls the DI container directly to ask for a
class is called the service locator pattern. Generally speaking, you should try to
avoid this pattern in your code; include your dependencies as constructor
arguments directly and let the DI container provide them for you.1
The DI container needs to know what to create when asked for IEmailSender, so you
must have registered an implementation, such as EmailSender, with the container.
Once an implementation is registered, the DI container can inject it anywhere. That
means you can inject framework-related services into your own custom services, as
long as they are registered with the container. It also means you can register alterna-
tive versions of framework services and have the framework automatically use those in
place of the defaults.
The flexibility to choose exactly how and which components you combine in your
applications is one of the selling points of DI. In the next section, you’ll learn how
to configure DI in your own ASP.NET Core application, using the default, built-in
container.
1
You can read about the Service Locator antipattern in Dependency Injection in .NET by Steven van Deursen and
Mark Seemann (Manning, 2017), http://mng.bz/i995.
Dependency Injection Container
UserController
1. A request is received to
the URL /RegisterUser.
Request
2. The routing module determines
that the request should be directed
to the RegisterUser action on the
UserController.
Routing
EmailSender
3. The controller activator
calls the DI container to create
an instance of the UserController,
including all of its dependencies.
Controller activator
4. The RegisterUser method on
the UserController instance is
invoked, passing in the
binding model.
Binding Model
Figure 10.4 The ASP.NET Core hosting model uses the DI container to fulfill dependencies
when creating controllers.
278 CHAPTER 10 Service configuration with dependency injection
10.2 Using the dependency injection container
In previous versions of ASP.NET, using dependency injection was entirely optional. In
contrast, to build all but the most trivial ASP.NET Core apps, some degree of DI is
required. As I’ve mentioned, the underlying framework depends on it, so things like
using MVC require you to configure the required services.
In this section, you’ll see how to register these framework services with the built-in
container, as well as how to register your own services. Once services are registered,
you can use them as dependencies and inject them into any of the services in your
application.
10.2.1 Adding ASP.NET Core framework services to the container
As I described earlier, ASP.NET Core uses DI to configure its internal components as
well as your own custom services. In order to be able to use these components at run-
time, the DI container needs to know about all the classes it will need. You register
these in the ConfigureServices method of your Startup class.
NOTE The dependency injection container is set up in the Configure-
Services method of your Startup class in Startup.cs.
Now, if you’re thinking, “Wait, I have to configure the internal components myself?”
then don’t panic. Although true in one sense—you do need to explicitly register the
components with the container in your app—all the libraries you’ll use expose handy
extension methods that you need to ensure you call.
For example, the MVC middleware exposes the AddMvc() extension method that
you saw in chapters 2, 3, and 4. Invoke the extension method in ConfigureServices
of Startup.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
It’s as simple as that. Under the hood, this call is registering multiple components
with the DI container, using the same APIs you’ll see shortly for registering your own
services.
Most nontrivial libraries that you add to your application will have services that you
need to add to the DI container. By convention, each library that has necessary services
should expose an Add*() extension method that you can call in ConfigureServices.
There’s no way of knowing exactly which libraries will require you to add services
to the container, it’s generally a case of checking the documentation for any libraries
you use. If you forget to add them, then you may find the functionality doesn’t work,
or you might get a handy exception like the one shown in figure 10.5. Keep an eye out
for these and be sure to register any services that you need!
Listing 10.6 Registering the MVC services with the DI container
The AddMvc extension method adds all
necessary services to the IServiceCollection.
279
Using the dependency injection container
It’s also worth noting that some of the Add*() extension methods allow you to specify
additional options when you call them, often by way of a lambda expression. You can
think of these as configuring the installation of a service into your application. The
MVC middleware, for example, provides a wealth of options for fine-tuning its behavior
if you want to get your fingers dirty, as shown by the IntelliSense snippet in figure 10.6.
Once you’ve added the required framework services, you can get down to business
and register your own services, so you can use DI in your own code.
10.2.2 Registering your own services with the container
In the first section of this chapter, I described a system for sending emails when a new
user registers on your application. Initially, UserController was manually creating an
instance of EmailSender, but you subsequently refactored this, so you inject an
instance of IEmailSender into the constructor instead.
Figure 10.5 If you fail to call AddMvc in the ConfigureServices of Startup,
you’ll get a friendly exception message at runtime.
Figure 10.6 Configuring services when adding them to the service collection. The AddMvc()
function allows you to configure a wealth of the internals of the MVC middleware.
280 CHAPTER 10 Service configuration with dependency injection
The final step to make this refactoring work is to configure your services with the
DI container. This will let it know what to use to fulfill the IEmailSender dependency.
If you don’t register your services, then you’ll get an exception at runtime, like the
one in figure 10.7. Luckily, this exception is useful, letting you know which service
wasn’t registered (IEmailSender) and which service needed it (UserController).
That won’t always be the case!
In order to completely configure the application, you need to register EmailSender
and all of its dependencies with the DI container, as shown in figure 10.8.
Configuring DI consists of making a series of statements about the services in your
app. For example:
 When a service requires IEmailSender, use an instance of EmailSender
 When a service requires NetworkClient, use an instance of NetworkClient
 When a service requires MessageFactory, use an instance of MessageFactory
NOTE You’ll also need to register the EmailServerSettings object with the
DI container—you’ll do that slightly differently, in the next section.
Dependency Injection Container
UserController
1. A request is received to
the URL /RegisterUser.
Request
Routing
IEmailSender
2. The Controller activator
calls the DI container to create
an instance of the UserController,
including its dependencies.
Controller activator
4. The exception passes back
up the middleware pipeline and
out to the user. Here, the
DeveloperExceptionPageMiddleware
displays the error in the browser.
3. As the IEmailSender has
not been registered, the DI
container can’t create the
UserController, so throws
an InvalidOperationException.
500
!
Figure 10.7 If you don’t register all of your required dependencies in ConfigureServices, you’ll get an
exception at runtime, telling you which service wasn’t registered.
281
Using the dependency injection container
These statements are made by calling various Add* methods on IServiceCollection
in the ConfigureServices method. Each method provides three pieces of informa-
tion to the DI container:
 Service type—TService. What class or interface will be requested as a depen-
dency. Often an interface, such as IEmailSender, but sometimes a concrete
type, such as NetworkClient or MessageFactory.
 Implementation type—TService or TImplementation. The class the container
should create to fulfill the dependency. Must be a concrete type, such as
EmailSender. May be the same as the service type, as for NetworkClient and
MessageFactory.
 Lifetime—Transient, Singleton, or Scoped. How long an instance of the service
should be used for. I’ll discuss lifetimes in section 10.3.
The following listing shows how you can configure EmailSender and its dependencies
in your application using three different methods: AddScoped<TService>, Add-
Singleton<TService>, and AddScoped<TService, TImplementation>. This tells the
DI container how to create each of the TService instances when they’re required.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddScoped<IEmailSender, EmailSender>();
Listing 10.7 Registering services with the DI container
Dependency Injection Container
EmailSender
IEmailSender NetworkClient MessageFactory
NetworkClient MessageFactory
You register services and
implementations in pairs.
For each pair, you indicate the type
of service that will be requested, and
what type of implementation to create.
Alternatively, the service and
implementation can be the same
type, such as for NetworkClient
and MessageFactory.
These can be the different types,
where the implementation implements
the service, such as the EmailSender
that implements IEmailSender.
Figure 10.8 Configuring the DI container in your application involves telling it what
type to use when a given service is requested; for example, “Use EmailSender when
IEmailSender is required.”
You’re using MVC, so
must call AddMvc.
Whenever you require
an IEmailSender, use
EmailSender.
282 CHAPTER 10 Service configuration with dependency injection
services.AddScoped<NetworkClient>();
services.AddSingleton<MessageFactory>();
}
And that’s all there is to dependency injection! It may seem a little bit like magic,1
but
you’re just giving the container instructions on how to make all of the constituent
parts. You give it a recipe for how to cook the chili, shred the lettuce, and grate the
cheese, so that when you ask for a burrito, it can put all the parts together and hand
you your meal!
The service type and implementation type are the same for NetworkClient and
MessageFactory, so there’s no need to specify the same type twice in the AddScoped
method, hence the slightly simpler signature. These generic methods aren’t the only
way to register services with the container, you can also provide objects directly, or by
using lambdas, as you’ll see in the next section.
10.2.3 Registering services using objects and lambdas
As I mentioned earlier, I didn’t quite register all the services required by UserController.
In all my previous examples, NetworkClient depends on EmailServerSettings,
which you’ll also need to register with the DI container for your project to run without
exceptions.
I avoided registering this object in the preceding example because you have to use
a slightly different approach. The preceding Add* methods use generics to specify the
Type of the class to register, but they don’t give any indication of how to construct an
instance of that type. Instead, the container makes a number of assumptions that you
have to adhere to:
 The class must be a concrete type.
 The class must have only a single “valid” constructor that the container can use.
 For a constructor to be “valid,” all constructor arguments must be registered
with the container, or must be an argument with a default value.
NOTE These limitations apply to the simple built-in DI container. If you
choose to use a third-party container in your app, then they may have a differ-
ent set of limitations.
The EmailServerSettings class doesn’t meet these requirements, as it requires you
to provide a host and port in the constructor, which are strings without default
values:
1
Under the hood, the built-in ASP.NET Core DI container uses reflection to create dependencies, but differ-
ent DI containers may use other approaches.
Whenever you require
a NetworkClient, use
NetworkClient.
Whenever you require a
MessageFactory, use MessageFactory.
283
Using the dependency injection container
public class EmailServerSettings
{
public EmailServerSettings(string host, int port)
{
Host = host;
Port = port;
}
public string Host { get; }
public int Port { get; }
}
You can’t register these primitive types in the container; it would be weird to say, “For
every string constructor argument, in any type, use the "smtp.server.com" value!”
Instead, you can create an instance of the EmailServerSettings object yourself
and provide that to the container, as shown next. The container will use the con-
structed object whenever an instance of the EmailServerSettings object is required.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddScoped<IEmailSender, EmailSender>();
services.AddSingleton<NetworkClient>();
services.AddScoped<MessageFactory>();
services.AddSingleton(
new EmailServerSettings
(
host: "smtp.server.com",
port: 25
));
}
This works fine if you only want to have a single instance of EmailServerSettings in
your application—the same object will be shared everywhere. But what if you want to cre-
ate a new object each time one is requested?
NOTE When the same object is used whenever it’s requested, it’s known as a
singleton. I’ll discuss singletons along with other lifetimes in detail in the next
section. The lifetime is how long the DI container should use a given object to
fulfill a service’s dependencies.
Instead of providing a single instance that the container will always use, you can also
provide a function that the container will invoke when it needs an instance of the type,
shown in figure 10.9.
Listing 10.8 Providing an object instance when registering services
This instance of
EmailServerSettings
will be used whenever
an instance is required.
284 CHAPTER 10 Service configuration with dependency injection
The easiest way to do this is to use a lambda function (an anonymous delegate), in
which the container will create a new EmailServerSettings object when it’s needed.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddScoped<IEmailSender, EmailSender>();
services.AddSingleton<NetworkClient>();
services.AddScoped<MessageFactory>();
services.AddScoped(
provider=>
new EmailServerSettings
(
host: "smtp.server.com",
port: 25
));
}
In this example, I’ve changed the lifetime of the created EmailServerSettings object
to be scoped instead of singleton and provided a factory lambda function that returns a
new EmailServerSettings object. Every time the container requires a new Email-
ServerSettings, it executes the function and uses the new object it returns.
NOTE I’ll discuss lifetimes shortly, but it’s important to notice that there are
two concepts of a singleton here. If you create an object and pass it to the con-
tainer, it’s always registered as a singleton. You can also register any arbitrary
class as a singleton and the container will only use one instance throughout
your application.
When you use a lambda to register your services, you’re provided with an IService-
Provider instance at runtime, called provider in listing 10.9. This is the public API of
the DI container itself, which you can access using GetService(). If you need to
Listing 10.9 Using a lambda factory function to register a dependency
Dependency Injection Container
EmailServerSettings
EmailServerSettings
An instance of the EmailServerSettings
is required by the DI container to create
an instance of NetworkClient.
Instead of creating the EmailServerSettings
instance using the constructor, the DI container
invokes the function, and uses the instance it returns.
provider => new EmailServerSettings (
host: "smtp.server.com",
port: 25
);
Figure 10.9 You can register a function with the DI container that will be invoked whenever
a new instance of a service is required.
Because you’re providing a
function to create the object,
you aren’t restricted to
singleton.
The lambda is
provided an instance
of IServiceProvider.
The constructor is now
called every time an
object is requested
instead of only once.
285
Using the dependency injection container
obtain dependencies to create an instance of your service, you can reach into the con-
tainer at runtime in this way, but you should avoid doing so if possible.
TIP Avoid calling GetService() in your factory functions if possible. Instead,
favor constructor injection—it’s more performant, as well as being simpler to
reason about.
At this point, all your dependencies are registered. But ConfigureServices is starting
to look a little messy, isn’t it? It’s entirely down to personal preference, but I like to
group my services into logical collections and create extension methods for them, as
in the following listing, to create an equivalent of the AddMvc() extension method. As
you add more and more features to your app, I think you’ll appreciate it too.
public static class EmailSenderServiceCollectionExtensions
{
public static IServiceCollection AddEmailSender(
this IServiceCollection services)
{
services.AddScoped<IEmailSender, EmailSender>();
services.AddSingleton<NetworkClient>();
services.AddScoped<MessageFactory>();
services.AddSingleton(
new EmailServerSettings
(
host: "smtp.server.com",
port: 25
));
return services;
}
}
Listing 10.10 Creating an extension method to tidy up adding multiple services
Open generics and dependency injection
As well as dependencies that have primitive dependencies in their constructor, you
also can’t use the generic registration methods to register open generics.
Open generics are types that contain a generic type parameter, such as Repository
<T>. The normal use case for this sort of type is to define a base behavior that you
can use with multiple generic types. In the Repository<T> example, you might inject
IRepository<Customer> into your services, which should inject an instance of
DbRepository<Customer>, for example.
To register these types, you must use a different overload of the Add* methods. For
example,
services.AddScoped(typeof(IRespository<>), typeof(DbRepository<>));
This will ensure that whenever a service constructor requires IRespository<T>, the
container will inject an instance of DbRepository<T>.
Extend the IServiceCollection
that’s provided in the
ConfigureServices method.
Cut and paste your
registration code from
ConfigureServices.
By convention, return the
IServiceCollection to allow
method chaining.
286 CHAPTER 10 Service configuration with dependency injection
With the preceding extension method created, the ConfigureServices method is
much easier to grok!
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddEmailSender();
}
So far, you’ve seen how to register the simple DI cases where you have a single imple-
mentation. In some scenarios, you might find you have multiple implementations of
an interface. In the next section, you’ll see how to register these with the container to
match your requirements.
10.2.4 Registering a service in the container multiple times
One of the advantages of coding to interfaces is that you can create multiple imple-
mentations of a service. For example, imagine you want to create a more generalized
version of IEmailSender so that you can send messages via SMS or Facebook, as well
as by email. You create an interface for it
public interface IMessageSender
{
public void SendMessage(string message);
}
as well as several implementations: EmailSender, SmsSender, and FacebookSender. But
how do you register these implementations in the container? And how can you inject
these implementations into your UserController? The answer varies slightly depend-
ing on if you want to use all of the implementations or only one of them.
INJECTING MULTIPLE IMPLEMENTATIONS OF AN INTERFACE
Imagine you want to send a message using each of the IMessageSender implementa-
tions whenever a new user registers, so they get an email, an SMS, and a Facebook
message, as shown in figure 10.10.
Welcome!
Welcome!
1. A new user registers
with your app and enters
their details, POSTing to the
RegisterUser action method.
Welcome!
2. Your app sends them a
welcome message by email,
SMS, and Facebook using the
IMessageSender implementations.
RegisterUser
UserController
Figure 10.10 When a user registers with your application, they call the RegisterUser
method. This sends them an email, an SMS, and a Facebook message using the
IMessageSender classes.
287
Using the dependency injection container
The easiest way to achieve this is to register all of the implementations in your DI con-
tainer and have it inject one of each type into UserController. UserController can
then use a simple foreach loop to call SendMessage() on each implementation, as in
figure 10.11.
You register multiple implementations of the same service with a DI container in
exactly the same way as for single implementations, using the Add* extension meth-
ods. For example:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddScoped<IMessageSender, EmailSender>();
services.AddScoped<IMessageSender, SmsSender>();
services.AddScoped<IMessageSender, FacebookSender>();
}
You can then inject IEnumerable<IMessageSender> into UserController, as shown in
the following listing. The container will inject an array of IMessageSender containing
one of each of the implementations you have registered, in the same order as you reg-
istered them. You can then use a standard foreach loop in the RegisterUser method
to call SendMessage on each implementation.
3. The RegisterUser method on
the UserController loops over the
IMessageSender instances and
calls SendMessage on each.
1. During Startup, multiple
implementations of IMessageSender
are registered with the DI container
using the normal Add* methods.
Dependency Injection Container
EmailSender SmsSender FacebookSender
IMessageSender IMessageSender
IMessageSender
UserController
IEnumerable<IMessageSender>
EmailSender SmsSender FacebookSender
foreach (var messageSender in _messageSenders)
{
messageSender.SendMessage(username);
}
2. The DI container creates one
of each IMessageSender implementation,
and injects them into the UserController
as an IEnumerable<IMessageSender>.
_messageSenders
Figure 10.11 You can register multiple implementations of a service with the DI container, such as
IEmailSender in this example. You can retrieve an instance of each of these implementations by
requiring IEnumerable<IMessageSender> in the UserController constructor.
288 CHAPTER 10 Service configuration with dependency injection
public class UserController : Controller
{
private readonly IEnumerable<IMessageSender> _messageSenders;
public UserController(
IEnumerable<IMessageSender> messageSenders)
{
_messageSenders = messageSenders;
}
public IActionResult RegisterUser(string username)
{
foreach (var messageSender in _messageSenders)
{
messageSender.SendMessage(username);
}
return View();
}
}
WARNING You must use IEnumerable<T> as the constructor argument to
inject all of the registered types of a service, T. Even though this will be
injected as a T[] array, you can’t use T[] or ICollection<T> as your construc-
tor argument. Doing so will cause an InvalidOperationException, as you
saw in figure 10.7.
It’s simple enough to inject all of the registered implementations of a service, but
what if you only need one? How does the container know which one to use?
INJECTING A SINGLE IMPLEMENTATION WHEN MULTIPLE SERVICES ARE REGISTERED
Imagine you’ve already registered all of the IMessageSender implementations, what
happens if you have a service that requires only one of them? For example
public class SingleMessageSender
{
private readonly IMessageSender _messageSender;
public SingleMessageSender(IMessageSender messageSender)
{
_messageSender = messageSender;
}
}
The container needs to pick a single IMessageSender to inject into this service, out of
the three implementations available. It does this by using the last registered imple-
mentation—the FacebookSender from the previous example.
NOTE The DI container will use the last registered implementation of a ser-
vice when resolving a single instance of the service.
Listing 10.11 Injecting multiple implementations of a service into a consumer
Requesting an
IEnumerable will
inject an array of
IMessageSender.
Each IMessageSender
in the IEnumerable is
a different
implementation.
289
Using the dependency injection container
This can be particularly useful for replacing built-in DI registrations with your own
services. If you have a custom implementation of a service that you know is registered
within a library’s Add* extension method, you can override that registration by regis-
tering your own implementation afterwards. The DI container will use your imple-
mentation whenever a single instance of the service is requested.
The main disadvantage with this approach is that you end up with multiple imple-
mentations registered—you can inject an IEnumerable<T> as before. Sometimes you
want to conditionally register a service, so you only ever have a single registered imple-
mentation.
CONDITIONALLY REGISTERING SERVICES USING TRYADD
Sometimes, you’ll only want to add an implementation of a service if one hasn’t
already been added. This is particularly useful for library authors; they can create a
default implementation of an interface and only register it if the user hasn’t already
registered their own implementation.
You can find a number of extension methods for conditional registration in the
Microsoft.Extensions.DependencyInjection.Extensions namespace, such as Try-
AddScoped. This checks to make sure a service hasn’t been registered with the con-
tainer before calling AddScoped on the implementation. The following listing shows
you conditionally adding SmsSender, only if there are no existing IMessageSender
implementations. As you just registered EmailSender, the container will ignore the
SmsSender registration, so it won’t be available in your app.
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IMessageSender, EmailSender>();
services.TryAddScoped<IMessageSender, SmsSender>();
}
Code like this often doesn’t make a lot of sense at the application level, but it can be
useful if you’re building libraries for use in multiple apps. The ASP.NET Core frame-
work, for example, uses TryAdd* in many places, which lets you easily register alterna-
tive implementations of internal components in your own application if you want.
That pretty much covers registering dependencies. Before we look in more depth
at the “lifetime” aspect of dependencies, we’ll take a quick detour and look at two ways
other than a constructor to inject dependencies in your app.
10.2.5 Injecting services into action methods and view templates
I mentioned in section 10.1 that the ASP.NET Core DI container only supports con-
structor injection, but there are two additional locations where you can use depen-
dency injection:
Listing 10.12 Conditionally adding a service using TryAddScoped
EmailSender is
registered with
the container.
There’s already an IMessageSender
implementation, so SmsSender isn’t
registered.
290 CHAPTER 10 Service configuration with dependency injection
1 Action methods
2 View templates
In this section, I’ll briefly discuss these two situations, how they work, and when you
might want to use them.
INJECTING SERVICES DIRECTLY INTO ACTION METHODS USING [FROMSERVICES]
MVC controllers typically contain multiple action methods that logically belong
together. You might group all the action methods related to managing user accounts
into the same controller, for example. This allows you to apply filters and authoriza-
tion to all of the action methods collectively, as you’ll see in chapter 13.
As you add additional action methods to a controller, you may find the controller
needs additional services to implement new action methods. With constructor injec-
tion, all of these dependencies are provided via the constructor. That means the DI
container must create all of the services for every action method in a controller, even
if none of them are required by the action method being called.
Consider listing 10.13 for example. This shows UserController with two stub
methods: RegisterUser and SignInUser. Each action method requires a different
dependency, so both dependencies will be created and injected, whichever action
method is called by the request. If ISignInManager or IMessageSender have lots of
dependencies themselves, the DI container may have to create lots of objects for a ser-
vice that is often not used.
public class UserController : Controller
{
private readonly IMessageSender _messageSender;
private readonly ISignInService _signInService;
public UserController(
IMessageSender messageSender, ISignInService signInService)
{
messageSenders = messageSender;
_signInService = signInService;
}
public IActionResult RegisterUser(string username)
{
_messageSender.SendMessage(username);
return View();
}
public IActionResult SignInUser(string username, string password)
{
_signInService.SignUserIn(username, password);
return View();
}
}
Listing 10.13 Injecting services into a controller via the constructor
Both IMessageSender
and ISignInService are
injected into the
constructor every time.
Only the RegisterUser
method uses
IMessageSender.
Only the SignInUser
method uses
ISignInService.
291
Using the dependency injection container
If you know a service is particularly expensive to create, you can choose to inject it as a
dependency directly into the action method, instead of into the controller’s construc-
tor. This ensures the DI container only creates the dependency when the specific action
method is invoked, as opposed to when any action method on the controller is
invoked.
NOTE Generally speaking, your controllers should be sufficiently cohesive
that this approach isn’t necessary. If you find you have a controller that’s
dependent on many services, each of which is used by a single action method,
you might want to consider splitting up your controller.
You can directly inject a dependency into an action method by passing it as a parame-
ter to the method and using the [FromServices] attribute. During model binding,
MvcMiddleware will resolve the parameter from the DI container, instead of from the
request values, like you saw in chapter 6. This listing shows how you could rewrite list-
ing 10.13 to use [FromServices] instead of constructor injection.
public class UserController : Controller
{
public IActionResult RegisterUser(
[FromServices] IMessageSender messageSender,
string username)
{
messageSender.SendMessage(username);
return View();
}
public IActionResult SignInUser(
[FromServices] ISignInService signInService
string username, string password)
{
signInService.SignUserIn(username, password);
return View();
}
}
You might be tempted to use the [FromServices] attribute in all of your action meth-
ods, but I’d encourage you to use standard constructor injection most of the time.
Having the constructor as a single location that declares all the dependencies of a
class can be useful, so I only use [FromServices] where creating an instance of a
dependency is expensive and is only used in a single action method.
INJECTING SERVICES INTO VIEW TEMPLATES
Injecting dependencies into the constructor is recommended, but what if you don’t
have a constructor? In particular, how do you go about injecting services into a Razor
view template when you don’t have control over how the template is constructed?
Listing 10.14 Injecting services into a controller using the [FromServices] attribute
The [FromServices]
attribute ensures
IMessageSender is resolved
from the DI container.
IMessageSender is only
available in RegisterUser.
ISignInService is resolved
from the DI container and
injected as a parameter.
Only the SignInUser
method can use
ISignInService.
292 CHAPTER 10 Service configuration with dependency injection
Imagine you have a simple service, HtmlGenerator, to help you generate HTML in
your view templates. The question is, how do you pass this service to your view tem-
plates, assuming you’ve already registered it with the DI container?
One option is to inject HtmlGenerator into your controller using constructor injec-
tion or the [FromServices] attribute. Then you can pass the service via ViewData or
the view model, as you saw in chapter 7. In some cases, that approach may make sense,
but other times, you might not want to have references to the HtmlGenerator service
in your controller or view model at all. In those cases, you can directly inject Html-
Generator into your view templates.
NOTE Some people take offense at injecting services into views in this way.
You definitely shouldn’t be injecting services related to business logic into
your views, but I think it makes sense for services that are related to HTML
generation.
You can inject a service into a Razor template with the @inject directive by providing
the type to inject and a name for the injected service in the template.
@inject HtmlGenerator helper
<h1>The page title</h1>
<footer>
@helper.Copyright()
</footer>
Injecting services directly into views can be a useful way of exposing UI-related ser-
vices to your view templates without having to closely couple your views to your con-
trollers and actions. You shouldn’t find you need to rely on it too much, but it’s a
useful tool to have.
That pretty much covers registering and using dependencies, but there’s one
important aspect I’ve only vaguely touched on: lifetimes, or, when does the container
create a new instance of a service? Understanding lifetimes is crucial to working with
DI containers, so it’s important to pay close attention to them when registering your
services with the container.
10.3 Understanding lifetimes: when are services created?
Whenever the DI container is asked for a particular registered service, for example an
instance of IMessageSender, it can do one of two things:
 Create and return a new instance of the service
 Return an existing instance of the service
The lifetime of a service controls the behavior of the DI container with respect to these
two options. You define the lifetime of a service during DI service registration. This
Listing 10.15 Injecting a service into a Razor view template with @inject
Injects an instance of HtmlGenerator
into the view, named helper
Uses the injected service by
calling the helper instance
293
Understanding lifetimes: when are services created?
dictates when a DI container will reuse an existing instance of the service to fulfill ser-
vice dependencies, and when it will create a new one.
DEFINITION The lifetime of a service is how long an instance of a service
should live in a container before it creates a new instance.
It’s important to get your head around the implications for the different lifetimes
used in ASP.NET Core, so this section looks at each available lifetime option and when
you should use it. In particular, you’ll see how the lifetime affects how often the DI
container creates new objects. In section 10.3.4, I’ll show you a pattern of lifetimes to
look out for, where a short-lifetime dependency is “captured” by a long-lifetime
dependency. This can cause some hard-to-debug issues, so it’s important to bear in
mind when configuring your app!
In ASP.NET Core, you can specify three different lifetimes when registering a ser-
vice with the built-in container:
 Transient—Every single time a service is requested, a new instance is created.
This means you can potentially have different instances of the same class within
the same dependency graph.
 Scoped—Within a scope, all requests for a service will give you the same object.
For different scopes you’ll get different objects. In ASP.NET Core, each web
request gets its own scope.
 Singleton—You’ll always get the same instance of the service, no matter which
scope.
NOTE These concepts align well with most other DI containers, but the ter-
minology often differs. If you’re familiar with a third-party DI container, be
sure you understand how the lifetime concepts align with the built-in
ASP.NET Core DI container.
To illustrate the behavior of the different lifetimes available to you, in this section, I’ll
use a simple representative example. Imagine you have DataContext, which has a con-
nection to a database, as shown in listing 10.13. It has a single property, RowCount,
which displays the number of rows in the Users table of a database. For the purposes
of this example, fix the number of rows in the constructor, so it will display the same
value every time you call RowCount, but different instances of the service will display a
different number.
public class DatabaseContext
{
static readonly Random _rand = new Random();
public DatabaseContext()
{
RowCount = _rand.Next(1, 1_000_000_000);
}
Listing 10.16 DataContext generating a random RowCount in its constructor
Generates a random
number between 1 and
1,000,000,000
294 CHAPTER 10 Service configuration with dependency injection
public int RowCount { get; }
}
You also have a Repository class that has a dependency on the DataContext, as
shown in the next listing. This also exposes a RowCount property, but this property del-
egates the call to its instance of DataContext. Whatever value DataContext was cre-
ated with, the Repository will display the same value.
public class Repository
{
private readonly DatabaseContext _dataContext;
public Repository(DatabaseContext dataContext)
{
_dataContext = dataContext;
}
public int RowCount => _dataContext.RowCount;
}
Finally, you have RowCountController, which takes a dependency on both Repository
and on DataContext directly. When the controller activator creates an instance of
RowCountController, the DI container will inject an instance of DataContext and an
instance of Repository. To create Repository, it will also inject an additional instance
of DataContext. Over the course of two consecutive requests, a total of four instances
of DataContext will be required, as shown in figure 10.12.
RowCountController records the value of RowCount from Repository and Data-
Context in a view model. This is then rendered using a Razor template (not shown).
public class RowCountController : Controller
{
private readonly Repository _repository;
private readonly DataContext _dataContext;
public RowCountController(
Repository repository,
DataContext dataContext)
{
_repository = repository;
_dataContext = dataContext;
}
public IActionResult Index()
{
var viewModel = new RowCountViewModel
{
DataContextCount = _dataContext.RowCount,
RepositoryCount = _repository.RowCount,
};
Listing 10.17 Repository service that depends on an instance of DataContext
Listing 10.18 RowCountController depends on DataContext and Repository
Read-only properly set in the constructor,
so always returns the same value.
An instance of
DataContext is
provided using DI.
RowCount returns the
same value as the current
instance of DataContext.
DataContext and Repository
are passed in using DI.
When invoked, the
action retrieves
RowCount from both
dependencies.
295
Understanding lifetimes: when are services created?
return View(viewModel);
}
}
The purpose of this example is to explore the relationship between the four Data-
Context instances, depending on the lifetimes you use to register the services with the
container. I’m generating a random number in DataContext as a way of uniquely
identifying a DataContext instance, but you can think of this as being a point-in-time
snapshot of the number of users logged in to your site, for example, or the amount of
stock in a warehouse.
I’ll start with the shortest-lived lifetime, move on to the common scoped lifetime,
and then take a look at singletons. Finally, I’ll show an important trap you should be
on the lookout for when registering services in your own apps.
10.3.1 Transient: everyone is unique
In the ASP.NET Core DI container, transient services are always created new, when-
ever they’re needed to fulfill a dependency. You can register your services using the
AddTransient extension methods:
services.AddTransient<DataContext>();
services.AddTransient<Repository>();
The view model is passed to the Razor
view and rendered to the screen.
Dependency Injection Container
Repository
RowCountController
DataContext
DataContext
For each request, two instances of
DataContext are required to build the
RowCountController instance.
First request
Dependency Injection Container
Repository
RowCountController
DataContext
DataContext
A total of four DataContext instances
is required for two requests.
Second request
Figure 10.12 The DI Container uses two instances of DataContext for each request.
Depending on the lifetime with which the DataContext type is registered, the container
might create one, two, or four different instances of DataContext.
296 CHAPTER 10 Service configuration with dependency injection
When registered in this way, every time a dependency is required, the container will
create a new one. This applies both between requests but also within requests; the
DataContext injected into the Repository will be a different instance to the one
injected into the RowCountController.
NOTE Transient dependencies can result in different instances of the same
type within a single dependency graph.
Figure 10.13 shows the results you get from two consecutive requests when you use the
transient lifetime for both services. Note that, by default, Controller instances are
also transient and are created new with every request.
Transient lifetimes can result in a lot of objects being created, so they make the most
sense for lightweight services with little or no state. It’s equivalent to calling new every
time you need a new object, so bear that in mind when using it. You probably won’t
use the transient lifetime too often; the majority of your services will probably be
scoped instead.
10.3.2 Scoped: let’s stick together, guys
The scoped lifetime states that a single instance of an object will be used within a
given scope, but a different instance will be used between different scopes. In
ASP.NET Core, a scope maps to a request, so within a single request the container will
use the same object to fulfill all dependencies.
For the row count example, that means that, within a single request (a single
scope), the same DataContext will be used throughout the dependency graph. The
DataContext injected into the Repository will be the same instance as that injected
into RowCountController.
Figure 10.13 When registered using the transient lifetime, all four DataContext objects are different. That
can be seen by the four different numbers displayed over two requests.
297
Understanding lifetimes: when are services created?
In the next request, you’ll be in a different scope, so the container will create a new
instance of DataContext, as shown in figure 10.14. A different instance means a differ-
ent RowCount for each request, as you can see.
You can register dependencies as scoped using the AddScoped extension methods.
In this example, I registered DataContext as scoped and left Repository as transient,
but you’d get the same results if they were both scoped:
services.AddScoped<DataContext>();
Due to the nature of web requests, you’ll often find services registered as scoped
dependencies in ASP.NET Core. Database contexts and authentication services are
common examples of services that should be scoped to a request—anything that you
want to share across your services within a single request, but that needs to change
between requests.
Generally speaking, you’ll find a lot of services registered using the scoped life-
time—especially anything that uses a database or is dependent on a specific request.
But some services don’t need to change between requests, such as a service that calcu-
lates the area of a circle, or that returns the current time in different time zones. For
these, a singleton lifetime might be more appropriate.
10.3.3 Singleton: there can be only one
The singleton is a pattern that came before dependency injection; the DI container
provides a robust and easy-to-use implementation of it. The singleton is conceptually
simple: an instance of the service is created when it’s first needed (or during registra-
tion, as in section 10.2.3) and that’s it: you’ll always get the same instance injected into
your services.
The singleton pattern is particularly useful for objects that are either expensive to
create or that don’t hold state. The latter point is important—any service registered as
a singleton should be thread safe.
Figure 10.14 Scoped dependencies use the same instance of DataContext within a single request, but a
new instance for a separate request. Consequently, the RowCounts are identical within a request.
298 CHAPTER 10 Service configuration with dependency injection
WARNING Singleton services must be thread safe in a web application, as
they’ll typically be used by multiple threads during concurrent requests.
Let’s consider what using singletons means for the row count example. I can update
the registration of DataContext to be a singleton in ConfigureServices, using
services.AddSingleton<DataContext>();
I then call the RowCountController.Index action method twice and observe the
results shown in figure 10.15. You can see that every instance has returned the same
value, indicating that all four instances of DataContext are the same single instance.
Singletons are convenient for objects that need to be shared or that are immutable
and expensive to create. A caching service should be a singleton, as all requests need
to share it. It must be thread safe though. Similarly, you might register a settings
object loaded from a remote server as a singleton, if you loaded the settings once at
startup and reused them through the lifetime of your app.
On the face of it, choosing a lifetime for a service might not seem too tricky, but
there’s an important “gotcha” that can come back to bite you in subtle ways, as you’ll
see shortly.
10.3.4 Keeping an eye out for captured dependencies
Imagine you’re configuring the lifetime for the DataContext and Repository exam-
ples. You think about the suggestions I’ve provided and decide on the following life-
times:
 DataContext—Scoped, as it should be shared for a single request
 Repository—Singleton, as it has no state of its own and is thread safe, so why
not?
Figure 10.15 Any service registered as a singleton will always return the same instance. Consequently, all the
calls to RowCount return the same value, both within a request and between requests.
299
Understanding lifetimes: when are services created?
WARNING This lifetime configuration is to explore a bug—don’t use it in
your code or you’ll experience a similar problem!
Unfortunately, you’ve created a captured dependency because you’re injecting a
scoped object, DataContext, into a singleton, Repository. As it’s a singleton, the same
Repository instance is used throughout the lifetime of the app, so the DataContext
that was injected into it will also hang around, even though a new one should be used
with every request. Figure 10.16 shows this scenario, where a new instance of Data-
Context is created for each scope, but the instance inside Repository hangs around
for the lifetime of the app.
Captured dependencies can cause subtle bugs that are hard to root out, so you should
always keep an eye out for them. These captured dependencies are relatively easy to
introduce, so always think carefully when registering a singleton service.
WARNING A service should only use dependencies with a lifetime longer than
or equal to the lifetime of the service. A service registered as a singleton can
only use singleton dependencies. A service registered as scoped can use
scoped or singleton dependencies. A transient service can use dependencies
with any lifetime.
Dependency Injection Container
Repository
RowCountController
DataContext
DataContext
First request
Dependency Injection Container
Repository
RowCountController
DataContext
DataContext
Second request
As the repository has been registered
as a singleton, the DataContext it uses
will act also as a singleton, even though
it is registered as scoped.
The DataContext dependency has been
captured by the repository, breaking the
scoped lifetime.
Figure 10.16 DataContext is registered as a scoped dependency, but Repository
is a singleton. Even though you expect a new DataContext for every request,
Repository captures the injected DataContext and causes it to be reused for the
lifetime of the app.
300 CHAPTER 10 Service configuration with dependency injection
At this point, I should mention that there’s one glimmer of hope in this cautionary
tale. ASP.NET Core 2.0 includes a new option that lets you check for these kinds of
captured dependencies and will throw an exception on application startup if it detects
them, as shown in figure 10.17.
This scope validation check has a performance impact, so by default it’s only enabled
when your app is running in a development environment, but it should help you
catch most issues of this kind. You can enable or disable this check regardless of envi-
ronment by setting the ValidateScopes option when creating your WebHostBuilder
in Program.cs, as shown in the following listing.
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseDefaultServiceProvider(options =>
{
options.ValidateScopes = true;
})
.Build();
}
Listing 10.19 Setting the ValidateScopes property to always validate scopes
In ASP
.NET Core 2.0, you will get
an Exception when the DI container
detects a captured dependency.
The exception message
describes which service
was captured...
...and which
service captured
the dependency
.
Figure 10.17 In ASP.NET Core 2.0, the DI container will throw an exception when it creates a
service with a captured dependency. By default, this check is only enabled for development
environments.
The default builder
sets ValidateScopes
to validate only in
development
environments.
You can override the
validation check with
the UseDefaultService-
Provider extension.
Setting to true will
validate scopes in all
environments. This
has performance
implications.
301
Summary
With that, you’ve reached the end of this introduction to DI in ASP.NET Core. You
now know how to add framework services to your app using Add* extension methods
like AddMvc(), as well as how to register your own service with the DI container. Hope-
fully, that will help you keep your code loosely coupled and easy to manage.
In the next chapter, we’ll look at the ASP.NET Core configuration model. You’ll
see how to load settings from a file at runtime, how to store sensitive settings safely,
and how to make your application behave differently depending on which machine
it’s running on. We’ll even use a bit of DI; it gets everywhere in ASP.NET Core!
Summary
 Dependency injection is baked into the ASP.NET Core framework. You need to
ensure your application adds all the framework's dependencies in Startup.
 The dependency graph is the set of objects that must be created in order to cre-
ate a specific requested “root” object.
 You should aim to use explicit dependencies over implicit dependencies in
most cases. ASP.NET Core uses constructor arguments to declare explicit
dependencies.
 The DI or IoC container is responsible for creating instances of services. It
knows how to construct an instance of a service by creating all of its dependen-
cies and passing these to the constructor.
 When discussing DI, the term service is used to describe any class or interface
registered with the container.
 You register services with a DI container so it knows which implementation to
use for each requested service. This typically takes the form of, “for interface X,
use implementation Y.”
 The default built-in container only supports constructor injection. If you
require other forms of DI, such as property injection, you can use a third-party
container.
 You can register services with the container by calling Add* extension methods
on IServiceCollection in ConfigureServices in Startup.
 If you forget to register a service that’s used by the framework or in your own
code, you’ll get an InvalidOperationException at runtime.
 When registering your services, you describe three things: the service type, the
implementation type, and the lifetime. The service type defines which class or
interface will be requested as a dependency. The implementation type is the
class the container should create to fulfill the dependency. The lifetime is how
long an instance of the service should be used for.
 You can register a service using generic methods if the class is concrete and all
its constructor arguments must be registered with the container or have default
values.
302 CHAPTER 10 Service configuration with dependency injection
 You can provide an instance of a service during registration, which will register
that instance as a singleton.
 You can provide a lambda factory function that describes how to create an
instance of a service with any lifetime you choose.
 Avoid calling GetService() in your factory functions if possible. Instead, favor
constructor injection—it’s more performant, as well as being simpler to reason
about.
 You can register multiple implementations for a service. You can then inject
IEnumerable<T> to get access to all of the implementations at runtime.
 If you inject a single instance of a multiple-registered service, the container
injects the last implementation registered.
 You can use the TryAdd* extension methods to ensure that an implementation
is only registered if no other implementation of the service has been registered.
 You define the lifetime of a service during DI service registration. This dictates
when a DI container will reuse an existing instance of the service to fulfill ser-
vice dependencies and when it will create a new one.
 The transient lifetime means that every single time a service is requested, a new
instance is created.
 The scoped lifetime means that within a scope, all requests for a service will give
you the same object. For different scopes, you’ll get different objects. In
ASP.NET Core, each web request gets its own scope.
 You’ll always get the same instance of a singleton service, no matter which
scope.
 A service should only use dependencies with a lifetime longer than or equal to
the lifetime of the service.
303
Configuring an ASP.NET
Core application
In part 1 of this book, you learned the basics of getting an ASP.NET Core app up
and running and how to use the MVC design pattern to create a traditional web
app or a Web API. Once you start building real applications, you will quickly find
that you want to tweak various settings at runtime, without having to recompile
your application. This chapter looks at how you can achieve this in ASP.NET Core
using configuration.
I know. Configuration sounds boring, right? But I have to confess, the configu-
ration model is one of my favorite parts of ASP.NET Core. It’s so easy to use, and so
This chapter covers
 Loading settings from multiple configuration
providers
 Storing sensitive settings safely
 Using strongly typed settings objects
 Using different settings in different hosting
environments
304 CHAPTER 11 Configuring an ASP.NET Core application
much more elegant than the previous version of ASP.NET. In section 11.2, you’ll learn
how to load values from a plethora of sources—JSON files, environment variables, and
command-line arguments—and combine them into a unified configuration object.
On top of that, ASP.NET Core brings the ability to easily bind this configuration to
strongly typed options objects. These are simple, POCO classes that are populated from
the configuration object, which you can inject into your services, as you’ll see in section
11.3. This lets you nicely encapsulate settings for different features in your app.
In the final section of this chapter, you’ll learn about the ASP.NET Core hosting
environments. You often want your app to run differently in different situations such as
when running on your developer machine compared to when you deploy it to a pro-
duction server. These different situations are known as environments. By letting the app
know in which environment it’s running, it can load a different configuration and
vary its behavior accordingly.
Before we get to that, let’s go back to basics: what is configuration, why do we need
it, and how does ASP.NET Core handle these requirements?
11.1 Introducing the ASP.NET Core configuration model
Configuration is the set of external parameters provided to an application that con-
trols the application’s behavior in some way. It typically consists of a mixture of settings
and secrets that the application will load at runtime.
DEFINITION A setting is any value that changes the behavior of your applica-
tion. A secret is a special type of setting that contains sensitive data, such as a
password, an API key for a third-party service, or a connection string.
The obvious question before we get started is to consider why you need app configura-
tion, and what sort of things you need to configure. You should normally move any-
thing that you can consider a setting or a secret out of your application code. That
way, you can easily change these values at runtime, without having to recompile your
application.
You might, for example, have an application that shows the locations of your brick-
and-mortar stores. You could have a setting for the connection string to the database
in which you store the details of the stores, but also settings such as the default loca-
tion to display on a map, the default zoom level to use, maybe even the locations of
the stores, as shown in figure 11.1. You wouldn’t necessarily want to have to recompile
your app to tweak these, so you can store them in external configuration sources
instead.
There’s also a security aspect to this; you don’t want to hardcode secret values like
API keys or passwords into your code, where they could be committed to source con-
trol and made publicly available. Even values embedded in your compiled application
can be extracted, so it’s best to externalize them whenever possible.
Virtually every web framework provides a mechanism for loading configuration
and the previous version of ASP.NET was no different. It used the <appsettings>
305
Introducing the ASP.NET Core configuration model
element in a web.config file to store key-value configuration pairs. At runtime, you’d
use the static (*wince*) ConfigurationManager to load the value for a given key from
the file. You could do more advanced things using custom configuration sections, but
this was painful, and so was rarely used, in my experience.
ASP.NET Core gives you a totally revamped experience. At the most basic level,
you’re still specifying key-value pairs at strings, but instead of getting those values from
a single file, you can now load them from multiple sources. You can load values from
files, but they can now be any format you like: JSON, XML, YAML, and so on. On top
of that, you can load values from environment variables, from command-line argu-
ments, from a database or from a remote service. Or you could create your own cus-
tom configuration provider.
DEFINITION ASP.NET Core uses configuration providers to load key-value pairs
from a variety of sources. Applications can use many different configuration
providers.
The ASP.NET Core configuration model also has the concept of overriding settings.
Each configuration provider can define its own settings, or it can overwrite settings
from a previous provider. You’ll see this super-useful feature in action in the next
section.
ASP.NET Core makes it simple to bind these key-value pairs, which are defined as
strings, to POCO-setting classes you define in your code. This model of strongly
typed configuration makes it easy to logically group settings around a given feature
and lends itself well to unit testing.
Figure 11.1 You can store the default map location, zoom level, and store
locations in configuration and load them at runtime.
306 CHAPTER 11 Configuring an ASP.NET Core application
Before we get into the details of loading configuration from providers, we’ll take a
step back and look at where this process happens—inside WebHostBuilder. For
ASP.NET Core 2.0 apps built using the default templates, that’s invariably inside the
WebHost.CreateDefaultBuilder() method.
11.2 Configuring your application with
CreateDefaultBuilder
As you saw in chapter 2, the default templates in ASP.NET Core 2.0 use the Create-
DefaultBuilder method. This is an opinionated helper method that sets up a num-
ber of defaults for your app.
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
In chapter 2, I glossed over this method, as you will only rarely need to change it for
simple apps. But as your application grows, and if you want to change how configura-
tion is loaded for your application, you may find you need to break it apart.
This listing shows an overview of the CreateDefaultBuilder method, so you can
see how WebHostBuilder is constructed.
public static IWebHostBuilder CreateDefaultBuilder(string[] args)
{
var builder = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((hostingContext, config) =>
{
// Configuration provider setup
})
Listing 11.1 Using CreateDefaultBuilder to set up configuration
Listing 11.2 The WebHost.CreateDefaultBuilder method
The entry point for your
application creates
IWebHost and calls Run.
CreateDefaultBuilder sets up
a number of defaults,
including configuration.
Creating an instance of
WebHostBuilder
Kestrel is the default HTTP
server in ASP.NET Core.
The content root defines the
directory where configuration
files can be found.
Configures application settings,
the topic of this chapter
307
Configuring your application with CreateDefaultBuilder
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConfiguration(
hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
logging.AddDebug();
})
.UseIISIntegration()
.UseDefaultServiceProvider((context, options) =>
{
options.ValidateScopes =
context.HostingEnvironment.IsDevelopment();
});
return builder;
}
The first method called on WebHostBuilder is UseKestrel. This extension method con-
figures the ASP.NET application to use the Kestrel HTTP server, the new high-
performance, cross-platform HTTP server that was created specifically for ASP.NET Core.
The call to UseContentRoot tells the application in which directory it can find any
configuration or view files it will need later. This is typically the folder in which the
application is running, hence the call to GetCurrentDirectory.
TIP ContentRoot is not where you store static files that the browser can
access directly—that’s the WebRoot, typically wwwroot.
ConfigureLogging is where you can specify the logging settings for your application.
We’ll look at logging in detail in chapter 17; for now, it’s enough to know that Create-
DefaultBuilder sets this up for you.
When you’re running ASP.NET Core applications in production, you need to run
them behind a reverse proxy. The call to UseIISIntegration ensures that if you’re
running your application on Windows, IIS can talk to your application, monitor its
health, and forward requests to it. When running the application on Linux or macOS,
this method call has no effect.
The last method call in CreateDefaultBuilder, UseDefaultServiceProvider,
configures your app to use the built-in DI container. It also sets the ValidateScopes
option based on the current HostingEnvironment. When running the application in
the development environment, the app will automatically check for captured depen-
dencies, as you learned about in chapter 10. You’ll learn more about hosting environ-
ments in section 11.5.
The ConfigureAppConfiguration() method is the focus of section 11.3. It’s
where you load the settings and secrets for your app, whether they’re in JSON files,
environment variables, or command-line arguments. In the next section, you’ll see
Sets up the logging
infrastructure
When running on windows,
IIS integration is
automatically configured.
Configures the DI container.
The ValidateScopes option
checks for captured
dependencies.
Returns WebHostBuilder
for further configuration
before calling Build()
308 CHAPTER 11 Configuring an ASP.NET Core application
how to use this method to load configuration values from various configuration pro-
viders using the ASP.NET Core ConfigurationBuilder.
11.3 Building a configuration object for your app
The ASP.NET Core configuration model centers on two main constructs: Configuration-
Builder and IConfigurationRoot.
NOTE ConfigurationBuilder describes how to construct the final configura-
tion representation for your app, and IConfigurationRoot holds the config-
uration values themselves.
You describe your configuration setup by adding a number of IConfiguration-
Providers to IConfigurationBuilder. These describe how to load the key-value pairs
from a particular source; for example, a JSON file or from environment variables, as
shown in figure 11.2. Calling Build() on ConfigurationBuilder queries each of
these providers for the values they contain to create the IConfigurationRoot
instance.
ASP.NET Core ships with a number of configuration providers, each provided by a
different NuGet package. In ASP.NET Core 2.0, all of these packages are included by
1. You start by creating an instance
of the ConfigurationBuilder.
2. You add providers to
the builder using various
extension methods. These
providers can pull
configuration values from
multiple sources.
ConfigurationBuilder
IConfigurationRoot
Build()
JsonConfigurationProvider
EnvironmentVariablesProvider
CommandLineProvider
AddJsonFile()
AddEnvironmentVariables()
Add CommandLine()
3. Calling Build() on the ConfigurationBuilder
loads the values from each provider and stores
them in the IConfigurationRoot.
key:value
key:value
key:value
Figure 11.2 Using ConfigurationBuilder to create an instance of IConfigurationRoot.
Configuration providers are added to the builder with extension methods. Calling Build() queries
each provider to create IConfigurationRoot.
309
Building a configuration object for your app
default, thanks to the Microsoft.AspNetCore.All metapackage. Some of the common
providers are:
 JSON files—Microsoft.Extensions.Configuration.Json
 XML files—Microsoft.Extensions.Configuration.Xml
 Environment variables—Microsoft.Extensions.Configuration.EnvironmentVariables
 Command-line arguments—Microsoft.Extensions.Configuration.CommandLine
 Azure Key Vault—Microsoft.Extensions.Configuration.AzureKeyVault
If none of these fits your requirements, you can find a whole host of alternatives on
GitHub and NuGet, and it’s not difficult to create your own custom provider. For
example, I wrote a simple configuration provider to read YAML files, available on
NuGet or GitHub.1
In most cases, the default providers will be sufficient. In particular, most templates
start with an appsettings.json file, which will contain a variety of settings, depending
on the template you choose. This listing shows the default file generated by the
ASP.NET Core 1.1 Web template without authentication.
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
}
}
As you can see, this file only contains settings to control logging to start with, but you
can add additional configuration for your app here too.
WARNING Don’t store sensitive values, such as production passwords, API
keys, or connection strings, in this file. You’ll see how to store these securely
in section 11.3.4.
Adding your own configuration values involves adding a key-value pair to the JSON.
It’s a good idea to “namespace” your settings by creating a base object for related set-
tings, as in the MapDisplay object shown here.
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
1
You can find my YAML provider on GitHub at https://github.com/andrewlock/NetEscapades.Configuration.
Listing 11.3 Default appsettings.json file created by an ASP.NET Core Web template
Listing 11.4 Adding additional configuration values to an appsettings.json file
310 CHAPTER 11 Configuring an ASP.NET Core application
}
},
"MapDisplay": {
"DefaultZoomLevel": 9,
"DefaultLocation": {
"latitude": 50.500,
"longitude": -4.000
}
}
}
I’ve nested the map settings inside the MapDisplay parent key; this creates a section
which will be useful later when it comes to binding your values to a POCO object. I
also nested the latitude and longitude keys under the DefaultLocation key. You
can create any structure of values that you like; the configuration provider will read
them just fine. Also, you can store them as any data type—numbers, in this case—but
be aware that the provider will read and store them internally as strings.
TIP The configuration keys are not case-sensitive, so bear that in mind when
loading from providers in which the keys are case-sensitive.
Now that you have a configuration file, it’s time for your app to load it using
ConfigurationBuilder! For this, return to the ConfigureAppConfiguration()
method exposed by WebHostBuilder in Program.cs.
11.3.1 Adding a configuration provider in Program.cs
The default templates in ASP.NET Core use the CreateDefaultBuilder helper
method to bootstrap WebHostBuilder for your app, as you saw in section 11.2. As
part of this configuration, the CreateDefaultBuilder method calls ConfigureApp-
Configuration and sets up a number of default configuration providers, which we’ll
look at in more detail throughout this chapter:
 JSON file provider—Loads settings from an optional JSON file called appsettings
.json.
 JSON file provider—Loads settings from an optional environment-specific JSON
file called appsettings.ENVIRONMENT.json. I’ll show how to use environment-
specific files in section 11.5.2.
 User Secrets—Loads secrets that are stored safely in development.
 Environment variables—Loads environment variables as configuration variables.
Great for storing secrets in production.
 Command-line arguments—Uses values passed as arguments when you run your
app.
Using the default builder ties you to this default set, but it’s entirely optional. If you
want to use different configuration providers, you can use your own WebHostBuilder
instance instead. You can set up configuration using the ConfigureAppConfiguration
Nest all the
configuration under
the MapDisplay key.
Values can be numbers in the
JSON file, but they’ll be converted
to strings when they’re read.
You can create deeply nested
structures to better organize
your configuration values.
311
Building a configuration object for your app
method, as shown in the following listing, in which you configure a JSON provider to
load the appsettings.json file. For brevity, I’ve omitted the logging and service provider
configuration that you saw in listing 11.2, but you’d need to include this in practice.
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration(AddAppConfiguration)
.ConfigureLogging(
(hostingContext, logging) => { /* Detail not shown */ })
.UseIISIntegration()
.UseDefaultServiceProvider(
(context, options) =>{ /* Detail not shown */ })
.UseStartup<Startup>()
.Build();
public static void AddAppConfiguration(
WebHostBuilderContext hostingContext,
IConfigurationBuilder config)
{
config.AddJsonFile("appsettings.json", optional: true);
}
}
TIP In listing 11.5, I extracted the configuration to a static helper method,
AddAppConfiguration, but you can also provide this inline as a lambda
method.
In ASP.NET Core 2.0, WebHostBuilder creates an IConfigurationBuilder instance
before invoking the ConfigureAppConfiguration method. All you need to do is add
the configuration providers for your application.
In this example, you’ve added a single JSON configuration provider by calling the
AddJsonFile extension method and providing a filename. You’ve also set the value of
optional to true. This tells the configuration provider to skip over files it can’t find at
runtime, instead of throwing FileNotFoundException. Note that the extension
method just registers the provider at this point, it doesn’t try to load the file yet.
And that’s it! The WebHostBuilder instance takes care of calling Build(), which
generates IConfigurationRoot, which represents your configuration object. This is
then registered as an IConfiguration instance with the DI container, so you can
Listing 11.5 Loading appsettings.json using a custom WebHostBuilder
Adds the
configuration
setup function to
WebHostBuilder
WebHostBuilder provides a
hosting context and an instance of
ConfigurationBuilder.
Adds a JSON configuration
provider, providing the filename
of the configuration file
312 CHAPTER 11 Configuring an ASP.NET Core application
inject it into your classes. You’d commonly inject this into the constructor of your
Startup class, so you can use it in the Configure and ConfigureServices methods:
public class Startup
{
public Startup(IConfiguration config)
{
Configuration = config
}
public IConfiguration Configuration { get; }
}
NOTE The ASP.NET Core 2.0 WebHostBuilder registers the configuration
object as an IConfiguration in the DI container, not an IConfiguration-
Root. In ASP.NET Core 1, you have to manually register the configuration
with DI yourself.
At this point, at the end of the Startup constructor, you have a fully loaded configura-
tion object! But what can you do with it? The IConfigurationRoot stores configuration
as a set of key-value string pairs. You can access any value by its key, using standard dic-
tionary syntax. For example, you could use
var zoomLevel = Configuration["MapDisplay:DefaultZoomLevel"];
to retrieve the configured zoom level for your application. Note that I used a : to des-
ignate a separate section. Similarly, to retrieve the latitude key, you could use
Configuration["MapDisplay:DefaultLocation:Latitude"];
You can also grab a whole section of the configuration using the GetSection(section)
method. This grabs a chunk of the configuration and resets the namespace. Another
way of getting the latitude key would be
Configuration.GetSection("MapDisplay”)["DefaultLocation:Latitude"];
Accessing setting values like this is useful in the ConfigureServices and Configure
methods of Startup, when you’re defining your application. When setting up your
application to connect to a database, for example, you’ll often load a connection
string from the Configuration object (you’ll see a concrete example of this in the
next chapter, when we look at Entity Framework Core).
If you need to access the configuration object like this from classes other than
Startup, you can use DI to take it as a dependency in your service’s constructor. But
accessing configuration using string keys like this isn’t particularly convenient; you
should try to use strongly typed configuration instead, as you’ll see in section 11.4.
WARNING ASP.NET Core 1 doesn’t register the configuration object with the
DI container by default. If you need this functionality, you can register it your-
self as a singleton by calling services.AddSingleton(Configuration) in
Startup.ConfigureServices.
313
Building a configuration object for your app
So far, this is probably all feeling a bit convoluted, and run-of-the-mill to load settings
from a JSON file, and I’ll grant you, it is. Where the ASP.NET Core configuration sys-
tem shines is when you have multiple providers.
11.3.2 Using multiple providers to override configuration values
You’ve seen that ASP.NET Core uses the builder pattern to construct the configura-
tion object, but so far, you’ve only configured a single provider. When you add provid-
ers, it’s important to consider the order in which you add them, as that defines the
order in which the configuration values are added to the underlying dictionary. Con-
figuration values from later providers will overwrite values with the same key from ear-
lier providers.
NOTE This bears repeating: the order you add configuration providers to
ConfigurationBuilder is important. Later configuration providers can over-
write the values of earlier providers.
Think of the configuration providers as adding a layer of configuration values to a
stack, where each layer may overlap with some or all of the layers below, as shown in fig-
ure 11.3. When you call Build(), ConfigurationBuilder collapses these layers down
into one, to create the final set of configuration values stored in IConfigurationRoot.
Update your code to load configuration from three different configuration provid-
ers—two JSON providers and an environment variable provider—by adding them to
ConfigurationBuilder. I’ve only shown the AddAppConfiguration method in this list-
ing for brevity.
Each configuration provider
adds a layer of configuration
values to the stack.
Calling Build() collapses the
stack, taking the “top-most”
configuration values, as
viewed from above.
The values at the top of a
vertical slice are the final
values found in the
IConfigurationRoot.
sharedsettings.json
appsettings.json
Environment
variables
Figure 11.3 Each configuration provider adds a “layer” of values to ConfigurationBuilder.
Calling Build() collapses that configuration. Later providers will overwrite configuration values with
the same key as earlier providers.
314 CHAPTER 11 Configuring an ASP.NET Core application
public class Program
{
/* Additional Program configuration*/
public static void AddAppConfiguration(
WebHostBuilderContext hostingContext,
IConfigurationBuilder config)
{
config
.AddJsonFile("sharedSettings.json", optional: true)
.AddJsonFile("appsettings.json", optional: true)
.AddEnvironmentVariables();
}
}
This design can be useful for a number of things. Fundamentally, it allows you to
aggregate configuration values from a number of different sources into a single, cohe-
sive object. To cement this in place, consider the configuration values in figure 11.4.
Listing 11.6 Loading from multiple providers in Startup.cs using ASP.NET Core 2.0
Loads configuration
from a different
JSON configuration
file before the
appsettings.json file
Adds the machine’s
environment variables as a
configuration provider
"StoreDetails:" - ""
"StoreDetails:Name" - "Head office"
"StoreDetails:Location:Latitiude" - "50.5"
"StoreDetails:Location:Longitude" - "-4.0"
"MyAppConnString" - "localDB;"
"MapDisplay:" - ""
"MapDisplay:DefaultZoomLevel" - 5
"MapDisplay:DefaultLocation:Latitude" - "50.5"
"MapDisplay:DefaultLocation:Longitude" - "-4.0"
"MyAppConnString - "productionDB;"
"GoogleMapsApiKey - "123456ABCD"
{
"MyAppConnString": "localDB;",
"MapDisplay": {
"DefaultZoomLevel": 5,
"DefaultLocation: {
"Latitude": 50.5,
"Longitude": -4.0
}
}
}
appsettings.json
{
"StoreDetails": {
"Name": "Head office",
"Location: {
"Latitude": 50.5,
"Longitude": -4.0
}
}
}
sharedsettings.json
Environment variables
MyAppConnString: "productionDB;"
GoogleMapsApiKey: "123456ABCD"
IConfigurationRoot
Each configuration provider adds a
number of configuration values to the
final IConfigurationRoot. They are
added in the order the configuration
providers were added to the
ConfigurationBuilder.
The Environment variables are loaded
after appsettings.json, so the “localDB”
MyAppConString value is overwritten
with the “productionDB” value.
Figure 11.4 The final IConfigurationRoot includes the values from each of the providers. Both
appsettings.json and the environment variables include the MyAppConnString key. As the
environment variables are added later, that configuration value is used.
315
Building a configuration object for your app
Most of the settings in each provider are unique and added to the final IConfiguration-
Root. But the "MyAppConnString" key appears both in appsettings.json and as an envi-
ronment variable. Because the environment variable provider is added after the JSON
providers, the environment variable configuration value is used in IConfiguration-
Root.
The ability to collate configuration from multiple providers is a handy trait on its
own, but this design is also useful when it comes to handling sensitive configuration
values, such as connection strings and passwords. The next section shows how to deal
with this problem, both locally on your development machine and on production
servers.
11.3.3 Storing configuration secrets safely
As soon as you build a nontrivial app, you’ll find you have a need to store some sort of
sensitive data as a setting somewhere. This could be a password, connection string, or
an API key for a remote service, for example.
Storing these values in appsettings.json is generally a bad idea as you should never
commit secrets to source control; the number of secret API keys people have commit-
ted to GitHub is scary! Instead, it’s much better to store these values outside of your
project folder, where they won’t get accidentally committed.
You can do this in a number of ways, but the easiest and most commonly used
approaches are to use environment variables for secrets on your production server
and User Secrets locally.
Neither approach is truly secure, in that they don’t store values in an encrypted
format. If your machine is compromised, attackers will be able to read the stored val-
ues as they’re stored in plaintext. They’re intended to help you avoid committing
secrets to source control.
TIP Azure Key Vault1
is a secure alternative, in that it stores the values
encrypted in Azure. But you will still need to use the following approach for
storing the Azure Key Value connection details!
Whichever approach you choose to store your application secrets, make sure you
aren’t storing them in source control, if possible. Even private repositories may not
stay private forever, so it’s best to err on the side of caution!
STORING SECRETS IN ENVIRONMENT VARIABLES IN PRODUCTION
You can add the environment variable configuration provider using the AddEnvironment-
Variables extension method, as you’ve already seen in listing 11.6. This will add all of
the environment variables on your machine as key-value pairs in the configuration
object.
1
The Azure Key Vault configuration provider is available as a Microsoft.Extensions.Configuration.AzureKey-
Vault NuGet package. For details on using it in your application, see the documentation at http://
mng.bz/96aQ.
316 CHAPTER 11 Configuring an ASP.NET Core application
You can create the same hierarchical sections that you typically see in JSON files by
using a colon, :, or a double underscore, __, to demarcate a section, for example:
MapSettings:MaxNumberOfPoints or MapSettings__MaxNumberOfPoints.
TIP Some environments, such as Linux, don’t allow “:” in environment vari-
ables, you must use the double underscore approach on these instead.
The environment variable approach is particularly useful when you’re publishing your
app to a self-contained environment, such as a dedicated server, Azure, or a Docker
container. You can set environment variables on your production machine, or on your
Docker container, and the provider will read them at runtime, overriding the defaults
specified in your appsettings.json files.1
For a development machine, environment settings are less useful, as all your apps
would be using the same variables. For example, if you set the Connection-
Strings__DefaultConnection environment variable, then that would be added for
every app you run locally. That sounds like more of a hassle than a benefit!
For development scenarios, you can use the User Secrets Manager. This effectively
adds per-app environment variables, so you can have different settings for each app,
but store them in a different location from the app itself.
STORING SECRETS WITH THE USER SECRETS MANAGER IN DEVELOPMENT
The idea behind User Secrets is to simplify storing per-app secrets outside of your
app’s project tree. This is similar to environment variables, but you use a unique key
per-app to keep the secrets segregated.
WARNING The secrets aren’t encrypted, so shouldn’t be considered secure.
They’re an improvement on storing them in your project folder.
Setting up User Secrets takes a bit more effort than using environment variables, as
you need to configure a tool to read and write them, add the User Secrets configura-
tion provider, and define a unique key for your application:
1 ASP.NET Core 2.0 includes the User Secrets NuGet package by default as part
of the Microsoft.AspNetCore.All metapackage. If you’re not using the meta-
package, you’ll need to explicitly add the Microsoft.Extensions.Configuration
.UserSecrets package to your project file:
<PackageReference
Include="Microsoft.Extensions.Configuration.UserSecrets"
Version="2.0.0" />
2 Add the SecretManager NuGet tools to your csproj file. Note, you need to do this
manually by editing the csproj file—you can’t use the NuGet UI in Visual Studio.2
1
For instructions on how to set environment variables for your operating system, see the documentation at
http://mng.bz/R052.
2
ASP.NET Core 2.1 (in preview at the time of writing) introduces the concept of global tools so you can install
the tools globally for all your applications. For preliminary details, see http://mng.bz/jGs6.
317
Building a configuration object for your app
<ItemGroup>
<DotNetCliToolReference
Include="Microsoft.Extensions.SecretManager.Tools"
Version="2.0.0" />
</ItemGroup>
3 Restore the NuGet package using dotnet restore. Visual Studio will do this
automatically for you after you save your edits to the csproj file.
4 If you’re using Visual Studio, right-click your project and choose Manage User
Secrets. This opens an editor for a secrets.json file in which you can store your
key-value pairs, as if it were an appsettings.json file, as shown in figure 11.5.
5 Add a unique identifier to your csproj file. Visual Studio does this automatically
when you click Manage User Secrets, but if you’re using the command line,
you’ll need to add it yourself. Typically, you’d use a unique ID, like a GUID:
<PropertyGroup>
<UserSecretsId>96eb2a39-1ef9-4d8e-8b20-8e8bd14038aa</UserSecretsId>
</PropertyGroup>
6 If you aren’t using Visual Studio, you can add User Secrets using the command
line:
dotnet user-secrets set GoogleMapsApiKey F5RJT9GFHKR7F
Figure 11.5 Click Manage User Secrets
to open an editor for the User Secrets app.
318 CHAPTER 11 Configuring an ASP.NET Core application
or you can edit the secret.json file directly using your favorite editor. The exact
location of this file depends on your operating system and may vary. Check the
documentation1
for details.
Phew, that’s a lot of setup, and you’re not done yet! You still need to update your app
to load the User Secrets at runtime. Thankfully, this is a lot simpler; you can use the
extension method included as part of the UserSecrets NuGet package to add them
to ConfigurationBuilder:
if(env.IsDevelopment())
{
configBuilder.AddUserSecrets<Startup>();
}
NOTE You should only use the User Secrets provider in development, not in
production, so you’re conditionally adding the provider to Configuration-
Builder. In production, you should use environment variables or Azure Key
Vault, as discussed earlier.
This method has a number of overloads, but the simplest is a generic method that you
can call using your application’s Startup class. The User Secrets provider needs to
read the UserSecretsId property that you (or Visual Studio) added to the csproj.
The Startup class acts as a simple marker to indicate which assembly contains this
property.
NOTE If you’re interested, the User Secrets package uses the UserSecretsId
property in your csproj file to generate an assembly-level UserSecretsId-
Attribute. The provider then reads this attribute at runtime to determine
the UserSecretsId of the app, and hence generates the path to the secrets.json
file.
And there you have it, safe storage of your secrets outside your project folder during
development. This might seem like overkill, but if you have anything that you con-
sider remotely sensitive that you need to load into configuration, then I strongly urge
you to use environment variables or User Secrets.
It’s almost time to leave configuration providers behind, but before we do, I’d like
to show you the ASP.NET Core configuration system’s party trick: reloading files on
the fly.
11.3.4 Reloading configuration values when they change
Besides security, not having to recompile your application every time you want to
tweak a value is one of the advantages of using configuration and settings. In the pre-
vious version of ASP.NET, changing a setting by editing web.config would cause your
1
The Secret Manager Tool 2.0.0 stores the secrets.json file in the user profile, but is subject to change in later
versions. See http://mng.bz/1B2v for details.
319
Building a configuration object for your app
app to have to restart. This beat having to recompile, but waiting for the app to start
up before it could serve requests was a bit of a drag.
In ASP.NET Core, you finally get the ability to edit a file and have the configura-
tion of your application automatically update, without having to recompile or restart.
A common scenario where you might find this useful is when you’re trying to
debug an app you have in production. You typically configure logging to one of a
number of levels, for example:
 Error
 Warning
 Information
 Debug
Each of these settings is more verbose than the last, but also provides more context. By
default, you might configure your app to only log warning- and error-level logs in pro-
duction, so you don’t generate too many superfluous log entries. Conversely, if you’re
trying to debug a problem, you want as much information as possible, so you might
want to use the debug log level.
Being able to change configuration at runtime means you can easily switch on
extra logs when you encounter an issue and switch them back afterwards by editing
your appsettings.json file.
NOTE Reloading is generally only available for file-based configuration pro-
viders, as opposed to the environment variable or User Secrets provider.
You can enable the reloading of configuration files when you add any of the file-based
providers to your ConfigurationBuilder. The Add*File extension methods include
an overload with a reloadOnChange parameter. If this is set to true, the app will moni-
tor the filesystem for changes to the file and will trigger a rebuild of IConfiguration-
Root, if needs be. This listing shows how to add configuration reloading to the
appsettings.json file loaded inside the AddAppConfiguration method.
public class Program
{
/* Additional Program configuration*/
public static void AddAppConfiguration(
WebHostBuilderContext hostingContext,
IConfigurationBuilder config)
{
config.AddJsonFile(
"appsettings.json",
optional: true
reloadOnChange: true);
}
}
Listing 11.7 Reloading appsettings.json when the file changes
IConfigurationRoot will be
rebuilt if the appsettings.json
file changes.
320 CHAPTER 11 Configuring an ASP.NET Core application
With that in place, any changes you make to the file will be mirrored in IConfiguration-
Root. But as I said at the start of this chapter, IConfigurationRoot isn’t the preferred
way to pass settings around in your application. Instead, as you’ll see in the next sec-
tion, you should favor strongly typed POCO objects.
11.4 Using strongly typed settings with the options pattern
Most of the examples I’ve shown so far have been how to get values into IConfiguration-
Root, as opposed to how to use them. You’ve seen that you can access a key using the
Configuration["key"] dictionary syntax, but using string keys like this feels messy
and prone to typos.
Instead, ASP.NET Core promotes the use of strongly typed settings. These are
POCO objects that you define and create and represent a small collection of settings,
scoped to a single feature in your app.
The following listing shows both the settings for your store locator component and
display settings to customize the homepage of the app. They’re separated into two
separate objects with "MapDisplay" and "HomePageSettings" keys corresponding to
the different areas of the app they impact.
{
"MapDisplay": {
"DefaultZoomLevel": 6,
"DefaultLocation": {
"latitude": 50.500,
"longitude": -4.000
}
},
"HomePageSettings": {
"Title": "Acme Store Locator",
"ShowCopyright": true
}
}
The simplest approach to making the homepage settings available to HomeController
would be to inject IConfiguration and access the values using the dictionary syntax;
for example:
public class HomeController : Controller
{
public HomeController(IConfiguration config)
{
var title = config["HomePageSettings:Title"];
var showCopyright = bool.Parse(
config["HomePageSettings:ShowCopyright"]);
}
}
Listing 11.8 Separating settings into different objects in appsettings.json
Settings related to the store
locator section of the app
General settings related to
the app’s homepage
321
Using strongly typed settings with the options pattern
But you don’t want to do this; too many strings for my liking! And that bool.Parse?
Yuk! Instead, you can use custom strongly typed objects, with all the type safety and
IntelliSense goodness that brings.
public class HomeController : Controller
{
public HomeController(IOptions<HomePageSettings> options)
{
HomePageSettings settings = options.Value;
var title = settings.Title;
var showCopyright = settings.ShowCopyright;
}
}
The ASP.NET Core configuration system includes a binder, which can take a collection
of configuration values and bind them to a strongly typed object, called an options class.
This is similar to the concept of binding from chapter 6, where request values were
bound to the arguments of your application model.
This section shows how to set up the binding of configuration values to a POCO
options class and how to make sure it reloads when the underlying configuration val-
ues change. We’ll also have a look at the different sorts of objects you can bind.
11.4.1 Introducing the IOptions interface
ASP.NET Core introduced strongly typed settings as a way of letting configuration
code adhere to the single responsibility principle and to allow injecting configuration
classes as explicit dependencies. They also make testing easier; instead of having to
create an instance of IConfiguration to test a service, you can create an instance of
the POCO options class.
For example, the HomePageSettings class shown in the previous example could be
simple, exposing just the values related to the homepage:
public class HomePageSettings
{
public string Title { get; set; }
public bool ShowCopyright { get; set; }
}
Your options classes need to be non-abstract and have a public parameterless con-
structor to be eligible for binding. The binder will set any public properties that
match configuration values, as you’ll see shortly.
Listing 11.9 Injecting strongly typed options into a controller using IOptions<T>
You can inject a strongly typed
options class using the
IOptions<> wrapper interface.
The Value property
exposes the POCO
settings object.
The binder can also
convert string values
directly to primitive types.
The settings object
contains properties
that are bound to
configuration values
at runtime.
322 CHAPTER 11 Configuring an ASP.NET Core application
To help facilitate the binding of configuration values to your custom POCO
options classes, ASP.NET Core introduces the IOptions<T> interface. This is a simple
interface with a single property, Value, which contains your configured POCO
options class at runtime. Options classes are set up in the ConfigureServices section
of Startup, as shown here.
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MapDisplaySettings>(
Configuration.GetSection("MapDisplay"));
services.Configure<HomePageSettings>(
Configuration.GetSection("HomePageSettings"));
}
TIP If you get a compilation error with the preceding overload of Configure<>,
add the Microsoft.Extensions.Options.ConfigurationExtensions NuGet pack-
age to your app. ASP.NET Core 2.0 includes this by default as part of the
metapackage, so it’s not often necessary.
Each call to Configure<T> sets up the following series of actions internally:
 Creates an instance of ConfigureOptions<T>, which indicates that IOptions<T>
should be configured based on configuration.
If Configure<T> is called multiple times, then multiple Configure-
Options<T> objects will be used, all of which can be applied to create the final
object, in much the same way as the IConfigurationRoot is built from multiple
layers.
 Each ConfigureOptions<T> instance binds a section of IConfigurationRoot to
an instance of the T POCO class. This sets any public properties on the options
class based on the keys in the provided ConfigurationSection.
Note that the section name ("MapDisplay" in listing 11.10) can have any
value; it doesn’t have to match the name of your options class.
 Registers the IOptions<T> interface in the DI container as a singleton, with the
final bound POCO object in the Value property.
This last step lets you inject your options classes into controllers and services by inject-
ing IOptions<T>, as you’ve seen. This gives you encapsulated, strongly typed access to
your configuration values. No more magic strings, woo-hoo!
WARNING If you forget to call Configure<T> and inject IOptions<T> into
your services, you won’t see any errors, but the T options class won’t be bound
to anything and will only have default values in its properties.
Listing 11.10 Configuring the options classes using Configure<T> in Startup.cs
Binds the MapDisplay
section to the POCO
options class
MapDisplaySettings
Binds the
HomePageSettings
section to the POCO
options class
HomePageSettings
323
Using strongly typed settings with the options pattern
The binding of the T options class to ConfigurationSection happens when you first
request IOptions<T>. The object is registered in the DI container as a singleton, so
it’s only bound once.
There’s one catch with this setup: you can’t use the reloadOnChange parameter I
described in section 11.2.3 to reload your strongly typed options classes when using
IOptions<T>. IConfigurationRoot will still be reloaded if you edit your appset-
tings.json files, but it won’t propagate to your options class.
If that seems like a step backwards, or even a deal-breaker, then don’t worry.
IOptions<T> has a cousin, IOptionsSnapshot<T>, for such an occasion.
11.4.2 Reloading strongly typed options with IOptionsSnapshot
In the previous section, you used IOptions<T> to provide strongly typed access to con-
figuration. This provided a nice encapsulation of the settings for a particular service,
but with a specific drawback: the options class never changes, even if you modify the
underlying configuration file from which it was loaded, for example appsettings.json.
This is often not a problem (you shouldn’t be modifying files on live production
servers anyway!), but if you need this functionality, you can use the IOptions-
Snapshot<T> interface introduced with ASP.NET Core 1.1.
WARNING IOptionsSnapshot is only available in ASP.NET Core 1.1 and
above. If you’re using ASP.NET Core 1.0, you won’t be able to reload the
IOptions<T> options classes when the underlying configuration changes.
Conceptually, IOptionsSnaphot<T> is identical to IOptions<T> in that it’s a strongly
typed representation of a section of configuration. The difference is when, and how
often, the POCO options objects are created when each of these are used.
 IOptions<T>—The instance is created once, when first needed. It always con-
tains the configuration when the object instance was first created.
 IOptionsSnapshot<T>—A new instance is created when needed, if the underly-
ing configuration has changed since the last instance was created.
IOptionsSnaphot<T> is automatically set up for your options classes at the same time
as IOptions<T>, so you can use it in your services in exactly the same way! This listing
shows how you could update your HomeController so that you always get the latest
configuration values in your strongly typed HomePageSettings options class.
public class HomeController : Controller
{
public HomeController(
IOptionsSnapshot<HomePageSettings> options)
{
HomePageSettings settings = options.Value;
var title = settings.Title;
}
}
Listing 11.11 Injecting reloadable options using IOptionsSnapshot<T>
IOptionsSnapshot<T> will
update if the underlying
configuration values change. The Value property
exposes the POCO
settings object, the
same as for
IOptions<T>.
The settings object will match the
configuration values at some
point, instead of at first run.
324 CHAPTER 11 Configuring an ASP.NET Core application
Whenever you edit the settings file and cause IConfigurationRoot to be reloaded,
IOptionsSnapshot<HomePageSettings> will be rebound. A new HomePageSettings
object is created with the new configuration values and will be used for all future
dependency injection. Until you edit the file again, of course! It’s as simple as that;
update your code to use IOptionsSnapshot<T> instead of IOptions<T> wherever you
need it.
The final thing I want to touch on in this section is the design of your POCO
options classes themselves. These are typically simple collections of properties, but
there are a few things to bear in mind so that you don’t get stuck debugging why the
binding seemingly hasn’t worked!
11.4.3 Designing your options classes for automatic binding
I’ve touched on some of the requirements for your POCO classes for the IOptions<T>
binder to be able to populate them, but there are a few rules to bear in mind.
The first key point is that the binder will be creating instances of your options
classes using reflection, so your POCO options classes need to:
 Be non-abstract
 Have a default (public parameterless) constructor
If your classes satisfy these two points, the binder will loop through all the properties
on your class and bind any it can. In the broadest sense, the binder can bind any prop-
erty which
 Is public
 Has a getter—the binder won’t write set-only properties
 Has a setter or a non-null value
 Is not an indexer
This listing shows an extensive options class, with a whole host of different types of
properties, some of which are valid to bind, and some of which aren’t.
public class TestOptions
{
public string String { get; set; }
public int Integer { get; set; }
public SubClass Object { get; set; }
public SubClass ReadOnly { get; } = new SubClass();
public Dictionary<string, SubClass> Dictionary { get; set; }
public List<SubClass> List { get; set; }
public IDictionary<string, SubClass> IDictionary { get; set; }
public IEnumerable<SubClass> IEnumerable { get; set; }
public ICollection<SubClass> IEnumerable { get; }
= new List<SubClass>();
Listing 11.12 An options class containing binding and nonbinding properties
The binder can bind simple
and complex object types,
and read-only properties
with a default.
The binder will also bind collections, including
interfaces; dictionaries must have string keys.
325
Using strongly typed settings with the options pattern
internal string NotPublic { get; set; }
public SubClass SetOnly { set => _setOnly = value; }
public SubClass NullReadOnly { get; } = null;
public SubClass NullPrivateSetter { get; private set; } = null;
public SubClass this[int i] {
get => _indexerList[i];
set => _indexerList[i] = value;
}
public List<SubClass> NullList { get; }
public Dictionary<int, SubClass> IntegerKeys { get; set; }
public IEnumerable<SubClass> ReadOnlyEnumerable { get; }
= new List<SubClass>();
public SubClass _setOnly = null;
private readonly List<SubClass> _indexerList
= new List<SubClass>();
public class SubClass
{
public string Value { get; set; }
}
}
As shown in the listing, the binder generally supports collections too—both imple-
mentations and interfaces. If the collection property is already initialized, it will use
that, but the binder can also create backing fields for them. If your property imple-
ments any of the following classes, the binder will create a List<> of the appropriate
type as the backing object:
 IReadOnlyList<>
 IReadOnlyCollection<>
 ICollection<>
 IEnumerable<>
WARNING You can’t bind to an IEnumerable<> property that has already
been initialized, as the underlying type doesn’t expose an Add function! You
can bind to an IEnumerable<> if you leave its initial value as null.
Similarly, the binder will create a Dictionary<,> as the backing field for properties
with dictionary interfaces, as long as they use string keys:
 IDictionary<,>
 IReadOnlyDictionary<,>
WARNING You can’t bind dictionaries with nonstring values, such as int.
Clearly there are quite a few nuances here, but if you stick to the simple cases from the
preceding example, you’ll be fine. Be sure to check for typos in your JSON files!
That brings us to the end of this section on strongly typed settings. In the next sec-
tion, we’ll look at how you can dynamically change your settings at runtime, based on
the environment in which your app is running.
The binder can’t
bind nonpublic,
set-only, null-read-
only, or indexer
properties.
These
collection
properties
can’t be
bound.
The backing fields
for SetOnly and
Indexer properties
326 CHAPTER 11 Configuring an ASP.NET Core application
11.5 Configuring an application for multiple environments
Any application that makes it to production will likely have to run in multiple environ-
ments. For example, if you’re building an application with database access, you’ll
probably have a small database running on your machine that you use for develop-
ment. In production, you’ll have a completely different database running on a server
somewhere else.
Another common requirement is to have different amounts of logging depending
on where your app is running. In development, it’s great to generate lots of logs as it
helps debugging, but once you get to production, too much logging can be over-
whelming. You’ll want to log warnings and errors, maybe information-level logs, but
definitely not debug-level logs!
To handle these requirements, you need to make sure your app loads different
configuration values depending on the environment it’s running in; load the produc-
tion database connection string when in production and so on. You need to consider
three aspects:
 How does your app identify which environment it’s running in?
 How do you load different configuration values based on the current environ-
ment?
 How can you change the environment for a particular machine?
This section tackles each of these questions in turn, so you can easily tell your develop-
ment machine apart from your production servers and act accordingly.
11.5.1 Identifying the hosting environment
ASP.NET Core identifies the current environment by using, perhaps unsurprisingly,
an environment variable! The WebHostBuilder created in Program.cs looks for a
magic environment variable called ASPNETCORE_ENVIRONMENT and uses it to create an
IHostingEnvironment object.
The IHostingEnvironment interface exposes a number of useful properties about
the running context of your app. Some of these you’ve already seen, such as Content-
RootPath, which points to the folder containing your application’s content files;
for example, the appsettings.json files. The property you’re interested in here is
EnvironmentName.
The IHostingEnvironment.EnvironmentName property is set to the value of the
ASPNETCORE_ENVIRONMENT environment variable, so it can be anything, but you should
stick to three commonly used values in most cases:
 "Development"
 "Staging"
 "Production"
ASP.NET Core includes a number of helper methods for working with these three val-
ues, so you’ll have an easier time if you stick to them. In particular, whenever you’re
327
Configuring an application for multiple environments
testing whether your app is running in a particular environment, you should use one
of the following extension methods:
 IHostingEnvironment.IsDevelopment()
 IHostingEnvironment.IsStaging()
 IHostingEnvironment.IsProduction()
 IHostingEnvironment.IsEnvironment(string environmentName)
These methods all make sure they do case-insensitive checks of the environment vari-
able, so you don’t get any wonky errors at runtime if you don’t capitalize the environ-
ment variable value.
TIP Where possible, use the IHostingEnvironment extension methods
over direct string comparison with EnvironmentValue, as they provide case-
insensitive matching.
IHostingEnvironment doesn’t do anything other than expose the details of your cur-
rent environment, but you can use it in a number of different ways. In chapter 8, you
saw the Environment Tag Helper, which you used to show and hide HTML based on
the current environment. Now you know where it was getting its information—
IHostingEnvironment.
You can use a similar approach to customize which configuration values you load
at runtime by loading different files when running in development versus production.
This is common and is included out of the box in most ASP.NET Core templates, and
in the ASP.NET Core 2.0 CreateDefaultBuilder helper method.
11.5.2 Loading environment-specific configuration files
The EnvironmentName value is determined early in the process of bootstrapping your
application, before your ConfigurationBuilder is created. This means you can
dynamically change which configuration providers are added to the builder, and
hence which configuration values are loaded when the IConfigurationRoot is built.
A common pattern is to have an optional, environment-specific appsettings
.ENVIRONMENT.json file that’s loaded after the default appsettings.json file. This list-
ing shows how you could achieve this in the AddAppConfiguration method of Program.
public class Program
{
/* Additional Program configuration*/
public static void AddAppConfiguration(
WebHostBuilderContext hostingContext,
IConfigurationBuilder config)
{
var env = hostingContext.HostingEnvironment;
config
Listing 11.13 Adding environment-specific appsettings.json files
The current
IHostingEnvironment is
available on
WebHostBuilderContext.
328 CHAPTER 11 Configuring an ASP.NET Core application
.AddJsonFile(
"appsettings.json",
optional: false)
.AddJsonFile
$"appsettings.{env.EnvironmentName}.json",
optional: true);
}
}
With this pattern, a global appsettings.json file contains settings applicable to most
environments. Additional, optional, JSON files called appsettings.Development.json,
appsettings.Staging.json, and appsettings.Production.json are subsequently added to
ConfigurationBuilder, depending on the current EnvironmentName.
Any settings in these files will overwrite values from the global appsettings.json, if
they have the same key, as you’ve seen previously. This lets you do things like set the
logging to be verbose in the development environment only and switch to more selec-
tive logs in production.
Another common pattern is to completely add or remove configuration providers
depending on the environment. For example, you might use the User Secrets pro-
vider when developing locally, but Azure Key Vault in production. This listing shows
how you could use IHostingEnvironment to conditionally include the User Secrets
provider.
public class Program
{
/* Additional Program configuration*/
public static void AddAppConfiguration(
WebHostBuilderContext hostingContext,
IConfigurationBuilder config)
{
var env = hostingContext.HostingEnvironment
config
.AddJsonFile(
"appsettings.json",
optional: false)
.AddJsonFile
$"appsettings.{env.EnvironmentName}.json",
optional: true);
if(env.IsDevelopment())
{
builder.AddUserSecrets<Startup>();
}
}
}
Another common place to customize your application based on the environment
is when setting up your middleware pipeline. In chapter 3, you learned about
Listing 11.14 Conditionally including the User Secrets configuration provider
It’s common to make the
base appsettings.json
compulsory.
Adds an optional
environment-
specific JSON file
where the
filename varies
with the
environment
Extension methods make checking the
environment simple and explicit.
In Staging and Production,
the User Secrets provider
wouldn’t be used.
329
Configuring an application for multiple environments
DeveloperExceptionPageMiddleware and how you should use it when developing
locally. The following listing shows how you can use IHostingEnvironment to control
your pipeline in this way, so that when you’re in Staging or Production, your app uses
ExceptionHandlerMiddleware instead.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
You can inject IHostingEnvironment anywhere in your app, but I’d advise against
using it in your own services, outside of Startup and Program. It’s far better to use the
configuration providers to customize strongly typed settings based on the current
hosting environment.
As useful as it is, setting IHostingEnvironment can be a little cumbersome if you
want to switch back and forth between different environments during testing. Person-
ally, I’m always forgetting how to set environment variables on the various operating
systems I use. The final skill I’d like to teach you is how to set the hosting environment
when you’re developing.
11.5.3 Setting the hosting environment
In this section, I’ll show you a couple of ways to set the hosting environment when
you’re developing. These makes it easy to test a specific app’s behavior in different
environments, without having to change the environment for all the apps on your
machine.
If your ASP.NET Core application can’t find an ASPNETCORE_ENVIRONMENT
environment variable when it starts up, it defaults to a production environment, as
shown in figure 11.6. This means that when you deploy to your configuration provid-
ers, you’ll be using the correct environment by default.
Listing 11.15 Using IHostingEnvironment to customize your middleware pipeline
In Development,
DeveloperExceptionPageMiddleware
is added to the pipeline.
In Production, the pipeline uses
ExceptionHandlerMiddleware instead.
330 CHAPTER 11 Configuring an ASP.NET Core application
TIP By default, the current hosting environment is logged to the console at
startup, which can be useful for quickly checking that the environment vari-
able has been picked up correctly.
If you’re working with the .NET CLI and a text editor, you’re probably going to be
comfortable setting environment variables from the command line for your OS, so
you’ll want to set your ASPNETCORE_ENVIRONMENT development machine envi-
ronment to Development.
If you’re using Visual Studio, you can set temporary environment variables for
when you debug your application. Double-click the Properties node and choose the
Debug tab; you can see in figure 11.7 that the ASPNETCORE_ENVIRONMENT is set
to Development, so there’s no need to dive back to the command line if you don’t
want to.
TIP These values are all stored in the properties/launchSettings.json file, so
you can edit them by hand if you prefer.1
1
In ASP.NET Core 2.1 you can use the launchSettings.json file with both the CLI tools and Visual Studio.
If the WebHostBuilder can’t find
the ASPNETCORE_ENVIRONMENT
variable at runtime, it will default
to Production.
Figure 11.6 By default, ASP.NET Core applications run in the Production hosting environment.
You can override this by setting the ASPNETCORE_ENVIRONMENT variable.
Figure 11.7 You can set temporary environment variables when you debug your application in Visual Studio.
331
Configuring an application for multiple environments
Visual Studio Code uses a similar approach with its own launch.json file. Add the
ASPNETCORE_ENVIRONMENT variable, as shown in figure 11.8.
These temporary environment variables are a great way to test your application in
different variables. In Visual Studio, you could even add a new debugging profile, IIS
Express (Production) perhaps, so you can quickly switch between testing different
environments.
There’s one final trick I’ve seen used. If you want to completely hardcode the envi-
ronment your app runs in to a single value, you can use the UseEnvironment exten-
sion method on WebHostBuilder, as shown next. This forces the app to use the
value you provide. I still suggest using one of the standard values though, preferably
EnvironmentName.Production.
public class Program
{
public static void Main(string[] args)
{
Listing 11.16 Hardcoding EnvironmentName with UseEnvironment
You can define temporary variables to
use when debugging your application
in the launch.json file.
Figure 11.8 Visual Studio code lets you define temporary environment variables to use when debugging. Your
application will treat them the same as any other environment variable but they won’t affect other applications,
only the ASP.NET Core app you’re debugging.
332 CHAPTER 11 Configuring an ASP.NET Core application
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
new WebHostBuilder()
.UseEnvironment(EnvironmentName.Production)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration( /* Not shown*/)
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
}
TIP This approach only makes sense if you always want to run your app in a
single environment. That’s overly restrictive, so I recommend setting the host-
ing environment using an environment variable or your IDE instead.
That brings us to the end of this chapter on configuration. Configuration isn’t glam-
orous, but it’s an essential part of all apps. The ASP.NET Core configuration provider
model handles a wide range of scenarios, letting you store settings and secrets in a
variety of locations.
Simple settings can be stored in appsettings.json, where they’re easy to tweak and
modify during development, and can be overwritten by using environment-specific
JSON files. Meanwhile, your secrets and sensitive settings can be stored outside
the project file, in the User Secrets manager, or as environment variables. This gives
you both flexibility and safety—as long as you don’t go writing your secrets to
appsettings.json!
In the next chapter, we’ll take a brief look at the new object relational mapper that
fits well with ASP.NET Core: Entity Framework Core. We’ll only be getting a taste of it
in this book, but you’ll learn how to load and save data, build a database from your
code, and migrate the database as your code evolves.
Summary
 Anything that could be considered a setting or a secret is normally stored as a
configuration value.
 ASP.NET Core uses configuration providers to load key-value pairs from a vari-
ety of sources. Applications can use many different configuration providers.
 ConfigurationBuilder describes how to construct the final configuration rep-
resentation for your app and IConfigurationRoot holds the configuration val-
ues themselves.
 You create a configuration object by adding configuration providers to an
instance of ConfigurationBuilder using extension methods such as AddJson-
File(). WebHostBuilder creates the ConfigurationBuilder instance for you
and calls Build() to create an instance of IConfigurationRoot.
 ASP.NET Core includes built-in providers for JSON files, XML files, environ-
ment files, command-line arguments, and Azure Key Vault, among others.
This ignores ASPNETCORE_ENVIRONMENT and
hardcodes the environment to Production.
333
Summary
 The order in which you add providers to ConfigurationBuilder is important;
subsequent providers replace the values of settings defined in earlier providers.
 Configuration keys aren’t case-sensitive.
 ASP.NET Core 1.x doesn’t register the IConfigurationRoot object with the DI
container by default. In ASP.NET Core 2.0, it’s registered as an IConfiguration
object.
 You can retrieve settings from IConfiguration directly using the indexer syn-
tax, for example Configuration["MySettings:Value"].
 In ASP.NET Core 2.0, the CreateDefaultBuilder method configures JSON,
environment variable, command-line argument, and User Secret providers for
you. You can customize the configuration providers used in your app by manu-
ally creating a WebHostBuilder and calling ConfigureAppConfiguration.
 In production, store secrets in environment variables. These can be loaded
after your file-based settings in the configuration builder.
 On development machines, the User Secrets Manager is a more convenient
tool than using environment variables. It stores secrets in your OS user’s profile,
outside the project folder.
 Be aware that neither environment variables nor the User Secrets Manager tool
encrypt secrets, they merely store them in locations that are less likely to be
made public, as they’re outside your project folder.
 File-based providers, such as the JSON provider, can automatically reload con-
figuration values when the file changes. This allows you to update configuration
values in real time, without having to restart your app.
 Use strongly typed POCO options classes to access configuration in your app.
 Use the Configure<T>() extension method in ConfigureServices to bind your
POCO options objects to ConfigurationSection.
 You can inject the IOptions<T> interface into your services using DI. You can
access the strongly typed options object on the Value property.
 If you want to reload your POCO options objects when your configuration
changes, use the IOptionsSnapshot<T> interface instead.
 Applications running in different environments, Development versus Produc-
tion for example, often require different configuration values.
 ASP.NET Core determines the current hosting environment using the
ASPNETCORE_ENVIRONMENT environment variable. If this variable isn’t set,
the environment is assumed to be Production.
 The current hosting environment is exposed as an IHostingEnvironment
interface. You can check for specific environments using IsDevelopment(),
IsStaging(), and IsProduction().
 You can use the IHostingEnvironment object to load files specific to the cur-
rent environment, such as appsettings.Production.json.
334
Saving data with Entity
Framework Core
Most applications that you’ll build with ASP.NET Core will require storing and
loading some kind of data. Even the examples so far in this book have assumed you
have some sort of data store—storing exchange rates, user shopping carts, or the
locations of physical main street stores. I’ve glossed over this for the most part but,
typically, you’ll store this data in a database.
This chapter includes
 What Entity Framework Core is and why you
should use it
 Adding Entity Framework Core to an ASP.NET Core
application
 Building a data model and using it to create a
database
 Querying, creating, and updating data using
Entity Framework Core
335
Working with databases can often be a rather cumbersome process. You have to
manage connections to the database, translate data from your application to a format
the database can understand, as well as hande a plethora of other subtle issues.
You can manage this complexity in a variety of ways, but I’m going to focus on
using a new library built for .NET Core: Entity Framework Core (EF Core). EF Core is
a library that lets you quickly and easily build database access code for your ASP.NET
Core applications. It’s modeled on the popular Entity Framework 6.x library, but has
significant changes that mean it stands alone in its own right and is more than an
upgrade.
The aim of this chapter is to provide a quick overview of EF Core and how you can
use it in your applications to quickly query and save to a database. You’ll have enough
knowledge to connect your app to a database, and to manage schema changes to the
database, but I won’t be going into great depth on any topics.
NOTE For an in-depth look at EF Core I recommend, Entity Framework Core in
Action by Jon P Smith (Manning, 2018). Alternatively, you can read about EF
Core and its cousin, Entity Framework, on the Microsoft documentation web-
site at https://docs.microsoft.com/en-us/ef/core/index.
Section 12.1 introduces EF Core and explains why you might want to use it in your
applications. You’ll learn how the design of EF Core helps you to quickly iterate on
your database structure and reduce the friction of interacting with a database.
In section 12.2, you’ll learn how to add EF Core to an ASP.NET Core app and con-
figure it using the ASP.NET Core configuration system. You’ll see how to build a
model for your app that represents the data you’ll store in the database and how to
hook it into the ASP.NET Core DI container.
NOTE For this chapter, and the rest of the book, I’m going to be using SQL
Server Express’ LocalDB feature. This is installed as part of Visual Studio 2017
(when you choose the .NET Desktop Development workload) and provides a
lightweight SQL Server engine.1
Very little of the code is specific to SQL
Server, so you should be able to follow along with a different database if you
prefer.
No matter how carefully you design your original data model, the time will come
when you need to change it. In section 12.3, I show how you can easily update your
model and apply these changes to the database itself, using EF Core for all the heavy
lifting.
Once you have EF Core configured and a database created, section 12.4 shows how
to use EF Core in your application code. You’ll see how to create, read, update, and
delete (CRUD) records, as well as some of the patterns to use when designing your
data access.
1
You can read more about LocalDB at http://mng.bz/24P8.
336 CHAPTER 12 Saving data with Entity Framework Core
In section 12.5, I highlight a few of the issues you’ll want to take into consideration
when using EF Core in a production app. A single chapter on EF Core can only offer a
brief introduction to all of the related concepts though, so if you choose to use EF
Core in your own applications—especially if this is your first time using such a data
access library—I strongly recommend reading more once you have the basics from
this chapter.
Before we get into any code, let’s look at what EF Core is, what problems it solves,
and when you might want to use it.
12.1 Introducing Entity Framework Core
Database access code is ubiquitous across web applications. Whether you’re building
an e-commerce app, a blog, or the Next Big Thing™, chances are you’ll need to inter-
act with a database.
Unfortunately, interacting with databases from app code is often a messy affair and
there are many different approaches you can take. For example, something as simple
as reading data from a database requires handling network connections, writing SQL
statements, and handling variable result data. The .NET ecosystem has a whole array
of libraries you can use for this, ranging from the low-level ADO.NET libraries, to
higher-level abstractions like EF Core.
In this section, I describe what EF Core is and the problem it’s designed to solve. I
cover the motivation behind using an abstraction such as EF Core, and how it helps to
bridge the gap between your app code and your database. As part of that, I present
some of the trade-offs you’ll make by using it in your apps, which should help you
decide if it’s right for your purposes. Finally, we’ll take a look at an example EF Core
mapping, from app code to database, to get a feel for EF Core’s main concepts.
12.1.1 What is EF Core?
EF Core is a library that provides an object-oriented way to access databases. It acts as
an object-relational mapper (ORM), communicating with the database for you and map-
ping database responses to .NET classes and objects, as shown in figure 12.1.
DEFINITION With an object-relational mapper (ORM), you can manipulate a
database using object-oriented concepts such as classes and objects by map-
ping them to database concepts such as tables and columns.
EF Core is based on, but is distinct from, the existing Entity Framework libraries (cur-
rently, up to version 6.x). It was built as part of the .NET Core push to work cross-
platform, but with additional goals in mind. In particular, the EF Core team wanted to
make a highly performant library that could be used with a wide range of databases.
There are many different types of databases, but probably the most commonly
used family is relational databases, accessed using Structured Query Language (SQL).
This is the bread and butter of EF Core; it can map Microsoft SQL Server, MySQL,
337
Introducing Entity Framework Core
Postgres, and many other relational databases. It even has a cool in-memory feature
you can use when testing to create a temporary database. EF Core uses a provider
model, so that support for other relational databases can be plugged in later, as they
become available.
NOTE EF Core was designed to work with nonrelational, NoSQL, or document
databases too but, at the time of writing, that feature hasn’t yet been imple-
mented.
That covers what EF Core is, but it doesn’t dig into why you’d want to use it. Why not
access the database directly using the traditional ADO.NET libraries? Most of the
arguments for using EF Core can be applied to ORMs in general, so what are the
advantages of an ORM?
12.1.2 Why use an object-relational mapper?
One of the biggest advantages an ORM brings is the speed with which you can develop
an application. You can stay in the familiar territory of object-oriented .NET, in many
cases without ever needing to directly manipulate a database or write custom SQL.
.NET classes map to
tables and properties
map to columns.
References between
types map to foreign
key relationships
between tables.
Product
Product
Product
Each object (an instance
of a class) maps to a
row in a table.
.NET Application Database
Products
PK
FK1
Id
CategoryId
Name
Price
PK
Categories
Id
Name
Product
Class
Properties:
+ Id: int
+ Name: string
+ Price: decimal
+ Category: Category
Category
Class
Properties:
+ Id: int
+ Name: string
Id
1
2
3
Products
CategoryId
12
45
123
Name
Mac mini
iPad
Xbox One
Price
479.00
339.00
280.00
Figure 12.1 EF Core maps .NET classes and objects to database concepts such as tables and rows.
338 CHAPTER 12 Saving data with Entity Framework Core
Imagine you have an e-commerce site and you want to load the details of a
product from the database. Using low-level database access code, you’d have to open a
connection to the database, write the necessary SQL using the correct table and column
names, read the data over the connection, create a POCO to hold the data, and manually
set the properties on the object, converting the data to the correct format as you go.
Sounds painful, right?
An ORM, such as EF Core, takes care of most of this for you. It handles the connec-
tion to the database, generating the SQL, and mapping data back to your POCO
objects. All you need to provide is a LINQ query describing the data you want to
retrieve.
ORMs serve as high-level abstractions over databases, so they can significantly
reduce the amount of plumbing code you need to write to interact with a database. At
the most basic level, they take care of mapping SQL statements to objects and vice versa,
but most ORMs take this a step further and provide a number of additional features.
ORMs like EF Core keep track of which properties have changed on any objects
they rehydrate from the database. This lets you load an object from the database by
mapping it from a database table, modify it in .NET code, and then ask the ORM to
update the associated record in the database. The ORM will work out which proper-
ties have changed and issue update statements for the appropriate columns, saving
you a bunch of work.
As is so often the case in software development, using an ORM has its drawbacks.
One of the biggest advantages of ORMs is also their Achilles heel—they hide you from
the database. Sometimes, this high level of abstraction can lead to problematic data-
base query patterns in your apps. A classic example is the N+1 problem, where what
should be a single database request turns into separate requests for every single row in
a database table.
Another commonly cited drawback is performance. ORMs are abstractions over a
number of concepts, and so inherently do more work than if you were to hand craft
every piece of data access in your app. Most ORMs, EF Core included, trade off some
degree of performance for ease of development.
That said, if you’re aware of the pitfalls of ORMs, you can often drastically simplify
the code required to interact with a database. As with anything, if the abstraction
works for you, use it, otherwise, don’t. If you only have minimal database access
requirements, or you need the best performance you can get, then an ORM such as
EF Core may not be the right fit.
An alternative is to get the best of both worlds: use an ORM for the quick develop-
ment of the bulk of your application, and then fall back to lower level APIs such as
ADO.NET for those few areas that prove to be the bottlenecks in your application.
That way, you can get good-enough performance with EF Core, trading off perfor-
mance for development time, and only optimize those areas that need it.
339
Introducing Entity Framework Core
Even if you do decide to use an ORM in your app, there are many different ORMs
available for .NET, of which EF Core is one. Whether EF Core is right for you will
depend on the features you need and the trade-offs you’re willing to make to get them.
The next section compares EF Core to Microsoft’s other offering, Entity Framework,
but there many other alternatives you could consider, such as Dapper and NHibernate,
each with their own set of trade-offs.
12.1.3 When should you choose EF Core?
Microsoft designed EF Core as a reimagining of the mature Entity Framework 6.x (EF
6.x) ORM, which it released in 2008. With ten years of development behind it, EF 6.x
is a stable and feature-rich ORM, though it runs on the full .NET Framework, not
.NET Core.
In contrast, EF Core, like .NET Core and ASP.NET Core, is a relatively new project.
The APIs of EF Core are designed to be close to that of EF 6.x—though they aren’t iden-
tical—but the core components have been completely rewritten. You should consider
EF Core as distinct from EF 6.x; upgrading directly from EF 6.x to EF Core is nontrivial.
Microsoft supports both EF Core and EF 6.x, and both will see ongoing improve-
ments, so which should you choose? You need to consider a number of things:
 Cross platform—EF Core targets .NET Standard, so it can be used in cross-
platform apps that target .NET Core. EF 6.x targets .NET Framework, so you’re
limited to Windows only.
 Database providers—Both EF 6.x and EF Core let you connect to various database
types by using pluggable providers. EF Core has a growing number of providers,
but there aren’t as many for EF 6.x. If there isn’t a provider for the database
you’re using, that’s a bit of a deal breaker!
 Performance—The performance of EF 6.x has been a bit of a black mark on its
record, so EF Core aims to rectify that. EF Core is designed to be fast and light-
weight, significantly outperforming EF 6.x. But it’s unlikely to ever reach the
performance of a more lightweight ORM, such as Dapper, or handcrafted SQL
statements.
 Features—Features are where you’ll find the biggest disparity between EF 6.x
and EF Core. EF Core has some features that EF 6.x doesn’t have (batching
statements, client-side key generation, in-memory database for testing), but EF
6.x is much more feature-rich than EF Core.
At the time of writing, EF Core is missing some headline features, such as lazy-
loading and full server-side Group By, but EF Core is under active development,
so those features will no doubt appear soon.1
For example, limited Group By
1
For a detailed list of feature differences between EF 6.x and EF Core, see the documentation at https://
docs.microsoft.com/en-us/ef/efcore-and-ef6/features.
340 CHAPTER 12 Saving data with Entity Framework Core
support should be available in EF Core 2.1. In contrast, EF 6.x will likely only see
incremental improvements and bug fixes, rather than major feature additions.
Whether these trade-offs and limitations are a problem for you will depend a lot on
your specific app. It’s a lot easier to start a new application bearing these limitations in
mind than trying to work around them later.
WARNING EF Core isn’t recommended for everyone. Be sure you understand
the trade-offs involved, and keep an eye on the guidance from the EF team
here: https://docs.microsoft.com/en-us/ef/efcore-and-ef6/choosing.
If you’re working on a new ASP.NET Core application, you want to use an ORM for
rapid development, and you don’t require any of the unavailable features, then EF
Core is a great contender. It’s also supported out of the box by various other subsys-
tems of ASP.NET Core. For instance, in chapter 14 you’ll see how to use EF Core with
the ASP.NET Core Identity authentication system for managing users in your apps.
Before we get into the nitty-gritty of using EF Core in your app, I’ll describe the
application we’re going to be using as the case study for this chapter. We’ll go over
the application and database details and how to use EF Core to communicate
between the two.
12.1.4 Mapping a database to your application code
EF Core focuses on the communication between an application and a database, so to
show it off, we need an application! This chapter uses the example of a simple cook-
ing app that lists recipes, and lets you view a recipe’s ingredients, as shown in figure
12.2. Users can browse for recipes, add new ones, edit recipes, and delete old ones.
This is obviously a simple application, but it contains all the database interactions you
need with its two entities, Recipe and Ingredient.
DEFINITION An entity is a .NET class that’s mapped by EF Core to the data-
base. These are classes you define, typically as POCO classes that can be saved
and loaded by mapping to database tables using EF Core.
When you interact with EF Core, you’ll be primarily using POCO entities and a data-
base context that inherits from the DbContext EF Core class. The entity classes are the
object-oriented representations of the tables in your database; they represent the data
you want to store in the database. You use the DbContext in your application to both
configure EF Core and to access the database at runtime.
NOTE You can potentially have multiple DbContexts in your application and
can even configure them to integrate with different databases.
When your application first uses EF Core, EF Core creates an internal representation
of the database, based on the DbSet<T> properties on your application’s DbContext
and the entity classes themselves, as shown in figure 12.3.
341
Introducing Entity Framework Core
For your recipe app, EF Core will build a model of the Recipe class because it’s
exposed on the AppDbContext as a DbSet<Recipe>. Furthermore, EF Core will loop
through all the properties on Recipe, looking for types it doesn’t know about, and
add them to its internal model. In your app, the Ingredients collection on Recipe
exposes the Ingredient entity as an ICollection<Ingredient>, so EF Core models
the entity appropriately.
Each entity is mapped to a table in the database, but EF Core also maps the rela-
tionships between the entities. Each recipe can have many ingredients, but each
ingredient (which has a name, quantity, and unit) belongs to one recipe, so this is a
many-to-one relationship. EF Core uses that knowledge to correctly model the equiva-
lent many-to-one database structure.
The main page of the application
shows a list of all current recipes.
Click View to show the detail
page for the recipe. This includes
the ingredients associated with
the recipe.
Click Create to add a new
recipe to the application.
You can also edit or delete the recipe.
Figure 12.2 The cookery app lists recipes. You can view, update, and delete recipes, or create new ones.
342 CHAPTER 12 Saving data with Entity Framework Core
NOTE Two different recipes, say fish pie and lemon chicken, may use an
ingredient that has both the same name and quantity, for example the juice
of one lemon, but they’re fundamentally two different instances. If you
update the lemon chicken recipe to use two lemons, you wouldn’t want this
change to automatically update the fish pie to use two lemons too!
EF Core uses the internal model it builds when interacting with the database. This
ensures it builds the correct SQL to create, read, update, and delete entities.
Right, it’s about time for some code! In the next section, you’ll start building the
recipe app. You’ll see how to add EF Core to an ASP.NET Core application, configure
a database provider, and design your application’s data model.
12.2 Adding EF Core to an application
In this section, we’re going to focus on getting EF Core installed and configured in
your ASP.NET Core recipe app. As we’re talking about EF Core in this chapter, I’m
Recipe
+ RecipeId: int
+ Name: string
+ Ingredients: ICollection<Ingredient>
Ingredient
+ IngredientId: int
+ RecipeId: int
+ Name: string
+ Quantity: decimal
+ Unit: string
1
0..*
The application’s DbContext serves
as the entry point for all interactions
with EF Core.
2. It scans all the properties
on known entities for linked
types and adds them to its
internal model.
AppDbContext
+ Recipes: DbSet<Recipe>
1. EF Core looks for any DbSet
properties on the DbContext (Recipes)
and adds them to its internal model.
3. EF Core uses relationships
between .NET classes to model
the relationship between
database tables.
.NET application
EF Core internal
model
Ingredients
PK IngredientId
RecipeId
Name
Quantity
Units
Recipes
PK RecipeId
Name
Figure 12.3 EF Core creates an internal model of your application’s data model by exploring the
types in your code. It adds all of the types referenced in the DbSet<> properties on your app’s
DbContext, and any linked types.
343
Adding EF Core to an application
not going to go into how to create the application in general—I created a simple MVC
app using Razor to render the HTML, nothing fancy.
Interaction with EF Core occurs in a service layer that encapsulates all of the data
access outside of the MVC framework, as shown in figure 12.4. This keeps your con-
cerns separated and makes both your services and MVC controllers testable.
Adding EF Core to an application is a multistep process:
1 Choose a database provider; for example, MySQL, Postgres, or MS SQL Server.
2 Install the EF Core NuGet packages.
3 Design your app’s DbContext and entities that make up your data model.
4 Register your app’s DbContext with the ASP.NET Core DI container.
5 Use EF Core to generate a migration describing your data model.
6 Apply the migration to the database to update the database’s schema.
This might seem a little daunting already, but we’ll walk through steps 1–4 in this sec-
tion, and steps 5–6 in section 12.3, so it won’t take long. Given the space constraints of
this chapter, I’m going to be sticking to the default conventions of EF Core in the
code I show. EF Core is far more customizable than it may initially appear, but I
1. A request is received to
the URL /recipe.
Request
Index
action method
View
2. The request is routed to the
RecipeController.Index action.
5. The controller uses the
List<RecipeSummary>
returned by the RecipeService
as the model for the ViewResult.
List<RecipeSummary>
Data model
RecipeService
Routing
RecipeController
HTML
3. The index action calls the
RecipeService to fetch the list
of RecipeSummary models.
EF Core
Db
4. The RecipeService calls into
EF Core to load the Recipes from
the database and uses them to
create instances of RecipeSummary.
Figure 12.4 Handling a request by loading data from a database using EF Core. Interaction with EF Core is
restricted to RecipeService only—the MVC controller doesn’t access EF Core directly.
344 CHAPTER 12 Saving data with Entity Framework Core
encourage you to stick to the defaults wherever possible. It will make your life easier in
the long run!
The first step in setting up EF Core is to decide which database you’d like to inter-
act with. It’s likely that a client or your company’s policy will dictate this to you, but it’s
still worth giving the choice some thought.
12.2.1 Choosing a database provider and installing EF Core
EF Core supports a range of databases by using a provider model. The modular
nature of EF Core means you can use the same high-level API to program against
different underlying databases, and EF Core knows how to generate the necessary
implementation-specific code and SQL statements.
You probably already have a database in mind when you start your application, and
you’ll be pleased to know that EF Core has got most of the popular ones covered. Add-
ing support for a particular database involves adding the correct NuGet package to
your csproj file. For example
 PostgreSQL—Npgsql.EntityFrameworkCore.PostgreSQL
 Microsoft SQL Server—Microsoft.EntityFrameworkCore.SqlServer
 MySQL—MySql.Data.EntityFrameworkCore
 SQLite—Microsoft.EntityFrameworkCore.SQLite
Some of the database provider packages are maintained by Microsoft, some are main-
tained by the open source community, and some may require a paid license (for
example, the Oracle provider, so be sure to check your requirements. You can find a
list of providers here at https://docs.microsoft.com/en-us/ ef/core/providers/.
You install a database provider into your application in the same way as any other
library: by adding a NuGet package to your project’s csproj file and running dotnet
restore from the command line (or letting Visual Studio automatically restore for you).
EF Core is inherently modular, so you’ll want to install four different packages. I’m
using the SQL Server database provider with LocalDB for the recipe app, so I’ll be
using the SQL Server packages.
 Microsoft.EntityFrameworkCore.SqlServer—This is the main database provider pack-
age for using EF Core at runtime. It also contains a reference to the main EF
Core NuGet package.
 Microsoft.EntityFrameworkCore.Design—This contains shared design-time compo-
nents for EF Core.
 Microsoft.EntityFrameworkCore.SqlServer.Design—More design-time components
for EF Core, this time specific to Microsoft SQL Server.
 Microsoft.EntityFrameworkCore.Tools.DotNet—This package is optional, but you’ll
use it later on to create and update your database from the command line.
TIP ASP.NET Core 2.0 includes the EF Core libraries as part of the framework-
provided Microsoft.AspNetCore.All package. If you reference this in your csproj
345
Adding EF Core to an application
file, you won’t need to explicitly include the default EF Core NuGet libraries.
But there’s no harm if you do! You’ll still need to add the Microsoft.Entity-
FrameworkCore.Tools.DotNet tooling package to use the EF Core .NET CLI
commands, unless you're using ASP.NET Core 2.1. ASP.NET Core 2.1 auto-
matically installs the tooling packages globally, so you don't need to install
them in your project.
Listing 12.1 shows the recipe app’s csproj after adding the EF Core libraries. I’m going
to be building a .NET Core 2.0 app, so the only package I need to add is the tooling
package. Remember, you add NuGet packages as PackageReference elements, and
tool packages as DotNetCliToolReference elements.
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference
Include="Microsoft.AspNetCore.All" Version="2.0.0 " />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference
Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools"
Version="2.0.0 " />
<DotNetCliToolReference
Include="Microsoft.EntityFrameworkCore.Tools.DotNet"
Version="2.0.0" />
</ItemGroup>
</Project>
With these packages installed and restored, you have everything you need to start
building the data model for your application. In the next section, we’ll create the
entity classes and the DbContext for your recipe app.
12.2.2 Building a data model
In section 12.1.4, I showed an overview of how EF Core builds up its internal model of
your database from the DbContext and entity models. Apart from this discovery mech-
anism, EF Core is pretty flexible in letting you define your entities the way you want to,
as POCO classes.
Some ORMs require your entities to inherit from a specific base class or decorate
your models with attributes to describe how to map them. EF Core heavily favors a
convention over configuration approach, as you can see in this listing, which shows
the Recipe and Ingredient entity classes for your app.
Listing 12.1 Installing EF Core into an ASP.NET Core 1.1 application
The app targets
.NET Core 2.0.
Default ASP.NET Core metapackage and
tools for creating base MVC app
Tools for creating migrations
and managing the database
using the .NET CLI
346 CHAPTER 12 Saving data with Entity Framework Core
public class Recipe
{
public int RecipeId { get; set; }
public string Name { get; set; }
public TimeSpan TimeToCook { get; set; }
public bool IsDeleted { get; set; }
public string Method { get; set; }
public ICollection<Ingredient> Ingredients { get; set; }
}
public class Ingredient
{
public int IngredientId { get; set; }
public int RecipeId { get; set; }
public string Name { get; set; }
public decimal Quantity { get; set; }
public string Unit { get; set; }
}
These classes conform to certain default conventions that EF Core uses to build up a
picture of the database it’s mapping. For example, the Recipe class has a RecipeId
property and the Ingredient class has an IngredientId property. EF Core identifies
this pattern of TId as indicating the primary key of the table.
DEFINITION The primary key of a table is a value that uniquely identifies the
row among all the others in the table. It’s often an int or a Guid.
Another convention visible here is the RecipeId property on the Ingredient class. EF
Core interprets this to be a foreign key pointing to the Recipe class. When considered
with ICollection<Ingredient> on the Recipe class, this represents a many-to-one
relationship, where each recipe has many ingredients, but each ingredient only
belongs to a single recipe, as shown in figure 12.5.
DEFINITION A foreign key on a table points to the primary key of a different
table, forming a link between the two rows.
Many other conventions are at play here, such as the names EF Core will assume for
the database tables and columns, or the database column types it will use for each
property, but I’m not going to discuss them here. The EF Core documentation con-
tains details about all of the conventions, as well as how to customize them for your
application: https://docs.microsoft.com/en-us/ef/core/modeling/relational/.
TIP You can also use DataAnnotations attributes to decorate your entity
classes, controlling things like column naming or string length. EF Core will
use these attributes to override the default conventions.
Listing 12.2 Defining the EF Core entity classes
A Recipe can have many
Ingredients, represented
by ICollection.
347
Adding EF Core to an application
As well as the entities, you also define the DbContext for your application. This is the
heart of EF Core in your application, used for all your database calls. Create a custom
DbContext, in this case called AppDbContext, and derive from the DbContext base
class, as shown next. This exposes the DbSet<Recipe> so EF Core can discover and
map the Recipe entity. You can expose multiple instances of DbSet<> in this way.
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options) { }
public DbSet<Recipe> Recipes { get; set; }
}
The AppDbContext for your app is simple, containing a list of your root entities, but
you can do a lot more with it in a more complex application. If you wanted, you could
completely customize how EF Core maps entities to the database, but for this app
you’re going to use the defaults.
NOTE You didn’t list Ingredient on AppDbContext, but it will be modeled by
EF Core as it’s exposed on the Recipe. You can still access the Ingredient
objects in the database, but you have to go via the Recipe entity’s Ingredients
property to do so, as you’ll see in section 12.4.
Listing 12.3 Defining the application DbContext
Recipe
+ RecipeId = 123
+ Ingredients = ICollection<>
The Recipe object can have
many Ingredients, indicated
by an ICollection<Ingredient>.
Each Ingredient belongs
to a single Recipe, indicated
by a RecipeId property on
Ingredient.
RecipeId
123
123
Ingredients
IngredientId
1
2
Name
Apple Juice
Corn Sugar
RecipeId
123
124
Recipes
Name
Apfelwein
Pork Wellington
Ingredient
+ IngredientId = 1
+ RecipeId = 123
Ingredient
+ IngredientId = 2
+ RecipeId = 123
The many-to-one relationship
between the entities corresponds
to a foreign key relationship
between the database tables.
Figure 12.5 Many-to-one relationships in code are translated to foreign key relationships between
tables.
The constructor options object, containing
details such as the connection string
You’ll use the Recipes
property to query the
database.
348 CHAPTER 12 Saving data with Entity Framework Core
For this simple example, your data model consists of these three classes: AppDb-
Context, Recipe, and Ingredient. The two entities will be mapped to tables and their
columns to properties, and you’ll use the AppDbContext to access them.
NOTE This code first approach is typical, but if you have an existing database,
you can automatically generate the EF entities and DbContext instead. (More
information can be found at http://mng.bz/Ymtw.)
The data model is complete, but you’re not quite ready to use it yet. Your ASP.NET Core
app doesn’t know how to create your AppDbContext, and your AppDbContext needs a
connection string so that it can talk to the database. In the next section, we’ll tackle
both of these issues, and will finish setting up EF Core in your ASP.NET Core app.
12.2.3 Registering a data context
Like any other service in ASP.Net Core, you should register your AppDbContext with
the DI container. When registering your context, you also configure a database pro-
vider and set the connection string, so EF Core knows how to talk with the database.
You register the AppDbContext in the ConfigureServices method of Startup.cs.
EF Core provides a generic AddDbContext<T> extension method for this purpose,
which takes a configuration function for a DbContextOptionsBuilder instance. This
builder can be used to set a host of internal properties of EF Core and lets you com-
pletely replace the internal services of EF Core if you want.
The configuration for your app is, again, nice and simple, as you can see in the fol-
lowing listing. You set the database provider with the UseSqlServer extension
method, made available by the Microsoft.EntityFrameworkCore.SqlServer package,
and pass it a connection string.
public void ConfigureServices(IServiceCollection services)
{
var connString = Configuration
.GetConnectionString("DefaultConnection");
services.AddDbContext<AppDbContext>(
options => options.UseSqlServer(connString));
// Add other services.
}
NOTE If you’re using a different database provider, for example a provider
for PostgreSQL, you will need to call the appropriate Use* method on the
options object when registering your AppDbContext.
The connection string is a typical secret as I discussed in the previous chapter, so load-
ing it from configuration makes sense. At runtime, the correct configuration string
Listing 12.4 Registering a DbContext with the DI container
The connection string is taken
from configuration, from the
ConnectionStrings section.
Register your app’s DbContext by
using it as the generic parameter.
Specify the database
provider in the
customization options
for the DbContext.
349
Managing changes with migrations
for your current environment will be used, so you can use different databases when
developing locally and in production.
TIP You can configure your AppDbContext in other ways and provide the
connection string, such as with the OnConfiguring method, but I recommend
the method shown here for ASP.NET Core websites.
You now have a DbContext, AppDbContext, registered with the DI container, and a
data model corresponding to your database. Code-wise, you’re ready to start using EF
Core, but the one thing you don’t have is a database! In the next section, you’ll see
how you can easily use the .NET CLI to ensure your database stays up to date with your
EF Core data model.
12.3 Managing changes with migrations
Managing schema changes for databases, such as when you need to add a new table or
a new column, is notoriously difficult. Your application code is explicitly tied to a par-
ticular version of a database, and you need to make sure the two are always in sync.
DEFINITION Schema refers to how the data is organized in a database, includ-
ing, among others, the tables, columns, and the relationships between them.
When you deploy an app, you can normally delete the old code/executable and
replace it with the new code—job done. If you need to roll back a change, delete that
new code and deploy an old version of the app.
The difficulty with databases is that they contain data! That means that blowing it
away and creating a new database with every deployment isn’t possible.
A common best practice is to explicitly version a database’s schema in addition to
your application’s code. You can do this in a number of ways but, typically, you need to
store a diff between the previous schema of the database and the new schema, often as
a SQL script. You can then use libraries such as DbUp and FluentMigrator1
to keep
track of which scripts have been applied and ensure your database schema is up to
date. Alternatively, you can use external tools that manage this for you.
EF Core provides its own version of schema management called migrations. Migra-
tions provide a way to manage changes to a database schema when your EF Core data
model changes. A migration is a C# code file in your application that defines how the
data model changed—which columns were added, new entities, and so on. Migrations
provide a record over time of how your database schema evolved as part of your appli-
cation, so the schema is always in sync with your app’s data model.
You can use command-line tools to create a new database from the migrations, or
to update an existing database by applying new migrations to it. You can even rollback
a migration, which will update a database to a previous schema.
1
DbUp and FluentMigrator are open source projects, available at https://github.com/fluentmigrator/
fluentmigrator and https://github.com/DbUp/DbUp, respectively.
350 CHAPTER 12 Saving data with Entity Framework Core
WARNING Applying migrations modifies the database, so you always have to
be aware of data loss. If you remove a table from the database using a migra-
tion and then rollback the migration, the table will be recreated, but the data
it previously contained will be gone forever!
In this section, you’ll see how to create your first migration and use it to create a data-
base. You’ll then update your data model, create a second migration, and use it to
update the database schema.
12.3.1 Creating your first migration
Before you can create migrations, you’ll need to install the necessary tooling. There
are two primary ways to do this:
 Package manager console—Provides a number of PowerShell cmdlets for use
inside Visual Studio’s Package Manager Console (PMC). You can install them
directly from the PMC or by adding the Microsoft.EntityFrameworkCore.Tools
package to your project. They’re also included as part of the Microsoft.AspNet-
Core.All metapackage.
 .NET CLI—Cross-platform tooling that you can run from the command line.
You can install these by adding the Microsoft.EntityFrameworkCore.Tools.Dot-
Net package to your project, as you did in listing 12.4.
In this book, I’ll be using the cross-platform .NET CLI tools, but if you’re familiar
with EF 6.X or prefer to use the Visual Studio PMC, then there are equivalent com-
mands for all of the steps you’re going to take.1
You’ve already installed the .NET CLI
tools by adding the package to your csproj. You can check they installed correctly by
running dotnet ef --help. This should produce a help screen like the one shown
in figure 12.6.
TIP If you get the No executable found matching command “dotnet-ef” message
when running the preceding command, make sure you have restored pack-
ages and check the folder you’re executing from. You need to run the dotnet
ef tools from the project folder in which you have registered your AppDb-
Context, not at the solution-folder level.
With the tools installed, you can create your first migration by running the following
command and providing a name for the migration—in this case, "InitialSchema":
dotnet ef migrations add InitialSchema
This command creates three files in your solution:
 Migration file—A file with the Timestamp_MigrationName.cs format. This
describes the actions to take on the database, such as Create table or Add column.
 Migration designer.cs file—This file describes EF Core’s internal model of your
data model at the point in time the migration was generated.
1
Documentation for PowerShell cmdlets can be found at http://mng.bz/lm6J.
351
Managing changes with migrations
 AppDbContextModelSnapshot.cs—This describes EF Core’s current internal model.
This will be updated when you add another migration, so it should always be
the same as the current, latest migration.
EF Core can use AppDbContextModelSnapshot.cs to determine a database’s
previous state when creating a new migration, without interacting with the data-
base directly.
These three files encapsulate the migration process but adding a migration doesn’t
update anything in the database itself. For that, you must run a different command to
apply the migration to the database.
TIP You can, and should, look inside the migration file EF Core generates to
check what it will do to your database before running the following com-
mands. Better safe than sorry!
You can apply migrations in one of three ways:
 Using the .NET CLI
 Using the Visual Studio PowerShell cmdlets
 In code, by obtaining an instance of your AppDbContext and calling context
.Database.Migrate().
Which is best for you is a matter of how you’ve designed your application, how you’ll
update your production database, and your personal preference. I’ll use the .NET CLI
for now, but I’ll discuss some of these considerations in section 12.5.
You can apply migrations to a database by running
dotnet ef database update
Figure 12.6 Running the dotnet ef --help command to check the .NET CLI EF Core
tools are installed correctly.
352 CHAPTER 12 Saving data with Entity Framework Core
from the project folder of your application. I won’t go into the details of how this
works, but this command performs four steps:
1 Builds your application.
2 Loads the services configured in your app’s Startup class, including AppDb-
Context.
3 Checks whether the database in the AppDbContext connection string exists. If
not, creates it.
4 Updates the database by applying any unapplied migrations.
If everything is configured correctly, as I showed in section 12.2, then running this
command will set you up with a shiny new database, such as the one shown in figure
12.7!
When you apply the migrations to the database, EF Core creates the necessary tables
in the database and adds the appropriate columns and keys. You may have also
noticed the __EFMigrationsHistory table. EF Core uses this to store the names of
migrations that it’s applied to the database. Next time you run dotnet ef database
update, EF Core can compare this table to the list of migrations in your app and will
apply only the new ones to your database.
In the next section, we’ll look at how this makes it easy to change your data model,
and update the database schema, without having to recreate the database from
scratch.
The __EFMigrationsHistory contains
a list of all the migrations that have
been applied to the database.
The entities in our data model,
Recipe and Ingredient, correspond
to tables in the database.
The properties on the Recipe
entity correspond to the columns
in the Recipes table.
Figure 12.7 Applying migrations to a database will create the database if it doesn’t exist and
update the database to match EF Core’s internal data model. The list of applied migrations is
stored in the __EFMigrationsHistory table.
353
Managing changes with migrations
12.3.2 Adding a second migration
Most applications inevitably evolve, whether due to increased scope or simple mainte-
nance. Adding properties to your entities, adding new entities entirely, and removing
obsolete classes—all are likely.
EF Core migrations make this simple. Change your entities to your desired state,
generate a migration, and apply it to the database, as shown in figure 12.8. Imagine
you decide that you’d like to highlight vegetarian and vegan dishes in your recipe app
by exposing IsVegetarian and IsVegan properties on the Recipe entity.
public class Recipe
{
public int RecipeId { get; set; }
public string Name { get; set; }
public TimeSpan TimeToCook { get; set; }
public bool IsDeleted { get; set; }
public string Method { get; set; }
public bool IsVegetarian { get; set; }
public bool IsVegan { get; set; }
public ICollection<Ingredient> Ingredients { get; set; }
}
Listing 12.5 Adding properties to the Recipe entity
dotnet ef migrations add ExtraRecipeFields
20170525215800_NewFields.cs
20170525220541_ExtraRecipeFields.cs
Db
dotnet ef database update
1. Update your entities by
adding new properties
and relationships.
Properties:
+ RecipeId: int
+ Name: string
+ TimeToCook: Timespan
+ IsVegetarian: boolean
Recipe
Class
2. Create a new migration
from the command line and
provide a name for it.
3. Creating a migration generates a
migration file and a migration designer file.
It also updates the app’s DbContext snapshot,
but it does not update the database.
4. You can apply the migration to
the database using the command line.
This will update the database schema
to match your entities.
>_
>_
+ Is Vegan:boolean
Figure 12.8 Creating a second migration and applying it to the database using the command-
line tools.
354 CHAPTER 12 Saving data with Entity Framework Core
After changing your entities, you need to update EF Core’s internal representation of
your data model. You do this in the exact same way as for the first migration, by calling
dotnet ef migrations add and providing a name for the migration:
dotnet ef migrations add ExtraRecipeFields
This creates a second migration in your project by adding the migration file and its
.designer.cs snapshot file and updating AppDbContextModelSnapshot.cs, as shown in
figure 12.9.
As before, this creates the migration’s files, but it doesn’t modify the database. You can
apply the migration, and update the database, by running
dotnet ef database update
This compares the migrations in your application to the __EFMigrationsHistory table
on your database to see which migrations are outstanding and then runs them.
EF Core will run the 20170525220541_ExtraRecipeFields migration, adding the
IsVegetarian and IsVegan fields to the database, as shown in figure 12.10.
Using migrations is a great way to ensure your database is versioned along with
your app code in source control. You can easily check out your app’s source code for a
historical point in time and recreate the database schema that your application used
at that point.
Migrations are easy to use when you’re working alone, or when you’re deploying to
a single web server, but even in these cases, there are important things to consider
Creating a migration adds a cs file
to your solution with a timestamp
and the name you gave the migration.
It also adds a Designer.cs file that
contains a snapshot of EF Core’s
internal data model at that
point in time.
The AppDbContextModelSnapshot
is updated to match the snapshot
for the new migration.
Figure 12.9 Adding a second migration adds a new migration file and a migration
Designer.cs file. It also updates AppDbContextModelSnapshot to match the new migration’s
Designer.cs file.
355
Querying data from and saving data to the database
when deciding how to manage your databases. For apps with multiple web servers
using a shared database, or for containerized applications, you have even more things
to think about.
This book is about ASP.NET Core, not EF Core, so I don’t want to dwell on data-
base management too much, but section 12.5 points out some of the things you need
to bear in mind when using migrations in production.
In the next section, we’ll get back to the meaty stuff—defining our business logic
and performing CRUD operations on the database.
12.4 Querying data from and saving data to the database
Let’s review where you are in creating the recipe application:
 You created a simple data model for the application, consisting of recipes and
ingredients.
 You generated migrations for the data model, to update EF Core’s internal
model of your entities.
 You applied the migrations to the database, so its schema matches EF Core’s
model.
In this section, you’ll build the business logic for your application by creating a
RecipeService. This will handle querying the database for recipes, creating new reci-
pes, and modifying existing ones. As this app only has a simple domain, I’ll be using
RecipeService to handle all of the requirements, but in your own apps you may have
multiple services that cooperate to provide the business logic.
NOTE For simple apps, you may be tempted to move this logic into your MVC
Controllers. I’d encourage you to resist this urge; extracting your business
logic to other services decouples the HTTP-centric nature of MVC from the
underlying business logic. This will often make your business logic easier to
test and more reusable.
Our database doesn’t have any data in it yet, so we’d better start by letting you create a
recipe!
Applying the second migration
to the database adds the new
fields to the Recipes table.
Figure 12.10 Applying the ExtraRecipeFields migration to the database adds the
IsVegetarian and IsVegan fields to the Recipes table.
356 CHAPTER 12 Saving data with Entity Framework Core
12.4.1 Creating a record
In this section, you’re going to build the functionality to let users create a recipe in
the app. This will primarily consist of a form that the user can use to enter all the
details of the recipe using Razor Tag Helpers, which you learned about in chapters 7
and 8. This form is posted to the CreateRecipe action on RecipeController, which
uses model binding and validation attributes to confirm the request is valid, as you saw
in chapter 6.
If the request is valid, the action method calls RecipeService to create the new
Recipe object in the database. As EF Core is the topic of this chapter, I’m going to
focus on this service alone, but you can always check out the source code for this book
if you want to see how the MVC controller and Razor templates fit together.
The business logic for creating a recipe in this application is simple—there is no
logic! Map the command binding model provided by the controller to a Recipe entity
and its Ingredients, add the Recipe object to AppDbContext, and save that in the
database, as shown in figure 12.11.
WARNING Many simple, equivalent, sample applications using EF or EF Core
allow you to bind directly to the Recipe entity as the view model for your MVC
actions. Unfortunately, this exposes a security vulnerability known as over-
posting and is a bad practice. If you want to avoid the boilerplate mapping
code in your applications, consider using a library such as AutoMapper
(http://automapper.org/).
Creating an entity in EF Core involves adding a new row to the mapped table. For your
application, whenever you create a new Recipe, you also add the linked Ingredient
entities. EF Core takes care of linking these all correctly by creating the correct
RecipeId for each Ingredient in the database.
The bulk of the code required for this example involves translating from Create-
RecipeCommand to the Recipe entity—the interaction with the AppDbContext consists
of only two methods: Add() and SaveChanges().
readonly AppDbContext _context;
public int CreateRecipe(CreateRecipeCommand cmd)
{
var recipe = new Recipe
{
Name = cmd.Name,
TimeToCook = new TimeSpan(
cmd.TimeToCookHrs, cmd.TimeToCookMins, 0),
Method = cmd.Method,
IsVegetarian = cmd.IsVegetarian,
IsVegan = cmd.IsVegan,
Ingredients = cmd.Ingredients?.Select(i =>
Listing 12.6 Creating a Recipe entity in the database
An instance of the AppDbContext is
injected in the class constructor using DI.
CreateRecipeCommand
is passed in from the
action method.
Create a Recipe by
mapping from the
command object to the
Recipe entity.
357
Querying data from and saving data to the database
new Ingredient
{
Name = i.Name,
Quantity = i.Quantity,
Unit = i.Unit,
}).ToList()
};
_context.Add(recipe);
_context.SaveChanges();
return recipe.RecipeId;
}
1. A request is POSTed to
the URL /recipe/Create.
Request
Create
action method
RedirectResult
2. The request is routed to
the Create action, and the
form body is bound to a
CreateRecipeCommand.
7. The action method uses
the RecipeId to create a
RedirectResult to the new
Recipe detail page.
Recipe
Map command
to entites
RecipeService.CreateRecipe()
3. The Create action calls the
CreateRecipe method on the
RecipeService, passing in the
CreateRecipeCommand.
Save using
DbContext
RecipeId
CreateRecipeCommand
SQL
4. A new Recipe object
is created from the
CreateRecipeCommand.
5. The Recipe is added
to EF Core using the
app’s DbContext.
6. EF Core generates the
SQL necessary to insert a
new row into the Recipes
table and returns the new
row’s RecipeId.
Db
Figure 12.11 Calling the Create action method and creating a new entity. A Recipe is created from the
CreateRecipeCommand and is added to the DbContext. EF Core generates the SQL to add a new row
to the Recipes table in the database.
Map each
CreateIngredientCommand
onto an Ingredient entity.
Tell EF Core to track the new entities.
Tell EF Core to write the
entities to the database.
EF Core populates the RecipeId field on
your new Recipe when it’s saved.
358 CHAPTER 12 Saving data with Entity Framework Core
All interactions with EF Core and the database start with an instance of AppDbContext,
which is typically DI injected via the constructor. Creating a new entity requires three
steps:
1 Create the Recipe and Ingredient entities.
2 Add the entities to EF Core’s list of tracked entities using _context.Add
(entity).
3 Execute the SQL INSERT statements against the database, adding the necessary
rows to the Recipe and Ingredient tables, by calling _context.SaveChanges().
If there’s a problem when EF Core tries to interact with your database—you haven’t
run the migrations to update the database schema, for example—it will throw an
exception. I haven’t shown it here, but it’s important to handle these in your applica-
tion so you’re not presenting users with an ugly error page when things go wrong.
Assuming all goes well, EF Core updates all the auto-generated IDs of your entities
(RecipeId on Recipe, and both RecipeId and IngredientId on Ingredient). Return
this ID to the MVC controller so it can use it, for example, to redirect to the View
Recipe page.
And there you have it—you’ve created your first entity using EF Core. In the next
section, we’ll look at loading these entities from the database so you can view them in
a list.
12.4.2 Loading a list of records
Now that you can create recipes, you need to write the code to view them. Luckily,
loading data is simple in EF Core, relying heavily on LINQ methods to control which
fields you need. For your app, you’ll create a method on RecipeService that returns
a summary view of a recipe, consisting of the RecipeId, Name, and TimeToCook as a
RecipeSummaryViewModel, as shown in figure 12.12.
NOTE Creating a view model is technically a UI concern rather than a busi-
ness logic concern. I’m returning them directly from RecipeService here
mostly to hammer home that you shouldn’t be using EF Core entities directly
in your action methods.
The GetRecipes method in RecipeService is conceptually simple and follows a com-
mon pattern for querying an EF Core database, as shown in figure 12.13.
EF Core uses a fluent chain of LINQ commands to define the query to return on the
database. The DbSet<Recipe> property on AppDataContext is an IQueryable, so you can
use all the usual Select() and Where() clauses that you would with other IQueryable
providers. EF Core will convert these into a SQL statement to query the database with
when you call an execute function such as ToList(), ToArray(), or Single().
You can also use the Select() extension method to map to objects other than your
entities as part of the SQL query. You can use this to efficiently query the database by
only fetching the columns you need.
359
Querying data from and saving data to the database
Listing 12.7 shows the code to fetch a list of RecipeSummaryViewModels, following the
same basic pattern as in figure 12.12. It uses a Where LINQ expression to filter out rec-
ipes marked as deleted, and a Select clause to map to the view models. The ToList()
command instructs EF Core to generate the SQL query, execute it on the database,
and build RecipeSummaryViewModel from the data returned.
1. A request is made
to the URL /recipe.
Request
Index
action method
ViewResult
2. The request is routed
to the Index action which
calls the RecipeService to
load the view models.
6. The action method
passes the view models
to the view.
RecipeService.GetRecipes()
3. The GetRecipes method uses
the app’s DbContext to query
the database for the data needed
for the view models.
Query using
DbContext
List<RecipeSummaryViewModel> SQL
4. EF Core generates
SQL and queries the
database.
5. The database returns the data
as rows, and EF Core maps them
to view model objects.
Db
Figure 12.12 Calling the Index action and querying the database to retrieve a list of
RecipeSummaryViewModels. EF Core generates the SQL to retrieve the necessary fields from the
database and maps them to view model objects.
_context.Recipes.Where(r => !r.IsDeleted).ToList()
AppDbContext DbSet
Property access
LINQ commands to modify
data returned
Execute query
command
Figure 12.13 The three parts of an EF Core database query.
360 CHAPTER 12 Saving data with Entity Framework Core
public ICollection<RecipeSummaryViewModel> GetRecipes()
{
return _context.Recipes
.Where(r => !r.IsDeleted)
.Select(r => new RecipeSummaryViewModel
{
Id = r.RecipeId,
Name = r.Name,
TimeToCook = "{r.TimeToCook.TotalMinutes}mins"
})
.ToList();
}
Notice that in the Select method, you convert the TimeToCook property from a Time-
Span to a string using string interpolation:
TimeToCook = $"{x.TimeToCook.TotalMinutes}mins"
I said before that EF Core converts the series of LINQ expressions into SQL, but that’s
only a half-truth; EF Core can’t or doesn’t know how to convert some expressions to
SQL. For those cases, such as in this example, EF Core finds the fields from the DB
that it needs in order to run the expression on the client side, selects those from the
database, and then runs the expression in C# afterwards. This lets you combine the
power and performance of database-side evaluation without compromising the func-
tionality of C#.
WARNING Client-side evaluation is both powerful and useful but has the
potential to cause issues. For certain queries, you could end up pulling back
all the data from the database and processing it in memory, instead of in the
database.
At this point, you have a list of records, displaying a summary of the recipe’s data, so
the obvious next step is to load the detail for a single record.
12.4.3 Loading a single record
For most intents and purposes, loading a single record is the same as loading a list of
records. They share the same common structure you saw in figure 12.13, but when
loading a single record, you’ll typically use a Where clause and execute a command
that restricts the data to a single entity.
Listing 12.8 shows the code to fetch a recipe by ID following the same basic pattern
as before (figure 12.12). It uses a Where() LINQ expression to restrict the query to a
single recipe, where RecipeId == id, and a Select clause to map to RecipeDetail-
ViewModel. The SingleOrDefault() clause will cause EF Core to generate the SQL
query, execute it on the database, and build the view model.
Listing 12.7 Loading a list of items using EF Core
A query starts from
a DbSet property
EF Core will only
query the Recipe
columns it needs to
map the view model
correctly
This executes the SQL query and
creates the final view models.
361
Querying data from and saving data to the database
NOTE SingleOrDefault() will throw an exception if the previous Where
clause returns more than one record. If you’re loading a single record by ID,
you can use the EF Core-specific Find method instead, which has some per-
formance benefits when used.
public RecipeDetailViewModel GetRecipeDetail(int id)
{
return _context.Recipes
.Where(x => x.RecipeId == id)
.Select(x => new RecipeDetailViewModel
{
Id = x.RecipeId,
Name = x.Name,
Method = x.Method,
Ingredients = x.Ingredients
.Select(item => new RecipeDetailViewModel.Item
{
Name = item.Name,
Quantity = $"{item.Quantity} {item.Unit}"
})
})
.SingleOrDefault();
}
Notice that, as well as mapping the Recipe to a RecipeDetailViewModel, you also map
the related Ingredients for a Recipe, as though you’re working with the objects
directly in memory. This is one of the advantages of using an ORM—you can easily
map child objects and let EF Core decide how best to build the underlying queries to
fetch the data.
NOTE EF Core logs all the SQL statements it runs as LogLevel.Information
events by default, so you can easily see what queries are being run against the
database.
Our app is definitely shaping up; you can create new recipes, view them all in a list,
and drill down to view individual recipes with their ingredients and method. Pretty
soon though, someone’s going to introduce a typo and want to change their model.
To do this, you’ll have to implement the U in CRUD: update.
12.4.4 Updating a model with changes
Updating entities when they have changed is generally the hardest part of CRUD
operations, as there are so many variables. Figure 12.14 gives an overview of this pro-
cess as it applies to your recipe app.
Listing 12.8 Loading a single item using EF Core
The id of the recipe to load
is passed as a parameter.
As before, a query starts
from a DbSet property. Limit the query to
the recipe with the
provided id.
Map the Recipe to a
RecipeDetailViewModel.
Load and map linked
Ingredients as part of
the same query.
Execute the query and map
the data to the view model.
362 CHAPTER 12 Saving data with Entity Framework Core
I’m not going to handle the relationship aspect in this book because that’s generally a
complex problem, and how you tackle it depends on the specifics of your data model.
Instead, I’ll focus on updating properties on the Recipe entity itself.1
For web applications, when you update an entity, you’ll typically follow the steps
outlined in Figure 12.14:
1 Read the entity from the database
2 Modify the entity’s properties
3 Save the changes to the database
You’ll encapsulate these three steps in a method on RecipeService, UpdateRecipe,
which takes UpdateRecipeCommand, containing the changes to make to the Recipe
entity.
NOTE As with the Create command, you don’t directly modify the entities in
the MVC controllers, ensuring you keep the UI concern separate from the
business logic.
The following listing shows the RecipeService.UpdateRecipe method, which
updates the Recipe entity. It’s the three steps you defined previously to read, modify,
1
For a detailed discussion on handling relationship updates in EF Core, see EF Core in Action by Jon P Smith
(Manning, 2018) https://livebook.manning.com#!/book/smith3/Chapter-3/109.
Command
Db
SQL
2. The DbContext generates
the SQL necessary to load the
entity from the database.
Read entity using
DbContext
Update properties
on entity
Db
SQL
Save entity using
DbContext
Update entity
relationships
Recipe
1. The update method
receives a command
indicating which entity
to update and the new
property values.
Recipe
3. The command is used
to update the properties
on the Recipe entity.
4. If the ingredients of the
Recipe have changed, these
are also updated using
the Command.
5. Save is called on the
DbContext, which generates
the necessary SQL to update
the entity in the database.
Figure 12.14 Updating an entity involves three steps: read the entity using EF Core, update the
properties of the entity, and call SaveChanges() on the DbContext to generate the SQL to update
the correct rows in the database.
363
Querying data from and saving data to the database
and save the entity. I’ve extracted the code to update the recipe with the new values to
a helper method.
public void UpdateRecipe(UpdateRecipeCommand cmd)
{
var recipe = _context.Recipes.Find(cmd.Id);
if(recipe == null) {
throw new Exception("Unable to find the recipe");
}
UpdateRecipe(recipe, cmd);
_context.SaveChanges();
}
static void UpdateRecipe(Recipe recipe, UpdateRecipeCommand cmd)
{
recipe.Name = cmd.Name;
recipe.TimeToCook =
new TimeSpan(cmd.TimeToCookHrs, cmd.TimeToCookMins, 0);
recipe.Method = cmd.Method;
recipe.IsVegetarian = cmd.IsVegetarian;
recipe.IsVegan = cmd.IsVegan;
}
In this example, I read the Recipe entity using the Find(id) method exposed by
DbSet. This is a simple helper method for loading an entity by its ID, in this case
RecipeId. I could’ve written an equivalent query directly using LINQ as
_context.Recipes.Where(r=>r.RecipeId == cmd.Id).FirstOrDefault();
Using Find() is a little more declarative and concise.
You may be wondering how EF Core knows which columns to update when you call
SaveChanges(). The simplest approach would be to update every column—if the field
hasn’t changed, then it doesn’t matter if you write the same value again. But EF Core
is a bit more clever than that.
EF Core internally tracks the state of any entities it loads from the database. It cre-
ates a snapshot of all the entity’s property values, so it can track which ones have
changed. When you call SaveChanges(), EF Core compares the state of any tracked
entities (in this case, the Repair entity) with the tracking snapshot. Any properties
that have been changed are included in the UPDATE statement sent to the database,
unchanged properties are ignored.
NOTE EF Core provides other mechanisms to track changes, as well as
options to disable change-tracking altogether. See the documentation or Jon
P Smith’s EF Core in Action (Manning, 2018) for details (https://livebook
.manning.com#!/book/smith3/Chapter-3/59).
Listing 12.9 Updating an existing entity with EF Core
Find is exposed directly by
Recipes and simplifies
reading an entity by id.
If an invalid id is provided,
recipe will be null.
A helper method
for setting the
new properties
on the Recipe
entity
Execute the SQL to save the
changes to the database.
Set the new values on the Recipe entity.
364 CHAPTER 12 Saving data with Entity Framework Core
With the ability to update recipes, you’re almost done with your recipe app. “But
wait,” I hear you cry, “we haven’t handled the D in CRUD—delete!” And that’s true,
but in reality, I’ve found few occasions when you want to delete data.
Let’s consider the requirements for deleting a recipe from the application, as
shown in figure 12.15. You need to add a (scary-looking) Delete button next to a rec-
ipe. After the user clicks Delete, the recipe is no longer visible in the list and can’t be
viewed.
Now, you could achieve this by deleting the recipe from the database, but the problem
with data is once it’s gone, it’s gone! What if a user accidentally deletes a record? Also,
deleting a row from a relational database typically has implications on other entities.
For example, you can’t delete a row from the Recipe table in your application without
also deleting all of the Ingredient rows that reference it, thanks to the foreign key con-
straint on Ingredient.RecipeId.
EF Core can easily handle these true deletion scenarios for you with the DbContext
.Remove(entity) command but, typically, what you mean when you find a need to
delete data is to archive it or hide it from the UI. A common approach to handling
The main page of the application
shows a list of all current recipes.
Clicking Delete returns you to
the list view, but the deleted
recipe is no longer visible.
Clicking View opens the
recipe detail page.
Figure 12.15 The desired behavior when deleting a recipe from the app. Clicking Delete should return you to
the application’s main list view, with the deleted recipe no longer visible.
365
Querying data from and saving data to the database
this scenario is to include some sort of “Is this entity deleted” flag on your entity, such
as the IsDeleted flag I included on the Recipe entity:
public bool IsDeleted {get;set;}
If you take this approach then, suddenly, deleting data becomes simpler, as it’s noth-
ing more than an update to the entity. No more issues of lost data and no more refer-
ential integrity problems.
NOTE The main exception I’ve found to this pattern is when you’re storing
your users’ personally identifying information. In these cases, you may be
duty-bound (and, potentially, legally bound) to scrub their information from
your database on request.
With this approach, you can create a delete method on RecipeService, which updates
the IsDeleted flag, as shown in the following listing. In addition, you should ensure
you have Where() clauses in all the other methods in your RecipeService, to ensure
you can’t display a deleted Recipe, as you saw in listing 12.9, for the GetRecipes()
method.
public void DeleteRecipe(int recipeId)
{
var recipe = _context.Recipes.Find(recipeId);
if(recipe == null) {
throw new Exception("Unable to find the recipe");
}
recipe.IsDeleted = true;
_context.SaveChanges();
}
This approach satisfies the requirements—it removes the recipe from the UI of the
application—but it simplifies a number of things. This soft delete approach won’t work
for all scenarios, but I’ve found it to be a common pattern in projects I’ve worked on.
TIP EF Core 2.0 gained a feature called Model-level query filters. These allow
you to specify a Where clause at the model level, so you could, for
example, ensure that EF Core never loads Recipes for which IsDeleted is
true. See this announcement post for details: https://github.com/aspnet/
EntityFrameworkCore/ issues/8923.
We’re almost at the end of this chapter on EF Core. We’ve covered the basics of add-
ing EF Core to your project and using it to simplify data access, but you’ll likely need
to read more into EF Core as your apps become more complex. In the final section of
this chapter, I’d like to pinpoint a number of things you need to take into consider-
ation before using EF Core in your own applications, so you’re familiar with some of
the issues you’ll face as your apps grow.
Listing 12.10 Marking entities as deleted in EF Core
Fetch the Recipe
entity by id.
If an invalid id is
provided, recipe
will be null.
Mark the Recipe as deleted.
Execute the SQL to save the
changes to the database.
366 CHAPTER 12 Saving data with Entity Framework Core
12.5 Using EF Core in production applications
This book is about ASP.NET Core, not EF Core, so I didn’t want to spend too much
time exploring EF Core. This chapter should’ve given you enough to get up and run-
ning, but you’ll definitely need to learn more before you even think about putting EF
Core into production. As I’ve said a number of time, I recommend EF Core in Action by
Jon P Smith (Manning, 2018) for details (https://livebook.manning.com/#!/
book/smith3/Chapter-11/), or exploring the EF Core documentation site at
https://docs.microsoft.com/en-us/ef/core/.
The following topics aren’t essential to getting started with EF Core, but you’ll
quickly come up against them if you build a production-ready app. This section isn’t a
prescriptive guide to how to tackle each of these; it’s more a set of things to consider
before you dive into production!
 Scaffolding of columns—EF Core uses conservative values for things like string
columns by allowing strings of unlimited length. In practice, you’ll often want
to restrict these and other data types to sensible values.
 Validation—You can decorate your entities with DataAnnotations validation attri-
butes, but EF Core won’t automatically validate the values before saving to the
database. This differs from EF 6.x behavior, in which validation was automatic.
 Handling concurrency—EF Core provides a number of ways to handle concur-
rency, where multiple users attempt to update an entity at the same time. One
partial solution is through the use of Timestamp columns on your entities.
 Synchronous vs. asynchronous—EF Core provides both synchronous and asyn-
chronous commands for interacting with the database. Often, async is better for
web apps, but there are nuances to this argument that make it impossible to rec-
ommend one approach over the other in all situations.
EF Core is a great tool for being productive when writing data-access code, but there
are some aspects of working with a database that are unavoidably awkward. The issue
of database management is one of the thorniest issues to tackle. This book is about
ASP.NET Core, not EF Core, so I don’t want to dwell on database management too
much. Having said that, most web applications use some sort of database, so the fol-
lowing issues are likely to impact you at some point.
 Automatic migrations—If you automatically deploy your app to production as
part of some sort of DevOps pipeline, you’ll inevitably need some way of apply-
ing migrations to a database automatically. You can tackle this in a number of
ways, such as scripting the .NET CLI, applying migrations in your app’s startup
code, or using a custom tool. Each approach has its pros and cons.
 Multiple web hosts—One specific consideration is whether you have multiple web
servers hosting your app, all pointing to the same database. If so, then applying
migrations in your app’s startup code becomes harder, as you must ensure only
one app can migrate the database at a time.
367
Summary
 Making backward-compatible schema changes—A corollary of the multiple-web host
approach is that you’ll often be in a situation where your app is accessing a data-
base that has a newer schema than the app thinks. That means you should nor-
mally endeavor to make schema changes backward-compatible wherever
possible.
 Storing migrations in a different assembly—In this chapter, I included all my logic
in a single project, but in larger apps, data access is often in a different project
to the web app. For apps with this structure, you must use slightly different com-
mands when using the .NET CLI or PowerShell cmdlets.
 Seeding data—When you first create a database, you often want it to have some
initial seed data, such as a default user. EF 6.X had a mechanism for seeding
data built in, whereas EF Core requires you to explicitly seed your database
yourself.
How you choose to handle each of these issues will depend on the infrastructure and
deployment approach you take with your application. None of them are particularly
fun to tackle but they’re an unfortunate necessity. Take heart though, they can all be
solved one way or another!
That brings us to the end of this chapter on EF Core. In the next chapter, we’ll
look at one of the slightly more advanced topics of MVC, the filter pipeline, and how
you can use it to reduce duplication in your code.
Summary
 EF Core is an object-relational mapper (ORM) that lets you interact with a data-
base by manipulating standard POCO classes, called entities, in your application.
 EF Core maps entity classes to tables, properties on the entity to columns in the
tables, and instances of entity objects to rows in these tables.
 EF Core uses a database-provider model that lets you change the underlying
database without changing any of your object manipulation code. EF Core has
database providers for Microsoft SQL Server, SQLite, PostgreSQL, MySQL, and
many others.
 EF Core is cross-platform and has good performance for an ORM, but has fewer
features than some other ORMs, such as EF 6.x.
 EF Core stores an internal representation of the entities in your application and
how they map to the database, based on the DbSet<T> properties on your appli-
cation’s DbContext. EF Core builds a model based on the entity classes them-
selves and any other entities they reference.
 You add EF Core to your app by adding a NuGet database provider package.
You should also install the Design packages for both EF Core and your database
provider (if available). These are used to generate and apply migrations to a
database.
368 CHAPTER 12 Saving data with Entity Framework Core
 EF Core includes a number of conventions for how entities are defined, such as
primary keys and foreign keys. You can customize how entities are defined
either declaratively, using DataAnotations, or using a fluent API.
 Your application uses a DbContext to interact with EF Core and the database.
You register it with a DI container using AddDbContext<T>, defining the data-
base provider and providing a connection string.
 EF Core uses migrations to track changes to your entity definitions. They’re
used to ensure your entity definitions, EF Core’s internal model, and the data-
base schema all match.
 After changing an entity, you can create a migration either using the .NET CLI
tool or using Visual Studio PowerShell cmdlets.
 To create a new migration with the .NET CLI, run dotnet ef migrations
add NAME in your project folder, where NAME is the name you want to give the
migration.
 You can apply the migration to the database using dotnet ef database update.
This will create the database if it doesn’t already exist and apply any outstand-
ing migrations.
 EF Core doesn’t interact with the database when it creates migrations, only
when you explicitly update the database.
 You can add entities to an EF Core database by creating a new entity, e, calling
_context.Add(e) on an instance of your application’s data context, _context,
and calling _context.SaveChanges(). This generates the necessary SQL
INSERT statements to add the new rows to the database.
 You can load records from a database using the DbSet<T> properties on your
app’s DbContext. These expose the IQueryable interface, so you can use LINQ
statements to filter and transform the data in the database before it’s returned.
If you use a C# expression that EF Core can’t translate to SQL, it will load the
data necessary to perform the operation in your app, instead of in the database.
 Updating an entity consists of three steps: read the entity from the database,
modify the entity, and save the changes to the database. EF Core will keep track
of which properties have changed so that it can optimize the SQL it generates.
 You can delete entities in EF Core using the Remove method, but you should
consider carefully whether you need this functionality. Often a soft delete tech-
nique using an IsDeleted flag on entities is safer and easier to implement.
 This chapter only covers a subset of the issues you must consider when using EF
Core in your application. Before using it in a production app, you should con-
sider, among other things: the data types generated for fields, validation, how to
handle concurrency, the seeding of initial data, handling migrations on a run-
ning application, and handling migrations in a web-farm scenario.
369
The MVC filter pipeline
In part 1, I covered the MVC framework of ASP.NET Core in some detail. You
learned about MvcMiddleware and how routing is used to select an action method
to execute. You also saw model binding, validation, and how to generate a response
by returning an IActionResult from your actions. In this chapter, I’m going to
head deeper into MvcMiddleware and look at the MVC filter pipeline, sometimes
called the MVC action invocation pipeline.
This chapter covers
 The MVC filter pipeline and how it differs from
middleware
 Creating custom filters to refactor complex
action methods
 Using authorization filters to protect your action
methods
 Short-circuiting the filter pipeline to bypass
action execution
 Injecting dependencies into filters
370 CHAPTER 13 The MVC filter pipeline
MVC uses a number of built-in filters to handle crosscutting concerns, such as
authorization (controlling which users can access which action methods in your appli-
cation). Any application that has the concept of users will use authorization filters as a
minimum, but filters are much more powerful than this single use case.
This chapter describes the MVC filter pipeline in the context of an MVC request.
You’ll learn how to create custom filters that you can use in your own apps, and how
you can use them to reduce duplicate code in your action methods. You’ll learn how
to customize your application’s behavior for specific actions, as well as how to apply fil-
ters globally to modify all of the actions in your app.
Think of the filter pipeline as a mini middleware pipeline running inside Mvc-
Middleware. Like the middleware pipeline in ASP.NET Core, the filter pipeline con-
sists of a series of components connected as a pipe, so the output of one filter feeds
into the input of the next.
This chapter starts by looking at the similarities and differences between MVC fil-
ters and middleware, and when you should choose one over the other. You’ll learn
about all the different types of filters and how they combine to create the filter pipe-
line for a request that reaches MvcMiddleware.
In section 13.2, I’ll take you through each filter type in detail, how they fit into the
MVC pipeline, and what to use them for. For each one, I’ll provide example imple-
mentations that you might use in your own application.
A key feature of filters is the ability to short-circuit a request by generating a response
and halting progression through the MVC filter pipeline. This is similar to the way
short-circuiting works in middleware, but there are subtle differences. On top of that,
the exact behavior is slightly different for each filter, which I cover in section 13.3.
You typically add MVC filters to the pipeline by implementing them as attributes
added to your controller classes and action methods. Unfortunately, you can’t easily
use DI with attributes due to the limitations of C#. In section 13.4, I’ll show you how to
use the ServiceFilterAttribute and TypeFilterAttribute base classes to enable
dependency injection in your filters.
Before we can start writing code, we should get to grips with the basics of the MVC
filter pipeline. The first section of this chapter explains what the pipeline is, why you
might want to use it, and how it differs from the middleware pipeline.
13.1 Understanding filters and when to use them
The MVC filter pipeline is a relatively simple concept, in that it provides hooks into the
normal MVC request, as shown in figure 13.1. Say you wanted to ensure that users can
create or edit products on an e-commerce app only if they’re logged in. The app
would redirect anonymous users to a login page instead of executing the action.
Without filters, you’d need to include the same code to check for a logged-in user
at the start of each specific action method. With this approach, MvcMiddleware would
still execute the model binding and validation, even if the user were not logged in.
371
Understanding filters and when to use them
With filters, you can use the hooks in the MVC request to run common code across
all, or a subset of, requests. This way, you can do a wide range of things, such as
 Ensuring a user is logged in before an action method, model binding, or valida-
tion runs
 Customizing the output format of particular action methods
 Handling model validation failures before an action method is invoked
 Catching exceptions from an action method and handling them in a
special way
In many ways, the MVC filter pipeline is like a middleware pipeline, but restricted to Mvc-
Middleware only. Like middleware, filters are good for handling crosscutting concerns
for your application and are a useful tool for reducing code duplication in many cases.
1. A request is received
to the URL /recipe/ .
1
Request
2. The routing module matches the
request to the RecipeController.View
action and sets id= .
1
Routing
Action
ViewResult Execution
MVC Controller
HTML
Model binding / validation
3. A variety of different MVC
filters run as part of the
MVC middleware.
4. Filters run before model
binding, before the action
method runs, and before
and after the IActionResult
is executed.
MvcMiddleware
Figure 13.1 Filters run at multiple points in MvcMiddleware in the normal
handling of a request.
372 CHAPTER 13 The MVC filter pipeline
In this section, I’ll describe the MVC filter pipeline and how it fits into the overall
MVC request. You’ll learn about the types of MVC filters, how you can add them to
your own apps, and how to control the order in which they execute when handling a
request.
13.1.1 The MVC filter pipeline
As you saw in figure 13.1, MVC filters run at a number of different points in the MVC
request. This linear view of an MVC request and the filter pipeline that you’ve used so
far doesn’t quite match up with how these filters execute. There are five types of filter,
each of which runs at a different stage in MvcMiddleware, as shown in figure 13.2.
Each stage lends itself to a particular use case, thanks to its specific location in Mvc-
Middleware, with respect to model binding, action execution, and result execution.
 Authorization filters—These run first in the pipeline, so are useful for protecting
your APIs and action methods. If an authorization filter deems the request
unauthorized, it will short-circuit the request, preventing the rest of the filter
pipeline from running.
 Resource filters—After authorization, resource filters are the next filters to run in
the pipeline. They can also execute at the end of the pipeline, in much the same
way that middleware components can handle both the incoming request and
the outgoing response. Alternatively, they can completely short-circuit the
request pipeline and return a response directly.
Thanks to their early position in the pipeline, resource filters can have a vari-
ety of uses. You could add metrics to an action method, prevent an action
method from executing if an unsupported content type is requested, or, as they
run before model binding, control the way model binding works for that
request.
 Action filters—Action filters run just before and after an action is executed. As
model binding has already happened, action filters let you manipulate the argu-
ments to the method—before it executes—or they can short-circuit the action
completely and return a different IActionResult. Because they also run after
the action executes, they can optionally customize IActionResult before it’s
executed.
 Exception filters—Exception filters can catch exceptions that occur in the filter
pipeline and handle them appropriately. They let you write custom MVC-
specific error-handling code, which can be useful in some situations. For exam-
ple, you could catch exceptions in Web API actions and format them differently
to exceptions in your MVC actions.
 Result filters—Result filters run before and after an action method’s IAction-
Result is executed. This lets you control the execution of the result, or even
short-circuit the execution of the result.
373
Understanding filters and when to use them
Exactly which filter you pick to implement will depend on the functionality you’re try-
ing to introduce. Want to short-circuit a request as early as possible? Resource filters
are a good fit. Need access to the action method parameters? Use an action filter.
Think of the filter pipeline as a small middleware pipeline that lives by itself in
MvcMiddleware. Alternatively, you could think of filters as hooks into the MVC action
invocation process, which let you run code at a particular point in a request’s lifecycle.
One of the main questions I hear when people learn about filters in ASP.NET Core
is “Why do we need them?” If the filter pipeline is like a mini middleware pipeline,
why not use a middleware component directly, instead of introducing the filter con-
cept? That’s an excellent point, which I’ll tackle in the next section.
13.1.2 Filters or middleware: which should you choose?
The filter pipeline is similar to the middleware pipeline in many ways, but there are a
number of subtle differences that you should consider when deciding which approach
to use. When considering the similarities, they have three main parallels:
Authorization
filters
Model binding
/ validation
Resource filters
Action
filters
Exception
filters
Result filters
Action
invocation
IActionResult Execution
Authorization filters run first
for every MVC request. If the
request isn’t authorized, it will
short-circuit the pipeline.
Resource filters run next
before model binding runs.
Action filters run before and after
the action method executes. As
they run after model binding, you
can use them to customize the
arguments passed to the action.
If an exception occurs somewhere
in the pipeline, the ExceptionFilter
will execute.
If the action method
returns an IActionResult,
the result filters will execute
before and after the
IActionResult is executed.
Resource filters also run
at the end of the pipeline after
the result has been executed.
Request Response
Figure 13.2 The MVC filter pipeline, including the five different filter stages. Some filter stages
(resource, action, and result) run twice, before and after the remainder of the pipeline.
374 CHAPTER 13 The MVC filter pipeline
 Requests pass through a middleware component on the way “in” and responses pass
through again on the way “out.” Resource, action, and result filters are also two-
way, though authorization and exception filters run only once for a request.
 Middleware can short-circuit a request by returning a response, instead of passing it on to
later middleware. Filters can also short-circuit the MVC filter pipeline by return-
ing a response.
 Middleware is often used for crosscutting application concerns, such as logging, perfor-
mance profiling, and exception handling. Filters also lend themselves to crosscut-
ting concerns.
In contrast, there are three main differences between middleware and filters:
 Middleware can run for all requests; filters will only run for requests that reach
MvcMiddleware.
 Filters have access to MVC constructs such as ModelState and IActionResults.
Middleware, in general, is independent from MVC, so can’t use these concepts.
 Filters can be easily applied to a subset of requests; for example, all actions on a sin-
gle controller. Middleware doesn’t have this concept as a first-class idea (though
you could achieve something similar with custom middleware components).
That’s all well and good, but how should we interpret these differences? When should
we choose one over the other?
I like to think of middleware versus filters as a question of specificity. Middleware is the
more general concept, so has the wider reach. If the functionality you need has no MVC-
specific requirements, then you should use a middleware component. Exception han-
dling is a great example of this; exceptions could happen anywhere in your application,
and you need to handle them, so using exception-handling middleware makes sense.
On the other hand, if you do need access to MVC constructs, or you want to behave
differently for some MVC actions, then you should consider using a filter. Ironically,
this can also be applied to exception handling. You don’t want exceptions in your Web
API controllers to automatically generate HTML error pages when the client is
expecting JSON. Instead, you could use an exception filter on your Web API actions
to render the exception to JSON, while letting the exception-handling middleware
catch errors from elsewhere in your app.
TIP Where possible, consider using middleware for crosscutting concerns.
Use filters when you need different behavior for different action methods, or
where the functionality relies on MVC concepts like ModelState validation.
The middleware versus filters argument is a subtle one, and it doesn’t matter which
you choose as long as it works for you. You can even use middleware components
inside the filter pipeline as filters, but that’s outside the scope of this book.1
1
The “middleware as filters” feature was introduced in ASP.NET Core 1.1. If you’re interested, I wrote an intro-
duction to the feature here: http://mng.bz/Mg97.
375
Understanding filters and when to use them
TIP The middleware as filters feature was introduced in ASP.NET Core 1.1
and is also available in 2.0. The canonical use case is for localizing requests to
multiple languages. I have a blog series on how to use the feature here:
http://mng.bz/a6Rb.
Filters can be a little abstract in isolation, so in the next section, we’ll look at some
code and learn how to write a custom filter in ASP.NET Core.
13.1.3 Creating a simple filter
In this section, I show how to create your first filters; in section 13.1.4, you’ll see how
to apply them to your MVC actions. We’ll start small, creating filters that just write to
the console, but in section 13.2, we’ll look at some more practical examples and dis-
cuss some of their nuances.
You implement an MVC filter for a given stage by implementing one of a pair of
interfaces—one synchronous (sync), one asynchronous (async):
 Authorization filters—IAuthorizationFilter or IAsyncAuthorizationFilter
 Resource filters—IResourceFilter or IAsyncResourceFilter
 Action filters—IActionFilter or IAsyncActionFilter
 Exception filters—IExceptionFilter or IAsyncExceptionFilter
 Result filters—IResultFilter or IAsyncResultFilter
You can use any POCO class to implement a filter, but you’ll typically implement them
as C# attributes, which you can use to decorate your MVC controllers and actions, as
you’ll see in section 13.1.4. You can achieve the same results with either the sync or
async interface, so which you choose should depend on whether any services you call
in the filter require async support.
NOTE You should implement either the sync interface or the async interface,
not both. If you implement both, then only the async interface will be used.
Listing 13.1 shows a resource filter that implements IResourceFilter and writes to
the console when it executes. The OnResourceExecuting method is called when a
request first reaches the resource filter stage of the filter pipeline. In contrast, the
OnResourceExecuted method is called after the rest of the pipeline has executed;
after model binding, action execution, result execution, and all intermediate filters
have run.
public class LogResourceFilter : Attribute, IResourceFilter
{
public void OnResourceExecuting(
ResourceExecutingContext context)
{
Console.WriteLine("Executing!");
}
Listing 13.1 Example resource filter implementing IResourceFilter
Executed at the start
of the pipeline, after
authorization filters.
The context contains the
HttpContext, routing details,
and information about the
current action.
376 CHAPTER 13 The MVC filter pipeline
public void OnResourceExecuted(
ResourceExecutedContext context)
{
Console.WriteLine("Executed”");
}
}
The interface methods are simple and are similar for each stage in the filter pipeline,
passing a context object as a method parameter. Each of the two-method sync filters
has an *Executing and an *Executed method. The type of the argument is different
for each filter, but it contains all of the details for the filter pipeline.
For example, the ResourceExecutingContext passed to the resource filter con-
tains the HttpContext object itself, details about the route that selected this action,
details about the action itself, and so on. Contexts for later filters will contain addi-
tional details, such as the action method arguments for an action filter and the Model-
State.
The context object for the ResourceExecutedContext method is similar, but it also
contains details about how the rest of the pipeline executed. You can check whether
an unhandled exception occurred, you can see if another filter from the same stage
short-circuited the pipeline, or you can see the IActionResult used to generate the
response.
These context objects are powerful and are the key to advanced filter behaviors
like short-circuiting the pipeline and handling exceptions. We’ll make use of them in
section 13.2 when creating more complex filter examples.
The async version of the resource filter requires implementing a single method, as
shown in listing 13.2. As for the sync version, you’re passed a ResourceExecuting-
Context object as an argument, and you’re passed a delegate representing the remain-
der of the filter pipeline. You must call this delegate (asynchronously) to execute the
remainder of the pipeline, which will return an instance of ResourceExecutedContext.
public class LogAsyncResourceFilter : Attribute, IAsyncResourceFilter
{
public async Task OnResourceExecutionAsync(
ResourceExecutingContext context,
ResourceExecutionDelegate next)
{
Console.WriteLine("Executing async!");
ResourceExecutedContext executedContext = await next();
Console.WriteLine("Executed async!");
}
}
Listing 13.2 Example resource filter implementing IAsyncResourceFilter
Executed after model
binding, action execution,
and result execution.
Contains additional context
information, such as the
IActionResult returned by the action
Executed at the start of the pipeline,
after authorization filters.
You’re provided a delegate, which
encapsulates the remainder of the
MVC filter pipeline.
Called before
the rest of
the pipeline
executes. Called after the rest of
the pipeline executes.
Executes the rest of the
pipeline and obtains a
ResourceExecutedContext
instance
377
Understanding filters and when to use them
The sync and async filter implementations have subtle differences, but for most pur-
poses they’re identical. I recommend implementing the sync version if possible, and
only falling back to the async version if you need to.
You’ve created a couple of filters now, so we should look at how to use them in the
application. In the next section, we’ll tackle two specific issues: how to control which
requests execute your new filters and how to control the order in which they execute.
13.1.4 Adding filters to your actions, your controllers, and globally
In section 13.1.2, I discussed the similarities and differences between middleware and
filters. One of those differences is that filters can be scoped to specific actions or con-
trollers, so that they only run for certain requests. Alternatively, you can apply a filter
globally, so that it runs for every MVC action.
By adding filters in different ways, you can achieve a number of different results.
Imagine you have a filter that forces you to log in to view an action. How you add the
filter to your app will significantly change your app’s behavior:
 Apply the filter to a single action—Anonymous users could browse the app as normal,
but if they tried to access the protected action, they would be forced to log in.
 Apply the filter to a controller—Anonymous users could access actions from other
controllers, but accessing any action on the protected controller would force
them to log in.
 Apply the filter globally—Users couldn’t use the app without logging in. Any
attempt to access an action would redirect the user to the login page.
NOTE ASP.NET Core comes with just such a filter out of the box, Authorize-
Filter. I’ll discuss this filter in section 13.2.1, and you’ll be seeing a lot more
of it in chapter 15.
As I described in the previous section, you normally create filters as attributes, and for
good reason—it makes applying them to MVC controllers and actions easy. In this sec-
tion, you’ll see how to apply LogResourceFilter from listing 13.1 to an action, a con-
troller, and globally. The level at which the filter applies is called its scope.
DEFINITION The scope of a filter refers to how many different actions it applies
to. A filter can be scoped to the action method, to the controller, or globally.
You’ll start at the most specific scope—applying filters to a single action. The follow-
ing listing shows an example of an MVC controller that has two action methods: one
with LogResourceFilter and one without.
public class HomeController : Controller
{
[LogResourceFilter]
public IActionResult Index()
{
return View();
}
Listing 13.3 Applying filters to an action method
LogResourceFilter will
run as part of the
pipeline when executing
this action.
378 CHAPTER 13 The MVC filter pipeline
public IActionResult About()
{
return View();
}
}
Alternatively, if you want to apply the same filter to every action method, you could
add the attribute at the controller scope, as in the next listing. Every action method in
the controller will use LogResourceFilter, without having to specifically decorate
each method:
[LogResourceFilter]
public class HomeController : Controller
{
public IActionResult Index ()
{
return View();
}
public IActionResult SendInvoice()
{
return View();
}
}
Filters you apply as attributes to controllers and actions are automatically discovered
by MvcMiddleware when your application starts up. For common attributes, you can
go one step further and apply filters globally, without having to decorate individual
controllers.
You add global filters in a different way to controller- or action-scoped filters—by
adding a filter directly to the MVC services, when configuring MVC in Startup. This
listing shows three equivalent ways to add a globally scoped filter.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Filters.Add(new LogResourceFilter());
options.Filters.Add(typeof(LogAsyncResourceFilter));
options.Filters.Add<LogAsyncResourceFilter>();
});
}
}
Listing 13.4 Applying filters to a controller
Listing 13.5 Applying filters globally to an application
This action method has no
filters at the action level.
The LogResourceFilter Is
added to every action on
the controller.
Every action in the
controller is decorated
with the filter.
Adds filters using
the MvcOptions
object
Alternatively, the
framework can
create a global
filter using a
generic type
parameter.
. . . or pass in the Type of the filter
and let the framework create it.
You can pass
an instance
of the filter
directly. . .
379
Understanding filters and when to use them
With three different scopes in play, you’ll often find action methods that have multi-
ple filters applied to them, some applied directly to the action method, and others
inherited from the controller or globally. The question then becomes: Which filters
run first?
13.1.5 Understanding the order of filter execution
You’ve seen that the filter pipeline contains five different stages, one for each type of
filter. These stages always run in the fixed order I described in section 13.1.1. But
within each stage, you can also have multiple filters of the same type (for example,
multiple resource filters) that are part of a single action method’s pipeline. These
could all have multiple scopes, depending on how you added them, as you saw in the
last section.
In this section, we’re thinking about the order of filters within a given stage and how
scope affects this. We’ll start by looking at the default order, then move on to ways to
customize the order to your own requirements.
THE DEFAULT SCOPE EXECUTION ORDER
When thinking about filter ordering, it’s important to remember that resource,
action, and result filters implement two methods: an *Executing before method and
an *Executed after method. The order in which each method executes depends on
the scope of the filter, as shown in figure 13.3 for the resource filter stage.
By default, filters execute from the broadest scope (global) to the narrowest
(action) when running the *Executing method for each stage. The *Executed meth-
ods run in reverse order, from the narrowest scope (action) to the broadest (global).
You’ll often find you need a bit more control over this order, especially if you have,
for example, multiple action filters applied at the same scope. The MVC framework
caters to this requirement by way of the IOrderedFilter interface.
Global filters run first
in each filter stage.
Base controller filter
Global scope filter
Controller scope filter
Action scope filter
The scope of the filters
determines the order in
which they run within a
single stage.
Filters scoped to the
controller level run
after global filters and
before action filters. Filters can also be applied
to base Controller classes.
Base class scoped filters
run later than filters on the
derived controllers.
Filters scoped to the
action level run last
in a filter stage.
Figure 13.3 The default filter ordering within a given stage, based on the scope of the filters.
For the *Executing method, globally scoped filters run first, followed by controller-scoped,
and finally, action-scoped filters. For the *Executed method, the filters run in reverse order.
380 CHAPTER 13 The MVC filter pipeline
OVERRIDING THE DEFAULT ORDER OF FILTER EXECUTION WITH IORDEREDFILTER
Filters are great for extracting crosscutting concerns from your controller actions, but
if you have multiple filters applied to an action, then you’ll often need to control the
precise order in which they execute.
Scope can get you some of the way, but for those other cases, you can implement
IOrderFilter. This interface consists of a single property, Order:
public interface IOrderedFilter
{
int Order { get; }
}
You can implement this property in your filters to set the order in which they execute.
MvcMiddleware will order the filters for a stage based on this value first, from lowest to
highest, and use the default scope order to handle ties, as shown in figure 13.4.
The filters for Order = -1 execute first, as they have the lowest Order value. The
controller filter executes first because it has a broader scope than the action filter. The
filters with Order=0 execute next, in the default scope order, as shown in figure 13.4.
Finally, the filter with Order=1 executes.
By default, if a filter doesn’t implement IOrderedFilter, it’s assumed to have
Order = 0. All of the filters that ship as part of ASP.NET Core have Order = 0, so you
can implement your own filters relative to these.
This section has covered most of the technical details you need to use filters and
create custom implementations for your own application. In the next section, you’ll
Controller scope filter
Order = -1
Controller scope filter
Order = 0
The Order and scope of the
filters determines the order
in which they run within
a single stage.
Filters with the lowest
Order number run first.
Scope is used to decide
tie breaks.
Filters with the highest
value of Order run last
for the stage.
Global scope filter
Order = 0
Action scope filter
Order = 0
Global scope filter
Order = 1
By default, filters have
an Order of 0.
Action scope filter
Order = -1
Figure 13.4 Controlling the filter order for a stage using the IOrderedFilter interface.
Filters are ordered by the Order property first, and then by scope.
381
Creating custom filters for your application
see some of the built-in filters provided by ASP.NET Core, as well as some practical
examples of filters you might want to use in your own applications.
13.2 Creating custom filters for your application
ASP.NET Core includes a number of filters that you can use, but often, the most use-
ful filters are the custom ones that are specific to your own apps. In this section, you’ll
work through each of the five types of filters. I’ll explain in more detail what they’re
for and when you should use them. I’ll point out examples of these filters that are part
of ASP.NET Core itself and you’ll see how to create custom filters for an example
application.
To give you something realistic to work with, you’ll start with a Web API controller
for accessing the recipe application from chapter 12. This controller contains two
actions: one for fetching a RecipeDetailViewModel and another for updating a
Recipe with new values. This listing shows your starting point for this chapter, includ-
ing both of the action methods.
[Route("api/recipe")]
public class RecipeApiController : Controller
{
private const bool IsEnabled = true;
public RecipeService _service;
public RecipeApiController(RecipeService service)
{
_service = service;
}
[HttpGet("{id}")]
public IActionResult Get(int id)
{
if (!IsEnabled) { return BadRequest(); }
try
{
if (!_service.DoesRecipeExist(id))
{
return NotFound();
}
var detail = _service.GetRecipeDetail(id);
Response.GetTypedHeaders().LastModified =
detail.LastModified;
return Ok(detail);
}
catch (Exception ex)
{
return GetErrorResponse(ex);
}
}
Listing 13.6 Recipe Web API controller before refactoring to use filters
These fields would be passed in as
configuration values and are used
to control access to actions.
If the API isn’t enabled,
block further execution.
If the requested Recipe
doesn’t exist, return a
404 response.
Fetch
RecipeDetailViewModel.
Sets the Last-Modified
response header to the
value in the model
Returns the
view model
with a 200
response
If an exception occurs, catch it,
and return the error in an
expected format, as a 500 error.
382 CHAPTER 13 The MVC filter pipeline
[HttpPost("{id}")]
public IActionResult Edit(
int id, [FromBody] UpdateRecipeCommand command)
{
if (!IsEnabled) { return BadRequest(); }
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (!_service.DoesRecipeExist(id))
{
return NotFound();
}
_service.UpdateRecipe(command);
return Ok();
}
catch (Exception ex)
{
return GetErrorResponse(ex);
}
}
private static IActionResult GetErrorResponse(Exception ex)
{
var error = new
{
Success = false,
Errors = new[]
{
ex.Message
}
};
return new ObjectResult(error)
{
StatusCode = 500
};
}
}
These action methods currently have a lot of code to them, which hides the intent of
each action. There’s also quite a lot of duplication between the methods, such as
checking that the Recipe entity exists, and formatting exceptions.
In this section, you’re going to refactor this controller to use filters for all the code
in the methods that’s unrelated to the intent of each action. By the end of the chapter,
you’ll have a much simpler controller that’s far easier to understand, as shown here.
If the API isn’t enabled,
block further execution.
Validates the binding model
and returns a 400 response
if there are errors
If the requested Recipe
doesn’t exist, return a
404 response.
Updates the Recipe from the command
and returns a 200 response
If an exception occurs, catch it,
and return the error in an
expected format, as a 500 error.
383
Creating custom filters for your application
[Route("api/recipe")]
[ValidateModel, HandleException, FeatureEnabled(IsEnabled = true)]
public class RecipeApiController : Controller
{
public RecipeService _service;
public RecipeApiController(RecipeService service)
{
_service = service;
}
[HttpGet("{id}"), EnsureRecipeExists, AddLastModifedHeader]
public IActionResult Get(int id)
{
var detail = _service.GetRecipeDetail(id);
return Ok(detail);
}
[HttpPost("{id}"), EnsureRecipeExists]
public IActionResult Edit(
int id, [FromBody] UpdateRecipeCommand command)
{
_service.UpdateRecipe(command);
return Ok();
}
}
I think you'll have to agree, the controller in listing 13.7 is much easier to read! In this
section, you’ll refactor the controller bit by bit, removing crosscutting code to get to
something more manageable. All the filters I’ll create in this section will use the sync
filter interfaces—I’ll leave it as an exercise for the reader to create their async coun-
terparts. You’ll start by looking at authorization filters and how they relate to security
in ASP.NET Core.
13.2.1 Authorization filters: protecting your APIs
Authentication and authorization are related, fundamental concepts in security that we’ll
be looking at in detail in chapters 14 and 15.
DEFINITION Authentication is concerned with determining who made a
request. Authorization is concerned with what a user is allowed to access.
Authorization filters run first in the MVC filter pipeline, before any other filters. They
control access to the action method by immediately short-circuiting the pipeline when
a request doesn’t meet the necessary requirements.
ASP.NET Core has a built-in authorization framework that you should use when
you need to protect your MVC application or your Web APIs. You can configure this
framework with custom policies that let you finely control access to your actions.
Listing 13.7 Recipe Web API controller after refactoring to use filters
The filters encapsulate
the majority of logic
common to multiple
action methods.
Placing filters at the action level
limits them to a single action.
The intent of the action,
return a Recipe view model,
is much clearer.
Placing filters at the action level
can be used to control the order
in which they execute.
The intent of the action, update
a Recipe, is much clearer.
384 CHAPTER 13 The MVC filter pipeline
TIP It’s possible to write your own authorization filters by implementing
IAuthorizationFilter or IAsyncAuthorizationFilter, but I strongly advise
against it. The ASP.NET Core authorization framework is highly configurable
and should meet all your needs.
At the heart of the ASP.NET Core authorization framework is an Authorization filter,
AuthorizeFilter, which you can add to the filter pipeline by decorating your actions
or controllers with the [Authorize] attribute. In its simplest form, adding the
[Authorize] attribute to an action, as in the following listing, means the request must
be made by an authenticated user to be allowed to continue. If you’re not logged in, it
will short-circuit the pipeline, returning a 401 Unauthorized response to the browser.
public class RecipeApiController : Controller
{
public IActionResult Get(int id)
{
// method body
}
[Authorize]
public IActionResult Edit(
int id, [FromBody] UpdateRecipeCommand command)
{
// method body
}
}
As with all filters, you can apply the [Authorize] attribute at the controller level to
protect all the actions on a controller, or even globally, to protect every method in
your app.
NOTE We’ll explore authorization in detail in chapter 15, including how to
add more detailed requirements, so that only specific sets of users can exe-
cute an action.
The next filters in the pipeline are resource filters. In the next section, you’ll extract
some of the common code from RecipeApiController and see how easy it is to create
a short-circuiting filter.
13.2.2 Resource filters: short-circuiting your action methods
Resource filters are the first general-purpose filters in the MVC filter pipeline. In sec-
tion 13.1.3, you saw minimal examples of both sync and async resource filters, which
logged to the console. In your own apps, you can use resource filters for a wide range
of purposes, thanks to the fact they execute so early (and late) in the filter pipeline.
Listing 13.8 Adding [Authorize] to an action method
The Get method has no
[Authorize] attribute, so can
be executed by anyone.
The Edit method can
only be executed if
you’re logged in.
Adds the AuthorizeFilter to the MVC
filter pipeline using [Authorize]
385
Creating custom filters for your application
The ASP.NET Core framework includes a few different implementations of
resource filters you can use in your apps, for example:
 ConsumesAttribute—Can be used to restrict the allowed formats an action
method can accept. If your action is decorated with [Consumes("application/
json")] but the client sends the request as XML, then the resource filter will
short-circuit the pipeline and return a 415 Unsupported Media Type response.
 DisableFormValueModelBindingAttribute—This filter prevents model bind-
ing from binding to form data in the request body. This can be useful if you
know an action method will be handling large file uploads that you need to
manage manually yourself. The resource filters run before model binding, so
you can disable the model binding for a single action in this way.2
resource filters are useful when you want to ensure the filter runs early in the pipeline,
before model binding. They provide an early hook into the pipeline for your logic, so
you can quickly short-circuit the request if you need to.
Look back at listing 13.6 and see if you can refactor any of the code into a
Resource filter. One candidate line appears at the start of both the Get and Edit
methods:
if (!IsEnabled) { return BadRequest(); }
This line of code is a feature toggle that you can use to disable the availability of the
whole API, based on the IsEnabled field. In practice, you’d probably load the
IsEnabled field from a database or configuration file so you could control the avail-
ability dynamically at runtime but, for this example, I’m using a hardcoded value.
This piece of code is self-contained, crosscutting logic, which is somewhat tangen-
tial to the main action method intent—a perfect candidate for a filter. You want to
execute the feature toggle early in the pipeline, before any other logic, so a resource
filter makes sense.
TIP Technically, you could also use an Authorization filter for this example,
but I’m following my own advice of “Don’t write your own Authorization
filters!”
The next listing shows an implementation of FeatureEnabledAttribute, which
extracts the logic from the action methods and moves it into the filter. I’ve also
exposed the IsEnabled field as a property on the filter.
public class FeatureEnabledAttribute : Attribute, IResourceFilter
{
public bool IsEnabled { get; set; }
2
For details on handling file uploads, see http://mng.bz/2rrk.
Listing 13.9 The FeatureEnabledAttribute resource filter
Defines whether the feature is enabled
386 CHAPTER 13 The MVC filter pipeline
public void OnResourceExecuting(
ResourceExecutingContext context)
{
if (!IsEnabled)
{
context.Result = new BadRequestResult();
}
}
public void OnResourceExecuted(
ResourceExecutedContext context) { }
}
This simple resource filter demonstrates a number of important concepts, which are
applicable to most filter types:
 The filter is an attribute as well as a filter. This lets you decorate your controller
and action methods with it using [FeatureEnabled(IsEnabled = true)].
 The filter interface consists of two methods—*Executing that runs before
model binding and *Executed that runs after the result has been executed. You
must implement both, even if you only need one for your use case.
 The filter execution methods provide a context object. This provides access to,
among other things, the HttpContext for the request and metadata about the
action method the middleware will execute.
 To short-circuit the pipeline, set the context.Result property to IAction-
Result. The MVC pipeline will execute this result to generate the response,
bypassing any remaining filters in the pipeline and the action method itself. In
this example, if the feature isn’t enabled, you bypass the pipeline by returning
BadRequestResult, which will return a 400 error to the client.
By moving this logic into the resource filter, you can remove it from your action meth-
ods, and instead decorate the whole API controller with a simple attribute:
[Route("api/recipe"), FeatureEnabled(IsEnabled = true)]
public class RecipeApiController : Controller
You’ve only extracted two lines of code from your action methods so far, but you’re on
the right track. In the next section, we’ll move on to Action filters and extract two
more filters from the action method code.
13.2.3 Action filters: customizing model binding and action results
Action filters run just after model binding, before the action method executes.
Thanks to this positioning, action filters can access all the arguments that will be used
to execute the action method, which makes them a powerful way of extracting com-
mon logic out of your actions.
On top of this, they also run just after the action method has executed and can
completely change or replace the IActionResult returned by the action if you want.
They can even handle exceptions thrown in the action.
Executes before model binding,
early in the filter pipeline
If the feature isn’t enabled,
short-circuits the pipeline
by setting the
context.Result property
Must be implemented to satisfy
IResourceFilter, but not needed
in this case.
387
Creating custom filters for your application
The ASP.NET Core framework includes a number of action filters out of the box.
One of these commonly used filters is ResponseCacheFilter, which sets HTTP cach-
ing headers on your action-method responses.
TIP Caching is a broad topic that aims to improve the performance of an
application over the naive approach. But caching can also make debugging
issues difficult and may even be undesirable in some situations. Consequently,
I often apply ResponseCacheFilter to my action methods to set HTTP cach-
ing headers that disable caching! You can read about this and other
approaches to caching in the docs at http://mng.bz/AMSQ.
The real power of action filters comes when you build filters tailored to your own apps
by extracting common code from your action methods. To demonstrate, I’m going to
create two custom filters for RecipeApiController:
 ValidateModelAttribute—This will return BadRequestResult if the model
state indicates that the binding model is invalid and will short-circuit the action
execution.
 EnsureRecipeExistsAttribute—This will use each action method’s id argu-
ment to validate that the requested Recipe entity exists before the action
method runs. If the Recipe doesn’t exist, the filter will return NotFoundResult
and will short-circuit the pipeline.
As you saw in chapter 6, MvcMiddleware automatically validates your binding models
for you before your actions execute, but it’s up to you to decide what to do about it.
For Razor pages, this can be somewhat complicated by the need to build a view model,
so you’ll often need some custom code in each action method to handle this. But for
Web API controllers, it’s common to return a 400 Bad Request response containing a
list of the errors, as shown in figure 13.5.
The request is POSTed to
the RecipeApiController.
The request body is bound to the
action method’s binding model.
A 400 Bad Request response is
sent, indicating that validation
failed for the request.
The response body is sent as a
JSON object, providing the name
of each field and the error.
Figure 13.5 Posting data to a Web API using Postman. The data is bound to the action
method’s binding model and validated. If validation fails, it’s common to return a 400
BadRequest response with a list of the validation errors.
388 CHAPTER 13 The MVC filter pipeline
It’s likely that all of your Web API controllers will use this approach, so an action filter
that automatically validates your binding models is a perfect fit. It’s normally the first
filter I create for any new project. Listing 13.10 shows a simple implementation that
you can use in your own apps.3
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(
ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result =
new BadRequestObjectResult(context.ModelState);
}
}
}
This attribute is self-explanatory and follows a similar pattern to the resource filter in
section 13.2.2, but with a few interesting points:
 I have derived from the abstract ActionFilterAttribute. This class imple-
ments IActionFilter and IResultFilter, as well as their async counterparts,
so you can override the methods you need as appropriate. This avoids needing
to add an unused OnActionExecuted() method, but is entirely optional and a
matter of preference.
 Action filters run after model binding has taken place, so context.ModelState
contains the validation errors if validation failed.
 Setting the Result property on context short-circuits the pipeline. But, due to
the position of the action filter stage, only the action method execution and
later action filters are bypassed; all the other stages of the pipeline run as
though the action had executed as normal.
If you apply this action filter to your RecipeApiController, you can remove
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Listing 13.10 The action filter for validating ModelState
3
ASP.NET Core 2.1 added this behavior by default for controllers decorated with the [ApiController] attri-
bute. For details, see https://blogs.msdn.microsoft.com/webdev/2018/02/27/asp-net-core-2-1-web-apis/.
For convenience, you derive from
the ActionFilterAttribute base class.
Overrides the Executing
method to run the filter
before the Action executes
Model binding and validation have already
run at this point, so you can check the state.
If the model isn’t valid, set the
Result property; this short-
circuits the action execution.
389
Creating custom filters for your application
from the start of both the action methods, as it will run automatically in the filter pipe-
line. You’ll use a similar approach to remove the duplicate code checking whether the id
provided as an argument to the action methods corresponds to an existing Recipe entity.
This listing shows the EnsureRecipeExistsAttribute action filter. This uses an
instance of RecipeService to check whether the Recipe exists and returns a 404 Not
Found if it doesn’t.
public class EnsureRecipeExistsAtribute : ActionFilterAttribute
{
public override void OnActionExecuting(
ActionExecutingContext context)
{
var service = (RecipeService) context.HttpContext
.RequestServices.GetService(typeof(RecipeService));
var recipeId = (int) context.ActionArguments["id"];
if (!service.DoesRecipeExist(recipeId))
{
context.Result = new NotFoundResult();
}
}
}
As before, you’ve derived from ActionFilterAttribute for simplicity and overridden
the OnActionExecuting method. The main functionality of the filter relies on the
DoesRecipeExist() method of RecipeService, so the first step is to obtain an
instance of RecipeService. The context parameter provides access to the Http-
Context for the request, which in turn lets you access the DI container and use
RequestServices.GetService() to return an instance of RecipeService.
WARNING This technique for obtaining dependencies is known as service loca-
tion and is generally considered an antipattern.4
In section 13.4, I’ll show a
much better way to use the DI container to inject dependencies into your filters.
As well as RecipeService, the other piece of information you need is the id argument
of the Get and Edit action methods. In action filters, model binding has already
occurred, so the arguments that the MVC middleware will use to execute the action
method are already known and are exposed on context.ActionArguments.
The action arguments are exposed as Dictionary<string, object>, so you can
obtain the id parameter using the "id" string key. Remember to cast the object to
the correct type.
Listing 13.11 An action filter to check whether a Recipe exists
4
For a detailed discussion on DI patterns and antipatterns, see Dependency Injection in .NET by Mark Seemann
(Manning, 2012) https://livebook.manning.com/#!/book/dependency-injection-in-dot-net/chapter-5/.
Fetches an
instance of
RecipeService
from the DI
container
Retrieves the id
parameter that
will be passed to
action method
when it executes
Checks whether a Recipe entity
with the given RecipeId exists
If it doesn’t exist, returns a
404 Not Found result and
short-circuits the pipeline.
390 CHAPTER 13 The MVC filter pipeline
TIP Whenever I see magic strings like this, I always like to try to replace them
by using the nameof operator. Unfortunately, nameof won’t work for method
arguments like this, so be careful when refactoring your code. I suggest
explicitly applying the action filter to the action method (instead of globally,
or to a controller) to remind you about that implicit coupling.
With RecipeService and id in place, it’s a case of checking whether the identifier cor-
responds to a Recipe entity and, if not, setting context.Result to NotFoundResult.
This will short-circuit the pipeline and bypass the action method altogether.
NOTE Remember, you can have multiple action filters running in a single
stage. Short-circuiting the pipeline by setting context.Result will prevent
later filters in the stage from running, as well as bypassing the action method
execution.
Before we move on, it’s worth mentioning a special case for action filters. The
Controller base class implements IActionFilter and IAsyncActionFilter itself.
If you find yourself creating an action filter for a single controller and you want
to apply it to every action in that controller, then you can override the appropriate
methods on your controller.
public class HomeController : Controller
{
public override void OnActionExecuting(
ActionExecutingContext context)
{ }
public override void OnActionExecuted(
ActionExecutedContext context)
{ }
}
If you override these methods on your controller, they’ll run in the action filter stage
of the filter pipeline for every action on the controller. The OnActionExecuting
Controller method runs before any other action filters, regardless of ordering or
scope, and the OnActionExecuted method runs after all other filters.
TIP The controller implementation can be useful in some cases, but you
can’t control the ordering related to other filters. Personally, I generally pre-
fer to break logic into explicit, declarative filter attributes but, as always, the
choice is yours.
With the resource and action filters complete, your controller is looking much tidier,
but there’s one aspect in particular that would be nice to remove: the exception han-
dling. In the next section, we’ll look at how to create a custom exception filter for
your controller, and why you might want to do this instead of using exception-
handling middleware.
Listing 13.12 Overriding action filter methods directly on a Controller
Derives from the Controller base class
Runs before any other action filters
for every action in the controller
Runs after all other action filters
for every action in the controller
391
Creating custom filters for your application
13.2.4 Exception filters: custom exception handling
for your action methods
In chapter 3, I went into some depth about types of error-handling middleware you
can add to your apps. These let you catch exceptions thrown from any later middle-
ware and handle them appropriately. If you’re using exception-handling middleware,
you may be wondering why we need exception filters at all!
The answer to this is pretty much the same as I outlined in section 13.1.2: filters are
great for crosscutting concerns, when you need behavior that’s either specific to MVC
or should only apply to certain routes.
Both of these can apply in exception handling. Exception filters run within Mvc-
Middleware, so they have access to the context in which the error occurred, such as
the action that was executing. This can be useful for logging additional details when
errors occur, such as the action parameters that caused the error.
WARNING If you use exception filters to record action method arguments,
make sure you’re not storing sensitive data, such as passwords or credit card
details, in your logs.
You can also use exception filters to handle errors from different routes in different
ways. Imagine you have both MVC and Web API controllers in your app, as we do in
the recipe app. What happens when an exception is thrown by an MVC controller?
As you saw in chapter 3, the exception travels back up the middleware pipeline,
and is caught by exception-handler middleware. The exception-handler middleware
will re-execute the pipeline and generate an MVC error page.
That’s great for your MVC controllers, but what about exceptions in your Web API
controllers? If your API throws an exception, and consequently returns HTML gener-
ated by the exception-handler middleware, that’s going to break a client who has
called the API expecting a JSON response!
Instead, exception filters let you handle the exception inside MvcMiddleware and
generate an appropriate response body. The exception handler middleware only
intercepts errors without a body, so it will let the modified Web API response pass
untouched.
Exception filters can catch exceptions from more than your action methods.
They’ll run if an exception occurs in MvcMiddleware
 During model binding or validation
 When the action method is executing
 When an action filter is executing
You should note that exception filters won’t catch exceptions thrown in any filters
other than action filters, so it’s important your resource and result filters don’t throw
exceptions. Similarly, they won’t catch exceptions thrown when executing IAction-
Result itself.
392 CHAPTER 13 The MVC filter pipeline
Now that you know why you might want an exception filter, go ahead and imple-
ment one for RecipeApiController, as shown next. This lets you safely remove the
try-catch block from your action methods, knowing that your filter will catch any
errors.
public class HandleExceptionAttribute : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
var error = new
{
Success = false,
Errors = new [] { context.Exception.Message }
};
context.Result = new ObjectResult(error)
{
StatusCode = 500
};
context.ExceptionHandled = true;
}
}
It’s quite common to have one or two different exception filters in your application,
one for your MVC controllers and one for your Web API controllers, but they’re not
always necessary. If you can handle all the exceptions in your application with a single
piece of middleware, then ditch the exception filters and go with that instead.
You’re coming to the last type of filter now, result filters, and with it, you’re almost
done refactoring your RecipeApiController. Custom result filters tend to be rela-
tively rare in the apps I’ve written, but they have their uses, as you’ll see.
13.2.5 Result filters: customizing action results before they execute
If everything runs successfully in the pipeline, and there’s no short-circuiting, then
the next stage of the pipeline after action filters are result filters. These run just
before and after the IActionResult returned by the Action method (or action filters)
is executed.
WARNING If the pipeline is short-circuited by setting context.Result, the
result filter stage won’t be run, but IActionResult will still be executed to
generate the response. The one exception to this rule is action filters—these
only short-circuit the action execution, as you saw in figure 13.2, and so result
filters run as normal, as though the action itself generated the response.
Listing 13.13 The HandleExceptionAttribute exception filter
ExceptionFilterAttibute is an abstract base
class that implements IExceptionFilter.
There’s only a single method to
override for IExceptionFilter.
Building a custom
object to return in
the response
Creates an ObjectResult
to serialize the error
object and to set the
response status code
Marks the exception as handled to
prevent it propagating out of
MvcMiddleware
393
Creating custom filters for your application
Result filters run immediately after action filters, so many of their use cases are similar,
but you typically use result filters to customize the way the IActionResult executes.
For example, ASP.NET Core has several result filters built into its framework:
 ProducesAttribute—This forces the Web API result to be serialized to a
specific output format. For example, decorating your action method with
[Produces("application/xml")] forces the formatters to try to format the
response as XML, even if the client doesn’t list XML in its Accept header.
 FormatFilterAttribute—Decorating an action method with this filter tells the
formatter to look for a route value or query string parameter called format, and
to use that to determine the output format. For example, you could call
/api/recipe/11?format=json and FormatFilter will format the response as
JSON, or call api/recipe/11?format=xml and get the response as XML.5
As well as controlling the output formatters, you can use result filters to make any last-
minute adjustments before IActionResult is executed and the response is generated.
As an example of the kind of flexibility available to you, in the following listing I
demonstrate setting the Last-Modified header, based on the object returned from
the action. This is a somewhat contrived example—it’s specific enough to a single
action that it doesn’t warrant being moved to a result filter—but, hopefully, you get
the idea!
public class AddLastModifedHeaderAttribute : ResultFilterAttribute
{
public override void OnResultExecuting(
ResultExecutingContext context)
{
if (context.Result is OkObjectResult result
&& result.Value is RecipeDetailViewModel detail)
{
var viewModelDate = detail.LastModified;
context.HttpContext.Response
.GetTypedHeaders().LastModified = viewModelDate;
}
}
}
I’ve used another helper base class here, ResultFilterAttribute, so you only need
to override a single method to implement the filter. Fetch the current IActionResult,
5
Remember, you need to explicitly configure the XML formatters if you want to serialize to XML. For details,
see http://mng.bz/0J2z.
Listing 13.14 Setting a response header in a result filter
ResultFilterAttribute provides a
useful base class you can override
You could also override the Executed
method, but the response would
already be sent by then.
Checks
whether the
action result
returned a 200
Ok result with
a view model
Checks whether
the view model
type is
RecipeDetailView
Model . . .
. . . if it is, fetches the LastModified field and
sets the Last-Modified header in the response
394 CHAPTER 13 The MVC filter pipeline
exposed on context.Result, and check that it’s OkObjectResult with RecipeDetail-
ViewModel. If it is, then fetch the LastModified field from the view model and add a
Last-Modified header to the response.
TIP GetTypedHeaders() is an extension method that provides strongly typed
access to request and response headers. It takes care of parsing and format-
ting the values for you. You can find it in the Microsoft.AspNetCore.Http
namespace.
As with resource and action filters, result filters can implement a method that runs
after the result has been executed, OnResultExecuted. You can use this method, for
example, to inspect exceptions that happened during the execution of IAction-
Result.
WARNING Generally, you can’t modify the response in the OnResult-
Executed method, as MvcMiddleware may have already started streaming the
response to the client.
That brings us to the end of this detailed look at each of the filters in the MVC pipe-
line. Looking back and comparing listings 13.6 and 13.7, you can see filters allowed us
to refactor the controllers and make the intent of each action method much clearer.
Writing your code in this way makes it easier to reason about, as each filter and action
has a single responsibility.
In the next section, I’ll take a slight detour into exactly what happens when you
short-circuit a filter. I’ve described how to do this, by setting the context.Result prop-
erty on a filter, but I haven’t yet described exactly what happens. For example, what if
there are multiple filters in the stage when it’s short-circuited? Do those still run?
13.3 Understanding pipeline short-circuiting
A brief warning: the topic of filter short-circuiting can be a little confusing. Unlike
middleware short-circuiting, which is cut-and-dried, the MVC filter pipeline is a bit
more nuanced. Luckily, you won’t often find you need to dig into it, but when you do,
you’ll be glad of the detail.
You short-circuit the authorization, resource, action, and result filters by setting
context.Result to IActionResult. Setting an action result in this way causes some,
or all, of the remaining pipeline to by bypassed. But the filter pipeline isn’t entirely
linear, as you saw in figure 13.2, so short-circuiting doesn’t always do an about-face
back down the pipeline. For example, short-circuited action filters only bypass action
method execution—the result filters and result execution stages still run.
The other difficultly is what happens if you have more than one type of filter. Let’s
say you have three resource filters executing in a pipeline. What happens if the second
filter causes a short circuit? Any remaining filters are bypassed, but the first resource fil-
ter has already run its *Executing command, as shown in figure 13.6. This earlier filter
gets to run its *Executed command too, with context.Cancelled = true, indicating
that a filter in that stage (the resource filter stage) short-circuited the pipeline.
395
Understanding pipeline short-circuiting
Understanding which other filters will run when you short-circuit a filter can be some-
what of a chore, but I’ve summarized each filter in table 13.1. You’ll also find it useful
to refer to figure 13.2 to visualize the shape of the pipeline when thinking about short-
circuits.
The most interesting point here is that short-circuiting an action filter doesn’t short-
circuit much of the pipeline at all. In fact, it only bypasses later action filters and the
action method execution itself. By primarily building action filters, you can ensure
that other filters, such as result filters that define the output format, run as usual, even
when your action filters short-circuit.
Table 13.1 The effect of short-circuiting filters on filter-pipeline execution
Filter type How to short-circuit? What else runs?
Authorization filters Set context.Result Nothing, the pipeline is immediately halted.
Resource filters Set context.Result Resource-filter *Executed functions from earlier
filters run with context.Cancelled = true.
Action filters Set context.Result Only bypasses action method execution. Action fil-
ters earlier in the pipeline run their *Executed
methods with context.Cancelled = true,
then result filters, result execution, and resource
filters’ *Executed methods all run as normal.
Exception filters Set context.Result and
Exception.Handled =
true
All resource-filter *Executed functions run.
Result filters Set context.Cancelled
= true
Result filters earlier in the pipeline run their
*Executed functions with
context.Cancelled = true. All resource-
filter *Executed functions run as normal.
Filter1
Filter 3
Filter2
OnResourceExecuting
OnResourceExecuting
OnResourceExecuted
1 1
. Resource filter runs
its Executing function.
∗
2. Resource filter 2 runs
its Executing function and
∗
short-circuits the pipeline
by setting context.Result.
context.Result
3. Resource filter 3
(or the rest of the
pipeline) never runs.
5. Resource filter runs its
1
Executed function. Cancelled
∗
is set to true, indicating the
pipeline was cancelled.
4. Resource filter 2 doesn’t
run its Executed function as
∗
it short-circuited the pipeline.
context.cancelled=true
Figure 13.6 The effect of short-circuiting a resource filter on other resource filters in that stage. Later
filters in the stage won’t run at all, but earlier filters run their OnResourceExecuted function.
396 CHAPTER 13 The MVC filter pipeline
The last thing I’d like to talk about in this chapter is how to use DI with your filters.
You saw in chapter 10 that DI is integral to ASP.NET Core, and in the next section,
you’ll see how to design your filters so that the framework can inject service depen-
dencies into them for you.
13.4 Using dependency injection with filter attributes
The previous version of ASP.NET used filters, but they suffered from one problem in
particular: it was hard to use services from them. This was a fundamental issue with
implementing them as attributes that you decorate your actions with. C# attributes
don’t let you pass dependencies into their constructors (other than constant values),
and they’re created as singletons, so there’s only a single instance for the lifetime of
your app.
In ASP.NET Core, this limitation is still there in general, in that filters are typically
created as attributes that you add to your controller classes and action methods. What
happens if you need to access a transient or scoped service from inside the singleton
attribute?
Listing 13.11 showed one way of doing this, using a pseudo service locator pattern
to reach into the DI container and pluck out RecipeService at runtime. This works
but is generally frowned upon as a pattern, in favor of proper DI. How can you add DI
to your filters?
The key is to split the filter into two. Instead of creating a class that’s both an attri-
bute and a filter, create a filter class that contains the functionality and an attribute
that tells MvcMiddleware when and where to use the filter.
Let’s apply this to the action filter from listing 13.11. Previously, I derived from
ActionFilterAttribute and obtained an instance of RecipeService from the context
passed to the method. In the following listing, I show two classes, EnsureRecipeExists-
Filter and EnsureRecipeExistsAttribute. The filter class is responsible for the func-
tionality and takes in RecipeService as a constructor dependency.
public class EnsureRecipeExistsFilter : IActionFilter
{
private readonly RecipeService _service;
public EnsureRecipeExistsFilter(RecipeService service)
{
_service = service;
}
public void OnActionExecuting(ActionExecutingContext context)
{
var recipeId = (int) context.ActionArguments["id"];
if (!_service.DoesRecipeExist(recipeId))
{
context.Result = new NotFoundResult();
}
}
Listing 13.15 Using DI in a filter by not deriving from Attribute
Doesn’t derive from
an Attribute class
RecipeService
is injected
into the
constructor.
The rest of
the method
remains the
same.
397
Using dependency injection with filter attributes
public void OnActionExecuted(ActionExecutedContext context) { }
}
public class EnsureRecipeExistsAttribute : TypeFilterAttribute
{
public EnsureRecipeExistsAttribute()
: base(typeof(EnsureRecipeExistsFilter)) {}
}
EnsureRecipeExistsFilter is a valid filter; you could use it on its own by adding it as
a global filter (as global filters don’t need to be attributes). But you can’t use it directly
by decorating controller classes and action methods, as it’s not an attribute. That’s
where EnsureRecipeExistsAttribute comes in.
You can decorate your methods with EnsureRecipeExistsAttribute instead. This
attribute inherits from TypeFilterAttribute and passes the Type of filter to create as
an argument to the base constructor. This attribute acts as a factory for EnsureRecipe-
ExistsFilter by implementing IFilterFactory.
When MvcMiddleware initially loads your app, it scans your actions and controllers,
looking for filters and filter factories. It uses these to form a filter pipeline for every
action in your app, as shown in figure 13.7.
When an action decorated with EnsureRecipeExistsAttribute is called, Mvc-
Middleware calls CreateInstance() on the attribute. This creates a new instance of
EnsureRecipeExistsFilter and uses the DI container to populate its dependencies
(RecipeService).
You must implement
the Executed action to
satisfy the interface.
Passes the type EnsureRecipeExistsFilter
as an argument to the base TypeFilter
constructor
Derives from
TypeFilter, which
is used to fill
dependencies
using the DI
container.
public class HomeController
{
[ValidateModel]
[EnsureRecipeExistsFilter]
public IActionResult Index()
{
return View();
}
}
ValidateModelAttribute
EnsureRecipeExistsFilter
IFilterFactory
Attributes that implement
filter interfaces are added
directly to the pipeline.
The MvcMiddleware calls CreateInstance () on each
IFilterFactory when a request is received to create
a filter instance, which is added to the pipeline.
CreateInstance()
The MvcMiddleware scans your
app looking for filters or attributes
that implement IFilterFactory.
Figure 13.7 The MvcMiddleware scans your app on startup to find both filters and attributes that
implement IFilterFactory. At runtime, the middleware calls CreateInstance() to get an
instance of the filter.
398 CHAPTER 13 The MVC filter pipeline
By using this IFilterFactory approach, you get the best of both worlds; you can dec-
orate your controllers and actions with attributes, and you can use DI in your filters.
Out of the box, two similar classes provide this functionality, which have slightly differ-
ent behaviors:
 TypeFilterAttribute—Loads all of the filter’s dependencies from the DI con-
tainer and uses them to create a new instance of the filter.
 ServiceFilterAttribute—Loads the filter itself from the DI container. The DI
container takes care of the service lifetime and building the dependency graph.
Unfortunately, you also have to explicitly register your filter with the DI con-
tainer in ConfigureServices in Startup:
services.AddTransient<EnsureRecipeExistsFilter>();
Whether you choose to use TypeFilterAttribute or ServiceFilterAttribute is
somewhat a matter of preference, and you can always implement a custom IFilter-
Factory if you need to. The key takeaway is that you can now use DI in your filters. If you
don’t need to use DI for a filter, then implement it as an attribute directly for simplicity.
TIP I like to create my filters as a nested class of the attribute class when
using this pattern. This keeps all the code nicely contained in a single file and
indicates the relationship between the classes.
That brings us to the end of this chapter on the filter pipeline. Filters are a somewhat
advanced topic, in that they aren’t strictly necessary for building basic apps, but I find
them extremely useful for ensuring my controller and action methods are simple and
easy to understand.
In the next chapter, we’ll take our first look at securing your app. We’ll discuss the
difference between authentication and authorization, the concept of identity in
ASP.NET Core, and how you can use the ASP.NET Core Identity system to let users
register and log in to your app.
Summary
 The filter pipeline executes as part of MvcMiddleware after routing has selected
an action method.
 The filter pipeline consists of authorization filters, resource filters, action fil-
ters, exception filters, and Result filters. Each filter type is grouped into a stage.
 Resource, action, and result filters run twice in the pipeline: an *Executing
method on the way in and an *Executed method on the way out.
 Authorization and exception filters only run once as part of the pipeline; they
don’t run after a response has been generated.
 Each type of filter has both a sync and an async version. For example, resource
filters can implement either the IResourceFilter interface or the IAsync-
ResourceFilter interface. You should use the synchronous interface unless
your filter needs to use asynchronous method calls.
399
Summary
 You can add filters globally, at the controller level, or at the action level. This is
called the scope of the filter.
 Within a given stage, global-scoped filters run first, then controller-scoped, and
finally, action-scoped.
 You can override the default order by implementing the IOrderedFilter inter-
face. Filters will run from lowest to highest Order and use scope to break ties.
 Authorization filters run first in the pipeline and control access to APIs.
ASP.NET Core includes an [Authorization] attribute that you can apply to
action methods so that only logged-in users can execute the action.
 Resource filters run after authorization filters, and again after a result has been
executed. They can be used to short-circuit the pipeline, so that an action
method is never executed. They can also be used to customize the model bind-
ing process for an action method.
 Action filters run after model binding has occurred, just before an action
method executes. They also run after the action method has executed. They can
be used to extract common code out of an action method to prevent duplication.
 The Controller base class also implements IActionFilter and IAsyncAction-
Filter. They run at the start and end of the action filter pipeline, regardless of
the ordering or scope of other action filters.
 Exception filters execute after action filters, when an action method has thrown
an exception. They can be used to provide custom error handling specific to
the action executed.
 Generally, you should handle exceptions at the middleware level, but exception
filters let you customize how you handle exceptions for specific actions or
controllers.
 Result filters run just before and after an IActionResult is executed. You can
use them to control how the action result is executed, or to completely change
the action result that will be executed.
 You can use ServiceFilterAttribute and TypeFilterAttribute to allow
dependency injection in your custom filters. ServiceFilterAttribute requires
that you register your filter and all its dependencies with the DI container,
whereas TypeFilterAttribute only requires that the filter’s dependencies have
been registered.
400
Authentication:
adding users to your
application with Identity
One of the selling points of a web framework like ASP.NET Core is the ability to
provide a dynamic app, customized to individual users. Many apps have the con-
cept of an “account” with the service, which you can “sign in” to and get a different
experience.
Depending on the service, an account gives you varying things: on some apps
you may have to sign in to get access to additional features, on others you might see
suggested articles. On an e-commerce app, you’d be able to make and view your
This chapter covers
 How authentication works in web apps in
ASP.NET Core
 Creating a project using the ASP.NET Core
Identity system
 Adding user functionality to an existing web app
401
past orders, on Stack Overflow you can post questions and answers, whereas on a news
site you might get a customized experience based on previous articles you’ve viewed.
When you think about adding users to your application, you typically have two
aspects to consider:
 Authentication—The process of creating users and letting them log in to your
app
 Authorization—Customizing the experience and controlling what users can do,
based on the current logged-in user
In this chapter, I’m going to be discussing the first of these points, authentication and
membership, and in the next chapter, I’ll tackle the second point, authorization. In
section 14.1, I discuss the difference between authentication and authorization, how
authentication works in a traditional ASP.NET Core web app, and ways you can archi-
tect your system to provide sign-in functionality.
I also touch on the typical differences in authentication between a traditional web
app and Web APIs used with client-side or mobile web apps. This book focuses on tra-
ditional web apps for authentication, but many of the principles are applicable to both.
In section 14.2, I introduce a user management system called ASP.NET Core Iden-
tity (or Identity for short). Identity integrates with EF Core and provides a number of
services for creating and managing users, storing and validating passwords, and sign-
ing users in and out of your app.
In section 14.3, you’ll create an app using a default template that includes
ASP.NET Core Identity out of the box. This will give you an app to explore, to see the
features Identity provides, as well as everything it doesn’t.
Creating an app is great for seeing an example of how the pieces fit together, but
you’ll often need to add users to an existing app. In section 14.4, you’ll see how you
can extract the Identity elements from the default template and apply it to an existing
app: the recipe application from chapters 12 and 13.
In section 14.5, you’ll customize the login process. It’s common to need to store
additional information about a user (such as their name or date of birth) and to pro-
vide permissions to them that you can later use to customize the app’s behavior (if the
user is a VIP, for example).
NOTE The authentication and authorization subsystem of ASP.NET Core
underwent some breaking changes between ASP.NET Core 1.1 and 2.0 to
solve some general design deficiencies. I only talk about the ASP.NET Core
2.0 system in this book. You can read about the changes at https://github
.com/aspnet/Announcements/issues/232. Additionally, ASP.NET Core 2.1,
in preview at the time of writing, hides much of the UI code from you by
default. You can generate the UI code again, if required, using the scaffolder
described in the following post http://mng.bz/tZZz.
402 CHAPTER 14 Authentication: adding users to your application with Identity
Before we break out the braces, let’s take a look at authentication and authorization
in ASP.NET Core, what’s happening when you sign in to a website, and some of the
ways to design your apps to provide this functionality.
14.1 Introducing authentication and authorization
When you add sign-in functionality to your app and control access to certain functions
based on the currently-signed-in user, you’re using two distinct aspects of security:
 Authentication—The process of determining who you are
 Authorization—The process of determining what you’re allowed to do
Generally, you need to know who the user is before you can determine what they’re
allowed to do, so authentication typically comes first, followed by authorization. In this
chapter, we’re only looking at authentication; we’ll cover authorization in chapter 15.
I’ll start by discussing how ASP.NET Core thinks about users, and cover some of the
terminology and concepts that are central to authentication. I always found this to be
the hardest part to grasp when first learning about authentication, so I’ll take it slow!
Next, we’ll look at what it means to sign in to a traditional web app. After all, you
only provide your password and sign in to an app on a single page—how does the app
know the request came from you for subsequent requests?
Finally, we’ll look at how authentication works when you need to support client-side
apps and mobile apps that call Web APIs, in addition to traditional web apps. Many of
the concepts are similar, but the requirement to support multiple types of users, tradi-
tional apps, client-side apps, and mobile apps has led to alternative solutions.
In the next section, we’ll look at a practical implementation of a user management
system called ASP.NET Core Identity, and how you can use this in your own projects.
14.1.1 Understanding users and claims in ASP.NET Core
The concept of a user is baked-in to ASP.NET Core. In this section, we’ll look at the
fundamental model used to describe a user, including its properties, such as an associ-
ated email address, as well as any permission-related properties, such as whether the
user is an admin user.
In chapter 3, you learned that the HTTP server, Kestrel, creates an HttpContext
object for every request it receives. This object is responsible for storing all of the
details related to that request, such as the request URL, any headers sent, the body of
the request, and so on.
The HttpContext object also exposes the current principal for a request as the User
property. This is ASP.NET Core’s view of which user made the request. Any time your
app needs to know who the current user is, or what they’re allowed to do, it can look
at the HttpContext.User principal.
DEFINITION You can think of the principal as the user of your app.
403
Introducing authentication and authorization
In ASP.NET Core, principals are implemented as ClaimsPrincipals, which has a col-
lection of claims associated with it, as shown in figure 14.1.
You can think about claims as properties of the current user. For example, you
could have claims for things like email, name, or date of birth.
DEFINITION A claim is a single piece of information about a principal, which
consists of a claim type and an optional value.
Claims can also be indirectly related to permissions and authorization, so you could
have a claim called HasAdminAccess or IsVipCustomer. These would be stored in
exactly the same way—as claims associated with the user principal.
NOTE Earlier versions of ASP.NET used a role-based approach to security,
rather than claims-based. The ClaimsPrincipal used in ASP.NET Core is
compatible with this approach for legacy reasons, but you should use the
claims-based approach for new apps.
Kestrel assigns a user principal to every request that arrives at your app. Initially, that
principal is a generic, anonymous, unauthenticated principal with no claims. How do
you log in, and how does ASP.NET Core know that you’ve logged in on subsequent
requests?
In the next section, we’ll look at how authentication works in a traditional web app
using ASP.NET Core, and the process of signing in to a user account.
14.1.2 Authentication in ASP.NET Core: services and middleware
Adding authentication to any web app involves a number of moving parts. The same gen-
eral process applies whether you’re building a traditional web app or a client-side app,
though there are often differences in implementation, as I’ll discuss in section 14.1.3:
Email=test@test.com
ClaimsPrincipal
FirstName=Andrew
LastName=Lock
HomePhone=555 123
HasAdminAccess
The principal associated with
the request is the current user.
The principal is implemented
by the ClaimsPrincipal class,
which has a collection of Claims.
Claims describe properties of
the principal. These normally
consist of a type and a value
but can be a name only.
Figure 14.1 The principal is the current user, implemented as ClaimsPrincipal. It contains a
collection of Claims that describe the user.
404 CHAPTER 14 Authentication: adding users to your application with Identity
 The client sends an identifier and a secret to the app, which identify the current
user. For example, you could send an email (identifier) and a password (secret).
 The app verifies that the identifier corresponds to a user known by the app and
that the corresponding secret is correct.
 If the identifier and secret are valid, the app can set the principal for the cur-
rent request, but it also needs a way of storing these details for subsequent
requests. For traditional web apps, this is typically achieved by storing an
encrypted version of the user principal in a cookie.
This is the typical flow for most web apps, but in this section, I’m going to look at how
it works in ASP.NET Core. The overall process is the same, but it’s good to see how this
pattern fits into the services, middleware, and MVC aspects of an ASP.NET Core appli-
cation. We’ll step through the various pieces at play in a typical app when you sign in
as a user, what that means, and how you can make subsequent requests as that user.
SIGNING IN TO AN ASP.NET CORE APPLICATION
When you first arrive on a site and sign in to a traditional web app, the app will send
you to a sign-in page and ask you to enter your username and password. After you sub-
mit the form to the server, the app redirects you to a new page, and you’re magically
logged in! Figure 14.2 shows what’s happening behind the scenes in an ASP.NET Core
app when you submit the form.
1. The user enters their login
details and clicks submit to
POST them to the server.
Request
AccountController.
Login
Redirect Result
5. Finally, the user principal is
serialized and returned as an
encrypted cookie to the browser.
SignInManager
3. The action method calls the
sign in manager. This loads the
user from the database using
EF Core and validates their
password.
4. If the password is correct,
the user is signed in. The User
property is set to the authenticated
user principal.
HttpContext.User
?
HttpContext.User
2. Initially, the User property
is set to an anonymous user
principal.
Db
Figure 14.2 Signing in to an ASP.NET Core application. SignInManager is responsible for setting
HttpContext.User to the new principal and serializing the principal to the encrypted cookie.
405
Introducing authentication and authorization
This shows the series of steps from the moment you submit the login form, to the
point the redirect is returned to the browser. When the request first arrives, Kestrel
creates an anonymous user principal and assigns it to the HttpContext.User property.
The request is then routed to a normal MVC controller, AccountController, which
reads the email and password from the request.
The meaty work happens inside the SignInManager service. This is responsible for
loading a user entity from the database with the provided username and validating
that the password they provided is correct.
WARNING Never store passwords in the database directly. They should be
hashed using a strong one-way algorithm. The ASP.NET Core Identity system
does this for you, but it’s always wise to reiterate this point!
If the password is correct, SignInManager creates a new ClaimsPrincipal from the
user entity it loaded from the database and adds the appropriate claims, such as the
email. It then replaces the old, anonymous, HttpContext.User principal with the new,
authenticated principal.
Finally, SignInManager serializes the principal, encrypts it, and stores it as a cookie.
A cookie is a small piece of text that’s sent back and forth between the browser and
your app along with each request, consisting of a name and a value.
This authentication process explains how you can set the user for a request when
they first log in to your app, but what about subsequent requests? You only send your
password when you first log in to the app, so how does the app know that it’s the same
user making the request?
AUTHENTICATING USERS FOR SUBSEQUENT REQUESTS
The key to persisting your identity across multiple requests lies in the final step of fig-
ure 14.2, where you serialized the principal in a cookie. Browsers will automatically
send this cookie with all requests made to your app, so you don’t need to provide your
password with every request.
ASP.NET Core uses the authentication cookie sent with the requests to rehydrate
ClaimsPrincipal and set the HttpContext.User principal for the request, as
shown in figure 14.3. The important thing to note is when this process happens—in
AuthenticationMiddleware.
When a request containing the authentication cookie is received, Kestrel creates
the default, unauthenticated, anonymous principal and assigns it to the HttpContext
.User principal. Any middleware that runs at this point, before Authentication-
Middleware, will see the request as unauthenticated, even if there’s a valid cookie.
TIP If it looks like your authentication system isn’t working, double-check
your middleware pipeline. Only middleware that runs after Authentication-
Middleware will see the request as authenticated.
406 CHAPTER 14 Authentication: adding users to your application with Identity
AuthenticationMiddleware is responsible for setting the current user for a request. The
middleware calls authentication services, which will read the cookie from the request and
deserialize it to obtain the ClaimsPrincipal created when the user logged in.
AuthenticationMiddleware sets the HttpContext.User principal to the new,
authenticated principal. All subsequent middleware will now know the user principal
for the request and can adjust behavior accordingly (for example, displaying the
user’s name on the homepage, or restricting access to some areas of the app).
The process described so far, in which a single app authenticates the user when
they log in and sets a cookie that’s read on subsequent requests, is common with tradi-
tional web apps, but it isn’t the only possibility. In the next section, we’ll take a look at
authentication for client-side and mobile apps, and how the authentication system
changes for those scenarios.
14.1.3 Authentication for APIs and distributed applications
The process I’ve outlined so far applies to traditional web apps, where you have a sin-
gle endpoint that’s doing all the work. It’s responsible for authenticating and manag-
ing users, as well as serving your app data, as shown in figure 14.4.
1. An authenticated user
makes a request to /recipes.
Request
HttpContext.User
?
2. The browser sends the
authentication cookie
with the request.
Authentication
middleware
Static file
middleware
3. Any middleware before the
authentication middleware
treat the request as though
it is unauthenticated.
Authentication
services
4. The authentication middleware
calls the Authentication services
which deserialize the user
principal from the cookie
and confirms it’s valid.
MVC middleware
HttpContext.User
6. All middleware after the
authentication middleware
see the request as from
the authenticated user.
5. The HttpContext.User property
is set to the deserialized principal,
and the request is now authenticated.
Figure 14.3 A subsequent request after signing in to an application. The cookie sent with the request
contains the user principal, which is validated and used to authenticate the request.
407
Introducing authentication and authorization
In addition to this traditional web app, it’s common to use ASP.NET Core as a Web
API to serve data for mobile and client-side SPAs. Similarly, the trend towards micro-
services on the backend means that even traditional web apps using Razor often need
to call Web APIs behind the scenes, as shown in figure 14.5.
In this situation, you have multiple apps and APIs, all of which need to understand
that the same user is making a request across all of the apps and APIs. If you keep the
same approach as before, where each app manages its own users, things can quickly
become complicated!
You’ll need to duplicate all of the sign-in logic between the apps and APIs, as well
as needing to have some central database holding the user details. Users may need to
sign in multiple times to access different parts of the service. On top of that, using
cookies becomes problematic for some mobile clients in particular, or where you’re
making requests to multiple domains (as cookies only belong to a single domain).
Browsers call traditional
web apps.
Traditional web apps serve requests
and handle authentication/authorization
of users.
Figure 14.4 Traditional apps typically handle all the functionality of an app: the business
logic, generating the UI, authentication, and user management.
Browsers call traditional
web apps.
Client-side and mobile
apps call APIs.
Both traditional web apps
and APIs call other APIs.
Each app or API needs to be
authenticated/authorized.
Figure 14.5 Modern applications typically need to expose Web APIs for mobile and client-side apps,
as well as potentially calling APIs on the backend. When all of these services need to authenticate
and manage users, this becomes complicated logistically.
408 CHAPTER 14 Authentication: adding users to your application with Identity
How can you improve this? The typical approach is to extract the code that’s common
to all of the apps and APIs, and move it to an identity provider, as shown in figure 14.6.
Instead of signing in to an app directly, the app redirects to an identity provider
app. The user signs in to this identity provider, which passes bearer tokens back to the
client that indicate who the user is and what they’re allowed to access. The clients and
apps can pass these tokens to the APIs, to provide information about the logged-in
user, without needing to re-authenticate or manage users directly.
This architecture is clearly more complicated on the face of it, as you’ve thrown a
whole new service—the identity provider—into the mix, but in the long run this has a
number of advantages:
 Users can share their identity between multiple services. As you’re logged in to the cen-
tral identity provider, you’re essentially logged in to all apps that use that ser-
vice. This gives you the “single-sign-on” experience, where you don’t have to
keep logging in to multiple services.
 Reduced duplication. All of the sign-in logic is encapsulated in the identity pro-
vider, so you don’t need to add sign-in screens to all your apps.
 Can easily add new providers. Whether you use the identity provider approach or
the traditional approach, it’s possible to use external services to handle the
authentication of users. You’ll have seen this on apps that allow you to “log in
using Facebook” or “log in using Google,” for example. If you use a centralized
identity provider, adding support for additional providers can be handled in
one place, instead of having to configure every app and API explicitly.
Instead of authenticating directly
with the app, browsers and APIs
authenticate with an Identity
Provider which issues tokens.
The tokens are passed
to the traditional web
apps and APIs.
Authentication is now centralized.
Tokens can be passed between
APIs and services as necessary.
Figure 14.6 An alternative architecture involves using a central identity provider to handle all
the authentication and user management for the system. Tokens are passed back and forth
between the identity provider, apps, and APIs.
409
Introducing authentication and authorization
Out of the box, ASP.NET Core supports architectures like this, and for consuming
issued bearer tokens, but it doesn’t include support for issuing those tokens. That
means you’ll need to use another library or service for the identity provider.
One option for an identity provider is to delegate all the authentication responsi-
bilities to a third-party identity provider, such as Okta, Auth0, or Azure Active Direc-
tory B2C. These will manage users for you, so user information and passwords are
stored in their database, rather than your own. The biggest advantage of this
approach is that you don’t have to worry about making sure your customer data is
safe; you can be pretty sure that a third party will protect it, as it’s their whole business!
TIP Wherever possible, I recommend this approach, as it delegates security
responsibilities to someone else. You can’t lose your user’s details if you never
had them!
Another common option is to build your own identity provider. This may sound like a
lot of work, but thanks to excellent libraries like OpenIddict (https://github.com/
openiddict) and IdentityServer4 (http://docs.identityserver.io), it’s perfectly possible
to write your own identity provider to serve bearer tokens that will be consumed by an
application.
An aspect often overlooked by people getting started with OpenIddict and Identity-
Server is that they aren’t prefabricated solutions. You, as a developer, need to write the
code that knows how to create a new user (normally in a database), how to load a
user’s details, and how to validate their password. In that respect, the development
process of creating an identity provider is similar to the traditional web app with
cookie authentication that I discussed in section 14.1.2.
In fact, you can almost think of an identity provider as a traditional web app that
only has account management pages. It also has the ability to generate tokens for
other services, but it contains no other app-specific logic. The need to manage users
in a database, as well as providing an interface for users to log in, is common to both
approaches and is the focus of this chapter.
NOTE Hooking up your apps and APIs to use an identity provider requires a
fair amount of tedious configuration, both of the app and the identity pro-
vider. For simplicity, this book will focus on traditional web apps using the
process outlined in section 14.1.2. For details on how to configure your apps
and client-side SPAs to use IdentityServer, see the excellent documentation at
http://docs.identityserver.io.
ASP.NET Core Identity (hereafter shortened to Identity) is a system that makes build-
ing the user management aspect of your app (or identity provider app) simpler. It
handles all of the boilerplate for saving and loading users to a database, as well as a
number of best practices for security, such as user lock out, password hashing, and two-
factor authentication.
410 CHAPTER 14 Authentication: adding users to your application with Identity
DEFINITION Two-factor authentication (2FA) is where you require an extra
piece of information to sign in, in addition to a password. This could involve
sending a code to a user’s phone by SMS, or using a mobile app to generate a
code, for example.
In the next section, I’m going to talk about the ASP.NET Core Identity system, the
problems it solves, when you’d want to use it, and when you might not want to use it. In
section 14.3, we’ll take a look at some code and see ASP.NET Core Identity in action.
14.2 What is ASP.NET Core Identity?
Whether you’re writing a traditional web app using Razor templates or are setting up
a new identity provider using a library like IdentityServer, you’ll need a way of persist-
ing details about your users, such as their usernames and passwords.
This might seem like a relatively simple requirement but, given this is related to
security and people’s personal details, it’s important you get it right. As well as storing
the claims for each user, it’s important to store passwords using a strong hashing algo-
rithm, to allow users to use 2FA where possible, and to protect against brute force
attacks, to name a few of the many requirements!
Although it’s perfectly possible to write all the code to do this manually and to
build your own authentication and membership system, I highly recommend you
don’t.
I’ve already mentioned third-party identity providers such as Auth0 or Azure Active
Directory B2C. These are Software-as-a-Service (SaaS) solutions that take care of the
user management and authentication aspects of your app for you. If you’re in the pro-
cess of moving apps to the cloud generally, then solutions like these can make a lot of
sense.
If you can’t or don’t want to use these solutions, then I recommend you consider
using the ASP.NET Core Identity system to store and manage user details in your data-
base. ASP.NET Core Identity takes care of some of the boilerplate associated with
authentication, but remains flexible and lets you control the login process for users.
NOTE ASP.NET Core Identity is an evolution of ASP.NET Identity, with some
design improvements and converted to work with ASP.NET Core.
By default, ASP.NET Core Identity uses EF Core to store user details in the database. If
you’re already using EF Core in your project, then this is a perfect fit. Alternatively, it’s
possible to write your own stores for loading and saving user details in another way.
Identity takes care of the low-level parts of user management, as shown in table 14.1.
As you can see from this list, Identity gives you a lot, but not everything—by a long shot!
The biggest missing piece is the fact that you need to provide all the UI for the
application, as well as tying all the individual Identity services together to create a
functioning sign-in process. That’s a pretty big missing piece, but it makes the Identity
system extremely flexible.
411
What is ASP.NET Core Identity?
Luckily, the .NET CLI and Visual Studio come with templates that give you a huge
amount of the UI boilerplate for free. And I do mean a huge amount—if you’re using
all of the features of Identity, such as 2FA and external login providers, then there’s a
lot of Razor to write!
NOTE ASP.NET Core 2.1 (in preview at the time of writing) uses new features
to deliver this UI code as a library package, drastically reducing the amount of
code you need to write. For details, see http://mng.bz/tZZz.
For that reason, I strongly recommend using one of the templates as a starting point,
whether you’re creating an app or adding user management to an existing app. But
the question still remains: when should you use Identity, and when should you con-
sider rolling your own?
I’m a big fan of Identity, so I tend to suggest it in most situations, as it handles a lot
of security-related things for you that are easy to mess up. I’ve heard a number of
arguments against it, some of which are valid, and others less so:
 I already have user authentication in my app. Great! In that case, you’re probably
right, Identity may not be necessary. But does your custom implementation use
2FA? Do you have account lockout? If not, and you need to add them, then con-
sidering Identity may be worthwhile.
 I don’t want to use EF Core. That’s a reasonable stance. You could be using Dap-
per, some other ORM, or even a document database for your database access.
Luckily, the database integration in Identity is pluggable, so you could swap out
the EF Core integration and use your own database integration libraries
instead.
Table 14.1 Which services are and aren’t handled by ASP.NET Core Identity
Managed by ASP.NET Core Identity Implemented by the developer
Database schema for storing users and claims. UI for logging in, creating, and managing users
(controller, actions, view models). This is included
in the default templates.
Creating a user in the database. Sending emails and SMS messages.
Password validation and rules. Customizing claims for users (adding new claims).
Handling user account lockout (to prevent brute-
force attacks).
Configuring third-party identity providers.
Managing and generating 2FA codes.
Generating password-reset tokens.
Saving additional claims to the database.
Managing third-party identity providers (for exam-
ple Facebook, Google, Twitter).
412 CHAPTER 14 Authentication: adding users to your application with Identity
 My use case is too complex for Identity. Identity provides lower-level services for
authentication, so you can compose the pieces however you like. It’s also exten-
sible, so if you need to, for example, transform claims before creating a princi-
pal, you can.
 I don’t want to build my own identity system. I’m glad to hear it. Using an external
identity provider like Azure Active Directory B2C or Auth0 is a great way of
shifting the responsibility and risk associated with storing users’ personal infor-
mation onto a third party.
Any time you’re considering adding user management to your ASP.NET Core applica-
tion, I’d recommend looking at Identity as a great option for doing so. In the next
section, I’ll demonstrate what Identity provides by creating a new MVC application
using the default Identity templates. In section 14.4, we’ll take that template and apply
it to an existing app instead.
14.3 Creating a project that uses ASP.NET Core Identity
I’ve covered authentication and Identity in general terms a fair amount now, but the
best way to get a feel for it is to see some working code. In this section, we’re going to
look at the default code generated by the ASP.NET Core templates with Identity, how
it works, and how Identity fits in.
14.3.1 Creating the project from a template
You’ll start by using the Visual Studio templates to generate a simple MVC application
that uses Identity for storing individual user accounts in a database.
TIP You can create an equivalent project using the .NET CLI by running
dotnet new mvc -au Individual -uld
To create the template using Visual Studio, you must be using VS 2017 Update 3 or
later and have the ASP.NET Core 2.0 SDK installed:1
1 Choose File > New > Project.
2 From the New Project dialog, choose .NET Core, and then select ASP.NET Core
Web Application.
3 Enter a Name, Location, and optionally a solution name, and click OK.
4 Choose the Web Application (Model-View-Controller) template and click Choose
Authentication to bring up the Authentication dialog, shown in figure 14.7.
5 Choose Individual User Accounts to create an application configured with EF
Core and ASP.NET Core Identity. Click OK.
1
ASP.NET Core 2.1 (in preview at the time of writing) uses new features to deliver this UI code as a library
package. If you have the 2.1 SDK installed, the templates won’t generate all the UI code as described, though
the behavior will be similar. For details, see http://mng.bz/tZZz.
413
Creating a project that uses ASP.NET Core Identity
6 Click OK to create the application. Visual Studio will automatically run dotnet
restore to restore all the necessary NuGet packages for the project.
7 Run the application to see the default app, as shown in figure 14.8.
Choose No Authentication to
create a template without
authentication.
Choose Individual User Accounts
to store local user accounts using
ASP
.NET Core Identity and EF Core.
Work or School Accounts will
configure the application to use
an external Identity Provider,
using Active Directory (or
Office365 for example) to
handle user management
and authentication.
Choose Windows Authentication for
intranet sites where the Windows
login of the user provides the
authentication mechanism.
Choose Store user
accounts in-app.
Figure 14.7 Choosing the authentication mode of the new ASP.NET Core application template in VS 2017
You can create new users
and sign in using the
log in widget.
Figure 14.8 The default template with individual account authentication looks similar to the no authentication
template, with the addition of a login widget in the top right of the page.
414 CHAPTER 14 Authentication: adding users to your application with Identity
This template should look familiar, with one particular twist: you now have Register
and Log-in buttons! Feel free to play with the template—creating a user, logging in
and out—to get a feel for the app. Once you’re happy, look at the code generated by
the template and the boilerplate it saved you from writing!
14.3.2 Exploring the template in Solution Explorer
At first glance, the number of files in the solution for a new template can be a bit over-
whelming, so I’ll talk through each aspect first. The template uses the standard folder
layout for the templates, where files are split by role (controller, service, data, and so
on), as shown in figure 14.9.
Starting from the top, the first folder contains your MVC controllers. Of particular
interest are AccountController and ManageController:
 AccountController—Used for registering as a new user, logging in, and send-
ing password reset emails.
 ManageController—Used for managing your user account when logged in.
Changing user details, password, and enabling two-factor authentication.
The ManageController is used
to manage your account, such
as changing you password.
The template includes an EF
Core DbContext and migrations to
configure the database schema
for ASP
.NET Core Identity.
The Models folder contains view
models for the MVC controllers.
The ApplicationUser is the EF
Core entity and is used as the
ASP
.NET Core Indentity user type.
The services folder contains stub
interfaces and services. These
can be expanded to provide
additional features such as
password reset emails and
two-factor authentication.
The AccountController is used
to create new users and sign in.
Figure 14.9 The project layout of the default template. Depending on your version of Visual Studio,
the exact files may vary slightly.
415
Creating a project that uses ASP.NET Core Identity
Most of the direct interaction between your app and the Identity system happens in
here, in the default templates, so we’ll look at these files in more detail in section 14.3.4.
The next folder, the Data folder, contains your application’s EF Core DbContext,
called ApplicationDbContext, and the migrations for configuring the database
schema to use Identity. I’ll discuss this schema in more detail in section 14.3.3.
The Models folder contains two types of models:
 The binding/view models for use with action methods and Razor view results.
 ApplicationUser, an EF Core entity model. This is the entity that the Identity
system stores in the database.
Next up is the Services folder. This contains stub services for sending an email and an
SMS, which are required in order to allow password resets and two-factor authentication.
NOTE These services are stubs and don’t do anything. You’ll need to hook
them up, often to a third-party service, to make them functional.2
In some ver-
sions of Visual Studio, the stub SMS service may be missing.
The last folder, Views, contains all the Razor view templates for the app. Of particular
note are the Account and Manage folders, which contain the view templates for
AccountController and ManageController.
In addition to all the new files included thanks to ASP.NET Core Identity, it’s worth
opening up Startup.cs and looking at the changes there. The most obvious change is
the additional configuration in ConfigureServices, which adds all the services Iden-
tity requires.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
services.AddMvc();
}
2
The documentation shows how to create a provider to send an email using SendGrid (https://docs.microsoft
.com/en-us/aspnet/core/security/authentication/accconfirm) and SMS using Twilio or ASPSMS (2FA).
Listing 14.1 Adding ASP.NET Core Identity services to ConfigureServices
ASP.NET Core Identity uses EF
Core, so it includes the standard
EF Core configuration.
Adds the Identity system,
and configures the user
and role types
Configures
Identity to
store its
data in EF
Core
Registers the
stub service for
sending email
Uses the default
Identity providers
forgenerating2FA
codes
Registers the stub service for
sending SMS; may be missing in
some versions of Visual Studio
416 CHAPTER 14 Authentication: adding users to your application with Identity
There’s one other change in Startup, in the Configure method:
app.UseAuthentication();
This adds AuthenticationMiddleware to the pipeline, so that you can authenticate
incoming requests, as you saw in figure 14.3. It’s placed early in the pipeline, before
MvcMiddleware, so that the HttpContext.User principal will be set correctly when the
MVC controllers execute.
Now you’ve got an overview of the additions made by Identity, we’ll look in a bit
more detail at the database schema and how Identity stores users in the database.
14.3.3 The ASP.NET Core Identity data model
Out of the box, and in the default templates, Identity uses EF Core to store user
accounts. It provides a base DbContext that you can inherit from, called Identity-
Context<TUser>, where TUser is the user entity in your app. It also provides a base
user entity, IdentityUser, to inherit from.
In the template, the app’s DbContext is called ApplicationDbContext, and the
user entity is called ApplicationUser. If you open up these files, you’ll see they’re
sparse; they only inherit from the Identity classes I described earlier. What do those
base classes give you? The easiest way to see is to update a database with the migrations
and take a look!
Applying the migrations is the same process as in chapter 12. Ensure the connec-
tion string points to where you want to create the database, and run
dotnet ef database update
to update the database with the migrations. If the database doesn’t yet exist, the CLI
will create it. Figure 14.10 shows what the database looks like for the default template.
The claims associated with
each user are stored in
AspNetUserClaims.
ASP
.NET Core uses EF Core
migrations. The history of
applied migrations is stored
in the __EFMigrationsHistory
table.
The AspNetUserLogins and
AspNetUserTokens are used
to manage details of third-
party logins like Facebook
and Google.
The AspNetRoles, AspNetRoleClaims,
and AspNetUserRoles provide
role-based authorization for
legacy reasons.
The user entities are stored in
the AspNetUsers table.
Figure 14.10 The database schema used by ASP.NET Core Identity
417
Creating a project that uses ASP.NET Core Identity
That’s a lot of tables! You shouldn’t need to interact with these tables directly—Iden-
tity handles that for you—but it doesn’t hurt to have a basic grasp of what they’re for:
 __EFMigrationsHistory—The EF Core migrations table that records which migra-
tions have been applied.
 AspNetUsers—The user profile table itself. This is where ApplicationUser is
serialized to. We’ll take a closer look at this table shortly.
 AspNetUserClaims—The claims associated with a given user. A user can have
many claims, so it’s modeled as a many-to-one relationship.
 AspNetUserLogins and AspNetUserTokens—These are related to third-party logins.
When configured, these let users sign in with a Google or Facebook account
(for example), instead of creating a password on your app.
 AspNetUserRoles, AspNetRoles, and AspNetRoleClaims—These tables are somewhat
of a legacy left over from the old role-based permission model of the pre-.NET
4.5 days, instead of the claims-based permission model. These tables let you
define roles that multiple users can belong to. Each role can be assigned a num-
ber of claims. These claims are effectively inherited by a user principal when
they are assigned that role.
You can explore these tables yourself, but the most interesting of them is the AspNet-
Users table, shown in figure 14.11.
All of the columns in the AspNetUsers table are security related—their email and
password hash, whether they have confirmed their email, whether they have 2FA
enabled, and so on. There’s no room for additional information, like the user’s name,
for example.
By default, ASP
.NET Core Identity
uses GUIDs for the user Id stored
as strings in the database.
The table contains all the neccessary
fields for authenticating a user, email
and phone number confirmation,
two factor authentication, and
account lockout.
Figure 14.11 The AspNetUsers table is used to store all the details required to authenticate a user.
418 CHAPTER 14 Authentication: adding users to your application with Identity
NOTE You can see from figure 14.11 that the primary key Id is stored as a
string column. By default, Identity uses Guid for the identifier. To customize
the data type, see http://mng.bz/Z7Y2.
Any extra properties of the user are stored as claims in the AspNetUserClaims table
associated with that user. This lets you add arbitrary additional information, without
having to change the database schema to accommodate it. Want to store the user’s
date of birth? Add a claim to that user—no need to change the database. You can see
this in action in section 14.5, when you add a Name claim to every new user.
It’s useful to have a mental model of the underlying database schema Identity uses,
but in day-to-day work, you won’t have to interact with it directly—that’s what Identity
is for! In the next section, we’ll look at the other end of the scale—the UI of the app—
and how the action methods and controllers interact with the Identity system.
14.3.4 Interacting with ASP.NET Core Identity: the MVC controllers
You saw in table 14.1 that Identity provides low-level constructs and services for adding
users to your application, but that you still need to put all the pieces together to create
your app. In the templates, this all happens in the MVC Controllers.
In this section, we’ll look at the main action methods they expose for creating
users and logging in, to get a feel for the authentication process and how you need to
interact with Identity.
NOTE I’m only going to cover the main action methods in this chapter. It’s
worth exploring the code in the templates to see how to include additional func-
tionality like two-factor authentication, external authentication, and password
reset emails.
CREATING NEW USERS WITH THE ACCOUNTCONTROLLER.REGISTER ACTION
AccountController is responsible for registering new users and logging them in. The
first action method a user will hit is the Register action, which will display the login
form, asking the user to provide an email and password, as shown in figure 14.12.
When they click submit, this will POST the form to the Register method of
AccountController, as shown in listing 14.2. This action is responsible for creating a
new user entity and signing the user in. This involves several steps:
 Create the user entity in the database
 Create and store claims associated with the user (the email and username
claims)
 Set the principal for the current request
 Serialize the principal to a cookie for subsequent requests
These steps would be relatively involved if you had to do them manually but, luckily,
Identity takes care of most of that for us.
419
Creating a project that uses ASP.NET Core Identity
public async Task<IActionResult> Register(
RegisterViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var user = new ApplicationUser {
UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(
user, model.Password);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user);
return RedirectToLocal(returnUrl);
}
AddErrors(result);
}
return View(model);
}
Listing 14.2 AccountController.Register POST action for creating new users
Figure 14.12 The user registration
form in the default template
The form is bound to
RegisterViewModel.
If the binding mode
is valid, creates an
instance of the
user entity
Checks that the provided
password is valid and
creates the new user in
the database
If the user
was created
successfully,
sign them in.
Updates the
HttpContext.User principal
and sets a cookie containing
the serialized principal
Redirects to the previous
page using a helper
method to protect against
open redirect attacks
If something went
wrong, adds the errors
to the model state and
redisplays the form
420 CHAPTER 14 Authentication: adding users to your application with Identity
As with all action methods for modifying data, the action first validates the binding
model. If that’s successful, the action creates a new instance of ApplicationUser and
attempts to create it in the database using the UserManager provided by the Identity
system. This validates that the password passes some password-strength validation (it is
of sufficient length for example) and creates the user in the database.
TIP You can customize the password rules used by Identity when you config-
ure your services. I’ll show how to do this in section 14.4.1.
If UserManager created the user successfully, then you use SignInManager to sign the
new user in. SignInManager is responsible for setting the HttpContext.User property
to your user principal and serializing the principal to a cookie for use in subsequent
requests, as you saw in figure 14.2.
Once the user is signed in, you can redirect them to what they were doing before
they signed in. Identity takes care of all the heavy lifting; you’re left to coordinate
individual services and to generate the appropriate user interaction using IAction-
Results.
SIGNING OUT WITH THE ACCOUNTCONTROLLER.LOGOUT ACTION
Great—so you can create a new user and sign in, but what if a user is finished with the
app and wants to sign out? Funnily enough, there’s a Logout action!
The Logout action method, shown in listing 14.3, is simple and asks SignInManager
to sign the user out. This will clear the HttpContext.User property and removes the
authentication cookie from the response so that, in subsequent requests, you’ll be
anonymous again. Finally, it redirects you to the HomeController index page.
public async Task<IActionResult> Logout()
{
await _signInManager.SignOutAsync();
return RedirectToAction(nameof(HomeController.Index), "Home");
}
On subsequent requests, there won’t be an authentication cookie, so the request will
be unauthenticated.
SIGNING IN WITH THE ACCOUNTCONTROLLER.LOGIN ACTION
The final method I want to look at is the Login action method. As with the Register
action, this displays a form to the user to enter their email and password, as shown in
figure 14.13, and POSTs it to the Login action on AccountController.
The login screen is a standard setup, with fields for email and password, and a link to
reset your password if you’ve forgotten it. On the right-hand side of the screen, you
can see a section for logging in using an external identity provider, such as Facebook
or Google.
Listing 14.3 The AccountController.Logout POST action for signing out
Replaces the HttpContext.User with
an anonymous principal and deletes
the authentication cookie
Redirects to the app’s homepage
421
Creating a project that uses ASP.NET Core Identity
NOTE Adding external providers is a fully supported feature of Identity,
which requires relatively minimal configuration to your app. Unfortunately,
you also have to register your app with the external provider, Google or Face-
book, for example, which can be a convoluted process!3
As with the Register method, the Identity system does all the hard work associated
with loading a user from the database, validating the password, checking that the
account isn’t locked, and whether they have enabled 2FA on the account. The follow-
ing listing shows that the SignInManager handles all this; you only need to display a
view or redirect to an action, as appropriate.
public async Task<IActionResult> Login(
LoginViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var result = await _signInManager.PasswordSignInAsync(
model.Email, model.Password, model.RememberMe);
if (result.Succeeded)
{
return RedirectToLocal(returnUrl);
}
3
For documentation on adding external providers see http://mng.bz/MR2m.
Listing 14.4 The AccountController.Login POST action for logging in
Figure 14.13 The login form in the default templates. The right-hand side of the screen can be used to
configure external identity providers, so users can log in to your app using their Google or Facebook accounts.
Attempts to sign
in with the email
and password
The sign-in succeeded, so the
HttpContext.User and authentication
cookie have been set.
422 CHAPTER 14 Authentication: adding users to your application with Identity
if (result.RequiresTwoFactor)
{
return RedirectToAction(nameof(SendCode),
new { ReturnUrl = returnUrl, model.RememberMe });
}
if (result.IsLockedOut)
{
return View("Lockout");
}
else
{
ModelState.AddModelError(
string.Empty, "Invalid login attempt.");
return View(model);
}
}
return View(model);
}
This method also showcases some of the additional features included in the templates,
such as 2FA and account lockout. These are disabled by default in the templates, but
the templates include comments that describe how to enable them.
You’ve covered the most important methods of AccountController now. If you
browse the templates, you’ll see others that handle 2FA and external login providers. I
strongly recommend turning on these features by following the comments in the code
and the tutorials in the documentation.
AccountController contains the most important Identity-related actions for log-
ging in and out. ManageController allows users to manage their account by configur-
ing 2FA or managing their third-party logins. As I said earlier, these features are
optional for the most part, but you’ll probably need the ChangePassword action in
your apps!
CHANGING YOUR PASSWORD WITH THE MANAGECONTROLLER.CHANGEPASSWORD ACTION
As you might expect by now, the Identity system exposes methods to handle the
change password functionality for you—your job is to wire it all up! This shows the
ChangePassword method from the default templates.
public async Task<IActionResult> ChangePassword(
ChangePasswordViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await _userManager.GetUserAsync(HttpContext.User);
if (user != null)
{
Listing 14.5 The ManageController.ChangePassword action method
If 2FA is enabled for
the user, they’ll be
redirected to an
action to generate
and validate a code.
Too many failed login attempts
have been made for the email
and lockout is enabled.
An error occurred, or the
view model wasn’t valid.
Redisplays the form.
Loads the user entity
for the currently-
signed-in principal
423
Adding ASP.NET Core Identity to an existing project
var result = await _userManager.ChangePasswordAsync(
user, model.OldPassword, model.NewPassword);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user);
return RedirectToAction(nameof(Index)));
}
AddErrors(result);
return View(model);
}
return RedirectToAction(nameof(Index));
}
The action goes through several steps to change a user’s password:
1 If the binding model isn’t valid, redisplay the form (never forget to do this!).
2 Load the current user using UserManager.
3 Attempt to change the user’s password by providing both the old and new pass-
word. UserManager will validate that the old password matches the existing hash
and that the new password meets the configured password-strength require-
ments.
4 If UserManager changed the password successfully, use SignInManager to sign
in the user principal again. This ensures the principal on HttpContext.User,
and stored in the cookie, matches the values on the database user entity.
Even with the Identity system handling all of the low-level security aspects for you,
there’s still quite a lot of code to configure correctly here. In fact, there’s so much
code associated with the Identity templates in general, that I rarely add Identity to an
existing app manually. Instead, I take a shortcut.
In the next section, you’ll see how easy it is to take the code generated by the tem-
plates and add it to an existing app, without having to figure it all out for yourself
from scratch.
14.4 Adding ASP.NET Core Identity to an existing project
In this section, we’re going to add users to the recipe application from chapters 12
and 13. This is a working app that you want to add user functionality to. In chapter 15,
we’ll extend this work to restrict control regarding who’s allowed to edit recipes on
the app.
By the end of this section, you’ll have an application with a registration page, a
login screen, and a manage account screen, like the default templates. You’ll also have
a persistent widget in the top right of the screen, showing the login status of the cur-
rent user, as shown in figure 14.14.
As with the default templates, I’m not going to set up external login providers or
2FA; I’m only concerned with adding ASP.NET Core Identity to an existing app that’s
already using EF Core.
If something went wrong, add the errors
to ModelState and redisplay the form.
If the password
was changed
successfully, sign
in again to update
the principal.
Validates the old
password is correct
and the new
password is valid,
and updates the
password
424 CHAPTER 14 Authentication: adding users to your application with Identity
TIP It’s worth making sure you’re comfortable with the new project tem-
plates before you go about adding Identity to an existing project. Create a test
app, set up an external login provider, configure the email and SMS, and
enable 2FA. This will take a bit of time, but it’ll be invaluable for deciphering
errors when you come to adding Identity to your own apps.
To add Identity to your app, you’ll need to:
1 Add the ASP.NET Core Identity NuGet packages.
2 Configure Startup to use AuthenticationMiddleware and add Identity ser-
vices to the DI container.
3 Update the EF Core data model with the Identity entities.
4 Add AccountController and ManageController, as well as the associated views
and view models.
This section will tackle each of these steps in turn. At the end of the section, you’ll
have successfully added user accounts to your recipe app.
14.4.1 Configuring the ASP.NET Core Identity services and middleware
You can add ASP.NET Core Identity to an existing app by referencing a single NuGet
package in your csproj, Microsoft.AspNetCore.Identity.EntityFrameworkCore:
<PackageReference
Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore"
Version="2.0.0" />
This one package will bring in all the additional required dependencies you need to
add Identity. Be sure to run dotnet restore after adding it to your project.
The Login widget shows the
current signed in user’s email
and a link for them to log out.
Figure 14.14 The recipe app after adding authentication
425
Adding ASP.NET Core Identity to an existing project
NOTE If your app uses the Microsoft.AspNetCore.All or Microsoft.AspNet-
Core.App package (used by the default templates), then the Identity package
is already included and you don’t need an explicit reference.
Once you’ve added the Identity package, you can update your Startup.cs file to
include the Identity services, as shown next. This is similar to the default template
setup you saw in listing 14.1, but make sure to reference your existing AppDbContext.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
options.Password.RequiredLength = 10)
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
services.AddMvc();
services.AddScoped<RecipeService>();
}
This adds all the necessary services and configures Identity to use EF Core. You can
also see that I’ve customized the password strength requirements in the AddIdentity
call, increasing the minimum password length to 10 characters.
You’ve also configured the IEmailSender and ISmsSender interfaces used by the
default templates. If your app already has email- or SMS-sending capabilities, you can
configure them to use your existing classes. For now, I copied across the stub imple-
mentations from the default templates.
NOTE You’ll need to implement IEmailSender at a minimum if you want
users to be able to reset their password when they forget it! See the documen-
tation for details: http://mng.bz/pJp3.
Configuring AuthenticationMiddleware is somewhat easier: add it to the pipeline in
the Configure method. As you can see in listing 14.7, I’ve added the middleware after
StaticFileMiddleware, but before MvcMiddleware. In this position, serving static files
won’t come with the overhead of authentication, but your MVC controllers will be
able to correctly read the user principal from HttpContext.User.
Listing 14.6 Adding ASP.NET Core Identity services to the recipe app
The existing
service
configuration
is unchanged.
Adds the
Identity
services to
the DI
container
Adds the stub services
to the DI container
Updates the password
rules to set the
minimum password
length to 10
Makes sure you use the
name of your existing
DbContext app
426 CHAPTER 14 Authentication: adding users to your application with Identity
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Recipe}/{action=Index}/{id?}");
});
}
You’ve configured your app to use Identity, so the next step is to update EF Core’s
data model. You’re already using EF Core in this app, so you need to update your data-
base schema to include the tables Identity requires.
14.4.2 Updating the EF Core data model to support Identity
You saw in section 14.3.3 that Identity provides a DbContext called IdentityDb-
Context, which you can inherit from. The IdentityDbContext base class includes the
necessary DbSet<T> to store your user entities using EF Core.
Updating an existing DbContext for Identity is simple—update your app’s DbContext
to inherit from IdentityDbContext, as shown in the following listing. You’ll also need to
copy the ApplicationUser entity class to your app.
public class AppDbContext : IdentityDbContext<ApplicationUser>
{
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options)
{ }
public DbSet<Recipe> Recipes { get; set; }
}
Effectively, by updating the base class of your context in this way, you’ve added a whole
load of new entities to EF Core’s data model. As you saw in chapter 12, whenever EF
Core’s data model changes, you need to create a new migration and apply those
changes to the database.
At this point, your app should compile, so you can add a new migration called
AddIdentitySchema using
dotnet ef migrations add AddIdentitySchema.
Listing 14.7 Adding AuthenticationMiddleware to the recipe app
Listing 14.8 Updating AppDbContext to use IdentityDbContext
Middleware after
AuthenticationMiddleware can read the
user principal from HttpContext.User.
StaticFileMiddleware will never see requests
as authenticated, even after you sign in.
Adds AuthenticationMiddleware
to the pipeline
The remainder
of the class
remains
the same
Updates to inherit from the
Identity context, instead of
directly from DbContext
427
Adding ASP.NET Core Identity to an existing project
The final—biggest—step is adding the controllers, view models, and view templates
for the app. Normally, this would be a lot of work, but by using the default templates
as a base, you can add the code much faster.
14.4.3 Adding the controllers, view models, and views
One of the most tedious parts of adding Identity to an existing app is adding all the UI
code to interact with it. You need the login page and the register page, obviously, but
if you’re also implementing features like 2FA and external login providers, there’s a
whole host of other pages you need.
To get around this, I like to copy and paste the default code from the templates
and tweak it later! I copied AccountController and ManageController, their view
models, and the associated view templates into their corresponding parts of the exist-
ing recipe app, and it pretty much works.
TIP To avoid namespace issues, I create a fresh, temporary project with the
same name as the project I’m moving to. This generates the code with all the
correct namespaces.
You need to make a couple more changes before you’re done. First off, you’ll need to
make sure your view templates are importing the correct namespaces for view models
and Identity. The best place for these is the _ViewImports.cshtml file, as you saw in
chapter 7.
@using Microsoft.AspNetCore.Identity
@using RecipeApplication
@using RecipeApplication.Models
@using RecipeApplication.Models.AccountViewModels
@using RecipeApplication.Models.ManageViewModels
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
The last tweak is to add the login widget from figure 14.8. This shows the current
login status of the user and lets you register or sign in. This widget is implemented as
a shared partial template, _LoginPartial.cshtml, which you can copy directly across to
the views/shared folder. All that remains is to invoke the partial by calling
@await Html.PartialAsync("_LoginPartial")
in the layout file of your app, _Layout.cshtml.
And there you have it, you’ve added Identity to an existing application. Admittedly,
copying the templates like this is a bit of a cheat, but as long as you can read through and
understand what they’re doing, there’s no harm in saving yourself some work. On top of
that, it reduces the chance of introducing accidental security bugs at the same time!4
Listing 14.9 Updating _ViewImports.cshtml to use additional namespaces
4
In ASP.NET Core 2.1 (currently in preview), using the default UI will be easier, as described in
https://blogs.msdn.microsoft.com/webdev/2018/03/02/aspnetcore-2-1-identity-ui/.
The Identity namespace
The namespace of the Identity
view models for the recipe app
428 CHAPTER 14 Authentication: adding users to your application with Identity
Getting started with Identity like this is pretty easy, and the documentation for
enabling additional features like 2FA and external providers is easily accessible and
highlighted in the code comments. One area where people quickly get confused is
when they want to extend the default templates. In the next section, I show how you
can tackle a common follow requirement: how to add additional claims to users when
they register.
14.5 Managing users: adding new claims to users
Very often, the next step after adding Identity to an application is to customize it. The
default templates only require an email and password to register. What if you need
more details, like a friendly name for the user? Also, I’ve mentioned that we use
claims for security, so what if you want to add a claim called IsAdmin to certain users?
You know that every user principal has a collection of claims so, conceptually, add-
ing any claim just requires adding it to the user’s collection. There are two main times
that you would want to grant a claim to a user:
 For every user, when they first register on the app. For example, you might want to add
a “Name” field to the Register form and add that as a claim to the user when
they register.
 Manually, after the user has already registered. This is common for claims used as
“permissions,” where an existing user might want to add an IsAdmin claim to a
specific user after they have registered on the app.
In this section, I show you the first approach, automatically adding new claims to a
user when they’re created. The latter approach is the more flexible and, ultimately, is
the approach many apps will need, especially line-of-business apps. Luckily, there’s
nothing conceptually difficult to it; it requires a simple UI that lets you view users and
add a claim through the same mechanism I’ll show here.
Let’s say you want to add a new Claim to a user called FullName. A typical approach
would be:
1 Add a “Name” field to the RegisterViewModel model of the Register action.
2 Add a “Name” input field to the Register.cshtml Razor template.
3 Create the new ApplicationUser entity as before, by calling CreateAsync on
UserManager<ApplicationUser>.
4 Add a new Claim to the user by calling _userManager.AddClaimAsync().
5 Sign the user in as before, using _signInManager.SignInAsync().
Steps 1 and 2 are fairly self-explanatory and just require updating the existing tem-
plates with the new field. Steps 3-5 all take place in the AccountController.Register
method, which you can compare to listing 14.2, where you didn’t add the extra claim.
429
Managing users: adding new claims to users
public async Task<IActionResult> Register(
RegisterViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var user = new ApplicationUser {
UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(
user, model.Password);
if (result.Succeeded)
{
var claim = new Claim("FullName", model.Name);
await _userManager.AddClaimAsync(user, nameClaim);
await _signInManager.SignInAsync(user);
return RedirectToLocal(returnUrl);
}
AddErrors(result);
}
return View(model);
}
This is all that’s required to add the new claim, but you’re not using it anywhere cur-
rently. What if you want to display it? Well, you’ve added a claim to the user principal,
which was assigned to the HttpContext.User property when you called SignInAsync.
That means you can access the claims anywhere you have access to HttpContext—in
your controllers or in view templates. For example, you could display the user’s Full-
Name claim anywhere in a Razor template with the following statement:
@User.Claims.FirstOrDefault(x=>x.Type == "FullName").Value
This finds the first claim on the current users with a Type of "FullName" and prints
the assigned value. The Identity system even includes a handy extension method that
tidies up this LINQ expression (found in the System.Security.Claims namespace):
@User.FindFirstValue("FullName")
And with that last titbit, we’ve reached the end of this chapter on ASP.NET Core Iden-
tity. I hope you’ve come to appreciate the amount of effort using Identity can save
you, especially when you make use of the templates that come with Visual Studio and
the .NET CLI.
ASP.NET Core Identity provides so many additional features, such as 2FA and
external login providers, that there isn’t space to cover them here. Although not
essential for adding user functionality to your app, I strongly recommend looking into
them. In particular, consider adding 2FA to give your users additional protection.
Listing 14.10 Adding a custom claim to a new user in the Register action
Creates an
instance of the
ApplicationUser
entity, as usual
Validates that the provided
password is valid and creates
the user in the database
Adds the new
claim to the
ApplicationUser’s
collection
Creates a claim,
with a string
name of
"FullName" and
the provided
value
Signs the user in by setting
the HttpContext.User, the
principal will include the
custom claim
430 CHAPTER 14 Authentication: adding users to your application with Identity
Adding user accounts and authentication to an app is typically the first step to cus-
tomizing your app further. Once you have authentication, you can have authorization,
which lets you lock down certain actions in your app, based on the current user. In the
next chapter, you’ll learn about the ASP.NET Core authorization system and how you
can use it to customize your apps, in particular, the recipe application, which is com-
ing along nicely!
Summary
 Authentication is the process of determining who you are.
 Authorization is the process of determining what you’re allowed to do.
 Every request in ASP.NET Core is associated with a user, also known as a princi-
pal. By default, without authentication, this is an anonymous user.
 The current principal for a request is exposed on HttpContext.User.
 Every user has a collection of claims. These claims are single pieces of informa-
tion about the user. Claims could be properties of the physical user, such as
Name and Email, or they could be related to things the user has, such as Has-
AdminAccess or IsVipCustomer.
 Earlier versions of ASP.NET used roles instead of claims. You can still use roles if
you need to, but you should use claims where possible.
 Authentication in ASP.NET Core is provided by AuthenticationMiddleware
and a number of authentication services. These services are responsible for set-
ting the current principal when a user logs in, saving it to a cookie, and loading
the principal from the cookie on subsequent requests.
 ASP.NET Core 2.0 includes support for consuming bearer tokens for authenti-
cating API calls, but it doesn’t include a way to create them. You can use a third-
party provider such as Azure AD B2C, or a library such as IdentityServer if you
need this ability.
 ASP.NET Core Identity handles low-level services needed for storing users in a
database, ensuring their passwords are stored safely, and for logging users in
and out. You must provide the UI for the functionality yourself and wire it up to
the Identity sub-system.
 The default template for an MVC Application with Individual Account Authenti-
cation uses ASP.NET Core Identity to store users in the database with EF Core. It
includes all the boilerplate code required to wire the UI up to the Identity system.
 You can use the UserManager<T> class to create new user accounts, load them
from the database, and change their passwords.
 SignInManager<T> is used to sign a user in and out, by assigning the principal
for the request and by setting an authentication cookie.
 You can update an EF Core DbContext to support Identity by deriving from
IdentityDbContext<TUser> where TUser is a class that derives from Identity-
User, called ApplicationUser in the default templates.
431
Summary
 You can add additional claims to a user using the UserManager<TUser>
.AddClaimAsync(TUser user, Claim claim) method.
 Claims consist of a type and a value. Both values are strings. You can use stan-
dard values for types exposed on the ClaimTypes class, such as ClaimTypes
.GivenName and ClaimTypes.FirstName, or you can use a custom string, such
as "FullName".
432
Authorization:
securing your application
In chapter 14, I showed how to add users to an ASP.NET Core application by add-
ing authentication. With authentication, users can register and log in to your app
using an email and password. Whenever you add authentication to an app, you
inevitably find you want to be able to restrict what some users can do. The process
of determining whether a user can perform a given action on your app is called
authorization.
This chapter covers
 Using authorization to control who can use your
app
 Using claims-based authorization with policies
 Creating custom policies to handle complex
requirements
 Authorizing a request depending upon the
resource being accessed
 Hiding elements from a Razor template that the
user is unauthorized to access
433
Introduction to authorization
On an e-commerce site, for example, you may have admin users who are allowed
to add new products and change prices, sales users who are allowed to view completed
orders, and customer users who are only allowed to place orders and buy products.
In this chapter, I show how to use authorization in an app to control what your
users can do. In section 15.1, I introduce authorization and put it in the context of a
real-life scenario you’ve probably experienced: an airport. I describe the sequence of
events, from checking in, passing through security, to entering an airport lounge, and
how these relate to the authorization concepts you’ll see in this chapter.
In section 15.2, I show how authorization fits into an ASP.NET Core web applica-
tion and how it relates to the ClaimsPrincipal class that you saw in the previous chap-
ter. You’ll see how to enforce the simplest level of authorization in an ASP.NET Core
app, ensuring that only authenticated users can execute an MVC action.
We’ll extend that approach in section 15.3 by adding in the concept of policies.
These let you make specific requirements about a given authenticated user, requiring
that they have a specific claim in order to execute an action.
You’ll use policies extensively in the ASP.NET Core authorization system, so in sec-
tion 15.4, we explore how to handle more complex scenarios. You’ll learn about
authorization requirements and handlers, and how you can combine them to create
specific policies that you can apply to your MVC actions.
Sometimes, whether a user is authorized for an action depends on which resource
or document they’re attempting to access. A resource is anything that you’re trying to
protect, so it could be a document or a post in a social media app.
For example, you may allow users to create documents and read documents from
other users, but only to edit documents that they created themselves. This type of
authorization, where you need the details of the document to determine authoriza-
tion, is called resource-based authorization, and is the focus of section 15.5.
In the final section of this chapter, I show how you can extend the resource-based
authorization approach to your MVC view models and Razor templates. This lets you
modify the UI to hide elements that users aren’t authorized to interact with. In particu-
lar, you’ll see how to hide the Edit button when a user isn’t authorized to edit the entity.
We’ll start by looking more closely at the concept of authorization, how it differs
from authentication, and how it relates to real-life concepts you might see in an airport.
15.1 Introduction to authorization
For people who are new to web apps and security, authentication and authorization
can sometimes be a little daunting. It certainly doesn’t help that the words look so sim-
ilar! The two concepts are often used together, but they’re definitely distinct:
 Authentication—The process of determining who made a request
 Authorization—The process of determining whether the requested action is
allowed
434 CHAPTER 15 Authorization: securing your application
Typically, authentication occurs first, so that you know who is making a request to your
app. For traditional web apps, your app authenticates a request by checking the
encrypted cookie that was set when the user logged in (as you saw in the previous
chapter). Web APIs typically use a header instead of a cookie for authentication, but
the process is the same.
Once a request is authenticated and you know who is making the request, you can
determine whether they’re allowed to execute an action on your server. This process is
called authorization and is the focus of this chapter.
Before we dive into code and start looking at authorization in ASP.NET Core, I’ll
put these concepts into a real-life scenario you’re hopefully familiar with: checking in
at an airport. To enter an airport and board a plane, you must pass through several
steps. In simplified form, these might look like:
1 Show passport at check-in desk. Receive a boarding pass.
2 Show boarding pass to enter security. Pass through security.
3 Show frequent flyer card to enter the airline lounge. Enter lounge.
4 Show boarding pass to board flight. Enter airplane.
Obviously, these steps, also shown in figure 15.1, will vary somewhat (I don’t have a
frequent flyer card!), but we’ll go with them for now. Let’s explore each step a little
further.
When you arrive at the airport, the first thing you do is go to the check-in counter.
Here, you can purchase a plane ticket, but in order to do so, you need to prove who
you are by providing a passport; you authenticate yourself. If you’ve forgotten your pass-
port, you can’t authenticate, and you can’t go any further.
Once you’ve purchased your ticket, you’re issued a boarding pass, which says
which flight you’re on. We’ll assume it also includes a BoardingPassNumber. You can
think of this number as an additional claim associated with your identity.
DEFINITION A claim is a piece of information about a user that consists of a
type and an optional value.
The next step is security. The security guards will ask you to present your boarding
pass for inspection, which they’ll use to check that you have a flight and so are allowed
deeper into the airport. This is an authorization process: you must have the required
claim (a BoardingPassNumber) to proceed.
If you don’t have a valid BoardingPassNumber, there are two possibilities for what
happens next:
 If you haven’t yet purchased a ticket—You’ll be directed back to the check-in desk,
where you can authenticate and purchase a ticket. At that point, you can try to
enter security again.
 If you have an invalid ticket—You won’t be allowed through security and there’s
nothing else you can do. If, for example, you show up with a boarding pass a week
late for your flight, they probably won’t let you through. (Ask me how I know!)
435
Introduction to authorization
Once you’re through security, you need to wait for your flight to start boarding, but
unfortunately there aren’t any seats free. Typical! Luckily, you’re a regular flier, and
you’ve notched up enough miles to achieve a “Gold” frequent flyer status, so you can
use the airline lounge.
You head to the lounge, where you’re asked to present your Gold Frequent Flyer
card to the attendant and they let you in. This is another example of authorization.
You must have a FrequentFlyerClass claim with a value of Gold in order to proceed.
Rejected
Rejected
Rejected
Purchase
ticket at
check-in desk
Pass through
Security
Present claims:
boarding pass
number
Present claims:
gold frequent
flyer class
Enter airline
lounge
Present claims:
boarding pass
number
Board airplane
At security, you must
present your boarding
pass number to proceed.
If you don’t have a valid boarding
pass, you’re not authorized to
pass through security.
To enter the airline lounge,
you must present a Gold
frequent flyer class card. If you’re not authorized,
you can’t enter the
airline lounge.
To board the airplane, you
must present a boarding pass.
If you’re authorized,
you can board the plane.
Figure 15.1 When boarding a plane at an airport, you pass through a number
of authorization steps. At each authorization step, you must present a claim in
the form of a boarding pass or a frequent flyer card. If you’re not authorized,
then access will be denied.
436 CHAPTER 15 Authorization: securing your application
NOTE You’ve used authorization twice so far in this scenario. Each time, you
presented a claim in order to proceed. In the first case, the presence of any
BoardingPassNumber was sufficient, whereas for the FrequentFlyerClass
claim, you needed the specific value of Gold.
When you’re boarding the airplane, you have one final authorization step, in which
you must present the BoardingPassNumber claim again. You presented this claim ear-
lier, but boarding the aircraft is a distinct action from entering security, so you have to
present it again.
This whole scenario has lots of parallels with requests to a web app:
 Both processes start with authentication.
 You have to prove who you are in order to retrieve the claims you need for autho-
rization.
 You use authorization to protect sensitive actions like entering security and the
airline lounge.
I’ll reuse this airport scenario throughout the chapter to build a simple web applica-
tion that simulates the steps you take in an airport. We’ve covered the concept of
authorization in general, so in the next section, we’ll look at how authorization works
in ASP.NET Core. You’ll start with the most basic level of authorization, ensuring only
authenticated users can execute an action, and look at what happens when you try to
execute such an action.
15.2 Authorization in ASP.NET Core
The ASP.NET Core framework has authorization built in,1
so you can use it anywhere
in your app, but it’s most common to apply authorization as part of MVC. For both tra-
ditional web apps and web APIs, users execute actions on your controllers. Authoriza-
tion occurs before these actions execute, as shown in figure 15.2. This lets you use
different authorization requirements for different action methods.
As you can see in figure 15.2, authorization occurs as part of MvcMiddleware, after
AuthenticationMiddeware has authenticated the request. You saw in chapter 13 that
authorization occurs as part of the MVC filter pipeline, in the (cunningly named)
authorization filters.
15.2.1 Preventing anonymous users from accessing your application
When you think about authorization, you typically think about checking whether a
particular user has permission to execute an action. In ASP.NET Core, you’d achieve
this by checking whether a user has a particular claim.
1
You can find the authentication and authorization packages for ASP.NET Core on GitHub at https://github
.com/aspnet/security.
437
Authorization in ASP.NET Core
There’s an even more basic level of authorization that you haven’t considered yet—
only allowing authenticated users to execute an action. This is even simpler than the
claims scenario (which we’ll come to later) as there are only two possibilities:
 The user is authenticated—The action executes as normal.
 The user is unauthenticated—The user can’t execute the action.
You can achieve this basic level of authorization by using the [Authorize] attribute, as
you saw in chapter 13, when we discussed authorization filters. You can apply this attri-
bute to your actions, as shown in the following listing, to restrict them to authenti-
cated (logged-in) users only. If an unauthenticated user tries to execute an action
protected with the [Authorize] attribute in this way, they’ll be redirected to the login
page.
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
[Authorize]
Listing 15.1 Applying [Authorize] to an action
Static file
middleware
Authentication
middleware
Index
action method
Authorize filter
Index view
A request is made
to the URL /recipe/index.
MvcMiddleware
The authentication middleware
deserializes the ClaimsPrincipal
from the encrypted cookie.
The authorize filter runs
after routing but before
model binding or validation.
If authorization is successful,
the action method executes
and generates a response
as normal.
If authorization fails, the
authorize filter returns an
error to the user, and the
action is not executed.
Figure 15.2 Authorization occurs before an action method executes as part of the MVC filter
pipeline in MvcMiddleware.
This action can be
executed by anyone, even
when not logged in.
Applies [Authorize] to
individual actions or
whole controllers
438 CHAPTER 15 Authorization: securing your application
public IActionResult AuthedUsersOnly()
{
return View();
}
}
You can apply the [Authorize] attribute at the action scope, controller scope, or glob-
ally, as you saw in chapter 13. Any action that has the [Authorize] attribute applied in
this way can be executed only by an authenticated user. Unauthenticated users will be
redirected to the login page.
Sometimes, especially when you apply the [Authorize] attribute globally, you
might need to “poke holes” in this authorization requirement. If you apply the
[Authorize] attribute globally, then any unauthenticated request will be redirected to
the login page for your app. But the [Authorize] attribute is global, so when the
login page tries to load, you’ll be unauthenticated and redirected to the login page
again. And now you’re stuck in an infinite redirect loop.
To get around this, you can designate specific actions to ignore the [Authorize]
attribute by applying the [AllowAnonymous] attribute to an action, as shown next.
This allows unauthenticated users to execute the action, so you can avoid the redirect
loop that would otherwise result.
[Authorize]
public class AccountController : Controller
{
public IActionResult ManageAccount()
{
return View();
}
[AllowAnonymous]
public IActionResult Login()
{
return View();
}
}
WARNING If you apply the [Authorize] attribute globally, be sure to add the
[AllowAnonymous] attribute to your login actions, error actions, password
reset actions, and any other actions that you need unauthenticated users to
execute.
If an unauthenticated user attempts to execute an action protected by the [Authorize]
attribute, traditional web apps will redirect them to the login page. But what about web
APIs? And what about more complex scenarios, where a user is logged in but doesn’t
have the necessary claims to execute an action? In the next section, we’ll look at how the
ASP.NET Core authentication services handle all of this for you.
Listing 15.2 Applying [AllowAnonymous] to allow unauthenticated access
This action can only be executed
by authenticated users.
Applied at the controller scope,
so user must be authenticated
for all actions on the controller.
Only authenticated users may
execute ManageAccount.
[AllowAnonymous] overrides
[Authorize] to allow
unauthenticated users.
Login can be executed
by anonymous users.
439
Authorization in ASP.NET Core
15.2.2 Handling unauthorized requests
In the previous section, you saw how to apply the [Authorize] attribute to an action
to ensure only authenticated users can execute it. In section 15.3, we’ll look at more
complex examples that require you to also have a specific claim. In both cases, you
must meet one or more authorization requirements (for example, you must be
authenticated) to execute the action.
If the user meets the authorization requirements, then they can execute the action
as normal and the request continues down the MVC filter pipeline. If they don’t meet
the requirements, the authorization filter will short-circuit the request. Depending on
why they failed authorization, the authorization filter will return one of two different
types of IActionResult:
 ChallengeResult—This indicates that the user was not authorized to execute
the action because they weren’t yet logged in.
 ForbidResult—This indicates that the user was logged in but didn’t meet the
requirements to execute the action. They didn’t have a required claim, for
example.
NOTE If you apply the [Authorize] attribute in basic form, as you did in sec-
tion 15.2.1, then the filter will only create ChallengeResults. The filter will
return ChallengeResult for unauthenticated users, but authenticated users
will always be authorized.
When authorization fails, the authorization filter short-circuits the MVC filter pipe-
line, and the ChallengeResult or ForbidResult executes to generate a response.
This response passes back through the middleware pipeline as usual. The exact
response generated depends on the type of application you’re building, and so the
type of authentication your application uses.
For traditional web apps using cookie authentication, such as when you use
ASP.NET Core Identity, as in chapter 14, ChallengeResult and ForbidResult redi-
rect users to a different page in your application. ChallengeResult indicates the user
isn’t yet authenticated, so they’re redirected to the login page for the app. After log-
ging in, they can attempt to execute the protected resource again.
ForbidResult means the request was from a user that already logged in, but
they’re still not allowed to execute the action. Consequently, the user is redirected to a
Forbidden or Access Denied web page, as shown in figure 15.3, which informs them
they can’t execute the action.
The preceding behavior is typical for traditional web apps, but web APIs typically
use a different approach to authentication, as you saw in chapter 14. Instead of log-
ging in using the web API directly, you’d typically log in to a third-party application
that provides a token to the client-side SPA or mobile app. The client-side app sends
this token when it makes a request to your web API.
Authenticating a request for a web API using tokens is essentially identical to a tra-
ditional web app that uses cookies; AuthenticationMiddleware deserializes the
440 CHAPTER 15 Authorization: securing your application
cookie or token to create the ClaimsPrincipal. The difference is in how a web API
handles ChallengeResult and ForbidResult.
When a web API app executes a ChallengeResult, it returns a 401 Unauthorized
error response to the caller. Similarly, when the app executes a ForbidResult, it
returns a 403 Forbidden response. The traditional web app essentially handled these
errors by automatically redirecting unauthorized users to the login or Access Denied
page, but the web API doesn’t do this. It’s up to the client-side SPA or mobile app to
detect these errors and handle them as appropriate.
The important takeaway from all this is that the framework largely handles these
intricacies for you. Whether you’re building a web API or a traditional MVC web app,
the authorization code looks the same in both cases. Apply your [Authorize] attri-
butes as normal and let the framework take care of the differences for you.
NOTE In chapter 14, you saw how to configure ASP.NET Core Identity as a
traditional web app. This chapter assumes you’re building a traditional web
app, but it’s equally applicable if you’re building a web API. Remember that a
web API will return 401 and 403 status codes directly, instead of redirecting
unauthorized users.
You’ve seen how to apply the most basic authorization requirement—restricting an
action to authenticated users only—but most apps need something more subtle than
this all-or-nothing approach.
Consider the airport scenario from section 15.1. Being authenticated (having a
passport) isn’t enough to get you through security. Instead, you also need a specific
claim: BoardingPassNumber. In the next section, we’ll look at how you can implement
a similar requirement in ASP.NET Core.
15.3 Using policies for claims-based authorization
In the previous chapter, you saw that authentication in ASP.NET Core centers around
a ClaimsPrincipal object, which represents the user. This object has a collection of
claims that contain pieces of information about the user, such as their name, email,
and date of birth.
Figure 15.3 In traditional web apps using cookie authentication, if you don’t
have permission to execute an action and you’re already logged in, you’ll be
redirected to an Access Denied page.
441
Using policies for claims-based authorization
You can use these to customize the app for each user, by displaying a welcome mes-
sage addressing the user by name, but you can also use claims for authorization. For
example, you might only authorize a user if they have a specific claim (such as Boarding-
PassNumber) or if a claim has a specific value (FrequentFlyerClass claim with the
value Gold).
In ASP.NET Core, the rules that define whether a user is authorized are encapsu-
lated in a policy.
DEFINITION A policy defines the requirements you must meet in order for a
request to be authorized.
Policies can be applied to an action using the [Authorize] attribute, similar to the
way you saw in section 15.2.1. This listing shows a controller and action that represent
the first step in the airport scenario. The AirportSecurity action method is pro-
tected by an [Authorize] attribute, but you’ve also provided a policy name: "Can-
EnterSecurity".
public class AirportController : Controller
{
public IActionResult Index()
{
return View();
}
[Authorize("CanEnterSecurity")]
public IActionResult AirportSecurity()
{
return View();
}
}
If a user attempts to execute the AirportSecurity action, the authorize filter will ver-
ify whether the user satisfies the policy’s requirements (we’ll look at the policy itself
shortly). This gives one of three possible outcomes:
 The user satisfies the policy.—The action will execute as normal.
 The user is unauthenticated.—The user will be redirected to the login page.
 The user is authenticated but doesn’t satisfy the policy.—The user will be redirected to
a “Forbidden” or “Access Denied” page.
These three outcomes correlate with the real-life outcomes you might expect when
trying to pass through security at the airport:
 You have a boarding pass.—You can enter security as normal.
 You’re unauthenticated.—You’re redirected to purchase a ticket.
 Your boarding pass is invalid (you turned up a day late, for example).—You’re blocked
from entering.
Listing 15.3 Applying an authorization policy to an action
The Index method can be
executed by
unauthenticated users.
Applying the
"CanEnterSecurity" policy
using [Authorize]
Only users that satisfy the
"CanEnterSecurity" policy can
execute the AirportSecurity action.
442 CHAPTER 15 Authorization: securing your application
Listing 15.3 shows how you can apply a policy to an action using the [Authorize]
attribute, but you still need to define the CanEnterSecurity policy.
You add policies to an ASP.NET Core application in the ConfigureServices
method of Startup.cs, as shown in listing 15.4. First, you add the authorization services
using AddAuthorization(), and then you can add policies by calling AddPolicy() on
the AuthorizationOptions object. You define the policy itself by calling methods on a
provided AuthorizationPolicyBuilder (called policyBuilder here).
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.AddPolicy(
"CanEnterSecurity",
policyBuilder => policyBuilder
.RequireClaim("BoardingPassNumber"));
});
// Additional service configuration
}
When you call AddPolicy you provide a name for the policy, which should match the
value you use in your [Authorize] attributes, and you define the requirements of the
policy. In this example, you have a single simple requirement: the user must have a
claim of type BoardingPassNumber. If a user has this claim, whatever its value, then the
policy will be satisfied and they’ll be authorized.
AuthorizationPolicyBuilder contains several methods for creating simple poli-
cies like this, as shown in table 15.1. For example, an overload of the RequireClaim()
method lets you specify a specific value that a claim must have. The following would let
you create a policy that the "BoardingPassNumber" claim must have a value of "A1234":
policyBuilder => policyBuilder.RequireClaim("BoardingPassNumber", "A1234");
Listing 15.4 Adding an authorization policy using AuthorizationPolicyBuilder
Table 15.1 Simple policy builder methods on AuthorizationPolicyBuilder
Method Policy behavior
RequireAuthenticatedUser() The required user must be authenticated. Creates a policy
similar to the default [Authorize] attribute, where you
don’t set a policy.
RequireClaim(claim, values) The user must have the specified claim. Optionally, with one
of the specified values.
RequireUsername(username) The user must have the specified username.
RequireAssertion(function) Executes the provided lambda function, which returns a
bool, indicating whether the policy was satisfied.
Calls AddAuthorization
to configure
AuthorizationOptions
Adds a new policy
Provides a name for the policy
Defines the policy
requirements using
AuthorizationPolicyBuilder
443
Creating custom policies for authorization
You can use these methods to build simple policies that can handle basic situations,
but often you’ll need something more complicated. What if you wanted to create a
policy that enforces only users over the age of 18 can execute the action method?
The DateOfBirth claim provides the information you need, but there’s not a sin-
gle correct value, so you couldn’t use the RequireClaim() method. You could use the
RequireAssertion() method and provide a function that calculates the age from the
DateOfBirth claim, but that could get messy pretty quickly.
For more complex policies that can’t be easily defined using the RequireClaim()
method, I recommend you take a different approach and create a custom policy, as
you’ll see in the following section.
15.4 Creating custom policies for authorization
You’ve already seen how to create a policy by requiring a specific claim, or requiring a
specific claim with a specific value, but often the requirements will be more complex
than that. Also, you’ll sometimes need to account for there being multiple ways to sat-
isfy a policy, any of which are valid.
Let’s return to the airport example. You’ve already configured the policy for pass-
ing through security, and now you’re going to configure the policy that controls
whether you’re authorized to enter the airline lounge.
As you saw in figure 15.1, you’re allowed to enter the lounge if you have a Frequent-
FlyerClass claim with a value of Gold. If this was the only requirement, you could use
AuthorizationPolicyBuilder to create a policy using:
options.AddPolicy("CanAccessLounge", policyBuilder =>
policyBuilder.RequireClaim("FrequentFlyerClass", "Gold");
But what if the requirements are more complicated than this? For example, you can
enter the lounge if
Role-based authorization vs. claims-based authorization
If you look at all of the methods available on the AuthorizationPolicyBuilder
type, you might notice that there’s a method I didn’t mention in table 15.1, Require-
Role(). This is a remnant of the role-based approach to authorization used in previ-
ous versions of ASP.NET, and I don’t recommend using it.
Before Microsoft adopted the claims-based authorization used by ASP.NET Core and
recent versions of ASP.NET, role-based authorization was the norm. Users were
assigned to one or more roles, such as Administrator or Manager, and authoriza-
tion involved checking whether the current user was in the required role.
This role-based approach to authorization is possible in ASP.NET Core, but it’s pri-
marily for legacy compatibility reasons. Claims-based authorization is the suggested
approach. Unless you’re porting a legacy app that uses roles, I suggest you embrace
claims-based authorization and leave those roles behind!
444 CHAPTER 15 Authorization: securing your application
 You’re a gold-class frequent flyer (have a FrequentFlyerClass claim with value
"Gold")
 You’re an employee of the airline (have an EmployeeNumber claim)
 You’re at least 18 years old (as calculated from the DateOfBirth claim)
If you’ve ever been banned from the lounge (you have an IsBannedFromLounge
claim), then you won’t be allowed in, even if you satisfy the other requirements.
There’s no way of achieving this complex set of requirements with the basic usage
of AuthorizationPolicyBuilder you’ve seen so far. Luckily, these methods are a
wrapper around a set of building blocks that you can combine to achieve the desired
policy.
15.4.1 Requirements and handlers: the building blocks of a policy
Every policy in ASP.NET Core consists of one or more requirements, and every require-
ment can have one or more handlers. For the airport lounge example, you have a sin-
gle policy ("CanAccessLounge"), two requirements (MinimumAgeRequirement and
AllowedInLoungeRequirement), and several handlers, as shown in figure 15.4.
For a policy to be satisfied, a user must fulfill all the requirements. If the user fails any
of the requirements, the authorize filter won’t allow the protected action to be exe-
cuted. A user must be allowed to access the lounge and must be over 18 years old.
Each requirement can have one or more handlers, which will confirm that the
requirement has been satisfied. For example, as shown in figure 15.4, AllowedIn-
LoungeRequirement has two handlers that can satisfy the requirement:
 FrequentFlyerHandler
 IsAirlineEmployeeHandler
CanAccessLounge Policy
A policy consists of one
or more requirements.
AllowedInLoungeRequirement
Each requirement can have
one or more handlers.
MinimnumAgeRequirement
FrequentFlyerHandler
IsAirlineEmployeeHandler
BannedFromLoungeHandler
DateOfBirthHandler
Figure 15.4 A policy can have many requirements, and every requirement can have many
handlers.
445
Creating custom policies for authorization
If the user satisfies either of these handlers, then AllowedInLoungeRequirement is sat-
isfied. You don’t need all handlers for a requirement to be satisfied, you just need one.
NOTE Figure 15.4 showed a third handler, BannedFromLoungeHandler, which
I’ll cover in the next section. It’s slightly different, in that it can only fail a
requirement, not satisfy it.
You can use requirements and handlers to achieve most any combination of behavior
you need for a policy. By combining handlers for a requirement, you can validate con-
ditions using a logical OR: if any of the handlers are satisfied, the requirement is satis-
fied. By combining requirements, you create a logical AND: all of the requirements
must be satisfied for the policy to be satisfied, as shown in figure 15.5.
TIP You can also add multiple policies to an action method by applying the
[Authorize] attribute multiple times, for example [Authorize("Policy1"),
Authorize("Policy2")]. All policies must be satisfied for the request to be
authorized.
I’ve highlighted requirements and handlers that will make up your "CanAccess-
Lounge" policy, so in the next section, you’ll build each of the components and apply
them to the airport sample app.
15.4.2 Creating a policy with a custom requirement and handler
You’ve seen all the pieces that make up a custom authorization policy, so in this sec-
tion, we’ll explore the implementation of the "CanAccessLounge" policy.
CREATING AN IAUTHORIZATIONREQUIREMENT TO REPRESENT A REQUIREMENT
As you’ve seen, a custom policy can have multiple requirements, but what is a require-
ment in code terms? Authorization requirements in ASP.NET Core are any class that
implements the IAuthorizationRequirement interface. This is a blank, marker inter-
face, which you can apply to any class to indicate that it represents a requirement.
Requirement
1
Requirement
2
AND AND
Requirement
N
AND
...
Handler
2A
Handler
2B
Policy
Satisfied? =
OR
Handler
M
...
For the policy to be satisfied, every
requirement must be satisfied.
If any of the handlers are satisfied,
the requirement is satisfied.
OR
OR
Figure 15.5 For a policy to be satisfied, every requirement must be satisfied. A requirement is
satisfied if any of the handlers are satisfied.
446 CHAPTER 15 Authorization: securing your application
If the interface doesn’t have any members, you might be wondering what the
requirement class needs to look like. Typically, they’re simple, POCO classes. The fol-
lowing listing shows AllowedInLoungeRequirement, which is about as simple as a
requirement can get! It has no properties or methods; it implements the required
IAuthorizationRequirement interface.
public class AllowedInLoungeRequirement
: IAuthorizationRequirement { }
This is the simplest form of requirement, but it’s also common for them to have one
or two properties that make the requirement more generalized. For example, instead
of creating the highly specific MustBe18YearsOldRequirement, you instead create a
parametrized MinimumAgeRequirement, as shown in the following listing. By providing
the minimum age as a parameter to the requirement, you can reuse the requirement
for other policies with different minimum age requirements.
public class MinimumAgeRequirement : IAuthorizationRequirement
{
public MinimumAgeRequirement(int minimumAge)
{
MinimumAge = minimumAge;
}
public int MinimumAge { get; }
}
The requirements are the easy part. They represent each of the components of the
policy that must be satisfied for the policy to be satisfied overall.
CREATING A POLICY WITH MULTIPLE REQUIREMENTS
You’ve created the two requirements, so now you can configure the "CanAccess-
Lounge" policy to use them. You configure your policies as you did before, in the
ConfigureServices method of Startup.cs. Listing 15.7 shows how you could do this by
creating an instance of each requirement and passing them to Authorization-
PolicyBuilder. The authorization handlers will use these requirement objects when
attempting to authorize the policy.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options =>
Listing 15.5 AllowedInLoungeRequirement
Listing 15.6 The parameterized MinimumAgeRequirement
Listing 15.7 Creating an authorization policy with multiple requirements
The interface identifies the class
as an authorization requirement.
The interface
identifies the
class as an
authorization
requirement.
The minimum
age is
provided
when the
requirement
is created.
Handlers can use the exposed
minimum age to determine whether
the requirement is satisfied.
447
Creating custom policies for authorization
{
options.AddPolicy(
"CanEnterSecurity",
policyBuilder => policyBuilder
.RequireClaim(Claims.BoardingPassNumber));
options.AddPolicy(
"CanAccessLounge",
policyBuilder => policyBuilder.AddRequirements(
new MinimumAgeRequirement(18),
new AllowedInLoungeRequirement()
));
});
// Additional service configuration
}
You now have a policy called "CanAccessLounge" with two requirements, so you can
apply it to an action method using the [Authorize] attribute, in exactly the same way
you did for the "CanEnterSecurity" policy:
[Authorize("CanAccessLounge")]
public IActionResult AirportLounge()
{
return View();
}
When a user attempts to execute the AirportLounge action, the authorize filter policy
will be executed and each of the requirements will be inspected. But you saw earlier
that the requirements are purely data; they indicate what needs to be fulfilled, they
don’t describe how that has to happen. For that, you need to write some handlers.
CREATING AUTHORIZATION HANDLERS TO SATISFY YOUR REQUIREMENTS
Authorization handlers contain the logic of how a specific IAuthorizationRequirement
can be satisfied. When executed, a handler can do one of three things:
 Mark the requirement handling as a success
 Not do anything
 Explicitly fail the requirement
Handlers should implement AuthorizationHandler<T>, where T is the type of requirement
they handle. For example, the following listing shows a handler for AllowedInLounge-
Requirement that checks whether the user has a claim called FrequentFlyerClass with a
value of Gold.
public class FrequentFlyerHandler :
AuthorizationHandler<AllowedInLoungeRequirement>
{
protected override Task HandleRequirementAsync(
Listing 15.8 FrequentFlyerHandler for AllowedInLoungeRequirement
Adds the previous
simple policy for
passing through
security
Adds a new
policy for the
airport lounge,
called
CanAccessLounge
Adds an instance of each
IAuthorizationRequirement object
The handler implements
AuthorizationHandler<T>.
You must override the abstract
HandleRequirementAsync method.
448 CHAPTER 15 Authorization: securing your application
AuthorizationHandlerContext context,
AllowedInLoungeRequirement requirement)
{
if(context.User.HasClaim("FrequentFlyerClass", "Gold"))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
This handler is functionally equivalent to the simple RequireClaim()handler you saw
at the start of section 15.4, but using the requirement and handler approach instead.
When a user attempts to execute the AirportLounge action method, the authoriza-
tion filter added by the [Authorize] attribute validates the "CanAccessLounge" pol-
icy. It loops through all of the requirements in the policy, and all of the handlers for
each requirement, calling the HandleRequirementAsync method for each.
The filter passes in the current AuthorizationHandlerContext and the require-
ment to be checked. The current ClaimsPrincipal being authorized is exposed on the
context as the User property. In listing 15.8, FrequentFlyerHandler uses the context to
check for a claim called FrequentFlyerClass with the Gold value, and if it exists, con-
cludes that the user is allowed to enter the airline lounge, by calling Succeed().
NOTE Handlers mark a requirement as being successfully satisfied by calling
context.Succeed() and passing in the requirement.
It’s important to note the behavior when the user doesn’t have the claim. Frequent-
FlyerHandler doesn’t do anything if this is the case (it returns a completed Task to
satisfy the method signature).
NOTE Remember, if any of the handlers associated with a requirement pass,
then the requirement is a success. Only one of the handlers has to succeed for
the requirement to be satisfied.
This behavior, whereby you either call context.Succeed() or do nothing, is typical
for authorization handlers. This listing shows the implementation of IsAirline-
EmployeeHandler, which uses a similar claim check to determine whether the require-
ment is satisfied.
public class IsAirlineEmployeeHandler :
AuthorizationHandler<AllowedInLoungeRequirement>
{
Listing 15.9 IsAirlineEmployeeHandler handler
The context contains details
such as the ClaimsPrincipal
user object.
The
requirement
to handle
Checks whether the user
has the FrequentFlyerClass
claim with the Gold value
If the user had the necessary claim,
then mark the requirement as
satisfied by calling Succeed.
If the requirement wasn’t
satisfied, do nothing.
The handler implements
AuthorizationHandler<T>.
449
Creating custom policies for authorization
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
AllowedInLoungeRequirement requirement)
{
if(context.User.HasClaim(c => c.Type == "EmployeeNumber"))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
This pattern of authorization handler is common,2
but in some cases, instead of
checking for a success condition, you might want to check for a failure condition. In
the airport example, you don’t want to authorize someone who was previously banned
from the lounge, even if they would otherwise be allowed to enter.
You can handle this scenario by using the context.Fail() method exposed on the
context, as shown in the following listing. Calling Fail() in a handler will always cause
the requirement, and hence the whole policy, to fail. You should only use it when you
want to guarantee failure, even if other handlers indicate success.
public class BannedFromLoungeHandler :
AuthorizationHandler<AllowedInLoungeRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
AllowedInLoungeRequirement requirement)
{
if(context.User.HasClaim(c => c.Type == "IsBanned"))
{
context.Fail();
}
return Task.CompletedTask;
}
}
In most cases, your handlers will either call Succeed() or will do nothing, but the
Fail() method is useful when you need this kill-switch to guarantee that a require-
ment won’t be satisfied.
NOTE Whether a handler calls Succeed(), Fail(), or neither, the authoriza-
tion system will always execute all of the handlers for a requirement, so you
can be sure your handlers will always be called.
2
I’ll leave the implementation of MinimumAgeHandler for MinimumAgeRequirement as an exercise for the
reader. You can find an example in the code samples for the chapter.
Listing 15.10 Calling context.Fail() in a handler to fail the requirement
You must override the abstract
HandleRequirementAsync
method.
Checks whether the
user has the
EmployeeNumber claim
If the user has the necessary claim,
then mark the requirement as
satisfied by calling Succeed.
If the requirement wasn’t
satisfied, do nothing.
You must override the abstract
HandleRequirementAsync
method.
The handler implements
AuthorizationHandler<T>.
Checks whether
the user has the
IsBanned claim
If the user has the claim,
then fail the requirement
by calling Fail. The whole
policy will fail.
If the claim wasn’t found, do nothing.
450 CHAPTER 15 Authorization: securing your application
The final step to complete your authorization implementation for the app is to regis-
ter the authorization handlers with the DI container, as shown in listing 15.11.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.AddPolicy(
"CanEnterSecurity",
policyBuilder => policyBuilder
.RequireClaim(Claims.BoardingPassNumber));
options.AddPolicy(
"CanAccessLounge",
policyBuilder => policyBuilder.AddRequirements(
new MinimumAgeRequirement(18),
new AllowedInLoungeRequirement()
));
});
services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
services.AddSingleton<IAuthorizationHandler, FrequentFlyerHandler>();
services
.AddSingleton<IAuthorizationHandler, BannedFromLoungeHandler>();
Services
.AddSingleton<IAuthorizationHandler, IsAirlineEmployeeHandler>();
// Additional service configuration
}
For this app, the handlers don’t have any constructor dependencies, so I’ve registered
them as singletons with the container. If your handlers have scoped or transient
dependencies (the EF Core DbContext, for example), then you might want to register
them as scoped instead, as appropriate.
You can combine the concepts of policies, requirements, and handlers in many
different ways to achieve your goals for authorization in your application. The exam-
ple in this section, although contrived, demonstrates each of the components you
need to apply authorization at the action method level, by creating policies and apply-
ing the [Authorize] attribute as appropriate.
There’s one area, however, where the [Authorize] attribute falls short: resource-
based authorization. The [Authorize] attribute uses an authorization filter to autho-
rize the user before the action method is executed, but what if you need to authorize
the action during the action method?
This is common when you’re applying authorization at the document or resource
level. If users are only allowed to edit documents they created, then you need to load
the document before you can tell whether you’re allowed to edit it! This isn’t possible
with the declarative [Authorize] attribute approach, so you must use an alternative,
Listing 15.11 Registering the authorization handlers with the DI container
451
Controlling access with resource-based authorization
imperative approach. In the next section, you’ll see how to apply this resource-based
authorization to an action method.
15.5 Controlling access with resource-based authorization
Resource-based authorization is a common problem for applications, especially when
you have users that can create or edit some sort of document. Consider the recipe
application you built in the previous 3 chapters. This app lets users create, view, and
edit recipes.
Up to this point, everyone is able to create new recipes, and anyone can edit a rec-
ipe, even if they haven’t logged in. Now you want to add some additional behavior:
 Only authenticated users should be able to create new recipes
 You can only edit recipes you created
You’ve already seen how to achieve the first of these requirements; decorate the Create
action method with an [Authorize] attribute and don’t specify a policy, as shown in this
listing. This will force the user to authenticate before they can create a new recipe.
public class RecipeController : Controller
{
[Authorize]
public IActionResult Create()
{
return View(new CreateRecipeCommand());
}
[HttpPost, Authorize]
public async Task<IActionResult> Create(CreateRecipeCommand command)
{
// Method body not shown for brevity
}
}
TIP When applying the [Authorize] attribute to single actions, it’s import-
ant to protect both the GET method and the POST action method.
Adding the [Authorize] attribute fulfills your first requirement, but unfortunately,
with the techniques you’ve seen so far, you have no way to fulfill the second. You could
apply a policy that either permits or denies a user the ability to edit all recipes, but
there’s currently no way to restrict this so that a user can only edit their own recipes.
In order to find out who created the Recipe, you must first load it from the data-
base. Only then can you attempt to authorize the user, taking the specific recipe
(resource) into account. The following listing shows a partially implemented method
for how this might look, where authorization occurs part way through the method,
after the Recipe object has been loaded.
Listing 15.12 Adding AuthorizeAttribute to RecipeController.Create
Users must be authenticated
to execute the Create action.
It’s important to protect
the HttpPost actions, too.
452 CHAPTER 15 Authorization: securing your application
public IActionResult Edit(int id)
{
var recipe = _service.GetRecipe(id);
var createdById = recipe.CreatedById;
// Authorize user based on createdById
if(isAuthorized)
{
return View(recipe);
}
}
The [Authorize] attribute can’t help you here; authorization filters run before model
binding and before the action method executes. In the next section, you’ll see the
approach you need to take to handle these situations and to apply authorization
inside the action.
WARNING Be careful when exposing the integer ID of your entities in the
URL, as in listing 15.13. Users will be able to edit every entity by modifying the
ID in the URL to access a different entity. Be sure to apply authorization
checks, otherwise you could expose a security vulnerability called Insecure
Direct Object Reference.3
15.5.1 Manually authorizing requests with IAuthorizationService
All of the approaches to authorization so far have been declarative. You apply the
[Authorize] attribute, with or without a policy name, and you let the framework take
care of performing the authorization itself.
For this recipe-editing example, you need to use imperative authorization, so you
can authorize the user after you’ve loaded the Recipe from the database. Instead of
applying a marker saying “Authorize this method,” you need to write some of the
authorization code yourself.
DEFINITION Declarative and imperative are two different styles of programming.
Declarative programming describes what you’re trying to achieve, and lets the
framework figure out how to achieve it. Imperative programming describes
how to achieve something by providing each of the steps needed.
ASP.NET Core exposes IAuthorizationService, which you can inject into your con-
trollers for imperative authorization. This listing shows how you can update the Edit
action shown in listing 15.13 to use the IAuthorizationService and verify whether
the action is allowed to continue execution.
Listing 15.13 The Edit action must load the Recipe before authorizing the request
3
You can read about this vulnerability and ways to counteract it on the Open Web Application Security Project
(OWASP): www.owasp.org/index.php/Top_10_2007-Insecure_Direct_Object_Reference.
The id of the recipe to edit is
provided by model binding.
You must load the Recipe from the
database before you know who created it.
You must authorize the
current user, to verify
they’re allowed to edit this
specific Recipe.
The action method
can only continue if
the user was
authorized.
453
Controlling access with resource-based authorization
public class RecipeController : Controller
{
private IAuthorizationService _authService
public RecipeController(IAuthorizationService authService)
{
_authService = authService;
}
[Authorize]
public async Task<IActionResult> Edit(int id)
{
var recipe = _service.GetRecipe(id);
var authResult = await _authService
.AuthorizeAsync(User, recipe, "CanManageRecipe");
if (!authResult.Succeeded)
{
return new ForbidResult();
}
return View(recipe);
}
}
IAuthorizationService exposes an AuthorizeAsync method, which requires three
things to authorize the request:
 The ClaimsPrincipal user object, exposed on the Controller as User
 The resource being authorized: recipe
 The policy to evaluate: "CanManageRecipe"
The authorization attempt returns an AuthorizationResult object, which indicates
whether the attempt was successful via the Succeeded property. If the attempt wasn’t
successful, then you should return a new ForbidResult, which will return a 403 For-
bidden response to the user.
WARNING In ASP.NET Core 1.x resource-based authorization, you’d return a
ChallengeResult instead of a ForbidResult and the framework would gener-
ate the appropriate 401 or 403 response, depending on whether you
were logged in or not. In the changes that came with ASP.NET Core 2.0,
this behavior was lost, so you have to work out whether to return a 401 or
403 yourself. The easiest approach is: mark your action methods with the
[Authorize] attribute and always return a ForbidResult.
You’ve configured the imperative authorization in the Edit method itself, but you still
need to define the "CanManageRecipe" policy that you use to authorize the user. This
is the same process as for declarative authorization, so you have to:
 Create a policy in ConfigureServices by calling AddAuthorization()
 Define one or more requirements for the policy
Listing 15.14 Using IAuthorizationService for resource-based authorization
IAuthorizationService
is injected into the
class constructor
using DI.
Only authenticated
users should be
allowed to edit recipes.
Load the recipe
from the database.
If authorization failed,
returns a 403
Forbidden result
Calls
IAuthorizationService,
providing
ClaimsPrinicipal,
resource, and the
policy name
If authorization was successful,
continues the action method
454 CHAPTER 15 Authorization: securing your application
 Define one or more handlers for each requirement
 Register the handlers in the DI container
With the exception of the handler, these steps are all identical to the declarative
authorization approach with the [Authorize] attribute, so I’ll only run through them
briefly here.
First, you can create a simple IAuthorizationRequirement. As with all require-
ments, this contains no functionality and simply implements the marker interface.
public class IsRecipeOwnerRequirement : IAuthorizationRequirement { }
Defining the policy in ConfigureServices is similarly simple, as you have only this sin-
gle requirement. Note that there’s nothing resource-specific in any of this code so far:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options => {
options.AddPolicy("CanManageRecipe", policyBuilder =>
policyBuilder.AddRequirements(new IsRecipeOwnerRequirement()));
});
}
You’re halfway there; all you need to do now is create an authorization handler for
IsRecipeOwnerRequirement and register it with the DI container.
15.5.2 Creating a resource-based AuthorizationHandler
Resource-based authorization handlers are essentially the same as the authorization
handler implementations you saw in section 15.4.2. The only difference is that the
handler also has access to the resource being authorized.
To create a resource-based handler, you should derive from the Authorization-
Handler<TRequirement, TResource> base class, where TRequirement is the type of
requirement to handle, and TResource is the type of resource that you provide when
calling IAuthorizationService. Compare this to the AuthorizationHandler<T> class
you implemented previously, where you only specified the requirement.
This listing shows the handler implementation for your recipe application. You can
see that you’ve specified the requirement as IsRecipeOwnerRequirement, the
resource as Recipe, and have implemented the HandleRequirementAsync method.
public class IsRecipeOwnerHandler :
AuthorizationHandler<IsRecipeOwnerRequirement, Recipe>
{
private readonly UserManager<ApplicationUser> _userManager;
public IsRecipeOwnerHandler(
UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
Listing 15.15 IsRecipeOwnerHandler for resource-based authorization
Injects an instance
of the
UserManager<T>
class using DI
Implements the
necessary base class,
specifying the
requirement and
resource type
455
Controlling access with resource-based authorization
protected override async Task HandleRequirementAsync(
AuthorizationHandlerContext context,
IsRecipeOwnerRequirement requirement,
Recipe resource)
{
var appUser = await _userManager.GetUserAsync(context.User);
if(appUser == null)
{
return;
}
if(resource.CreatedById == appUser.Id)
{
context.Succeed(requirement);
}
}
}
This handler is slightly more complicated than the examples you’ve seen previously,
primarily because you’re using an additional service, UserManager<>, to load the
ApplicationUser entity based on ClaimsPrincipal from the request.
The other significant difference is that the HandleRequirementAsync method is
called with a Recipe resource. This is the same object that you provided when calling
AuthorizeAsync on IAuthorizationService. You can use this resource to verify
whether the current user created it. If so, you Succeed() the requirement, otherwise
you do nothing.
Our final task is to add IsRecipeOwnerHandler to the DI container. Your handler
uses an additional dependency, UserManager<>, which you know uses EF Core, so you
should register the handler as a scoped service:
services.AddScoped<IAuthorizationHandler, IsRecipeOwnerHandler>();
TIP If you’re wondering how to know whether you register a handler as
scoped or a singleton, think back to chapter 10. Essentially, if you have
scoped dependencies, then you must register the handler as scoped; other-
wise, singleton is fine.
With everything hooked up, you can take the application for a spin. If you try to edit a
recipe you didn’t create by clicking the Edit button on the recipe, you’ll either be
redirected to the login page (if you hadn’t yet authenticated) or you’ll be presented
with an Access Denied page, as shown in figure 15.6.
By using resource-based authorization, you’re able to enact more fine-grained
authorization requirements that you can apply at the level of an individual document
or resource. Instead of only being able to authorize that a user can edit any recipe, you
can authorize whether a user can edit this recipe.
All of the authorization techniques you’ve seen so far have focused on server-side
checks. Both the [Authorize] attribute and resource-based authorization approaches
focus on stopping users from executing a protected action on the server. This is
As well as the
context and
requirement,
you’re also
provided the
resource instance.
If you aren’t authenticated,
then appUser will be null.
Checks whether the
current user created
the Recipe by checking
the CreatedById
property
If the user created the document,
Succeed the requirement;
otherwise, do nothing.
456 CHAPTER 15 Authorization: securing your application
important from a security point of view, but there’s another aspect you should con-
sider too: the user experience when they don’t have permission.
Now you’ve protected the code executing on the server, but arguably, the Edit but-
ton should never have been visible to the user if they weren’t going to be allowed to
edit the recipe! In the next section, we’ll look at how you can conditionally hide the
Edit button by using resource-based authorization in your view models.
15.6 Hiding elements in Razor templates
from unauthorized users
All of the authorization code you’ve seen so far has revolved around protecting action
methods on the server side, rather than modifying the UI for users. This is important
and should be the starting point whenever you add authorization to an app.
Yes
Is the
current user
authenticated?
Did the current
user create the
recipe?
No
No
Yes
Click Edit
button.
Access denied.
Edit recipe.
Login page
View recipe
401 Unauthorized
403 Fobidden
Figure 15.6 If you’re not authorized to edit a recipe, you’ll be redirected to an Access Denied page.
If you’re not logged in, you’ll be redirected to the login page.
457
Hiding elements in Razor templates from unauthorized users
WARNING Malicious users can easily circumvent your UI, so it’s important to
always authorize your actions on the server, never on the client alone.
From a user-experience point of view, however, it’s not friendly to have buttons or
links that look like they’re available but present you with an Access Denied page when
they’re clicked. A better experience would be for the links to be disabled, or not visi-
ble at all.
You can achieve this in several ways in your own Razor templates. In this section,
I’m going to show you how to add an additional property to the page view model,
CanEditRecipe, which the view template will use to change the rendered HTML.
TIP An alternative approach would be to inject IAuthorizationService
directly into the view template using the @inject directive, as you saw in chap-
ter 10.
When you’re finished, the rendered HTML will look unchanged for recipes you cre-
ated, but the Edit button will be hidden when viewing a recipe someone else created,
as shown in figure 15.7.
The following listing shows RecipeDetailViewModel, which is used to render the rec-
ipe page shown in figure 15.7. I’ve added an additional property, called CanEdit-
Recipe, which you’ll set in the RecipeDetail action method, based on whether the
current user can edit the recipe.
If the user created
the recipe, they can
see the edit button
for the recipe.
For recipes created
by other users, the
edit button is hidden.
Figure 15.7 Although the HTML will appear unchanged, the Edit button is hidden when someone else
has created the recipe.
458 CHAPTER 15 Authorization: securing your application
public class RecipeDetailViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Method { get; set; }
public DateTimeOffset LastModified { get; set; }
public IEnumerable<Item> Ingredients { get; set; }
public bool CanEditRecipe {get; set;}
public class Item
{
public string Name { get; set; }
public string Quantity { get; set; }
}
}
The View.cshtml Razor template only requires a simple change: adding an if clause
around the rendering of the edit link:
@if(Model.CanEditRecipe)
{
<a asp-action="Edit" asp-route-id="@Model.Id"
class="btn btn-primary">Edit</a>
}
The final step is to set the CanEditRecipe value as appropriate in the View action
method. But how do you set that value?
Hopefully, the previous section has given you a clue—you can use IAuthorization-
Service to determine whether the current user has permission to edit the Recipe, by
calling AuthorizeAsync, as shown here.
public async Task<IActionResult> View(int id)
{
var recipe = _service.GetRecipe(id);
var authResult = await _authService.AuthorizeAsync(
User, recipe, "CanManageRecipe");
model.CanEditRecipe = authResult.Succeeded;
return View(model);
}
Instead of blocking execution of the method (as you did in the Edit action), use the
result of the call to AuthorizeAsync to set the CanEditRecipe value on the view
model. This ensures that only users who will be able to execute the Edit action see the
link to that page.
Listing 15.16 RecipeDetailViewModel used by the View.cshtml template
Listing 15.17 Setting a view model property using IAuthorizationService
The CanEditRecipe property will
be used to control whether the
Edit button is rendered.
Loads the Recipe resource for
use with IAuthorizationService
Verifies whether
the user is
authorized to edit
the Recipe
Sets the CanEditRecipe property on
the view model as appropriate
459
Summary
WARNING Note that you didn’t remove the server-side authorization check
from the Edit action. This is important for preventing malicious users from
circumventing your UI.
With that final change, you’ve finished adding authorization to the recipe application.
Anonymous users can browse the recipes created by others, but they must log in to
create new recipes. Additionally, users can only edit the recipes that they created, and
won’t see an edit link for other people’s recipes.
Authorization is a key aspect of most apps, so it’s important to bear it in mind from
an early point. Although it’s possible to add authorization later, as you did with the
recipe app, it’s normally preferable to consider authorization sooner rather than later
in the app’s development.
In the next chapter, we’re going to be looking at your ASP.NET Core application
from a different point of view. Instead of focusing on the code and logic behind your
app, we’re going to look at how you prepare an app for production. You’ll see how to
specify the URLs your application uses, and how to publish an app so that it can be
hosted in IIS. Finally, you’ll learn about the bundling and minification of client-side
assets, why you should care, and how to use BundlerMinifier in ASP.NET Core.
Summary
 Authorization is the process of determining what a user can do. It’s distinct
from authentication, the process of determining who a user is. Authentication
typically occurs before authorization.
 You can use authorization in any part of your application, but it’s typically
applied to MVC controllers and actions, using the [Authorize] attribute to
control which actions a user can execute.
 The simplest form of authorization requires that a user is authenticated before
executing an action. You can achieve this by applying the [Authorize] attribute
to an action, to a controller, or globally.
 Claims-based authorization uses the current user’s claims to determine whether
they’re authorized to execute an action. You define the claims needed to exe-
cute an action in a policy.
 Policies have a name and are configured in Startup.cs as part of the call to
AddAuthorization() in ConfigureServices. You define the policy using Add-
Policy(), passing in a name and a lambda that defines the claims needed.
 You can apply a policy to an action or controller by specifying the policy in the
authorize attribute, for example [Authorize("CanAccessLounge")].
 If an unauthenticated user attempts to execute a protected action, they’ll typi-
cally be redirected to the login page for your app. If they’re already authenti-
cated, but don’t have the required claims, they’ll be shown an Access Denied
page instead.
460 CHAPTER 15 Authorization: securing your application
 For complex authorization policies, you can build a custom policy. A custom
policy consists of one or more requirements, and a requirement can have one
or more handlers.
 For a policy to be authorized, every requirement must be satisfied. For a
requirement to be satisfied, one or more of the associated handlers must indi-
cate success, and none must indicate explicit failure.
 AuthorizationHandler<T> contains the logic that determines whether a
requirement is satisfied. If a requirement requires that users are over 18, the
handler could look for a DateOfBirth claim and calculate the user’s age.
 Handlers can mark a requirement as satisfied by calling context.Succeed
(requirement). If a handler can’t satisfy the requirement, then it shouldn’t call
anything on the context, as a different handler could call Succeed() and satisfy
the requirement.
 If a handler calls context.Fail(), then the requirement will fail, even if a dif-
ferent handler marked it as a success using Succeed().
 Resource-based authorization uses details of the resource being protected to
determine whether the current user is authorized. For example, if a user is only
allowed to edit their own documents, then you need to know the author of the
document before you can determine whether they’re authorized.
 Resource-based authorization uses the same policy, requirements, and handlers
system as before. Instead of applying authorization with the [Authorize] attri-
bute, you must manually call IAuthorizationService and provide the resource
you’re protecting.
 You can modify the user interface to account for user authorization by adding
additional properties to your view models. If a user isn’t authorized to execute
an action, you can remove or disable the link to that action method in the UI.
You should always authorize on the server, even if you’ve removed links from
the UI.
461
Publishing and deploying
your application
We’ve covered a vast amount of ground so far in this book. We’ve gone over the
basic mechanics of building an ASP.NET Core application, such as configuring
dependency injection, loading app settings, and building a middleware pipeline.
We’ve looked at the UI side, using Razor templates and layouts to build an HTML
response. And we’ve looked at higher-level abstractions, such as EF Core and
ASP.NET Core Identity, that let you interact with a database and add users to your
application.
This chapter covers
 Publishing an ASP.NET Core application
 Hosting an ASP.NET Core application in IIS
 Customizing the URLs for an ASP.NET Core app
 Optimizing client-side assets with bundling and
minification
462 CHAPTER 16 Publishing and deploying your application
In this chapter, we’re taking a slightly different route. Instead of looking at ways to
build bigger and better applications, we focus on what it means to deploy your appli-
cation so that users can access it. You’ll learn about
 The process of publishing an ASP.NET Core application so that it can be
deployed to a server.
 How to prepare a reverse proxy (IIS) to host your application.
 How to optimize your app so that it’s performant once deployed.
We start by looking again at the ASP.NET Core hosting model in section 16.1 and
examining why it’s a good idea to host your application behind a reverse proxy instead
of exposing your app directly to the internet. I show you the difference between run-
ning an ASP.NET Core app in development using dotnet run and publishing the app
for use on a remote server. Finally, I describe some of the options available to you
when deciding how and where to deploy your app.
In section 16.2, I show you how to deploy your app to one such option, a Windows
server running IIS (Internet Information Services). This will be a typical deployment
scenario for many developers already familiar with ASP.NET, so it acts as a useful case
study, but it’s certainly not the only possibility. I won’t go into all the technical details
of configuring the venerable IIS system, but I’ll show the bare minimum required to
get it up and running. If your focus is cross-platform development, then don’t worry, I
don’t dwell on IIS for too long!
In section 16.3, I provide an introduction to hosting on Linux. You’ll see how it dif-
fers from hosting applications on Windows, learn the changes you need to make to
your apps, and find out about some gotchas to look out for. I describe how reverse
proxies on Linux differ from IIS and point you to some resources you can use to con-
figure your environments, rather than giving exhaustive instructions in this book.
If you’re not hosting your application using IIS, then you’ll likely need to set the
URL that your ASP.NET Core app is using when you deploy your application. In sec-
tion 16.4, I show two approaches to this: using the special ASPNETCORE_URLS environ-
ment variable and loading the URLs using ConfigurationBuilder. Although
generally not an issue during
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf
ASP.NET Core in Action (2018).pdf

ASP.NET Core in Action (2018).pdf

  • 1.
    M A NN I N G Andrew Lock
  • 2.
    Useful .NET CLI(dotnet) commands. Use --help to see all the optional arguments Command Description Run from dotnet restore Restores the NuGet packages for all the projects in the solution. Solution folder dotnet build Builds all the projects in the solution. To build in Release mode, use the -c switch. Solution folder dotnet run Runs the project in the current folder. Use during development to run your app. Project folder dotnet publish -c Release –o <Folder> Publishes the project to the provided folder. This copies all the required files to the provided output folder so that it can be deployed. Project folder dotnet test Builds the project and executes any unit tests found in the project. Requires the .NET Test SDK and a testing framework adapter (see chapter 20). Project folder dotnet add package <Name> Installs the NuGet package with the pro- vided name into the current project. Optionally, specify a package version, for example -v 2.1.0. Project folder dotnet new --list Views all the installed templates for creat- ing ASP.NET Core apps, libraries, test proj- ects, solution files, and much more. Anywhere dotnet new --install <PackageName> Installs a new template from the provided NuGet package name. For example, dotnet new --install "NetEscapades.Templates::*". Anywhere dotnet new <Template> –o <Folder> -n <NewName> Creates a new item from the provided tem- plate, specifying the folder in which to place the item, and the name for the item. Anywhere, empty folder for new projects dotnet ef --help Displays help for managing your database and migrations using EF Core. Requires the EF Core tools (see chapter 12). Project folder dotnet ef migrations add <Name> Adds a new EF Core migration to the proj- ect with the provided name. Project folder dotnet ef database update Applies pending EF Core migrations to a database. Warning—this will modify your database! Project folder
  • 3.
  • 5.
    ASP.NET Core in Action ANDREWLOCK M A N N I N G SHELTER ISLAND
  • 6.
    For online informationand ordering of this and other Manning books, please visit www.manning.com. The publisher offers discounts on this book when ordered in quantity. For more information, please contact Special Sales Department Manning Publications Co. 20 Baldwin Road PO Box 761 Shelter Island, NY 11964 Email: orders@manning.com ©2018 by Manning Publications Co. All rights reserved. No part of this publication may be reproduced, stored in a retrieval system, or transmitted, in any form or by means electronic, mechanical, photocopying, or otherwise, without prior written permission of the publisher. Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in the book, and Manning Publications was aware of a trademark claim, the designations have been printed in initial caps or all caps. Recognizing the importance of preserving what has been written, it is Manning’s policy to have the books we publish printed on acid-free paper, and we exert our best efforts to that end. Recognizing also our responsibility to conserve the resources of our planet, Manning books are printed on paper that is at least 15 percent recycled and processed without the use of elemental chlorine. Manning Publications Co. Development editor: Marina Michaels 20 Baldwin Road Technical development editor: Kris Athi PO Box 761 Review editor: Ivan Martinović Shelter Island, NY 11964 Project manager: David Novak Copyeditor: Safis Editing Proofreader: Elizabeth Martin Technical proofreader: Tanya Wilke Typesetter: Dottie Marsico Cover designer: Marija Tudor ISBN 9781617294617 Printed in the United States of America 1 2 3 4 5 6 7 8 9 10 – EBM – 23 22 21 20 19 18
  • 7.
    v brief contents PART 1GETTING STARTED WITH MVC..........................................1 1 ■ Getting started with ASP.NET Core 3 2 ■ Your first application 28 3 ■ Handling requests with the middleware pipeline 61 4 ■ Creating web pages with MVC controllers 93 5 ■ Mapping URLs to methods using conventional routing 120 6 ■ The binding model: retrieving and validating user input 148 7 ■ Rendering HTML using Razor views 174 8 ■ Building forms with Tag Helpers 204 9 ■ Creating a Web API for mobile and client applications using MVC 234 PART 2 BUILDING COMPLETE APPLICATIONS.............................. 265 10 ■ Service configuration with dependency injection 267 11 ■ Configuring an ASP.NET Core application 303 12 ■ Saving data with Entity Framework Core 334 13 ■ The MVC filter pipeline 369 14 ■ Authentication: adding users to your application with Identity 400 15 ■ Authorization: securing your application 432 16 ■ Publishing and deploying your application 461
  • 8.
    BRIEF CONTENTS vi PART 3EXTENDING YOUR APPLICATIONS...................................499 17 ■ Monitoring and troubleshooting errors with logging 501 18 ■ Improving your application’s security 534 19 ■ Building custom components 572 20 ■ Testing your application 607
  • 9.
    vii contents preface xvii acknowledgments xix aboutthis book xxi about the author xxv about the cover illustration xxvi PART 1 GETTING STARTED WITH MVC...........................1 1 Getting started with ASP.NET Core 3 1.1 An introduction to ASP.NET Core 4 Using a web framework 4 ■ The benefits and limitations of ASP.NET 5 ■ What is ASP.NET Core? 6 1.2 When to choose ASP.NET Core 8 What type of applications can you build? 8 ■ If you’re new to .NET development 10 ■ If you’re a .NET Framework developer creating a new application 12 ■ Converting an existing ASP.NET application to ASP.NET Core 16 1.3 How does ASP.NET Core work? 17 How does an HTTP web request work? 17 ■ How does ASP.NET Core process a request? 19 1.4 Choosing a platform for ASP.NET Core 21 Advantages of using .NET Framework 21 ■ Advantages of using .NET Core 22
  • 10.
    CONTENTS viii 1.5 Preparing yourdevelopment environment 23 If you’re a Windows user 24 ■ If you’re a Linux or macOS user 25 2 Your first application 28 2.1 A brief overview of an ASP.NET Core application 29 2.2 Creating your first ASP.NET Core application 32 Using a template to get started 32 ■ Building the application 35 2.3 Running the web application 36 2.4 Understanding the project layout 38 2.5 The csproj project file: defining your dependencies 40 2.6 The Program class: building a web host 41 2.7 The Startup class: configuring your application 44 Adding and configuring services 45 ■ Defining how requests are handled with middleware 47 2.8 MVC middleware and the home controller 52 2.9 Generating HTML with Razor template views 54 3 Handling requests with the middleware pipeline 61 3.1 What is middleware? 63 3.2 Combining middleware in a pipeline 66 Simple pipeline scenario 1: a holding page 67 ■ Simple pipeline scenario 2: Handling static files 70 ■ Simple pipeline scenario 3: An MVC web application 73 3.3 Handling errors using middleware 78 Viewing exceptions in development: DeveloperExceptionPage 80 ■ Handling exceptions in production: ExceptionHandlerMiddleware 82 Handling other errors: StatusCodePagesMiddleware 86 Disabling error handling middleware for Web APIs 90 4 Creating web pages with MVC controllers 93 4.1 An introduction to MVC 95 The MVC design pattern 95 ■ MVC in ASP.NET Core 98 Adding the MvcMiddleware to your application 104 ■ What makes a controller a controller? 111 4.2 MVC controllers and action methods 113 Accepting parameters to action methods 114 ■ Using ActionResult 116
  • 11.
    CONTENTS ix 5 MappingURLs to methods using conventional routing 120 5.1 What is routing? 122 5.2 Routing to MVC controllers and actions 125 5.3 Routing using conventions 129 Understanding route templates 131 ■ Using optional and default values 133 ■ Adding additional constraints to route parameters 135 ■ Defining default values and constraints using anonymous objects 138 ■ Matching arbitrary URLs with the catch-all parameter 139 5.4 Handling multiple matching actions for a route 140 5.5 Generating URLs from route parameters 142 Generating URLs based on an action name 143 ■ Generating URLs based on a route name 144 ■ Generating URLs with ActionResults 145 6 The binding model: retrieving and validating user input 148 6.1 Understanding the M in MVC 149 6.2 From request to model: making the request useful 152 Binding simple types 155 ■ Binding complex types 158 Choosing a binding source 162 6.3 Handling user input with model validation 164 The need for validation 164 ■ Using DataAnnotations attributes for validation 165 ■ Validating on the server for safety 168 ■ Validating on the client for user experience 171 7 Rendering HTML using Razor views 174 7.1 Views: rendering the user interface in MVC 175 7.2 Creating Razor views 179 Selecting a view from a controller 181 ■ Introducing Razor templates 182 ■ Passing data to views 185 7.3 Creating dynamic web pages with Razor 187 Using C# in Razor templates 188 ■ Adding loops and conditionals to Razor templates 189 ■ Rendering HTML with Raw 191 7.4 Layouts, partial views, and _ViewStart 193 Using layouts for shared markup 194 ■ Overriding parent layouts using sections 196 ■ Using partial views to encapsulate markup 198 ■ Running code on every view with _ViewStart and _ViewImports 200
  • 12.
    CONTENTS x 8 Building formswith Tag Helpers 204 8.1 Catering to editors with Tag Helpers 206 8.2 Creating forms using Tag Helpers 209 The Form Tag Helper 214 ■ The Label Tag Helper 215 The Input and Textarea Tag Helpers 217 ■ The Select Tag Helper 220 ■ The Validation Message and Validation Summary Tag Helpers 225 8.3 Generating links with the Anchor Tag Helper 228 8.4 Cache-busting with the Append Version Tag Helper 229 8.5 Using conditional markup with the Environment Tag Helper 230 9 Creating a Web API for mobile and client applications using MVC 234 9.1 What is a Web API and when should you use one? 235 9.2 Creating your first Web API Controller 238 9.3 Applying the MVC design pattern to a Web API 242 9.4 Attribute routing: taking fine-grained control of your URLs 246 Ordering of conventional and attribute routes 249 Combining route attributes to keep your route templates DRY 251 ■ Using token replacement to reduce duplication in attribute routing 253 ■ Handling multiple matching actions with attribute routing 254 9.5 Enabling additional input formatters: binding to XML data 255 9.6 Generating a response from a model 258 Customizing the default formatters: adding XML support 260 Choosing a response format with content negotiation 262 PART 2 BUILDING COMPLETE APPLICATIONS...............265 10 Service configuration with dependency injection 267 10.1 Introduction to dependency injection 268 Understanding the benefits of dependency injection 269 Creating loosely coupled code 274 ■ Dependency injection in ASP.NET Core 276
  • 13.
    CONTENTS xi 10.2 Usingthe dependency injection container 278 Adding ASP.NET Core framework services to the container 278 Registering your own services with the container 279 Registering services using objects and lambdas 282 Registering a service in the container multiple times 286 Injecting services into action methods and view templates 289 10.3 Understanding lifetimes: when are services created? 292 Transient: everyone is unique 295 ■ Scoped: let’s stick together, guys 296 ■ Singleton: there can be only one 297 Keeping an eye out for captured dependencies 298 11 Configuring an ASP.NET Core application 303 11.1 Introducing the ASP.NET Core configuration model 304 11.2 Configuring your application with CreateDefaultBuilder 306 11.3 Building a configuration object for your app 308 Adding a configuration provider in Program.cs 310 Using multiple providers to override configuration values 313 ■ Storing configuration secrets safely 315 Reloading configuration values when they change 318 11.4 Using strongly typed settings with the options pattern 320 Introducing the IOptions interface 321 ■ Reloading strongly typed options with IOptionsSnapshot 323 ■ Designing your options classes for automatic binding 324 11.5 Configuring an application for multiple environments 326 Identifying the hosting environment 326 ■ Loading environment-specific configuration files 327 ■ Setting the hosting environment 329 12 Saving data with Entity Framework Core 334 12.1 Introducing Entity Framework Core 336 What is EF Core? 336 ■ Why use an object-relational mapper? 337 ■ When should you choose EF Core? 339 Mapping a database to your application code 340 12.2 Adding EF Core to an application 342 Choosing a database provider and installing EF Core 344 Building a data model 345 ■ Registering a data context 348
  • 14.
    CONTENTS xii 12.3 Managing changeswith migrations 349 Creating your first migration 350 ■ Adding a second migration 353 12.4 Querying data from and saving data to the database 355 Creating a record 356 ■ Loading a list of records 358 Loading a single record 360 ■ Updating a model with changes 361 12.5 Using EF Core in production applications 366 13 The MVC filter pipeline 369 13.1 Understanding filters and when to use them 370 The MVC filter pipeline 372 ■ Filters or middleware: which should you choose? 373 ■ Creating a simple filter 375 Adding filters to your actions, your controllers, and globally 377 Understanding the order of filter execution 379 13.2 Creating custom filters for your application 381 Authorization filters: protecting your APIs 383 ■ Resource filters: short-circuiting your action methods 384 ■ Action filters: customizing model binding and action results 386 ■ Exception filters: custom exception handling for your action methods 391 Result filters: customizing action results before they execute 392 13.3 Understanding pipeline short-circuiting 394 13.4 Using dependency injection with filter attributes 396 14 Authentication: adding users to your application with Identity 400 14.1 Introducing authentication and authorization 402 Understanding users and claims in ASP.NET Core 402 Authentication in ASP.NET Core: services and middleware 403 Authentication for APIs and distributed applications 406 14.2 What is ASP.NET Core Identity? 410 14.3 Creating a project that uses ASP.NET Core Identity 412 Creating the project from a template 412 ■ Exploring the template in Solution Explorer 414 ■ The ASP.NET Core Identity data model 416 ■ Interacting with ASP.NET Core Identity: the MVC controllers 418 14.4 Adding ASP.NET Core Identity to an existing project 423 Configuring the ASP.NET Core Identity services and middleware 424 ■ Updating the EF Core data model to
  • 15.
    CONTENTS xiii support Identity426 ■ Adding the controllers, view models, and views 427 14.5 Managing users: adding new claims to users 428 15 Authorization: securing your application 432 15.1 Introduction to authorization 433 15.2 Authorization in ASP.NET Core 436 Preventing anonymous users from accessing your application 436 ■ Handling unauthorized requests 439 15.3 Using policies for claims-based authorization 440 15.4 Creating custom policies for authorization 443 Requirements and handlers: the building blocks of a policy 444 Creating a policy with a custom requirement and handler 445 15.5 Controlling access with resource-based authorization 451 Manually authorizing requests with IAuthorizationService 452 Creating a resource-based AuthorizationHandler 454 15.6 Hiding elements in Razor templates from unauthorized users 456 16 Publishing and deploying your application 461 16.1 Understanding the ASP.NET Core hosting model 463 Running vs. publishing an ASP.NET Core app 465 Choosing a deployment method for your application 468 16.2 Publishing your app to IIS 470 Configuring IIS for ASP.NET Core 470 Preparing and publishing your application to IIS 473 16.3 Hosting an application on Linux 475 Running an ASP.NET Core app behind a reverse proxy on Linux 476 ■ Preparing your app for deployment to Linux 478 16.4 Configuring the URLs for your application 480 Using an environment variable 480 ■ Using configuration values 481 16.5 Optimizing your client-side assets using BundlerMinifier 485 Speeding up an app using bundling and minification 488 Adding BundlerMinifier to your application 490 ■ Using minified files in production with the environment tag helper 494 ■ Serving common files from a CDN 495
  • 16.
    CONTENTS xiv PART 3 EXTENDINGYOUR APPLICATIONS ...................499 17 Monitoring and troubleshooting errors with logging 501 17.1 Using logging effectively in a production app 503 Highlighting problems using custom log messages 504 The ASP.NET Core logging abstractions 505 17.2 Adding log messages to your application 507 Log level: how important is the log message? 509 ■ Log category: which component created the log 511 ■ Formatting messages and capturing parameter values 512 17.3 Controlling where logs are written using logging providers 514 Adding a new logging provider to your application 515 Replacing the default ILoggerFactory with Serilog 517 17.4 Changing log verbosity with filtering 521 17.5 Structured logging: creating searchable, useful logs 526 Adding a structured logging provider to your app 527 ■ Using scopes to add additional properties to your logs 530 18 Improving your application’s security 534 18.1 Adding HTTPS to an application 535 Setting up IIS and IIS Express to use encryption 58 Creating a self-signed certificate for local development 541 Using HTTPS directly in Kestrel 544 ■ Enforcing HTTPS for your whole app 545 18.2 Defending against cross-site scripting (XSS) attacks 548 18.3 Protecting from cross-site request forgery (CSRF) attacks 551 18.4 Calling your web APIs from other domains using CORS 556 Understanding CORS and how it works 557 ■ Adding CORS to your whole app with middleware 559 ■ Adding CORS to specific MVC actions with EnableCorsAttribute 561 Configuring CORS policies 562 18.5 Exploring other attack vectors 564 Detecting and avoiding open redirect attacks 564 ■ Avoiding SQL injection attacks with EF Core and parameterization 566 Preventing insecure direct object references 568 ■ Protecting your users’ passwords and data 568
  • 17.
    CONTENTS xv 19 Buildingcustom components 572 19.1 Customizing your middleware pipeline 574 Creating simple endpoints with the Run extension 575 Branching middleware pipelines with the Map extension 576 Adding to the pipeline with the Use extension 578 ■ Building a custom middleware component 581 19.2 Handling complex configuration requirements 583 Partially building configuration to configure additional providers 584 ■ Using services to configure IOptions with IConfigureOptions 586 19.3 Using a third-party dependency injection container 589 19.4 Creating a custom Razor Tag Helper 591 Printing environment information with a custom Tag Helper 592 ■ Creating a custom Tag Helper to conditionally hide elements 595 19.5 View components: adding logic to partial views 597 19.6 Building a custom validation attribute 601 20 Testing your application 607 20.1 An introduction to testing in ASP.NET Core 608 20.2 Unit testing with xUnit 610 Creating your first test project 611 ■ Running tests with dotnet test 613 ■ Referencing your app from your test project 614 ■ Adding Fact and Theory unit tests 617 Testing failure conditions 620 20.3 Unit testing custom middleware 621 20.4 Unit testing MVC controllers 624 20.5 Integration testing: testing your whole app with a Test Host 627 Creating a TestServer using the Test Host package 628 Using your application’s Startup file for integration tests 630 Rendering Razor views in TestServer integration tests 632 Extracting common setup into an xUnit test fixture 635 20.6 Isolating the database with an in-memory EF Core provider 637 appendix A Getting to grips with .NET Core and .NET Standard 644 appendix B Useful references 663 index 669
  • 19.
    xvii preface ASP.NET has along history—Microsoft released the first version in 2002 as part of the original .NET Framework 1.0. Since then, it’s been through multiple iterations, each version bringing added features and extensibility. But each iteration was built on the same underlying framework provided by System.Web.dll. This library is a part of the .NET Framework, and so comes pre-installed in all versions of Windows. This brings mixed blessings. On the one hand, the ASP.NET 4.X framework is a reliable, battle-tested platform for building modern applications on Windows. On the other hand, it’s also limited by this reliance—changes to the underlying System .Web.dll are far-reaching and, consequently, slow to roll out. It also fundamentally excludes the many developers building on and deploying to Linux or macOS. When I first began looking into ASP.NET Core, I was one of those developers. A Windows user at heart, I was issued with a Mac by my employer, and so was stuck work- ing on a virtual machine all day. ASP.NET Core promised to change all that, allowing me to develop natively on both my Windows machine and Mac. I was relatively late to the party in many respects, only taking an active interest just before the time of the RC2 release of ASP.NET Core. By that point, there had already been eight (!) beta releases, many of which contained significant breaking changes; by not diving in fully until RC2, I was spared the pain of dodgy tooling and changing APIs. What I saw really impressed me. ASP.NET Core let developers leverage their exist- ing knowledge of the .NET framework, and of ASP.NET MVC applications in particu- lar, while baking-in current best practices like dependency injection, strongly typed configuration, and logging. On top of that, you could build and deploy cross-plat- form. I was sold.
  • 20.
    PREFACE xviii This book cameabout largely due to my approach to learning about ASP.NET Core. Rather than simply reading documentation and blog posts, I decided to try something new and start writing about what I learned. Each week, I would dedicate some time to exploring a new aspect of ASP.NET Core, and I’d write a blog post about it. When the possibility of writing a book came about, I jumped at the chance— another excuse to dive further into the framework! Since I started this book, a lot has changed, both with the book and ASP.NET Core. The first major release of the framework, in June 2016, still had many rough edges; the tooling experience in particular. But with the release of ASP.NET Core 2.0 in August 2017, the framework has really come into its own. I think ASP.NET Core 2.0 is a no- brainer for most people, so the book has evolved from targeting version 1.0 when I first started, to targeting 2.0 now. I describe the major differences between the versions in the book where appropriate, but I strongly encourage you to use ASP.NET Core 2.0 when you have the choice. This book covers everything you need to get started with ASP.NET Core, whether you’re completely new to web development or an existing ASP.NET developer. It focuses very much on the framework itself, so I don’t go into details about client-side frameworks such as Angular and React, or technologies like Docker. Personally, I find it a joy to work with ASP.NET Core apps compared to apps using the previous version of ASP.NET, and I hope that passion comes through in this book!
  • 21.
    xix acknowledgments While there’s onlyone name on the cover of this book, a plethora of people contrib- uted to both its writing and production. In this section, I’d like to thank everyone who has encouraged me, contributed, and put up with me for the past year. First, and most important, I’d like to thank my girlfriend, Becky. Your continual support and encouragement means the world to me and has kept me going through such a busy time. You’ve taken the brunt of my stress and pressure and I’m eternally grateful. I love you always. I’d also like to thank my whole family for their support—in particular, my parents, Jan and Bob, for putting up with my ranting and supplying me with coffee when holed up working on my visits back home. To my sister, Amanda, thanks for your always upbeat chats, and to Kathy and Jon, thanks for all the words of encouragement. On a professional level, I’d like to thank Manning for giving me this opportunity. Brian Sawyer “discovered” me and encouraged me to submit a proposal for what even- tually became this book. Dan Maharry initiated the project as my development editor and helped craft the approach for the book and its target audience. Those first few months and rewrites were hard work, but the book is better for it. Dan passed the baton to Marina Michaels, who served as development editor for the remainder of the book. With a keen eye and daunting efficiency, Marina was instrumental in getting this book to the finish line. Kris Athi read through numerous drafts and provided invaluable feedback. Tanya Wilke was the technical proofer, coming on board at the last minute with a grueling schedule to meet. To everyone at Manning who helped get this book published, heartfelt thanks. Thanks to all of the MEAP reviewers for their comments, which helped improve the
  • 22.
    ACKNOWLEDGMENTS xx book in innumerableways: Adam Bell, Andy Kirsch, Björn Nordblom, Emanuele Origgi, George Onofrei, Jason Pike, Joel Clermont, Jonathan Wood, Jose Diaz, Ken Muse, Ludvig Gislason, Mark Elston, Mark Harris, Matthew Groves, Nicolas Paez, Pasquale Zirpoli, Richard Michaels, Ronald E Lease, and Wayne Mather. A special thanks to Dave Harney for his meticulous comments in the forums. I would have never been in the position to write this book if it weren’t for the excellent content produced by the .NET community and those I follow on Twitter. In particular, thanks to Jon Galloway for regularly featuring my blog on the ASP.NET community standup. David Pine and Mike Brind were instrumental in my achieving the Microsoft Val- ued Professional (MVP). MVPs themselves, I thank them both for their hard work in the community. Thanks to Chris Hayford for giving me my first ASP.NET job while I was still finish- ing my PhD and for giving me my first chance to explore ASP.NET Core. Finally, thanks to Mick Delany for the chance to really put ASP.NET Core through its paces, and for giving me the time to work on this book.
  • 23.
    xxi about this book Thisbook is about the ASP.NET Core framework, what it is, and how you can use it to build web applications. While some of this content is already available online, it’s scat- tered around the internet in disparate documents and blog posts. This book guides you through building your first applications, introducing additional complexity as you cement previous concepts. I present each topic using relatively short examples, rather than building on a sin- gle example application throughout the book. There are merits to both approaches, but I wanted to ensure the focus remained on the specific topics being taught, without the mental overhead of navigating an increasingly large project. By the end of the book, you should have a solid understanding of how to build apps with ASP.NET Core, its strengths and weaknesses, and how to leverage its fea- tures to build apps securely. While I don’t spend a lot of time on application architec- ture, I do make sure to point out best practices, especially where I only superficially cover architecture in the name of brevity. Who should read this book This book is for C# developers who are interested in learning a cross-platform web framework. It doesn’t assume you have any experience building web applications; you may be a mobile or desktop developer, for example, though previous experience with ASP.NET or another web framework is undoubtedly beneficial. Other than a working knowledge of C# and .NET, I assume some knowledge of common object-oriented practices and a basic understanding of relational databases in general. To avoid the scope of the book ballooning, I assume a passing familiarity with HTML and CSS, and of JavaScript’s place as a client-side scripting language. You
  • 24.
    ABOUT THIS BOOK xxii don’tneed to know any JavaScript or CSS frameworks for this book, though ASP.NET Core works well with both if that is your forte. Web frameworks naturally touch on a wide range of topics, from the database and network, to visual design and client-side scripting. I provide as much context as possi- ble, but where the subject matter is too great, I include links to sites and books where you can learn more. How this book is organized This book is divided into three parts, twenty chapters, and two appendices. Ideally, you’ll read the book cover to cover and then use it as a reference, but I realize that won’t suit everyone. While I use small sample apps to demonstrate a topic, some chap- ters build on the work of previous ones, so the content will make more sense when read sequentially. I strongly suggest reading the chapters in part 1 in sequence, as each chapter builds on topics introduced in the previous chapters. Part 2 is best read sequentially, though most of the chapters are independent if you wish to jump around. You can read the chapters in part 3 out of order, though I recommend only doing so after you’ve covered parts 1 and 2. Part 1 provides a general introduction to ASP.NET Core and the overall architec- ture of a web application built using the framework. Once we have covered the basics, we dive into the Model-View-Controller (MVC) architecture, which makes up the bulk of most ASP.NET Core web applications.  Chapter 1 introduces ASP.NET Core and its place in the web development land- scape. It discusses when you should and shouldn’t use ASP.NET Core, the basics of web requests in ASP.NET Core, and the options available for a development environment.  Chapter 2 walks through all the components of a basic ASP.NET Core applica- tion, discussing their roles and how they combine to generate a response to a web request.  Chapter 3 describes the middleware pipeline, the main application pipeline in ASP.NET Core. It defines how incoming requests are processed and how a response should be generated.  Chapter 4 gives an overview of the MVC middleware component. This large component is responsible for managing the pages and APIs in your application, and for generating responses to web requests.  Chapter 5 describes the MVC routing system. Routing is the process of mapping incoming request URLs to a specific class and method, which executes to gener- ate a response.  Chapter 6 looks at model binding, the process of mapping form data and URL parameters passed in a request to concrete C# objects.
  • 25.
    ABOUT THIS BOOKxxiii  Chapter 7 shows how to generate HTML web pages using the Razor template language.  Chapter 8 builds on chapter 7 by introducing Tag Helpers, which can greatly reduce the amount of code required to build forms and web pages.  Chapter 9 describes how to use the MVC middleware to build APIs that can be called by client-side apps. Part 2 covers important topics related to building fully featured web applications, once you’ve understood the basics.  Chapter 10 describes how to use ASP.NET Core’s built-in dependency injection container to configure your application’s services.  Chapter 11 discusses how to read settings and secrets in ASP.NET Core, and how to map them to strongly typed objects.  Chapter 12 introduces Entity Framework Core for saving data into a relational database.  Chapter 13 builds on the topics in part 1 by introducing the MVC filter pipeline.  Chapter 14 describes how to add user profiles and authentication to your appli- cation using ASP.NET Core Identity.  Chapter 15 builds on the previous chapter by introducing authorization for users, so you can restrict which pages a signed-in user can access.  Chapter 16 looks at how to publish your app, how to configure your app for a production environment, and how to optimize your client-side assets. The four chapters that make up part 3 cover important, cross-cutting aspects of ASP.NET Core development.  Chapter 17 shows how to configure logging in your application, and how to write log messages to multiple locations.  Chapter 18 explores some of the security considerations you should take into account when developing your application, including how to configure your application for HTTPS.  Chapter 19 describes how to build and use a variety of custom components, including custom middleware, custom Tag Helpers, and custom validation attri- butes.  Chapter 20 shows how to test an ASP.NET Core application with the xUnit test- ing framework. It covers both unit tests and integration tests using the Test Host. The two appendices provide supplementary information. Appendix A provides some background to .NET Core and .NET Standard, how they fit in the .NET landscape, and how you should use them in your apps. Appendix B contains a number of links that I’ve found useful in learning about ASP.NET Core.
  • 26.
    ABOUT THIS BOOK xxiv Aboutthe code Source code is provided for all chapters except chapters 1 and 5. You can view the source code for each chapter in my GitHub repository at https://github.com/ andrewlock/asp-dot-net-core-in-action. A ZIP file containing all the source code is also available from the publisher’s website at https://www.manning.com/books/asp-dot- net-core-in-action. All the code examples in this book use ASP.NET Core 2.0 and were built using both Visual Studio and Visual Studio Code. Equivalent examples for ASP.NET Core 1.x are available for chapters 3–13 in the source code but are not referenced in the book itself. To build and run the examples, you’ll need to install the .NET Core SDK, as described in chapter 1. The source code is formatted in a fixed-width font like this to separate it from ordinary text. Sometimes code is also in bold to highlight code that has changed from previous steps in the chapter, such as when a new feature adds to an existing line of code. In many cases, the original source code has been reformatted; we’ve added line breaks and reworked indentation to accommodate the available page space in the book. In rare cases, even this was not enough, and listings include line-continuation markers (➥). Additionally, comments in the source code have often been removed from the listings when the code is described in the text. Code annotations accompany many of the listings, highlighting important concepts. Book forum Purchase of ASP.NET Core in Action includes free access to a private web forum run by Manning Publications where you can make comments about the book, ask technical questions, and receive help from the author and from other users. To access the forum, go to https://forums.manning.com/forums/asp-dot-net-core-in-action. You can also learn more about Manning's forums and the rules of conduct at https://forums .manning.com/forums/about. Manning’s commitment to our readers is to provide a venue where a meaningful dialogue between individual readers and between readers and the author can take place. It is not a commitment to any specific amount of participation on the part of the author, whose contribution to the forum remains voluntary (and unpaid). We sug- gest you try asking the author some challenging questions lest his interest stray! The forum and the archives of previous discussions will be accessible from the publisher’s website as long as the book is in print.
  • 27.
    xxv about the author ANDREWLOCK graduated with an engineering degree from Cam- bridge University, specializing in software engineering, and went on to obtain a PhD in digital image processing. He has been developing professionally using .NET for the last seven years, using a wide range of technologies, including WinForms, ASP.NET WebForms, ASP.NET MVC, and ASP.NET Web Pages. His focus is currently on the new ASP.NET Core framework, exploring its capabilities and deploying several new applications. Andrew has a very active blog at https://andrewlock.net, dedicated to ASP.NET Core. It’s frequently featured in the community spotlight by the ASP.NET team at Microsoft, on the .NET blog, and in the weekly community stand-ups, and recently earned him the Microsoft Valued Profossional (MVP) award.
  • 28.
    xxvi about the coverillustration The caption for the illustration on the cover of ASP.NET Core in Action is “The Captain Pasha. Kapudan pasha, admiral of the Turkish navy.” The Kapudan Pasha was the highest military rank of the Ottoman Navy from 1567 until 1867 when the post was abolished and replaced with the Naval Minister. The illustration is taken from a collec- tion of costumes of the Ottoman Empire published on January 1, 1802, by William Miller of Old Bond Street, London. The title page is missing from the collection and we have been unable to track it down to date. The book’s table of contents identifies the figures in both English and French, and each illustration bears the names of two artists who worked on it, both of whom would no doubt be surprised to find their art gracing the front cover of a computer programming book ... two hundred years later. The collection was purchased by a Manning editor at an antiquarian flea market in the “Garage” on West 26th Street in Manhattan. The seller was an American based in Ankara, Turkey, and the transaction took place just as he was packing up his stand for the day. The Manning editor didn’t have on his person the substantial amount of cash that was required for the purchase, and a credit card and check were both politely turned down. With the seller flying back to Ankara that evening, the situation was get- ting hopeless. What was the solution? It turned out to be nothing more than an old- fashioned verbal agreement sealed with a handshake. The seller simply proposed that the money be transferred to him by wire, and the editor walked out with the bank information on a piece of paper and the portfolio of images under his arm. Needless to say, we transferred the funds the next day, and we remain grateful and impressed by this unknown person’s trust in one of us. We at Manning celebrate the inventiveness, the initiative, and, yes, the fun of the computer business with book covers based on the rich diversity of regional life of two centuries ago‚ brought back to life by the pictures from this collection.
  • 29.
    Part 1 Getting startedwith MVC Web applications are everywhere these days, from social media web apps and news sites, to the apps on your phone. Behind the scenes, there is almost always a server running a web application or web API. Web applications are expected to be infinitely scalable, deployed to the cloud, and highly performant. Getting started can be overwhelming at the best of times and doing so with such high expectations can be even more of a challenge. The good news for you as readers is that ASP.NET Core was designed to meet those requirements. Whether you need a simple website, a complex e-commerce web app, or a distributed web of microservices, you can use your knowledge of ASP.NET Core to build lean web apps that fit your needs. ASP.NET Core lets you build and run web apps on Windows, Linux, or macOS. It’s highly modular, so you only use the components you need, keeping your app as compact and per- formant as possible. In part 1, you’ll go from a standing start all the way to building your first web applications and APIs. Chapter 1 gives a high-level overview of ASP.NET Core, which you’ll find especially useful if you’re new to web development in general. You’ll get your first glimpse of a full ASP.NET Core application in chapter 2, in which we look at each component of the app in turn and see how they work together to generate a response. Chapter 3 looks in detail at the middleware pipeline, which defines how incoming web requests are processed and how a response is generated. We take a detailed look at one specific piece of middleware, the MVC middleware, in chap- ters 4 through 6. This is the main component used to generate responses in ASP.NET Core apps, so we examine the behavior of the middleware itself, routing,
  • 30.
    2 PART 1Getting started with MVC and model binding. In Chapters 7 and 8, we look at how to build a UI for your appli- cation using the Razor syntax and Tag Helpers, so that users can navigate and interact with your app. Finally, in chapter 9, we explore specific features of ASP.NET Core that let you build web APIs, and how that differs from building UI-based applications. There’s a lot of content in part 1, but by the end, you’ll be well on your way to building simple applications with ASP.NET Core. Inevitably, I gloss over some of the more complex configuration aspects of the framework, but you should get a good understanding of the MVC middleware and how you can use it to build dynamic web apps. In later parts of this book, we’ll dive deeper into the framework, where you’ll learn how to configure your application and add extra features, such as user profiles.
  • 31.
    3 Getting started with ASP.NETCore Choosing to learn and develop with a new framework is a big investment, so it’s important to establish early on whether it’s right for you. In this chapter, I’ll pro- vide some background about ASP.NET Core, what it is, how it works, and why you should consider it for building your web applications. If you’re new to .NET development, this chapter will help you to choose a devel- opment platform for your future apps. For existing .NET developers, I’ll also pro- vide guidance on whether now is the right time to consider moving your focus to .NET Core, and the advantages ASP.NET Core can bring over previous versions of ASP.NET. By the end of this chapter, you should have a good idea of the model you intend to follow, and the tools you’ll need to get started—so without further ado, let’s dive in! This chapter covers  What is ASP.NET Core?  How ASP.NET Core works  Choosing between .NET Core and .NET Framework  Preparing your development environment
  • 32.
    4 CHAPTER 1Getting started with ASP.NET Core 1.1 An introduction to ASP.NET Core ASP.NET Core is the latest evolution of Microsoft’s popular ASP.NET web framework, released in June 2016. Recent versions of ASP.NET have seen many incremental updates, focusing on high developer productivity and prioritizing backwards compati- bility. ASP.NET Core bucks that trend by making significant architectural changes that rethink the way the web framework is designed and built. ASP.NET Core owes a lot to its ASP.NET heritage and many features have been car- ried forward from before, but ASP.NET Core is a new framework. The whole technol- ogy stack has been rewritten, including both the web framework and the underlying platform. At the heart of the changes is the philosophy that ASP.NET should be able to hold its head high when measured against other modern frameworks, but that existing .NET developers should continue to be left with a sense of familiarity. 1.1.1 Using a web framework If you’re new to web development, it can be daunting moving into an area with so many buzzwords and a plethora of ever-changing products. You may be wondering if they’re all necessary—how hard can it be to return a file from a server? Well, it’s perfectly possible to build a static web application without the use of a web framework, but its capabilities will be limited. As soon as you want to provide any kind of security or dynamism, you’ll likely run into difficulties, and the original sim- plicity that enticed you will fade before your eyes! Just as you may have used desktop or mobile development frameworks for building native applications, ASP.NET Core makes writing web applications faster, easier, and more secure. It contains libraries for common things like  Creating dynamically changing web pages  Letting users log in to your web app  Letting users use their Facebook account to log in to your web app using OAuth  Providing a common structure to build maintainable applications  Reading configuration files  Serving image files  Logging calls to your web app The key to any modern web application is the ability to generate dynamic web pages. A dynamic web page displays different data depending on the current logged-in user, for example, or it could display content submitted by users. Without a dynamic frame- work, it wouldn’t be possible to log in to websites or to have any sort of personalized data displayed on a page. In short, websites like Amazon, eBay, and Stack Overflow (seen in figure 1.1) wouldn’t be possible. Hopefully, it’s clear that using a web framework is a sensible idea for building high- quality web applications. But why choose ASP.NET Core? If you’re a C# developer, or even if you’re new to the platform, you’ve likely heard of, if not used, the previous ver- sion of ASP.NET—so why not use that instead?
  • 33.
    5 An introduction toASP.NET Core 1.1.2 The benefits and limitations of ASP.NET To understand why Microsoft decided to build a new framework, it’s important to understand the benefits and limitations of the existing ASP.NET web framework. The first version of ASP.NET was released in 2002 as part of .NET Framework 1.0, in response to the then conventional scripting environments of classic ASP and PHP. ASP.NET Web Forms allowed developers to rapidly create web applications using a graphical designer and a simple event model that mirrored desktop application- building techniques. The ASP.NET framework allowed developers to quickly create new applications, but over time, the web development ecosystem changed. It became apparent that ASP.NET Web Forms suffered from many issues, especially when building larger appli- cations. In particular, a lack of testability, a complex stateful model, and limited influ- ence over the generated HTML (making client-side development difficult) led developers to evaluate other options. In response, Microsoft released the first version of ASP.NET MVC in 2009, based on the Model-View-Controller pattern, a common web design pattern used in other frameworks such as Ruby on Rails, Django, and Java Spring. This framework allowed you to separate UI elements from application logic, made testing easier, and provided tighter control over the HTML-generation process. ASP.NET MVC has been through four more iterations since its first release, but they have all been built on the same underlying framework provided by the System .Web.dll file. This library is part of .NET Framework, so it comes pre-installed with all User otifications n Currently logged in user Viewing statistics Events and jobs based on location and user profile Questions submitted u by sers User votes update scores on the server Answers submitted by users Figure 1.1 The Stack Overflow website (http://stackoverflow.com) is built using ASP.NET and is almost entirely dynamic content.
  • 34.
    6 CHAPTER 1Getting started with ASP.NET Core versions of Windows. It contains all the core code that ASP.NET uses when you build a web application. This dependency brings both advantages and disadvantages. On the one hand, the ASP.NET framework is a reliable, battle-tested platform that’s a great choice for build- ing modern applications on Windows. It provides a wide range of features, which have seen many years in production, and is well known by virtually all Windows web developers. On the other hand, this reliance is limiting—changes to the underlying System .Web.dll are far-reaching and, consequently, slow to roll out. This limits the extent to which ASP.NET is free to evolve and results in release cycles only happening every few years. There’s also an explicit coupling with the Windows web host, Internet Informa- tion Service (IIS), which precludes its use on non-Windows platforms. In recent years, many web developers have started looking at cross-platform web frameworks that can run on Windows, as well as Linux and macOS. Microsoft felt the time had come to create a framework that was no longer tied to its Windows legacy, thus ASP.NET Core was born. 1.1.3 What is ASP.NET Core? The development of ASP.NET Core was motivated by the desire to create a web frame- work with four main goals:  To be run and developed cross-platform  To have a modular architecture for easier maintenance  To be developed completely as open source software  To be applicable to current trends in web development, such as client-side applications and deploying to cloud environments In order to achieve all these goals, Microsoft needed a platform that could provide underlying libraries for creating basic objects such as lists and dictionaries, and per- forming, for example, simple file operations. Up to this point, ASP.NET development had always been focused, and dependent, on the Windows-only .NET Framework. For ASP.NET Core, Microsoft created a lightweight platform that runs on Windows, Linux, and macOS called .NET Core, as shown in figure 1.2. .NET Core shares many of the same APIs as .NET Framework, but it’s smaller and currently only implements a subset of the features .NET Framework provides, with the goal of providing a simpler implementation and programming model. It’s a com- pletely new platform, rather than a fork of .NET Framework, though it uses similar code for many of its APIs. With .NET Core alone, it’s possible to build console applications that run cross- platform. Microsoft created ASP.NET Core to be an additional layer on top of console applications, such that converting to a web application involves adding and compos- ing libraries, as shown in figure 1.3.
  • 35.
    7 An introduction toASP.NET Core Figure 1.2 The relationship between ASP.NET Core, ASP.NET, .NET Core, and .NET Framework. ASP.NET Core runs on both .NET Framework and .NET Core, so it can run cross-platform. Conversely, ASP.NET runs on .NET Framework only, so is tied to the Windows OS. ASP.NET Core ASP.NET / ASP.NET MVC .NET Core .NET Framework Web ramework f .NET latform p Windows Linux macOS Windows Operating ystem s .NET Core runs on . multiple platforms .NET Framework runs . on Windows only ASP .NET Core runs on both .NET Core and . .NET Framework ASP .NET 4.5 runs on .NET Framework only. ASP.NET Core console application ASP.NET Core Kestrel web server Web application logic You write a .NET Core console app that starts up an instance . of an ASP .NET Core web server Configuration Logging Static files HTML generation Your web application logic is run by Kestrel. You’ll use various libraries to enable features such as logging and HTML generation as required. Microsoft provides, by default, a cross-platform web server called Kestrel. Figure 1.3 The ASP.NET Core application model. The .NET Core platform provides a base console application model for running command-line apps. Adding a web server library converts this into an ASP.NET Core web app. Additional features, such as configuration and logging, are added by way of additional libraries.
  • 36.
    8 CHAPTER 1Getting started with ASP.NET Core By adding an ASP.NET Core web server to your .NET Core app, your application can run as a web application. ASP.NET Core is composed of many small libraries that you can choose from to provide your application with different features. You’ll rarely need all the libraries available to you and you only add what you need. Some of the libraries are common and will appear in virtually every application you create, such as the ones for reading configuration files or performing logging. Other libraries build on top of these base capabilities to provide application-specific functionality, such as third-party logging-in via Facebook or Google. Most of the libraries you’ll use in ASP.NET Core can be found on GitHub, in the Microsoft ASP.NET Core organization repositories at https://github.com/aspnet. You can find the core libraries here, such as the Kestrel web server and logging libraries, as well as many more peripheral libraries, such as the third-party authentication libraries. All ASP.NET Core applications will follow a similar design for basic configuration, as suggested by the common libraries, but in general the framework is flexible, leaving you free to create your own code conventions. These common libraries, the extension libraries that build on them, and the design conventions they promote make up the somewhat nebulous term ASP.NET Core. 1.2 When to choose ASP.NET Core Hopefully, you now have a general grasp of what ASP.NET Core is and how it was designed. But the question remains: should you use it? Microsoft will be heavily pro- moting ASP.NET Core as its web framework of choice for the foreseeable future, but switching to or learning a new web stack is a big ask for any developer or company. This section describes some of the highlights of ASP.NET Core and gives advice on the sort of applications you should build with it, as well as the sort of applications you should avoid. 1.2.1 What type of applications can you build? ASP.NET Core provides a generalized web framework that can be used on a variety of applications. It can most obviously be used for building rich, dynamic websites, whether they’re e-commerce sites, content-based sites, or large n-tier applications— much the same as the previous version of ASP.NET. Currently, there’s a comparatively limited number of third-party libraries available for building these types of complex applications, but there are many under active development. Many developers are working to port their libraries to work with ASP.NET Core—it will take time for more to become available. For example, the open source content management system (CMS), Orchard1 (figure 1.4), is currently avail- able as a beta version of Orchard Core, running on ASP.NET Core and .NET Core. Traditional, server-side-rendered web applications are the bread and butter of ASP.NET development, both with the previous version of ASP.NET and ASP.NET 1 The Orchard project (www.orchardproject.net/). Source code at https://github.com/OrchardCMS/.
  • 37.
    9 When to chooseASP.NET Core Core. Additionally, single-page applications (SPAs), which use a client-side framework that commonly talks to a REST server, are easy to create with ASP.NET Core. Whether you’re using Angular, Ember, React, or some other client-side framework, it’s easy to create an ASP.NET Core application to act as the server-side API. DEFINITION REST stands for REpresentational State Transfer. RESTful appli- cations typically use lightweight and stateless HTTP calls to read, post (cre- ate/update), and delete data. ASP.NET Core isn’t restricted to creating RESTful services. It’s easy to create a web service or remote procedure call (RPC)-style service for your application, depending on your requirements, as shown in figure 1.5. In the simplest case, your application might expose only a single endpoint, narrowing its scope to become a microservice. ASP.NET Core is perfectly designed for building simple services thanks to its cross- platform support and lightweight design. You should consider multiple factors when choosing a platform, not all of which are technical. One example is the level of support you can expect to receive from its creators. For some organizations, this can be one of the main obstacles to adopting open source software. Luckily, Microsoft has pledged to provide full support for each Figure 1.4 The ASP.NET Community Blogs website (https://weblogs.asp.net) is built using the Orchard CMS. Orchard 2 is available as a beta version for ASP.NET Core development.
  • 38.
    10 CHAPTER 1Getting started with ASP.NET Core major and minor point release of the ASP.NET Core framework for three years2 . And as all development takes place in the open, you can sometimes get answers to your questions from the general community, as well as Microsoft directly. When deciding whether to use ASP.NET Core, you have two primary dimensions to consider: whether you’re already a .NET developer, and whether you’re creating a new application or looking to convert an existing one. 1.2.2 If you’re new to .NET development If you’re new to .NET development and are considering ASP.NET Core, then wel- come! Microsoft is pushing ASP.NET Core as an attractive option for web develop- ment beginners, but taking .NET cross-platform means it’s competing with many other frameworks on their own turf. ASP.NET Core has many selling points when compared to other cross-platform web frameworks:  It’s a modern, high-performance, open source web framework.  It uses familiar design patterns and paradigms. 2 View the support policy at www.microsoft.com/net/core/support. Figure 1.5 ASP.NET Core can act as the server-side application for a variety of different clients: it can serve HTML pages for traditional web applications, it can act as a REST API for client-side SPA applications, or it can act as an ad-hoc RPC service for client applications. Browser Traditional web application Server Client Synchronous request via HTTP Response: HTML web page SPA web application REST API Asynchronous request via HTTP Response: partial page data as JSON or XML Client application RPC service Synchronous or asynchronous request via HTTP Response: data as JSON, XML or binary
  • 39.
    11 When to chooseASP.NET Core  C# is a great language (or you can use VB.NET or F# if you prefer).  You can build and run on any platform. ASP.NET Core is a re-imagining of the ASP.NET framework, built with modern soft- ware design principles on top of the new .NET Core platform. Although new in one sense, .NET Core has drawn significantly from the mature, stable, and reliable .NET Framework, which has been used for well over a decade. You can rest easy knowing that by choosing ASP.NET Core and .NET Core, you’ll be getting a dependable plat- form as well as a fully-featured web framework. Many of the web frameworks available today use similar, well-established design patterns, and ASP.NET Core is no different. For example, Ruby on Rails is known for its use of the Model-View-Controller (MVC) pattern; Node.js is known for the way it processes requests using small discrete modules (called a pipeline); and dependency injection is found in a wide variety of frameworks. If these techniques are familiar to you, you should find it easy to transfer them across to ASP.NET Core; if they’re new to you, then you can look forward to using industry best practices! NOTE You’ll encounter MVC in chapter 4, a pipeline in chapter 3, and dependency injection in chapter 10. The primary language of .NET development, and ASP.NET Core in particular, is C#. This language has a huge following, and for good reason! As an object-oriented C- based language, it provides a sense of familiarity to those used to C, Java, and many other languages. In addition, it has many powerful features, such as Language Inte- grated Query (LINQ), closures, and asynchronous programming constructs. The C# language is also designed in the open on GitHub, as is Microsoft’s C# compiler, code- named Roslyn.3 NOTE I will use C# throughout this book and will highlight some of the newer features it provides, but I won’t be teaching the language from scratch. If you want to learn C#, I recommend C# in Depth by Jon Skeet (Manning, 2008). One of the major selling points of ASP.NET Core and .NET Core is the ability to develop and run on any platform. Whether you’re using a Mac, Windows, or Linux, you can run the same ASP.NET Core apps and develop across multiple environments. As a Linux user, a wide range of distributions are supported (RHEL, Ubuntu, Debian, CentOS, Fedora, and openSUSE, to name a few), so you can be confident your operating system of choice will be a viable option. Work is even underway to enable ASP.NET Core to run on the tiny Alpine distribution, for truly compact deployments to containers. 3 The C# language and .NET Compiler Platform GitHub source code repository can be found at https:// github.com/dotnet/roslyn.
  • 40.
    12 CHAPTER 1Getting started with ASP.NET Core As well as running on each platform, one of the selling points of .NET is the ability to write and compile only once. Your application is compiled to Intermediate Language (IL) code, which is a platform-independent format. If a target system has the .NET Core platform installed, then you can run compiled IL from any platform. That means you can, for example, develop on a Mac or a Windows machine and deploy the exact same files to your production Linux machines. This compile-once, run-anywhere promise has finally been realized with ASP.NET Core and .NET Core. 1.2.3 If you’re a .NET Framework developer creating a new application If you’re currently a .NET developer, then the choice of whether to invest in ASP.NET Core for new applications is a question of timing. Microsoft has pledged to provide continued support for the older ASP.NET framework, but it’s clear their focus is pri- marily on the newer ASP.NET Core framework. In the long term then, if you want to take advantage of new features and capabilities, it’s likely that ASP.NET Core will be the route to take. Whether ASP.NET Core is right for you largely depends on your requirements and your comfort with using products that are early in their lifecycle. The main benefits over the previous ASP.NET framework are  Cross-platform development and deployment  A focus on performance as a feature  A simplified hosting model  Regular releases with a shorter release cycle  Open source  Modular features Built with containers in mind Traditionally, web applications were deployed directly to a server, or more recently, to a virtual machine. Virtual machines allow operating systems to be installed in a layer of virtual hardware, abstracting away the underlying hardware. This has several advantages over direct installation, such as easy maintenance, deployment, and recovery. Unfortunately, they’re also heavy both in terms of file size and resource use. This is where containers come in. Containers are far more lightweight and don’t have the overhead of virtual machines. They’re built in a series of layers and don’t require you to boot a new operating system when starting a new one. That means they’re quick to start and are great for quick provisioning. Containers, and Docker in partic- ular, are quickly becoming the go-to platform for building large, scalable systems. Containers have never been a particularly attractive option for ASP.NET applications, but with ASP.NET Core, .NET Core, and Docker for Windows, that’s all changing. A lightweight ASP.NET Core application running on the cross-platform .NET Core frame- work is perfect for thin container deployments. You can learn more about your deploy- ment options in chapter 16.
  • 41.
    13 When to chooseASP.NET Core As a .NET developer, if you aren’t using any Windows-specific constructs, such as the Registry, then the ability to build and deploy applications cross-platform opens the door to a whole new avenue of applications: take advantage of cheaper Linux VM hosting in the cloud, use Docker containers for repeatable continuous integration, or write .NET code on your Mac without needing to run a Windows virtual machine. ASP.NET Core, in combination with .NET Core, makes all of this possible. It’s important to be aware of the limitations of cross-platform applications—not all the .NET Framework APIs are available in .NET Core. It’s likely that most of the APIs you need will make their way to .NET Core over time, but it’s an important point to be aware of. See the “Choosing a platform for ASP.NET Core” section later in this chap- ter to determine if cross-platform is a viable option for your application. NOTE With the release of .NET Core 2.0 in August 2017, the number of APIs available dramatically increased, more than doubling the API surface area. The hosting model for the previous ASP.NET framework was a relatively complex one, relying on Windows IIS to provide the web server hosting. In a cross-platform environ- ment, this kind of symbiotic relationship isn’t possible, so an alternative hosting model has been adopted, one which separates web applications from the underlying host. This opportunity has led to the development of Kestrel: a fast, cross-platform HTTP server on which ASP.NET Core can run. Instead of the previous design, whereby IIS calls into specific points of your appli- cation, ASP.NET Core applications are console applications that self-host a web server and handle requests directly, as shown in figure 1.6. This hosting model is conceptu- ally much simpler and allows you to test and debug your applications from the com- mand line, though it doesn’t remove the need to run IIS (or equivalent) in production, as you’ll see in section 1.3. Changing the hosting model to use a built-in HTTP web server has created another opportunity. Performance has been somewhat of a sore point for ASP.NET applications in the past. It’s certainly possible to build high-performing applications— Stack Overflow (http://stackoverflow.com) is testament to that—but the web frame- work itself isn’t designed with performance as a priority, so it can end up being some- what of an obstacle. To be competitive cross-platform, the ASP.NET team have focused on making the Kestrel HTTP server as fast as possible. TechEmpower (www.techempower.com/ benchmarks) has been running benchmarks on a whole range of web frameworks from various languages for several years now. In Round 13 of the plain text bench- marks, TechEmpower announced that ASP.NET Core with Kestrel was the fastest mainstream full-stack web framework, in the top ten of all frameworks!4 4 As always in web development, technology is in a constant state of flux, so these benchmarks will evolve over time. Although ASP.NET Core may not maintain its top ten slot, you can be sure that performance is one of the key focal points of the ASP.NET Core team.
  • 42.
    14 CHAPTER 1Getting started with ASP.NET Core ASP.NET application A request is sent to IIS. Request Response IIS calls into specific methods in the ASP .NET . application Control transfers back and forth between IIS and ASP .NET as . events are raised Application_BeginRequest() {} Application_AuthenticateRequest(){} Application_AuthorizeRequest(){} Application_ProcessRequest() {} Application_EndRequest() {} Application_HandleError() {} IIS ASP.NET Core application A request is sent to IIS. Request Response IIS passes raw request . to Kestrel web server Kestrel processes the incoming request and passes it to the rest of . the application IIS / Apache / Nginx ASP.NET Core Kestrel web server Application handles request and generates response Figure 1.6 The difference between hosting models in ASP.NET (top) and ASP.NET Core (bottom). With the previous version of ASP.NET, IIS is tightly coupled with the application. The hosting model in ASP.NET Core is simpler; IIS hands off the request to a self-hosted web server in the ASP.NET Core application and receives the response, but has no deeper knowledge of the application. Web servers: naming things is hard One of the difficult aspects of programing for the web is the confusing array of often conflicting terminology. For example, if you’ve used IIS in the past, you may have described it as a web server, or possibly a web host. Conversely, if you’ve ever built an application using Node.js, you may have also referred to that application as a web server. Alternatively, you may have called the physical machine on which your appli- cation runs a web server! Similarly, you may have built an application for the internet and called it a website or a web application, probably somewhat arbitrarily based on the level of dynamism it displayed.
  • 43.
    15 When to chooseASP.NET Core Many of the performance improvements made to Kestrel did not come from the ASP.NET team themselves, but from contributors to the open source project on GitHub.5 Developing in the open means you typically see fixes and features make their way to production faster than you would for the previous version of ASP.NET, which was dependent on .NET Framework and, as such, had long release cycles. In contrast, ASP.NET Core is completely decoupled from the underlying .NET platform. The entire web framework is implemented as NuGet packages, independent of the underlying platform on which it builds. NOTE NuGet is a package manager for .NET that enables importing libraries into your projects. It’s equivalent to Ruby Gems, npm for JavaScript, or Maven for Java. To enable this, ASP.NET Core was designed to be highly modular, with as little cou- pling to other features as possible. This modularity lends itself to a pay-for-play approach to dependencies, where you start from a bare-bones application and only add the additional libraries you require, as opposed to the kitchen-sink approach of previous ASP.NET applications. Even MVC is an optional package! But don’t worry, this approach doesn’t mean that ASP.NET Core is lacking in features; it means you need to opt in to them. Some of the key infrastructure improvements include  Middleware “pipeline” for defining your application’s behavior  Built-in support for dependency injection  Combined UI (MVC) and API (Web API) infrastructure  Highly extensible configuration system  Scalable for cloud platforms by default using asynchronous programming 5 The Kestrel HTTP server GitHub project can be found at https://github.com/aspnet/KestrelHttpServer. In this book, when I say “web server” in the context of ASP.NET Core, I am referring to the HTTP server that runs as part of your ASP.NET Core application. By default, this is the Kestrel web server, but that’s not a requirement. It would be possible to write a replacement web server and substitute if for Kestrel if you desired. The web server is responsible for receiving HTTP requests and generating responses. In the previous version of ASP.NET, IIS took this role, but in ASP.NET Core, Kestrel is the web server. I will only use the term web application to describe ASP.NET Core applications in this book, regardless of whether they contain only static content or are completely dynamic. Either way, they’re applications that are accessed via the web, so that name seems the most appropriate!
  • 44.
    16 CHAPTER 1Getting started with ASP.NET Core Each of these features was possible in the previous version of ASP.NET but required a fair amount of additional work to set up. With ASP.NET Core, they’re all there, ready, and waiting to be connected! Microsoft fully supports ASP.NET Core, so if you have a new system you want to build, then there’s no significant reason not to. The largest obstacle you’re likely to come across is a third-party library holding you back, either because they only support older ASP.NET features, or they haven’t been converted to work with .NET Core yet. Hopefully, this section has whetted your appetite with some of the many reasons to use ASP.NET Core for building new applications. But if you’re an existing ASP.NET developer considering whether to convert an existing ASP.NET application to ASP.NET Core, that’s another question entirely. 1.2.4 Converting an existing ASP.NET application to ASP.NET Core In contrast with new applications, an existing application is presumably already pro- viding value, so there should always be a tangible benefit to performing what may amount to a significant rewrite in converting from ASP.NET to ASP.NET Core. The advantages of adopting ASP.NET Core are much the same as for new applications: cross-platform deployment, modular features, and a focus on performance. Determin- ing whether or not the benefits are sufficient will depend largely on the particulars of your application, but there are some characteristics that are clear indicators against conversion:  Your application uses ASP.NET Web Forms  Your application is built using WCF or SignalR  Your application is large, with many “advanced” MVC features If you have an ASP.NET Web Forms application, then attempting to convert it to ASP.NET Core isn’t advisable. Web Forms is inextricably tied to System.Web.dll, and as such will likely never be available in ASP.NET Core. Converting an application to ASP.NET Core would effectively involve rewriting the application from scratch, not only shifting frameworks but also shifting design paradigms. A better approach would be to slowly introduce Web API concepts and try to reduce the reliance on legacy Web Forms constructs such as ViewData. You can find many resources online to help you with this approach, in particular, the www.asp.net/web-api website. Similarly, if your application makes heavy use of SignalR, then now may not be the time to consider an upgrade. ASP.NET Core SignalR is under active development but has only been released in alpha form at the time of writing. It also has some significant architectural changes compared to the previous version, which you should take into account. Windows Communication Foundation (WCF) is currently not supported either, but it’s possible to consume WCF services by jumping through some slightly obscure hoops. Currently, there’s no way to host a WCF service from an ASP.NET Core applica- tion, so if you need the features WCF provides and can’t use a more conventional REST service, then ASP.NET Core is probably best avoided.
  • 45.
    17 How does ASP.NETCore work? If your application was complex and made use of the previous MVC or Web API extensibility points or message handlers, then porting your application to ASP.NET Core could be complex. ASP.NET Core is built with many similar features to the previ- ous version of ASP.NET MVC, but the underlying architecture is different. Several of the previous features don’t have direct replacements, and so will require rethinking. The larger the application, the greater the difficulty you’re likely to have convert- ing your application to ASP.NET Core. Microsoft itself suggests that porting an appli- cation from ASP.NET MVC to ASP.NET Core is at least as big a rewrite as porting from ASP.NET Web Forms to ASP.NET MVC. If that doesn’t scare you, then nothing will! So, when should you port an application to ASP.NET Core? As I’ve already men- tioned, the best opportunity for getting started is on small, green-field, new projects instead of existing applications. That said, if the application in question is small, with little custom behavior, then porting might be a viable option. Small implies reduced risk and probably reduced complexity. If your application consists primarily of MVC or Web API controllers and associated Razor views, then moving to ASP.NET Core may be feasible. 1.3 How does ASP.NET Core work? By now, you should have a good idea of what ASP.NET Core is and the sort of applica- tions you should use it for. In this section, you’ll see how an application built with ASP.NET Core works, from the user requesting a URL, to a page being displayed on the browser. To get there, first you’ll see how an HTTP request works for any web server, and then you’ll see how ASP.NET Core extends the process to create dynamic web pages. 1.3.1 How does an HTTP web request work? As you know, ASP.NET Core is a framework for building web applications that serve data from a server. One of the most common scenarios for web developers is building a web app that you can view in a web browser. The high-level process you can expect from any web server is shown in figure 1.7. The process begins when a user navigates to a website or types a URL in their browser. The URL or web address consists of a hostname and a path to some resource on the web app. Navigating to the address in your browser sends a request from the user’s computer to the server on which the web app is hosted, using the HTTP protocol. DEFINITION The hostname of a website uniquely identifies its location on the internet by mapping via the Domain Name Service (DNS) to an IP Address. Examples include microsoft.com, www.google.co.uk, and facebook.com. The request passes through the internet, potentially to the other side of the world, until it finally makes its way to the server associated with the given hostname on which the web app is running. The request is potentially received and rebroadcast at multi- ple routers along the way, but it’s only when it reaches the server associated with the hostname that the request is processed.
  • 46.
    18 CHAPTER 1Getting started with ASP.NET Core Once the server receives the request, it will check that it makes sense, and if it does, will generate an HTTP response. Depending on the request, this response could be a web page, an image, a JavaScript file, or a simple acknowledgment. For this example, I’ll assume the user has reached the homepage of a web app, and so the server responds with some HTML. The HTML is added to the HTTP response, which is then sent back across the internet to the browser that made the request. As soon as the user’s browser begins receiving the HTTP response, it can start dis- playing content on the screen, but the HTML page may also reference other pages and links on the server. To display the complete web page, instead of a static, colorless, http://thewebsite.com/the/page.html 1. User requests a web page by a URL. http://thewebsite.com/the/page.html Welcome to the web page! 5. Browser renders HTML on page. 4. Server sends HTML in HTTP response back to browser. HTTP response 3. Server interprets request and generates appropriate HTML. 2. Browser sends HTTP request to server. HTTP request <HTML> <HEAD></HEAD <BODY></BODY> </HTML> Figure 1.7 Requesting a web page. The user starts by requesting a web page, which causes an HTTP request to be sent to the server. The server interprets the request, generates the necessary HTML, and sends it back in an HTTP response. The browser can then display the web page.
  • 47.
    19 How does ASP.NETCore work? raw HTML file, the browser must repeat the request process, fetching every refer- enced file. HTML, images, CSS for styling, and JavaScript files for extra behavior are all fetched using the exact same HTTP request process. Pretty much all interactions that take place on the internet are a facade over this same basic process. A basic web page may only require a few simple requests to fully render, whereas a modern, large web page may take hundreds. The Amazon.com homepage (www.amazon.com), for example, currently makes 298 requests, including 6 CSS files, 14 JavaScript files, and 245 image files! Now you have a feel for the process, let’s see how ASP.NET Core dynamically gen- erates the response on the server. 1.3.2 How does ASP.NET Core process a request? When you build a web application with ASP.NET Core, browsers will still be using the same HTTP protocol as before to communicate with your application. ASP.NET Core itself encompasses everything that takes place on the server to handle a request, including verifying the request is valid, handling login details, and generating HTML. Just as with the generic web page example, the request process starts when a user’s browser sends an HTTP request to the server, as shown in figure 1.8. A reverse-proxy server captures the request, before passing it to your application. In Windows, the reverse-proxy server will typically be IIS, and on Linux or macOS it might be NGINX or Apache. DEFINITION A reverse proxy is software responsible for receiving requests and forwarding them to the appropriate web server. The reverse proxy is exposed directly to the internet, whereas the underlying web server is exposed only to the proxy. This setup has several benefits, primarily security and performance for the web servers. The request is forwarded from the reverse proxy to your ASP.NET Core application. Every ASP.NET Core application has a built-in web server, Kestrel by default, which is responsible for receiving raw requests and constructing an internal representation of the data, an HttpContext object, which can be used by the rest of the application. From this representation, your application should have all the details it needs to create an appropriate response to the request. It can use the details stored in Http- Context to generate an appropriate response, which may be to generate some HTML, to return an “access denied” message, or to send an email, all depending on your application’s requirements. Once the application has finished processing the request, it will return the response to the web server. The ASP.NET Core web server will convert the representa- tion into a raw HTTP response and send it back to the reverse proxy, which will for- ward it to the user’s browser.
  • 48.
    20 CHAPTER 1Getting started with ASP.NET Core To the user, this process appears to be the same as for the generic HTTP request shown in figure 1.7—the user sent an HTTP request and received an HTTP response. All the differences are server-side, within our application. You may be thinking that having a reverse proxy and a web server is somewhat redundant. Why not have one or the other? Well, one of the benefits is the decoupling of your application from the underlying operating system. The same ASP.NET Core web server, Kestrel, can be cross-platform and used behind a variety of proxies without putting any constraints on a particular implementation. Alternatively, if you wrote a new ASP.NET Core web server, you could use that in place of Kestrel without needing to change anything else about your application. Another benefit of a reverse proxy is that it can be hardened against potential threats from the public internet. They’re often responsible for additional aspects, ASP.NET Core infrastructure and application logic Reverse proxy (IIS/NGINX/Apache) ASP.NET Core web server (Kestrel) 1. HTTP request is made to the server and is received by the reverse proxy. 7. HTTP response is sent to browser. 2. Request is forwarded by IIS/ NGINX/Apache to ASP.NETCore. 3. ASP .NET Core web server receives the HTTP request and passes it to the middleware. Request Response 5. Response passes through middleware back to web server. 4. Request is processed by the application, which generates a response. ASP.NET Core application 6. Web server forwards response to reverse proxy. Figure 1.8 How an ASP.NET Core application processes a request. A request is received from a browser at the reverse proxy, which passes the request to the ASP.NET Core application, which runs a self-hosted web server. The web server processes the request and passes it to the body of the application, which generates a response and returns it to the web server. The web server relays this to the reverse proxy, which sends the response to the browser.
  • 49.
    21 Choosing a platformfor ASP.NET Core such as restarting a process that has crashed. Kestrel can stay as a simple HTTP server without having to worry about these extra features when it’s used behind a reverse proxy. Think of it as a simple separation of concerns: Kestrel is concerned with gener- ating HTTP responses; a reverse proxy is concerned with handling the connection to the internet. You’ve seen how requests and responses find their way to and from an ASP.NET Core application, but I haven’t yet touched on how the response is generated. In part 1 of this book, we’ll look at the components that make up a typical ASP.NET Core appli- cation and how they all fit together. A lot goes into generating a response in ASP.NET Core, typically all within a fraction of a second, but over the course of the book we’ll step through an application slowly, covering each of the components in detail. Before we dive in, you need to choose an underlying platform for your first ASP.NET Core application and set up a development environment in which to build it. 1.4 Choosing a platform for ASP.NET Core ASP.NET Core was developed along with .NET Core and is often mentioned in the same breath, so it can be easy to forget that ASP.NET Core is platform-agnostic. You can build and run an ASP.NET Core application on both .NET Core or .NET Frame- work. The same features will be available in both cases, so why would you choose one over the over? Which route is right for you depends on both your history and the application you’re looking to build, so in this section I’ve highlighted some advan- tages and disadvantages to consider. 1.4.1 Advantages of using .NET Framework One of the most significant advantages of the full .NET framework is its maturity—it has been developed for 16 years, has been battle-hardened, and extensively deployed. For some, this maturity will be a significant deciding factor. It will already be installed on your servers and building an ASP.NET Core on top involves (relatively) little risk to your existing environment. For others, particularly existing ASP.NET developers, the cross-platform and container-friendly .NET Core won’t hold any appeal. These developers will, by neces- sity, be used to deploying to Windows servers, and it’s perfectly reasonable to want to continue to do so, while still taking advantage of all ASP.NET Core has to offer. The biggest reason to stick with the full .NET Framework when .NET Core was first released was because you needed to make use of Windows-specific features, such as the Registry or Directory Services. Microsoft have since released a compatibility pack6 that makes these APIs available in .NET Core, but they’re only available when running .NET Core on Windows, not on Linux or macOS. If you know your app relies on many Windows-only features, then .NET Framework may be the easiest option. 6 The Windows Compatibility Pack is designed to help port code from .NET Framework to .NET Core. See http://mng.bz/50hu.
  • 50.
    22 CHAPTER 1Getting started with ASP.NET Core WARNING If you choose to run on .NET Framework only, you won’t be able to easily run your application cross-platform. One advantage of using .NET Framework is that it has the greatest library support, in the form of NuGet packages. Library authors are being encouraged to make their libraries work identically on both .NET Framework and .NET Core by targeting .NET Standard, but that transition is a slow process. .NET Standard7 defines the APIs that are available on a given .NET platform. It’s made up of multiple versions (for example, 1.1 and 1.2), each of which adds addi- tional APIs compared to previous versions. Think of it as an “interface” for various .NET frameworks; the frameworks (such as .NET Core, .NET Framework, and Mono) all “implement” a version of .NET Standard. TIP You can create a new type of library that targets .NET Standard instead of targeting a specific framework. That allows you to use your library across multiple platforms, including .NET Core and .NET Framework. See appendix A for further details. .NET Standard 2.0 vastly increases the number of APIs available to libraries that target it, covering almost the same area as .NET Framework 4.6.1. At the time of writing, 56% of packages on NuGet.org target the full framework rather than .NET Standard, so if your application currently relies on one of those packages, you’ll have to choose .NET Framework for your ASP.NET Core application. TIP .NET Standard 2.0 contains a compatibility shim that allows you to refer- ence .NET Framework 4.6.1 libraries from a .NET Standard library. For details, see http://mng.bz/jH8Y and appendix A. 1.4.2 Advantages of using .NET Core If you’re considering ASP.NET Core for a project, the chances are you’re also inter- ested in the associated features .NET Core brings, such as the cross-platform capabili- ties. If that’s the case, then those features are obvious reasons to choose .NET Core as the underlying platform to use with ASP.NET Core. The open source nature of .NET Core development can be a big deciding factor for some people. Open source development means you can clearly see how features and bugs are being addressed. If there’s a particular feature you feel strongly about or a bug that’s plaguing you, you can always submit a pull-request and see your code in the .NET Core platform! Related to this, and the highly modular design of .NET Core, it’s likely that the plat- form will see a faster release cycle than other platforms. Updates to .NET Framework require a massive amount of regression testing to ensure there are no subtle interac- tions that could break old applications. In contrast, installations of .NET Core are 7 The .NET Standard GitHub repository can be found at https://github.com/dotnet/standard/blob/master/ docs/faq.md.
  • 51.
    23 Preparing your developmentenvironment independent of one another, so you can install multiple versions of .NET Core side-by- side. .NET Core also follows semantic versioning (SemVer), so you can be sure that your old applications won’t be affected by installing a new version of the framework. WARNING Be aware that the faster release cycle generally means larger changes between .NET Core versions when you update your apps. For exam- ple, upgrading from .NET Core 1.0 to 2.0 is a significant and potentially breaking change. Which platform you choose will depend on your use case. The full .NET Framework is still supported, and is being actively developed, but it’s clear the focus of Microsoft is with .NET Core right now. If you’re starting a new application from scratch and the libraries you require have been updated to use .NET Standard, then .NET Core seems to make the most logical choice for the future. One final option is to multitarget your application, allowing it to run on both .NET Core and .NET Framework. This requires a little more effort to set up and maintain in terms of dependency wrangling, but it’s a viable option if you’re going to need to run in both environments. In this book, I’m going to be targeting the .NET Core plat- form, but all the examples should work equally with .NET Framework without any modification. Once you’ve selected a platform for your ASP.NET Core applications, it’s time to prepare your development environment—the last step before you build your first ASP.NET Core application! 1.5 Preparing your development environment For .NET developers in a Windows-centric world, Visual Studio was pretty much a developer requirement in the past. But with .NET Core and ASP.NET Core going cross-platform, that’s no longer the case. All of ASP.NET Core (creating new projects, building, testing, and publishing) can be run from the command line for any supported operating system. All you need is the .NET Core SDK and tooling, which provides the .NET Command Line Interface (CLI). Alternatively, if you’re on Windows, and not comfortable with the command line, you can still use File > New Project in Visual Studio to dive straight in. With ASP.NET Core, it’s all about choice! In a similar vein, you can now get a great editing experience outside of Visual Stu- dio thanks to the OmniSharp project.8 This is a set of libraries and editor plugins that provide code suggestions and autocomplete (IntelliSense) across a wide range of edi- tors and operating systems. How you setup your environment will likely depend on which operating system you’re using and what you’re used to. Remember that, if you’re using .NET Core, the operating system you choose for development has no bearing on the final systems you can run on—whether you 8 Information about the OmniSharp project can be found at www.omnisharp.net. Source code can be found at https://github.com/omnisharp.
  • 52.
    24 CHAPTER 1Getting started with ASP.NET Core choose Windows, macOS, or Linux for development, you can deploy to any supported system. 1.5.1 If you’re a Windows user For a long time, Windows has been the best system for building .NET applications, and with the availability of Visual Studio that’s still the case. Visual Studio (figure 1.9) is a full-featured integrated development environment (IDE), which provides one of the best all-around experiences for developing ASP.NET Core applications. Luckily, the Visual Studio Community edition is now free for open source, students, and small teams of developers! Visual studio comes loaded with a whole host of templates for building new projects, debugging, and publishing, with- out ever needing to touch a command prompt. Sometimes, though, you don’t want a full-fledged IDE. Maybe you want to quickly view or edit a file, or you don’t like the sometimes unpredictable performance of Visual Studio. In those cases, a simple editor may be all you want or need, and Visual Studio Code is a great choice. Visual Studio Code (figure 1.10) is an open source, lightweight editor that provides editing, IntelliSense, and debugging for a wide range of lan- guages, including C# and ASP.NET Core. Figure 1.9 Visual Studio provides the most complete ASP.NET Core development environment for Windows users.
  • 53.
    25 Preparing your developmentenvironment Whether you install Visual Studio or another editor, such as Visual Studio Code, you’ll need to install the .NET Core tooling to start building ASP.NET Core apps. You can either download it from the ASP.NET website (https://get.asp.net) or select the .NET Core cross-platform development workload during Visual Studio 2017 installation. 1.5.2 If you’re a Linux or macOS user As a Linux or macOS user, you have a whole host of choices. OmniSharp has plugins for most popular editors, such as Vim, Emacs, Sublime, Atom, and Brackets, not to mention the cross-platform Visual Studio Code. Install the appropriate plugin to your favorite and you’ll be writing C# in no time. Again, you’ll need to install the .NET Core SDK from the ASP.NET website (https://get.asp.net) to begin .NET Core and ASP.NET Core development. This will give you the .NET Core runtime and the .NET CLI to start building ASP.NET Core applications. The .NET CLI contains everything you need to get started, including several pro- ject templates. You don’t get a huge number to choose from by default, but you can Figure 1.10 Visual Studio Code provides cross-platform IntelliSense and debugging.
  • 54.
    26 CHAPTER 1Getting started with ASP.NET Core install new ones from GitHub or NuGet if you want more variety. You can easily create applications from the predefined templates to quick-start your development, as shown in figure 1.11. In addition, in May 2017, Microsoft released Visual Studio for Mac. With VS for Mac you can build cross ASP.NET Core apps, using a similar editor experience to Visual Studio, but on an app designed natively for macOS. VS for Mac is still young, but if you’re a macOS user, then it’s a great choice and will no doubt see many updates. In this book, I’ll be using Visual Studio for most of the examples, but you’ll be able to follow along using any of the tools I’ve mentioned. The rest of the book assumes you’ve successfully installed .NET Core and an editor on your computer. You’ve reached the end of this chapter; whether you’re new to .NET or an exist- ing .NET developer, there’s a lot to take in—frameworks, platforms, .NET Framework (which is a platform!). But take heart: you now have all the background you need and, hopefully, a development environment to start building applications using ASP.NET Core. Figure 1.11 The .NET CLI includes several templates by default, as shown here. You can also install additional templates or create your own.
  • 55.
    27 Summary In the nextchapter, you’ll create your first application from a template and run it. We’ll walk through each of the main components that make up your application and see how they all work together to render a web page. Summary  ASP.NET Core is a new web framework built with modern software architecture practices and modularization as its focus.  It’s best used for new, “green-field” projects with few external dependencies.  Existing technologies such as WCF and SignalR can’t currently be used with ASP.NET Core, but work is underway to integrate them.  Fetching a web page involves sending an HTTP request and receiving an HTTP response.  ASP.NET Core allows dynamically building responses to a given request.  An ASP.NET Core application contains a web server, which serves as the entry- point for a request.  ASP.NET Core apps are protected from the internet by a reverse-proxy server, which forwards requests to the application.  ASP.NET Core can run on both .NET Framework and .NET Core. If you need Windows-specific features such as the Windows Registry, you should use .NET Framework, but you won’t be able to run cross-platform. Otherwise, choose .NET Core for the greatest reach and hosting options.  The OmniSharp project provides C# editing plugins for many popular editors, including the cross-platform Visual Studio Code editor.  On Windows, Visual Studio provides the most complete all-in-one ASP.NET Core development experience, but development using the command line and an editor is as easy as on other platforms.
  • 56.
    28 Your first application Afterreading chapter 1, you should have a general idea of how ASP.NET Core applications work and when you should use them. You should have also set up a development environment to start building applications. In this chapter, you’ll dive right in by creating your first web app. You’ll get to kick the tires and poke around a little to get a feel for how it works, and in later chapters, I’ll show how you go about customizing and building your own applications. As you work through this chapter, you should begin to get a grasp of the various components that make up an ASP.NET Core application, as well as an understand- ing of the general application-building process. Most applications you create will start from a similar template, so it’s a good idea to get familiar with the setup as soon as possible. This chapter covers  Creating your first ASP.NET Core web application  Running your application  Understanding the components of your application
  • 57.
    29 A brief overviewof an ASP.NET Core application DEFINITION A template provides the basic code required to build an application. You can use a template as the starting point for building your own apps. I’ll start by showing how to create a basic ASP.NET Core application using one of the Visual Studio templates. If you’re using other tooling, such as the .NET CLI, then you’ll have similar templates available. I use Visual Studio 2017 and ASP.NET Core 2.0 in this chapter, but I also provide tips for working with the .NET CLI. TIP You can view the application code for this chapter in the GitHub repository for the book at https://github.com/andrewlock/asp-dot-net-core-in-action. Once you’ve created your application, I’ll show you how to restore all the necessary dependencies, compile your application, and run it to see the HTML output. The application will be simple in some respects—it will only have three different pages— but it’ll be a fully configured ASP.NET Core application. Having run your application, the next step is to understand what’s going on! We’ll take a journey through all the major parts of an ASP.NET Core application, looking at how to configure the web server, the middleware pipeline, and HTML generation, among other things. We won’t go into detail at this stage, but you’ll get a feel for how they all work together to create a complete application. We’ll begin by looking at the plethora of files created when you start a new project and learn how a typical ASP.NET Core application is laid out. In particular, I’ll focus on the Program.cs and Startup.cs files. Virtually the entire configuration of your appli- cation takes place in these two files, so it’s good to get to grips with what they are for and how they’re used. You’ll see how to define the middleware pipeline for your appli- cation, and how you can customize it. Finally, you’ll see how the app generates HTML in response to a request, looking at each of the components that make up the MVC middleware. You’ll see how it con- trols what code is run in response to a request, and how to define the HTML that should be returned for a particular request. At this stage, don’t worry if you find parts of the project confusing or complicated; you’ll be exploring each section in detail as you move through the book. By the end of the chapter, you should have a basic understanding of how ASP.NET Core applica- tions are put together, right from when your application is first run to when a response is generated. Before we begin though, we’ll review how ASP.NET Core appli- cations handle requests. 2.1 A brief overview of an ASP.NET Core application In chapter 1, I described how a browser makes an HTTP request to a server and receives a response, which it uses to render HTML on the page. ASP.NET Core allows you to dynamically generate that HTML depending on the particulars of the request so that you can, for example, display different data depending on the current logged-in user.
  • 58.
    30 CHAPTER 2Your first application Say you want to create a web app to display information about your company. You could create a simple ASP.NET Core app to achieve this, especially if you might later want to add dynamic features to your app. Figure 2.1 shows how the application would handle a request for a page in your application. Much of this diagram should be familiar to you from figure 1.8 in chapter 1; the request and response, the reverse proxy, and the ASP.NET Core web server are all still there, but you’ll notice that I’ve expanded the ASP.NET Core application itself to show the middleware pipeline and the MVC middleware. This is the main custom part of your app that goes into generating the response from a request. Middleware pipeline Web host/reverse proxy (IIS/NGINX/Apache) ASP.NET Core web server (Kestrel) 1. An HTTP request is made to the server to the homepage. 7. The HTML response is sent to browser. 2. Request is forwarded by IIS/NGINX/Apache to your ASP .NET Core app. 3. The ASP .NET Core web server receives the HTTP request and passes it to middleware. Request Response 6. Response passes through the middleware back to the web server. 4. The middleware processes the request and passes it on to the MVC middleware. ASP.NET Core application MVC middleware 5. MVC middleware generates an HTML response. Figure 2.1 An overview of an ASP.NET Core application. The ASP.NET Core application contains a number of blocks that process an incoming request from the browser. Every request passes to the middleware pipeline. It potentially modifies it, then passes it to the MVC middleware at the end of the pipeline to generate a response. The response passes back through the middleware, to the server, and finally, out to the browser.
  • 59.
    31 A brief overviewof an ASP.NET Core application The first port of call after the reverse proxy forwards a request is the ASP.NET Core web server, which is the default cross-platform Kestrel server. Kestrel takes the raw incoming request and uses it to generate an HttpContext object the rest of the application can use. NOTE Kestrel isn’t the only HTTP server available in ASP.NET Core, but it’s the most performant and is cross-platform. I’ll only refer to Kestrel through- out the book. The main alternative, HTTP.sys, only runs on Windows and can’t be used with IIS.1 Kestrel is responsible for receiving the request data and constructing a C# representa- tion of the request, but it doesn’t attempt to handle the request directly. For that, Kes- trel hands the HttpContext to the middleware pipeline found in every ASP.NET Core application. This is a series of components that processes the incoming request to per- form common operations such as logging, handling exceptions, or serving static files. NOTE You’ll learn about the middleware pipeline in detail in the next chapter. After the middleware pipeline comes the MVC block. This is responsible for generat- ing the HTML that makes up the pages of a typical ASP.NET Core web app. It’s also typically where you find most of the business logic of your app, by calling out to vari- ous services in response to the data contained in the original request. Not every app needs an MVC block, but it’s typically how you’ll build most apps that display HTML to a user. NOTE I’ll cover MVC controllers in chapter 4 and generating HTML in chap- ters 7 and 8. 1 If you want to learn more about HTTP.sys, the documentation describes the server and how to use it: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/httpsys. The HttpContext object The HttpContext constructed by the ASP.NET Core web server is used by the appli- cation as a sort of storage box for a single request. Anything that’s specific to this particular request and the subsequent response can be associated with it and stored in it. This could include properties of the request, request-specific services, data that’s been loaded, or errors that have occurred. The web server fills the initial Http- Context with details of the original HTTP request and other configuration details and passes it on to the rest of the application.
  • 60.
    32 CHAPTER 2Your first application Most ASP.NET Core applications follow this basic architecture, and the example in this chapter is no different. First, you’ll see how to create and run your application, then we’ll look at how the code corresponds to the outline in figure 2.1. Without fur- ther ado, let’s create an application! 2.2 Creating your first ASP.NET Core application You can start building applications with ASP.NET Core in many different ways, depending on the tools and operating system you’re using. Each set of tools will have slightly different templates, but they have many similarities. The example used throughout this chapter is based on a Visual Studio 2017 template, but you can easily follow along with templates from the .NET CLI or Visual Studio for Mac. REMINDER This chapter uses Visual Studio 2017 and ASP.NET Core 2.0. Getting an application up and running typically follows four basic steps, which we’ll work through in this chapter: 1 Generate—Create the base application from a template to get started. 2 Restore—Restore all the packages and dependencies to the local project folder using NuGet. 3 Build—Compile the application and generate all the necessary assets. 4 Run—Run the compiled application. 2.2.1 Using a template to get started Using a template can quickly get you up and running with an application, automati- cally configuring many of the fundamental pieces for you. Both Visual Studio and the .NET CLI come with a number of standard templates for building web applications, console applications, and class libraries. To create your first web application, open Visual Studio and perform the following steps: 1 Choose File > New > Project. 2 From the Templates node on the left, choose .NET Core, and then select ASP.NET Core Web Application. 3 Enter a Name, Location, and a solution name (optional) and click OK, as shown in figure 2.2. 4 On the following screen (figure 2.3):  Select ASP.NET Core 2.0. The generated application will target ASP.NET Core 2.0.  Select Web Application (Model-View-Controller). This ensures you create a traditional web application that generates HTML and is designed to be viewed by users in a web browser directly. The other web application template uses the
  • 61.
    33 Creating your firstASP.NET Core application new Razor Pages2 functionality of ASP.NET Core 2.0. The Web API template generates an application that returns data in a format that can be consumed by single-page applications (SPAs) and APIs. The Angular, React.js, and React.js and Redux templates create applications for specific SPAs.  Ensure No Authentication is specified. You’ll learn how to add users to your app in chapter 14.  Ensure Enable Docker Support is unchecked.  Click OK. 2 Razor Pages is a page-based alternative framework to the traditional MVC approach. It was introduced in ASP .NET Core 2.0. You can read about it here: https://docs.microsoft.com/en-us/aspnet/core/mvc/razor-pages/. Select ASP .NET Core Web Application. Click OK to select the template and create your project. Choose .NET Core from templates menu. Enter a name and location for your project and solution. Figure 2.2 The new project dialog. To create a new .NET Core application, select ASP.NET Core Web Application from the .NET Core templates. Enter a name, location, and a solution name (optional) and click OK.
  • 62.
    34 CHAPTER 2Your first application 5 Wait for Visual Studio to generate the application from the template. Once Visual Studio has finished generating the application, you’ll be presented with an introductory page about ASP.NET Core, and you should be able to see that Visual Studio has created and added a number of files to your project, as shown in figure 2.4. NOTE If you’re developing using the .NET CLI, you can create a similar application by running dotnet new mvc –o WebApplication2 from the com- mand line. The -o argument ensures the CLI creates the template in a sub- folder called WebApplication2. Ensure the authentication scheme is set to No Authentication. Select ASP .NET Core 2.0. Select Web Application (Model-View-Controller). Click OK to generate the application from the selected template. Uncheck Enable Docker Support. Figure 2.3 The web application template screen. This screen follows on from the New Project dialog and lets you customize the template that will generate your application. For this starter project, you’ll create an MVC web application without authentication.
  • 63.
    35 Creating your firstASP.NET Core application 2.2.2 Building the application At this point, you have most of the files necessary to run your application, but you’ve got two steps left. First, you need to ensure all the dependencies used by your project are copied to your local directory, and second, you need to compile your application so that it can be run. The first of these steps isn’t strictly necessary, as both Visual Studio and the .NET CLI automatically restore packages when they first create your project, but it’s good to know what’s going on. In earlier versions of the .NET CLI, before 2.0, you needed to manually restore packages using dotnet restore. You can compile your application by choosing Build > Build Solution, by using the shortcut Ctrl+Shift+B, or by running dotnet build from the command line. If you build from Visual Studio, the output window that shows the progress of the build, and assuming everything is hunky dory, will compile your application, ready for running. You can also run the dotnet build console commands from the Package Manager Console in Visual Studio. TIP Visual Studio and the .NET CLI tools will automatically build your appli- cation when you run it if they detect that a file has changed, so you generally won’t need to explicitly perform this step yourself. Solution Explorer shows the files in your project. An introductary page is shown when your project is first created. Figure 2.4 Visual Studio after creating a new ASP.NET Core application from a template.
  • 64.
    36 CHAPTER 2Your first application 2.3 Running the web application You’re ready to run your first application and there are a number of different ways to go about it. In Visual Studio, you can either click the green arrow on the toolbar next to IIS Express, or press the F5 shortcut. Visual Studio will automatically open a web browser window for you with the appropriate URL and, after a second or two, you should be pre- sented with your brand new application, as shown in figure 2.5. Alternatively, you can run the application from the command line with the .NET CLI tools using dotnet run and open the URL in a web browser manually, using the address provided on the com- mand line. NuGet packages and the .NET Core command line interface One of the foundational components of .NET Core cross-platform development is the .NET Core command line interface (CLI). This provides a number of basic commands for creating, building, and running .NET Core applications. Visual Studio effectively calls these automatically, but you can also invoke them directly from the command line if you’re using a different editor. The most common commands during development are  dotnet restore  dotnet build  dotnet run Each of these commands should be run inside your project folder and will act on that project alone. All ASP.NET Core applications have dependencies on a number of different external libraries, which are managed through the NuGet package manager. These dependen- cies are listed in the project, but the files of the libraries themselves aren’t included. Before you can build and run your application, you need to ensure there are local cop- ies of each dependency in your project folder. The first command, dotnet restore, will ensure your application’s NuGet dependencies are copied to your project folder. With the 2.0 version of the .NET CLI, you no longer need to explicitly run this com- mand; later commands will implicitly restore packages for you, if necessary. ASP.NET Core projects list their dependencies in the project’s csproj file. This is an XML file that lists each dependency as a PackageReference node. When you run dotnet restore, it uses this file to establish which NuGet packages to download and copy to your project folder. Any dependencies listed are available for use in your application. You can compile your application using dotnet build. This will check for any errors in your application and, if there are no issues, will produce an output that can be run using dotnet run. Each command contains a number of switches that can modify its behavior. To see the full list of available commands, run dotnet --help or to see the options available for a particular command, new for example, run dotnet new --help
  • 65.
    37 Running the webapplication By default, this page shows a variety of links to external resources and a big banner car- ousel at the top of the page, which scrolls through several images. At the top of the page are three links: Home, About, and Contact. The Home link is the page you’re currently on. Clicking About or Contact will take you to a new page, as shown in figure 2.6. As you’ll see shortly, you’ll use MVC in your application to define these three pages and to build the HTML they display. Figure 2.5 The homepage of your new ASP.NET Core application Figure 2.6 The About page of your application. You can navigate between the three pages of the application using the Home, About, and Contact links in the application’s header. The app generates the content of the pages using MVC.
  • 66.
    38 CHAPTER 2Your first application At this point, you need to notice a couple of things. First, the header containing the links and the application title “WebApplication2” is the same on all three pages. Sec- ond, the title of the page, as shown in the tab of the browser, changes to match the current page. You’ll see how to achieve these features in chapter 7, when we discuss the rendering of HTML using Razor templates. There isn’t any more to the user experience of the application at this stage. Click around a little and, once you’re happy with the behavior of the application, roll up your sleeves—it’s time to look at some code! 2.4 Understanding the project layout When you’re new to a framework, creating an application from a template like this can be a mixed blessing. On the one hand, you can get an application up and running quickly, with little input required on your part. Conversely, the number of files can sometimes be overwhelming, leaving you scratching your head working out where to start. The basic web application template doesn’t contain a huge number of files and folders, as shown in figure 2.7, but I’ll run through the major ones to get you oriented. The .sln and Git version control files are found outside the project folder. The root project folder is nested in a top-level solution directory. Solution Explorer from Visual Studio Folder view from Window s Explorer or Visual Studio Code The wwwroot and Properties folders are shown as special nodes in Visual Studio, but they do exist on disk. The Connected Services and Dependencies nodes do not exist on disk. Program.cs and Startup.cs control the startup and configuration of your application at runtime. The csproj file contains all the details required to build your project, including the NuGet packages used by your project. Figure 2.7 The Solution Explorer and folder on disk for a new ASP.NET Core application. The Solution Explorer also displays the Connected Services and Dependencies nodes, which list the NuGet and client-side dependencies, though the folders themselves don’t exist on disk.
  • 67.
    39 Understanding the projectlayout The first thing to notice is that the main project, WebApplication2, is nested in a top- level directory with the name of the solution, also WebApplication2 in this case. Within this top-level folder, you’ll also find the solution (.sln) file for use by Visual Stu- dio and files related to Git version control,3 though these are hidden in Visual Studio’s Solution Explorer view. NOTE Visual Studio uses the concept of a solution to work with multiple pro- jects. The example solution only consists of a single project, which is listed in the .sln file. Inside the solution folder, you’ll find your project folder, which in turn contains five subfolders—Models, Controllers, Views, Properties, and wwwroot. Models, Control- lers, and Views (unsurprisingly) contain the MVC Model, Controller, and View files you’ll use to build your application. The Properties folder contains a single file, launchSettings.json, which controls how Visual Studio will run and debug the applica- tion. The wwwroot folder is special, in that it’s the only folder in your application that browsers are allowed to directly access when browsing your web app. You can store your CSS, JavaScript, images, or static HTML files in here and browsers will be able to access them. They won’t be able to access any file that lives outside of wwwroot. Although the wwwroot and Properties folders exist on disk, you can see that Solu- tion Explorer shows them as special nodes, out of alphabetical order, near the top of your project. You’ve got two more special nodes in the project, Dependencies and Connected Services, but they don’t have a corresponding folder on disk. Instead, they show a collection of all the dependencies, such as NuGet packages, client-side depen- dencies, and remote services that the project relies on. In the root of your project folder, you’ll find several JSON files, such as appsettings .json, bundleconfig.json, and bower.json. These provide various configuration set- tings, some of which are used at runtime, and others which are used to build your app at compile time. NOTE Bower is a client-side asset management system for obtaining CSS and JavaScript libraries. Bower is no longer supported, and so the ASP.NET team are exploring alternatives to fulfill this role. The bower.json file will likely be removed from the default templates and replaced in ASP.NET Core 2.1. The most important file in your project is WebApplication2.csproj, as it describes how to build your project. Visual Studio doesn’t explicitly show the csproj file in Solution Explorer, but you can edit it if you right-click the project name and choose Edit WebApplication2.csproj. We’ll have a closer look at this file in the next section. 3 The Git files will only be added if you select Create new Git repository from the New Project dialog. You don’t have to use Git, but I strongly recommend using some sort of version control when you build applications. If you’re somewhat familiar with Git, but still find it a bit daunting, and a rebase terrifying, I highly recommend reading http://think-like-a-git.net/. It helped me achieve Git enlightenment.
  • 68.
    40 CHAPTER 2Your first application Finally, Visual Studio shows two C# files in the project folder—Program.cs and Startup.cs. In sections 2.6 and 2.7, you’ll see how these fundamental classes are responsible for configuring and running your application. 2.5 The csproj project file: defining your dependencies The csproj file is the project file for .NET applications and contains the details required for the .NET tooling to build your project. It defines the type of project being built (web app, console app, or library), which platform the project targets (.NET Core, .NET Framework 4.5, Mono, and so on), and which NuGet packages the project depends on. The project file has been a mainstay of .NET applications, but in ASP.NET Core it has had a facelift to make it easier to read and edit. These changes include:  No GUIDs—Previously, Global Unique Identifiers (GUIDs) were used for many things, now they’re rarely used in the project file.  Implicit file includes—Previously, every file in the project had to be listed in the csproj file for it to be included in the build. Now, files are automatically com- piled.  No paths to NuGet package dlls—Previously, you had to include the path to the dll files contained in NuGet packages in the csproj, as well as listing the dependen- cies in a packages.xml file. Now, you can reference the NuGet package directly in your csproj, and don’t need to specify the path on disk. All of these changes combine to make the project file far more compact than you’ll be used to from previous .NET projects. The following listing shows the entire csproj file for your sample app. <Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>netcoreapp2.0</TargetFramework> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" /> </ItemGroup> <ItemGroup> <DotNetCliToolReference Version="2.0.0" Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" /> </ItemGroup> </Project> Listing 2.1 The csproj project file, showing SDK, target framework, and references The SDK attribute specifies the type of project you’re building. The TargetFramework is the framework you’ll run on, in this case, .NET Core 2.0. You reference NuGet packages with the PackageReference element. Additional tools used by Visual Studio to generate controllers and views at design time
  • 69.
    41 The Program class:building a web host For simple applications, you probably won’t need to change the project file much. The Sdk attribute on the Project element includes default settings that describe how to build your project, whereas the TargetFramework element describes the framework your appli- cation will run on. For .NET Core 2.0 projects, this will have the netcoreapp2.0 value; if you’re running on the full .NET Framework, 4.6.1, this would be net461. TIP With the new csproj style, Visual Studio users can right-click a project in Solution Explorer and choose Edit <projectname>.csproj, without having to close the project first. The most common changes you’ll make to the project file are to add additional NuGet packages using the PackageReference element. By default, your app refer- ences a single NuGet package, Microsoft.AspNetCore.All. This is a metapackage that includes all of the packages associated with ASP.NET Core 2.0. It’s only available when you’re targeting .NET Core, but it means you don’t have to reference each individual ASP.NET Core package. DEFINITION A metapackage is a NuGet package that contains no code, refer- encing one or more other NuGet packages instead. By adding the metapack- age to your app, you can conveniently and implicitly add all of the packages it references. In ASP.NET Core 2.1, the Microsoft.AspNetCore.App metapack- age package is referenced by default instead. You can read about the differ- ence between the App and All metapackages at https://github.com/aspnet/ Announcements/issues/287. As well as NuGet package references, you can add command-line tools to your project file. The default template includes a tool used under the covers by Visual Studio for code generation. You’ll see how to add new tools in chapter 12. The simplified project file format is much easier to edit by hand than previous ver- sions, which is great if you’re developing cross-platform. But if you’re using Visual Stu- dio, don’t feel like you have to take this route. You can still use the GUI to add project references, exclude files, manage NuGet packages, and so on. Visual Studio will update the project file itself, as it always has. TIP For further details on the changes to the csproj format, see the docu- mentation at https://docs.microsoft.com/en-us/dotnet/core/tools/csproj. The project file defines everything Visual Studio and the .NET CLI need to build your app. Everything, that is, except the code! In the next section, we’ll take a look at the entry point for your ASP.NET Core application—the Program.cs class. 2.6 The Program class: building a web host All ASP.NET Core applications start in the same way as .NET Console applications— with a Program.cs file. This file contains a static void Main function, which is a stan- dard characteristic of console apps. This method must exist and is called whenever
  • 70.
    42 CHAPTER 2Your first application you start your web application. In ASP.NET Core applications, it’s used to build and run an IWebHost instance, as shown in the following listing, which shows the default Program.cs file. The IWebHost is the core of your ASP.NET Core application, contain- ing the application configuration and the Kestrel server that listens for requests and sends responses. public class Program { public static void Main(string[] args) { BuildWebHost(args) .Run(); } public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() .Build(); } The Main function contains all the basic initialization code required to create a web server and to start listening for requests. It uses a WebHostBuilder, created by the call to CreateDefaultBuilder, to define how the IWebHost is configured, before instanti- ating the IWebHost with a call to Build(). NOTE You’ll find this pattern of using a builder object to configure a com- plex object repeated throughout the ASP.NET Core framework. It’s a useful technique for allowing users to configure an object, delaying its creation until all configuration has finished. It’s one of the patterns described in the “Gang of Four” book, Design Patterns: Elements of Reusable Object-Oriented Software (Addison Wesley, 1994). Much of your app’s configuration takes place in the WebHostBuilder created by the call to CreateDefaultBuilder, but it delegates some responsibility to a separate class, Startup. The Startup class referenced in the generic UseStartup<> method is where you configure your app’s services and define your middleware pipeline. In section 2.7, we’ll spend a while delving into this crucial class. At this point, you may be wondering why you need two classes for configuration: Program and Startup. Why not include all of your app’s configuration in one class or the other? Figure 2.8 shows the typical split of configuration components between Program and Startup. Generally speaking, Program is where you configure the infrastructure Listing 2.2 The default Program.cs configures and runs an IWebHost Create an IWebHost using the BuildWebHost method. Run the IWebHost, start listening for requests and generating responses. Create a WebHostBuilder using the default configuration. The Startup class defines most of your application’s configuration. Build and return an instance of IWebHost from the WebHostBuilder.
  • 71.
    43 The Program class:building a web host of your application, such as the HTTP server, integration with IIS, and configuration sources. In contrast, Startup is where you define which components and features your application uses, and the middleware pipeline for your app. The Program class for two different ASP.NET Core applications will generally be simi- lar, but the Startup classes will often differ significantly (though they generally follow a similar pattern, as you’ll see shortly). You’ll rarely find that you need to modify Pro- gram as your application grows, whereas you’ll normally update Startup whenever you add additional features. For example, if you add a new NuGet dependency to your project, you’ll normally need to update Startup to make use of it. The Program class is where a lot of app configuration takes place, but in the default templates this is hidden inside the CreateDefaultBuilder method. The Create- DefaultBuilder method is a static helper method, introduced in ASP.NET Core 2.0, to simplify the bootstrapping of your app by creating a WebHostBuilder with some common configuration. In chapter 11, we’ll peek inside this method and explore the configuration system, but for now, it’s enough to keep figure 2.8 in mind, and to be aware that you can completely change the IWebHost configuration if you need to. Once the configuration of the WebHostBuilder is complete, the call to Build pro- duces the IWebHost instance, but the application still isn’t handling HTTP requests yet. It’s the call to Run that starts the HTTP server listening. At this point, your applica- tion is fully operational and can respond to its first request from a remote browser. Program Startup HTTP server (Kestrel) Content root IIS integration Dependency injection Middleware pipeline MVC configuration Loads configuration settings at runtime, such as connection strings, usernames, and passwords Program.cs is used to configure infrastructure that rarely changes over the lifetime of a project. Startup is used to configure the majority of your application’s custom behavior. The middleware pipeline is defined in code in Startup. To correctly create classes at runtime, dependencies are registered with a container. Logging Application settings Figure 2.8 The difference in configuration scope for Program and Startup. Program is concerned with infrastructure configuration that will typically remain stable throughout the lifetime of the project. In contrast, you’ll often modify Startup to add new features and to update application behavior.
  • 72.
    44 CHAPTER 2Your first application 2.7 The Startup class: configuring your application As you’ve seen, Program is responsible for configuring a lot of the infrastructure for your app, but you configure some of your app’s behavior in Startup. The Startup class is responsible for configuring two main aspects of your application:  Service registration—Any classes that your application depends on for providing functionality—both those used by the framework and those specific to your application—must be registered so that they can be correctly instantiated at runtime.  Middleware and MVC—How your application handles and responds to requests. You configure each of these aspects in its own method in Startup, service registration in ConfigureServices, and middleware configuration in Configure. A typical outline of Startup is shown in the following listing. public class Startup { public void ConfigureServices(IServiceCollection services) { // method details } public void Configure(IApplicationBuilder app) { // method details } } The WebHostBuilder created in Program calls ConfigureServices and then Configure, as shown in figure 2.9. Each call configures a different part of your application, making it available for subsequent method calls. Any services registered in the Configure- Services method are available to the Configure method. Once configuration is com- plete, an IWebHost is created by calling Build() on the WebHostBuilder. An interesting point about the Startup class is that it doesn’t implement an inter- face as such. Instead, the methods are invoked by using reflection to find methods with the predefined names of Configure and ConfigureServices. This makes the class more flexible and enables you to modify the signature of the method to accept addi- tional parameters that are fulfilled automatically. I’ll cover how this works in detail in chapter 10, for now it’s enough to know that anything that’s configured in ConfigureServices can be accessed by the Configure method. DEFINITION Reflection in .NET allows you to obtain information about types in your application at runtime. You can use reflection to create instances of classes at runtime, and to invoke and access them. Listing 2.3 An outline of Startup.cs showing how each aspect is configured Configure services by registering services with the IServiceCollection. Configure the middleware pipeline for handling HTTP requests.
  • 73.
    45 The Startup class:configuring your application Given how fundamental the Startup class is to ASP.NET Core applications, the rest of section 2.7 walks you through both ConfigureServices and Configure, to give you a taste of how they’re used. I won’t explain them in detail (we have the rest of the book for that!), but you should keep in mind how they follow on from each other and how they contribute to the application configuration as a whole. 2.7.1 Adding and configuring services ASP.NET Core uses small, modular components for each distinct feature. This allows individual features to evolve separately, with only a loose coupling to others, and is generally considered good design practice. The downside to this approach is that it places the burden on the consumer of a feature to correctly instantiate it. Within your application, these modular components are exposed as one or more services that are used by the application. DEFINITION Within the context of ASP.Net Core, service refers to any class that provides functionality to an application and could be classes exposed by a library or code you’ve written for your application. For example, in an e-commerce app, you might have a TaxCalculator that calculates the tax due on a particular product, taking into account the user’s location in the Program Startup WebHostBuilder ConfigureServices() Configure() IWebHost Once configuration is complete, the IWebHost is created by calling Build() on the WebHostBuilder. The IWebHost is created in Program using the Builder pattern, and the CreateDefaultBuilder helper method. Build() The middleware pipeline is defined in the Configure method. It controls how your application responds to requests. The WebHostBuilder calls out to Startup to configure your application. To correctly create classes at runtime, dependencies are registered with a container in the ConfigureServices method. Figure 2.9 The WebHostBuilder is created in Program.cs and calls methods on Startup to configure the application’s services and middleware pipeline. Once configuration is complete, the IWebHost is created by calling Build() on the WebHostBuilder.
  • 74.
    46 CHAPTER 2Your first application world. Or you might have a ShippingCostService that calculates the cost of shipping to a user’s location. A third service, OrderTotalCalculatorService, might use both of these services to work out the total price the user must pay for an order. Each ser- vice provides a small piece of independent functionality, but you can combine them to create a complete application. This is known as the single responsibility principle. DEFINITION The single responsibility principle (SRP) states that every class should be responsible for only a single piece of functionality—it should only need to change if that required functionality changes. It’s one of the five main design principles promoted by Robert C. Martin in Agile Software Develop- ment, Principles, Patterns, and Practices (Pearson, 2011). The OrderTotalCalculatorService needs access to an instance of ShippingCost- Service and TaxCalculator. A naïve approach to this problem is to use the new key- word and create an instance of a service whenever you need it. Unfortunately, this tightly couples your code to the specific implementation you’re using and can com- pletely undo all the good work achieved by modularizing the features in the first place. In some cases, it may break the SRP by making you perform initialization code as well as using the service you created. One solution to this problem is to make it somebody else’s problem. When writing a service, you can declare your dependencies and let another class fill those depen- dencies for you. Your service can then focus on the functionality for which it was designed, instead of trying to work out how to build its dependencies. This technique is called dependency injection or the inversion of control (IoC) principle and is a well-recognized design pattern that is used extensively. DEFINITION Design patterns are solutions to common software design problems. Typically, you’ll register the dependencies of your application into a “container,” which can then be used to create any service. This is true for both your own custom application services and the framework services used by ASP.NET Core. You must reg- ister each service with the container before it can be used in your application. NOTE I’ll describe the dependency inversion principle and the IoC con- tainer used in ASP.NET Core in detail in chapter 10. In an ASP.NET Core application, this registration is performed in the Configure- Services method. Whenever you use a new ASP.NET Core feature in your applica- tion, you’ll need to come back to this method and add in the necessary services. This is not as arduous as it sounds, as shown here, taken from the example application. public class Startup { // This method gets called by the runtime. // Use this method to add services to the container. Listing 2.4 Startup.ConfigureServices: adding services to the IoC container
  • 75.
    47 The Startup class:configuring your application public void ConfigureServices(IServiceCollection services) { // Add framework services. services.AddMvc(); } } You may be surprised that a complete MVC application only includes a single call to add the necessary services, but the AddMvc() method is an extension method that encapsulates all the code required to set up the MVC services. Behind the scenes, it adds various Razor services for rendering HTML, formatter services, routing services, and many more! As well as registering framework-related services, this method is where you’d register any custom services you have in your application, such as the example TaxCalculator discussed previously. The IServiceCollection is a list of every known service that your application will need to use. By adding a new service to it, you ensure that whenever a class declares a dependency on your service, the IoC container knows how to provide it. With your services all configured, it’s time to move on to the final configuration step, defining how your application responds to HTTP requests. 2.7.2 Defining how requests are handled with middleware So far, in the WebHostBuilder and Startup class, you’ve defined the infrastructure of the application and registered your services with the IoC container. In the final config- uration method of Startup, Configure, you define the middleware pipeline for the application, which is what defines the app’s behavior. Here’s the Configure method for the template application. public class Startup { public void Configure( IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseBrowserLink(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseStaticFiles(); app.UseMvc(routes => { Listing 2.5 Startup.Configure: defining the middleware pipeline The IApplicationBuilder is used to build the middleware pipeline. Other services can be accepted as parameters. Different behavior when in development or production Only runs in a development environment Only runs in a production environment Adds the static file middleware Adds the MVC middleware
  • 76.
    48 CHAPTER 2Your first application routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); } } As I described previously, middleware consists of small components that execute in sequence when the application receives an HTTP request. They can perform a whole host of functions, such as logging, identifying the current user for a request, serving static files, and handling errors. The IApplicationBuilder that’s passed to the Configure method is used to define the order in which middleware executes. The order of the calls in this method is important, as the order they’re added to the builder in is the order they’ll execute in the final pipeline. Middleware can only use objects created by previous middleware in the pipeline—it can’t access objects created by later middleware. If you’re perform- ing authorization in middleware to restrict the users that may access your application, you must ensure it comes after the authentication middleware that identifies the cur- rent user. WARNING It’s important that you consider the order of middleware when adding it to the pipeline. Middleware can only use objects created by earlier middleware in the pipeline. You should also note that an IHostingEnvironment parameter is used to provide dif- ferent behavior when you’re in a development environment. When you’re running in development (when EnvironmentName is set to "Development"), the Configure method adds one piece of exception-handling middleware to the pipeline; in produc- tion, it adds a different one. The IHostingEnvironment object contains details about the current environment, as determined by the WebHostBuilder in Program. It exposes a number of properties:  ContentRootPath—Location of the working directory for the app, typically the folder in which the application is running  WebRootPath—Location of the wwwroot folder that contains static files  EnvironmentName—Whether the current environment is a development or pro- duction environment IHostingEnvironment is already set by the time Startup is invoked; you can’t change these values using the application settings in Startup. EnvironmentName is typically set externally by using an environment variable when your application starts. NOTE You’ll learn about hosting environments and how to change the cur- rent environment in chapter 11. In development, DeveloperExceptionPageMiddleware (added by the UseDeveloper- ExceptionPage() call) ensures that, if your application throws an exception that isn’t
  • 77.
    49 The Startup class:configuring your application caught, you’ll be presented with as much information as possible in the browser to diagnose the problem, as shown in figure 2.10. It’s akin to the “yellow screen of death” in the previous version of ASP.NET, but this time it’s white, not yellow! NOTE The default templates also add BrowserLinkMiddleware in develop- ment, which reloads your browser when it detects changes in your project. But I personally find this unreliable, so tend to remove it from my projects. When you’re running in a production environment, exposing this amount of data to users would be a big security risk. Instead, ExceptionHandlerMiddleware is registered so that, if users encounter an exception in your method, they will be presented with a friendly error page that doesn’t reveal the source of the problems. If you run the default template in production mode and trigger an error, then you’ll be presented with the message shown in figure 2.11 instead. Obviously, you’d need to update this page to be more visually appealing and more user-friendly, but at least it doesn’t reveal the inner workings of your application! Figure 2.10 The developer exception page contains many different sources of information to help you diagnose a problem, including the exception stack trace and details about the request that generated the exception.
  • 78.
    50 CHAPTER 2Your first application The next piece of middleware added to the pipeline is StaticFileMiddleware, using this statement: app.UseStaticFiles(); This middleware is responsible for handling requests for static files such as CSS files, JavaScript files, and images. When a request arrives at the middleware, it checks to see if the request is for an existing file. If it is, then the middleware returns the file. If not, the request is ignored and the next piece of middleware can attempt to handle the request. Figure 2.12 shows how the request is processed when a static file is requested. When the static-file middleware handles a request, other middleware that comes later in the pipeline, such as the MVC middleware, won’t be called at all. Which brings us to the final and most substantial piece of middleware in the pipe- line: the MVC middleware. It’s responsible for interpreting the request to determine which method to invoke, for reading parameters from the request, and for generating the final HTML. Despite that, the only configuration required, aside from adding the middleware to the pipeline, is to define a route that will be used to map requests to a handler. For each request, the MVC middleware uses this route to determine the appropriate controller and action method on the controller to invoke, and hence, which HTML to generate. Phew! You’ve finally finished configuring your application with all the settings, ser- vices, and middleware it needs. Configuring your application touches on a wide range of different topics that we’ll delve into further throughout the book, so don’t worry if you don’t fully understand all the steps yet. Figure 2.11 The default exception-handling page. In contrast to the developer exception page, this doesn’t reveal any details about your application to users. In reality, you’d update the message to something more user-friendly.
  • 79.
    51 The Startup class:configuring your application Once the application is configured, it can start handling requests. But how does it han- dle them? I’ve already touched on StaticFileMiddleware, which will serve the image and CSS files to the user, but what about the requests that require an HTML response? In the rest of this chapter, I’ll give you a glimpse into the MVC middleware and how it generates HTML. Error handler middleware Web host / reverse proxy (IIS/NGINX/Apache) ASP.NET Core web server 1. HTTP request is made for a static file at http://localhost:50714/css/site.css. 7. HTTP response containing the site.css page is sent to browser. 2. Request is forwarded by IIS/NGINX/Apache to ASP .NET Core. 3. ASP .NET Core web server receives the HTTP request and passes it to the middleware. Request Response 6. Response passes through the middleware back to the web server. 4. The request passes through the error handler middleware unmodified and into the static file middleware. ASP.NET Core application Static file middleware As the static file middleware handled the request, the MVC middleware is never run, and never sees the request. MVC middleware 5. The static file middleware handles the request by returning the appropriate site.css file, short-circuiting the pipeline. Figure 2.12 An overview of a request for a static file at /css/site.css for an ASP.NET Core application. The request passes through the middleware pipeline until it’s handled by the static file middleware. This returns the requested CSS file as the response, which passes back to the web server. The MVC middleware is never invoked and never sees the request.
  • 80.
    52 CHAPTER 2Your first application 2.8 MVC middleware and the home controller When an ASP.NET Core application receives a request, it progresses through the mid- dleware pipeline until a middleware component can handle it, as you saw in figure 2.12. Normally, the final piece of middleware in a pipeline is the MVC middleware. This middleware will attempt to match a request’s path to a configured route, which defines which controller and action method to invoke. A path is the remainder of the request URL, once the domain has been removed. For example, for a request to www.microsoft.com/account/manage, the path is /account/manage. DEFINITION An action is a method that runs in response to a request. A con- troller is a class that contains a number of action methods that can be logi- cally grouped. Once the middleware has selected a controller and action for a given request, the appropriate class will be instantiated and the action method invoked. Controllers are ordinary classes, though they often inherit from the Controller base class to provide access to a number of helper methods, as shown in the following listing. Similarly, there’s nothing special about action methods, other than that they typically return an IActionResult, which contains instructions for handling the request. public class HomeController : Controller { public IActionResult Index() { return View(); } public IActionResult About() { ViewData["Message"] = "Your application description page."; return View(); } public IActionResult Contact() { ViewData["Message"] = "Your contact page."; return View(); } public IActionResult Error() { Listing 2.6 The HomeController—an MVC controller Action methods typically return a ViewResult by calling the View() helper method. MVC controllers can inherit from a helper base class but don’t have to. Data can be passed to a view using the ViewData dictionary. Data can be passed to a view using the ViewData dictionary. If not specified, the name of the view is taken from the name of the action method.
  • 81.
    53 MVC middleware andthe home controller return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); } } This example shows the HomeController, which exposes four different action meth- ods. You may remember from figures 2.5 and 2.6 that the application contains three links: Home, About, and Contact. These links correspond to the Index, About, and Contact action methods. ExceptionMiddlewareHandler calls the remaining action method, Error, when an error occurs in production. Each method calls the View function that’s defined on the Controller base class. This method creates a ViewResult object, which implements IActionResult. By returning a ViewResult, the action is asking the MVC middleware to invoke a view to generate the HTML for a response. The name of the view is taken from the name of the action. So, in the case of the first action method, the view will be called Index. NOTE Action methods can use async and await if you need to use asynchro- nous programming.4 In that case, the return value would be Task<IAction- Result>. As well as returning a ViewResult, the About and Contact action methods also set a value in the ViewData dictionary. This dictionary only lasts for the duration of the request and can be used to send arbitrary values to the view, which it can use when generating the HTML response. The Error method uses a different approach to pass data from the controller to the view. A dedicated class, a view model, is created and passed to the View() method with data to display in the view. DEFINITION A view model is a simple object that contains data required by the view to render a UI. The action methods shown in listing 2.6 and discussed in paragraphs that follow are simple, and you may be wondering why they’re worth having. If all they do is return a view to generate, then why do we need controllers at all? The key thing to remember here is that you now have a framework for performing arbitrarily complex functions in response to a request. You could easily update the action methods to load data from the database, send an email, add a product to a bas- ket, or create an order—all in response to a simple HTTP request. This extensibility is where a lot of the power in MVC lies. 4 For guidance on asynchronous programming, when to use it, and how to use the async and await keywords, see https://docs.microsoft.com/en-us/dotnet/csharp/async. Data can also be passed to a view using a view model.
  • 82.
    54 CHAPTER 2Your first application The other important point is that you’ve separated the execution of these meth- ods from the generation of the HTML, as shown in figure 2.13. If the logic changes and you need to add behavior to an action, you don’t need to touch the HTML gen- eration code, so you’re less likely to introduce bugs. Conversely, if you need to change the UI slightly, change the color of the title for example, then your action method logic is safe. With this in mind, all that’s left to demonstrate is how to generate the HTML that you send to the client. 2.9 Generating HTML with Razor template views When an action method returns a ViewResult, it’s signaling to the MVC middleware that it should find a Razor template and generate the appropriate view. In the example, the action method doesn’t specify the name of the view to find, so the middleware attempts to find an appropriate file by using naming conventions, as shown in figure 2.14. Razor view templates are stored in cshtml files (a portmanteau of cs and html) within the Views folder of your project. Generally, they’re named according to their associated actions, and reside in subfolders corresponding to the name of the control- ler. When a controller returns a ViewResult, the Razor engine attempts to locate the appropriate view template, as illustrated in figure 2.15. For example, considering the HomeController you’ve already seen, the view template for the About action method can be found at ViewsHomeAbout.cshtml, relative to the base project path. Index action MVC HomeController ViewResult View HTML response Request 1. HTTP request is made to MVC page at /Home/Index. 2. The MVC HomeController handles the request by executing the Index action. 3. The Index action returns a ViewResult. 4. The Razor view engine executes the ViewResult using a View template to generate an HTML response. 5. The HTML response is returned to the middleware pipeline. Figure 2.13 The execution of an MVC controller in response to a request. The HomeController handles a request to /Home/Index by executing the Index action. This generates a ViewResult that contains data that a view uses to generate the HTML response. The execution of the action is independent of the HTML generation, so you can modify one without affecting the other.
  • 83.
    55 Generating HTML withRazor template views If the MVC middleware can’t find a view with the appropriate name in the expected location, it will search in one other location. The Shared folder contains views that any controller can access. For example, the previous HomeController contains an Error action method, but there’s no corresponding cshtml template in the ViewsHome folder. In the ViewsShared folder, however, there’s an appropriately named view, so the template can be found and rendered without error. TIP You can modify all of these conventions, including the algorithm shown in figure 2.15, during initial configuration. In fact, you can replace the whole Razor templating engine if required, but that’s beyond the scope of this book. Razor view files reside in the Views folder. Views for the HomeController will be found in the Home folder by default. Views in the Shared folder can be called by any controller. Views have the same name as their corresponding action method. Figure 2.14 View files are located at runtime based on naming conventions. Razor view files reside in a folder based on the name of the associated MVC controller and are named with the name of the action method that requested it. Views in the Shared folder can be used by any controller.
  • 84.
    56 CHAPTER 2Your first application Has an absolute view path been provided? Yes No Does a view exist in views/controller/action? Controller name Action name Yes Use provided absolute path. Use views/controller/action. Use views/shared/action. Does a view exist in views/shared/action? Yes No Throw exception: InvalidOperationException MVC action creates a ViewResult. Action name No Does a view exist at the provided path? No Throw exception: InvalidOperationException Yes Figure 2.15 The algorithm used to locate view templates in ASP.NET Core when an action creates a ViewResult. If the ViewResult has been created using a specific path to a view, and a view exists at the path, then the view will be used. If not, the controller name and action name will be used to try and find the view. If that doesn’t exist, the Shared folder and action name will be used. If no view exists at any of the locations, an InvalidOperationException will be thrown.
  • 85.
    57 Generating HTML withRazor template views Once the template has been located, it can be executed. Figure 2.16 shows the result of executing the About action method and rendering the associated Razor template. The following listing shows the contents of the About.cshtml file. You can see that the file consists of a combination of some standalone C# code, some standalone HTML, and some points where you’re writing C# values in the HTML. @{ ViewData["Title"] = "About"; } <h2>@ViewData["Title"]</h2> <h3>@ViewData["Message"]</h3> <p>Use this area to provide additional information.</p> This file, although small, demonstrates three features of Razor that you can use when building your templates. The simplest and most obvious point is that standalone, static HTML is always valid in a template and will be rendered as is in the response. Second, you can write ordinary C# code in Razor templates by using this construct @{ /* C# code here */ } Any code between the curly braces will be executed but won’t be written to the response. In the listing you’re setting the title of the page by writing a key to the View- Data dictionary, but you aren’t writing anything to the response at this point: @{ ViewData["Title"] = "About"; } Listing 2.7 A simple Razor template—About.cshtml Figure 2.16 Rendering a Razor template to HTML. The About action is invoked and returns a ViewResult. The MVC middleware searches for the appropriate Razor template and executes it to generate the HTML displayed in the browser. C# code that doesn’t write to the response HTML with dynamic C# values written to the response Standalone, static HTML
  • 86.
    58 CHAPTER 2Your first application Another feature shown in this template is dynamically writing C# variables to the HTML stream using the @ symbol. This ability to combine dynamic and static markup is what gives Razor templates their power. In the example, you’re fetching the "Title" and "Message" values from the ViewData dictionary and writing the values to the response inside the <h2> and <h3> tags, respectively: <h2>@ViewData["Title"]</h2> <h3>@ViewData["Message"]</h3> You may also remember that you previously set the value of the "Message" key in the About action method, which you’re now retrieving and writing to the response. This is one of the ways to pass data from an action method to a view—we’ll discuss others in chapter 7. At this point, you might be a little confused by the template from the listing when compared to the output shown in figure 2.16. The title and the message values appear in both the listing and figure, but some parts of the final web page don’t appear in the template. How can that be? Razor has the concept of layouts, which are base templates that define the common elements of your application, such as headers and footers. These layouts define a writeable section in which they will render the action method template, such as the code in listing 2.7. The HTML of the layout combines with the action method tem- plate to produce the final HTML that’s sent to the browser. This prevents you having to duplicate code for the header and footer in every page and means that, if you need to tweak something, you’ll only need to do it in one place. NOTE I’ll cover Razor templates, including layouts, in detail in chapter 7. And there you have it, a complete ASP.NET Core MVC application! Before I move on, we’ll take one last look at how our application handles a request. Figure 2.17 shows a request to the /Home/About path being handled by the sample application. You’ve seen everything here already, so the process of handling a request should be familiar. It shows how the request passes through the middleware pipeline before being han- dled by the MVC middleware. A view is used to generate the HTML response, which passes back through the middleware to the ASP.NET Core web server, before being sent to the user’s browser. It’s been a pretty intense trip, but you now have a good overview of how an entire application is configured and how it handles a request using MVC. In the next chap- ter, you’ll take a closer look at the middleware pipeline that exists in all ASP.NET Core applications. You’ll learn how it’s composed, how you can use it to add functionality to your application, and how you can use it to create simple HTTP services.
  • 87.
    59 Generating HTML withRazor template views Error handling middleware Web host / reverse proxy (IIS/NGINX/Apache) ASP.NET Core web server 1. HTTP request is made to the URL /Home/About. 8. HTTP response containing HTML for the About page is sent to browser. . 2. Request is forwarded by IIS/NGINX/Apache to ASP .NET Core. 3. ASP .NET Core web server receives the HTTP request and passes it to the middleware. Request Response 7. The HTML response passes back through each middleware to the ASP .NET Core web server. 4. Request path /Home/About is for an MVC web page, so it passes through the middleware pipeline unmodified. ASP.NET Core application MVC middleware 6. The About action returns a ViewResult, which is used to generate the HTML response. Static file middleware HomeController View 5. The HomeController handles the request by executing the About action. Figure 2.17 An overview of a request to the /Home/About page for the sample ASP.NET Core application. The request is received by the reverse proxy and passed to the ASP.NET Core web server. It then passes through the middleware pipeline unchanged, until it’s handled by the MVC middleware. The MVC middleware generates an HTML response by executing a view template. The response passes back through the middleware, to the server, and finally, out to the browser.
  • 88.
    60 CHAPTER 2Your first application Summary  The csproj file contains details of how to build your project, including which NuGet packages it depends on. It’s used by Visual Studio and the .NET CLI to build your application.  Restoring the NuGet packages for an ASP.NET Core application downloads all your project’s dependencies so it can be built and run.  Program.cs defines the static void Main entry point for your application. This function is run when your app starts, the same as for console applications.  Program.cs is where you build an IWebHost instance, using a WebHostBuilder. The helper method, WebHost.CreateDefaultBuilder() can be used to create a WebHostBuilder that uses the Kestrel HTTP server, loads configuration set- tings, sets up logging, and adds IIS integration if necessary. Calling Build() cre- ates the IWebHost instance.  You can start the web server and begin accepting HTTP requests by calling Run on the IWebHost.  Startup is responsible for service configuration and defining the middleware pipeline.  All services, both framework and custom application services, must be regis- tered in the call to ConfigureServices in order to be accessed later in your application.  Middleware is added to the application pipeline with IApplicationBuilder. Middleware defines how your application responds to requests.  The order in which middleware is registered defines the final order of the mid- dleware pipeline for the application. Typically, MvcMiddleware is the last middle- ware in the pipeline. Earlier middleware, such as StaticFileMiddleware, will attempt to handle the request first. If the request is handled, MvcMiddleware will never receive the request.  An MVC controller consists of a group of actions that can be invoked in response to a request, based on the request URL. An action can return a ViewResult, which will cause a Razor view to be executed and HTML to be gen- erated.  Razor cshtml files are located, by convention, in a folder corresponding to the associated controller, and are named the same as their associated action.  Razor templates can contain standalone C#, standalone HTML, and dynamic HTML generated from C# values. By combining all three, you can build highly dynamic applications.  Razor layouts define common elements of a web page, such as headers and footers. They let you extract this code into a single file, so you don’t have to duplicate it across every Razor template.
  • 89.
    61 Handling requests withthe middleware pipeline In the previous chapter, you had a whistle-stop tour of a complete ASP.NET Core application to see how the components come together to create a web application. In this chapter, we focus in on one small subsection: the middleware pipeline. The middleware pipeline is one of the most important parts of configuration for defining how your application behaves and how it responds to requests. Under- standing how to build and compose middleware is key to adding functionality to your applications. In this chapter, you’ll learn what middleware is and how to use it to create a pipeline. You’ll see how you can chain multiple middleware components together, with each component adding a discrete piece of functionality. The examples in this This chapter covers  What middleware is  Serving static files using middleware  Adding functionality using middleware  Combining middleware to form a pipeline  Handling exceptions and errors with middleware
  • 90.
    62 CHAPTER 3Handling requests with the middleware pipeline chapter are limited to using existing middleware components, showing how to arrange them in the correct way for your application. In chapter 19, you’ll learn how to build your own middleware components and incorporate them into the pipeline. We’ll begin by looking at the concept of middleware, all the things you can achieve with it, and how a middleware component often maps to a “cross-cutting concern.” These are the functions of an application that cut across multiple different layers. Log- ging, error handling, and security are classic cross-cutting concerns that are all required by many different parts of your application. As all requests pass through the middleware pipeline, it’s the preferred location to configure and handle these aspects. In section 3.2, I’ll explain how you can compose individual middleware compo- nents into a pipeline. You’ll start out small, with a web app that only displays a holding page. From there, you’ll learn how to build a simple static-file server that returns requested files from a folder on disk. Next, you’ll move on to a more complex pipeline containing multiple middleware. You’ll use this example to explore the importance of ordering in the middleware pipe- line and to see how requests are handled when your pipeline contains more than one middleware. In section 3.3, you’ll learn how you can use middleware to deal with an important aspect of any application: error handling. Errors are a fact of life for all applications, so it’s important that you account for them when building your app. As well as ensur- ing that your application doesn’t break when an exception is thrown or an error occurs, it’s important that users of your application are informed about what went wrong in a user-friendly way. You can handle errors in a number of different ways, but as one of the classic cross- cutting concerns, middleware is well placed to provide the required functionality. In section 3.3, I’ll show how you can use middleware to handle exceptions and errors using existing middleware provided by Microsoft. In particular, you’ll learn about three different components:  DeveloperExceptionPageMiddleware—Provides quick error feedback when building an application  ExceptionHandlerMiddleware—Provides a user-friendly generic error page in production  StatusCodePagesMiddleware—Converts raw error status codes into user- friendly error pages By combining these pieces of middleware, you can ensure that any errors that do occur in your application won’t leak security details and won’t break your app. On top of that, they will leave users in a better position to move on from the error, giving them as friendly an experience as possible. You won’t see how to build your own middleware in this chapter—instead, you’ll see that you can go a long way using that provided as part of ASP.NET Core. Once you understand the middleware pipeline and its behavior, it will be much easier to under- stand when and why custom middleware is required. With that in mind, let’s dive in!
  • 91.
    63 What is middleware? 3.1What is middleware? The word middleware is used in a variety of contexts in software development and IT, but it’s not a particularly descriptive word—what is middleware? In ASP.NET Core, middleware is C# classes that can handle an HTTP request or response. Middleware can  Handle an incoming HTTP request by generating an HTTP response.  Process an incoming HTTP request, modify it, and pass it on to another piece of middleware.  Process an outgoing HTTP response, modify it, and pass it on to either another piece of middleware, or the ASP.NET Core web server. You can use middleware in a multitude of ways in your own applications. For example, a piece of logging middleware might note when a request arrived and then pass it on to another piece of middleware. Meanwhile, an image-resizing middleware compo- nent might spot an incoming request for an image with a specified size, generate the requested image, and send it back to the user without passing it on. The most important piece of middleware in most ASP.NET Core applications is the MvcMiddleware class. This class normally generates all your HTML pages and API responses and is the focus of most of this book. Like the image-resizing middleware, it typically receives a request, generates a response, and then sends it back to the user, as shown in figure 3.1. 6. The response is returned to the ASP.NET Core . web server Logging middleware Image resizing middleware MVC middleware 1. ASP .NET Core web server passes the request to the middleware pipeline. 2. The logging middleware notes the time the request arrived and passes the request on to the next . middleware 3. If the request is for an image of a specific size, the image resize middleware will handle it. If not, the request is passed on to . the next middleware 5. The response passes back through each middleware that . ran previously in the pipeline Request 4. If the request makes it through the pipeline to the MVC middleware, it will handle the request and . generate a response Response Figure 3.1 Example of a middleware pipeline. Each middleware handles the request and passes it on to the next middleware in the pipeline. After a middleware generates a response, it passes it back through the pipeline. When it reaches the ASP.NET Core web server, the response is sent to the user’s browser.
  • 92.
    64 CHAPTER 3Handling requests with the middleware pipeline DEFINITION This arrangement, where a piece of middleware can call another piece of middleware, which in turn can call another, and so on, is referred to as a pipeline. You can think of each piece of middleware as a section of pipe— when you connect all the sections, a request flows through one piece and into the next. One of the most common use cases for middleware is for the cross-cutting concerns of your application. These aspects of your application need to occur with every request, regardless of the specific path in the request or the resource requested. These include things like  Logging each request  Adding standard security headers to the response  Associating a request with the relevant user  Setting the language for the current request In each of these examples, the middleware would receive a request, modify it, and then pass the request on to the next piece of middleware in the pipeline. Subsequent middle- ware could use the details added by the earlier middleware to handle the request in some way. For example, in figure 3.2, the authentication middleware associates the request with a user. The authorization middleware uses this detail to verify whether the user has permission to make that specific request to the application or not. If the user has permission, the authorization middleware will pass the request on to the MVC middleware to allow it to generate a response. If the user doesn’t have 6. The response is returned to the ASP .NET Core web server. . The authorization middleware handled the request, so the MVC middleware is never run. Authentication middleware Authorization middleware MVC middleware 1. ASP .NET Core web server passes the request to middleware pipeline. 2. The authentication middleware associates a user with the current request. 3. The authorization middleware uses the user associated with the request to determine whether the request is allowed to execute. 5. The response passes back through each middleware that ran previously in the pipeline. Request 4. If the user is not allowed, the authorization middleware will short-circuit the pipeline. Response Figure 3.2 Example of a middleware component modifying the request for use later in the pipeline. Middleware can also short-circuit the pipeline, returning a response before the request reaches later middleware.
  • 93.
    65 What is middleware? permission,the authorization middleware can short-circuit the pipeline, generating a response directly. It returns the response to the previous middleware before the MVC middleware has even seen the request. A key point to glean from this is that the pipeline is bidirectional. The request passes through the pipeline in one direction until a piece of middleware generates a response, at which point the response passes back through the pipeline, passing through each piece of middleware for a second time, until it gets back to the first piece of middleware. Finally, this first/last piece of middleware will pass the response back to the ASP.NET Core web server. As you saw in chapter 2, you define the middleware pipeline in code as part of your initial application configuration in Startup. You can tailor the middleware pipeline specifically to your needs—simple apps may need only a short pipeline, whereas large apps with a variety of features may use much more middleware. Middleware is the fun- damental source of behavior in your application—ultimately, the middleware pipeline is responsible for responding to any HTTP requests it receives. Requests are passed to the middleware pipeline as HttpContext objects. As you saw in chapter 2, the ASP.NET Core web server builds an HttpContext object from an incoming request, which passes up and down the middleware pipeline. When you’re using existing middleware to build a pipeline, this is a detail you’ll rarely have to deal with. But, as you’ll see in the final section of this chapter, its presence behind the scenes provides a route to exerting extra control over your middleware pipeline. The HttpContext object We mentioned the HttpContext in chapter 2, and it’s sitting behind the scenes here too. The ASP.NET Core web server constructs an HttpContext, which the ASP.NET Core application uses as a sort of storage box for a single request. Anything that’s specific to this particular request and the subsequent response can be associated with and stored in it. This could include properties of the request, request-specific services, data that’s been loaded, or errors that have occurred. The web server fills the initial HttpContext with details of the original HTTP request and other configura- tion details and passes it on to the rest of the application. All middleware has access to the HttpContext for a request. It can use this, for example, to determine whether the request contained any user credentials, which page the request was attempting to access, and to fetch any posted data. It can then use these details to determine how to handle the request. Once the application has finished processing the request, it will update the Http- Context with an appropriate response and return it through the middleware pipeline to the web server. The ASP.NET Core web server will then convert the representation into a raw HTTP response and send it back to the reverse proxy, which will forward it to the user’s browser.
  • 94.
    66 CHAPTER 3Handling requests with the middleware pipeline That’s pretty much all there is to the concept of middleware. In the next section, I’ll discuss ways you can combine middleware components to create an application, and how to use middleware to separate the concerns of your application. 3.2 Combining middleware in a pipeline Generally speaking, each middleware component has a single primary concern. It will handle one aspect of a request only. Logging middleware will only deal with logging the request, authentication middleware is only concerned with identifying the current user, and static-file middleware is only concerned with returning static files (when requested). Each of these concerns is highly focused, which makes the components themselves small and easy to reason about. It also gives your app added flexibility; adding a static- file middleware doesn’t mean you’re forced into having image-resizing behavior or authentication. Each of these features is an additional piece of middleware. To build a complete application, you compose multiple middleware components together into a pipeline, as shown in the previous section. Each middleware has access to the original request, plus any changes made by previous middleware in the pipe- line. Once a response has been generated, each middleware can inspect and/or mod- ify the response as it passes back through the pipeline, before it’s sent to the user. This allows you to build complex application behaviors from small, focused components. In the rest of this section, you’ll see how to create a middleware pipeline by com- posing small components together. Using standard middleware components, you’ll learn to create a holding page and to serve static files from a folder on disk. Finally, you’ll take another look at the default middleware pipeline you built previously, in chapter 2, and decompose it to understand why it’s built like it is. Middleware vs. HTTP modules and HTTP handlers In the previous version of ASP.NET, the concept of a middleware pipeline isn’t used. Instead, we have HTTP modules and HTTP handlers. An HTTP handler is a process that runs in response to a request and generates the response. For example, the ASP.NET page handler runs in response to requests for .aspx pages. Alternatively, you could write a custom handler that returns resized images when an image is requested. HTTP modules handle the cross-cutting concerns of applications, such as security, logging, or session management. They run in response to the lifecycle events that a request progresses through when it’s received by the server. Examples of events include BeginRequest, AcquireRequestState, and PostAcquireRequestState. This approach works, but it’s sometimes tricky to reason about which modules will run at which points. Implementing a module requires a relatively detailed understand- ing of the state of the request at each individual lifecycle event. The middleware pipeline makes understanding your application far simpler. The pipe- line is completely defined in code, specifying which components should run, and in which order.
  • 95.
    67 Combining middleware ina pipeline 3.2.1 Simple pipeline scenario 1: a holding page For your first app, and your first middleware pipeline, you’ll learn how to create an app consisting of a holding page. This can be useful when you’re first setting up your application, to ensure it’s processing requests without errors. TIP Remember, you can view the application code for this book in the GitHub repository at https://github.com/andrewlock/asp-dot-net-core-in- action. In previous chapters, I’ve mentioned that the ASP.NET Core framework is composed of many small, individual libraries. You typically add a piece of middleware by refer- encing a package in your application’s csproj project file and configuring the middle- ware in the Configure method of your Startup class. Microsoft ships a few standard middleware components with ASP.NET Core for you to choose from, though you can obviously also use third-party components from NuGet and GitHub, or you can build your own custom middleware. NOTE I’ll discuss building custom middleware in chapter 19. Unfortunately, there isn’t a definitive list of Microsoft middleware available anywhere. With a bit of searching on https://nuget.org you can often find middleware with the functionality you need. Alternatively, the ASP.NET Core GitHub repositories (https://github.com/aspnet) contain source code for all the Microsoft middleware. Regretabbly they’re split across multiple different repositories, so hunting them down can be a bit of a chore. For example, the StaticFiles repository unsurprisingly contains the StaticFileMiddleware, whereas the Security repository contains a variety of mid- dleware for performing both authentication and authorization. In this section, you’ll see how to create one of the simplest middleware pipelines, consisting of WelcomePageMiddleware only. WelcomePageMiddleware is designed to quickly provide a sample page when you’re first developing an application, as you can see in figure 3.3. You wouldn’t use it in a production app, but it’s a single, self-contained middleware component you can use to ensure your application is running correctly. TIP WelcomePageMiddleware is contained in the Microsoft.AspNetCore .Diagnostics NuGet package, which most new project templates include by default as part of the Microsoft.AspNetCore.All metapackage. The metapack- age includes all of the packages in ASP.NET Core, so you won’t need to include other packages if the metapackage is referenced.1 If the metapackage isn’t included, you’ll need to add the package using the PackageReference element in your csproj file. Even though this application is simple, the exact same process occurs when it receives an HTTP request, as shown in figure 3.4. 1 For a description of the benefits of the Microsoft.AspNetCore.All metapackage and how it works, see my blog post at http://mng.bz/bSw8.
  • 96.
    68 CHAPTER 3Handling requests with the middleware pipeline The request passes to the ASP.NET Core web server, which builds a representation of the request and passes it to the middleware pipeline. As it’s the first (only!) middle- ware in the pipeline, WelcomePageMiddleware receives the request and must decide how to handle it. The middleware responds by generating an HTML response, no matter what request it receives. This response passes back to the ASP.NET Core web server, which forwards it on to the user to display in their browser. As with all ASP.NET Core applications, you define the middleware pipeline in the Configure method of Startup by adding middleware to an IApplicationBuilder object. To create your first middleware pipeline, consisting of a single middleware component, you can add the middleware. using Microsoft.AspNetCore.Builder; namespace WebApplication3 { public class Startup { public void Configure(IApplicationBuilder app) { app.UseWelcomePage(); } } } Listing 3.1 Startup for a Welcome page middleware pipeline Figure 3.3 The Welcome page middleware response. Every request to the application, at any path, will return the same Welcome page response. The Startup class is very simple for this basic application. The Configure method is used to define the middleware pipeline. The only middleware in the pipeline
  • 97.
    69 Combining middleware ina pipeline As you can see, the Startup for this application is very simple. The application has no configuration and no services, so Startup doesn’t have a constructor or a Configure- Services method. The only required method is Configure, in which you call Use- WelcomePage. You build the middleware pipeline in ASP.NET Core by calling methods on IApplicationBuilder, but this interface doesn’t define methods like UseWelcomePage itself.Instead,theseareextension methods,definedineachmiddleware’sNuGetpackage. Using extension methods allows these packages to effectively add functionality to the IApplicationBuilder class, while keeping their functionality isolated from it. Under the hood, the methods are typically calling another extension method to add the middleware to the pipeline. For example, behind the scenes, the UseWelcomePage method adds the WelcomePageMiddleware to the pipeline using UseMiddleware<WelcomePageMiddleware>(); This convention of creating an extension method for each piece of middleware and starting the method name with Use is designed to improve discoverability when add- ing middleware to your application. You still have to remember to reference the appropriate NuGet package in your csproj project file before the extension methods will become available! Welcome page middleware Web host / reverse proxy (IIS/NGINX/Apache) ASP.NET Core web server 1. The browser makes an HTTP request to the server. 6. The HTTP response containing the HTML is sent to the browser. 2. Request is forwarded by IIS/NGINX/Apache to ASP .NET Core. 3. ASP .NET Core web server receives the HTTP request, builds an HttpContext object, and passes it to the middleware pipeline. Request Response 4. The request is handled by the Welcome page middleware, which generates an HTML response and returns it to the pipeline. ASP.NET Core application 5. The response is passed to the ASP .NET Core web server. Figure 3.4 WelcomePageMiddleware handles a request. The request passes from the reverse proxy to the ASP.NET Core web server and, finally, to the middleware pipeline, which generates an HTML response.
  • 98.
    70 CHAPTER 3Handling requests with the middleware pipeline Calling the UseWelcomePage method adds WelcomePageMiddleware as the next middleware in the pipeline. Although you’re only using a single middleware compo- nent here, it’s important to remember that the order in which you make calls to IApplicationBuilder in Configure defines the order that the middleware will run in the pipeline. WARNING Always take care when adding middleware to the pipeline and con- sider the order in which it will run. A component can’t access data created by later middleware if it comes before it in the pipeline. This is the most basic of applications, returning the same response no matter which URL you navigate to, but it shows how easy it is to define your application behavior using middleware. Now we’ll make things a little more interesting and return a differ- ent response when you make requests to different paths. 3.2.2 Simple pipeline scenario 2: Handling static files In this section, I’ll show how to create one of the simplest middleware pipelines you can use for a full application: a static file application. Most web applications, including those with dynamic content, serve a number of pages using static files. Images, JavaScript, and CSS stylesheets are normally saved to disk during development and are served up when requested, normally as part of a full HTML page request. For now, you’ll use StaticFileMiddleware to create an application that only serves static files from the wwwroot folder when requested, as shown in figure 3.5. In this example, a static HTML file called Example.html exists in the wwwroot folder. When you request the file using the /Example.html path, it’s loaded and returned as the response to the request. Static file middleware Request FILE FILE 1. The static file middleware handles the request by returning the file requested. 2. The file stream is sent back through the middleware pipeline and out to the browser. 3. The browser displays the file returned in the response. 9 9 Figure 3.5 Serving a static HTML file using the static file middleware
  • 99.
    71 Combining middleware ina pipeline If the user requests a file that doesn’t exist in the wwwroot folder, for example Missing.html, then the static file middleware won’t serve a file. Instead, a 404 HTTP error code response will be sent to the user’s browser, which will show its default “File Not Found” page, as shown in figure 3.6. NOTE How this page looks will depend on your browser. In some browsers, for example Internet Explorer (IE), you might see a completely blank page. Building the middleware pipeline for this application is easy, consisting of a single piece of middleware, StaticFileMiddleware, as you can see in the following listing. You don’t need any services, so configuring the middleware pipeline in Configure with UseStaticFiles is all that’s required. using Microsoft.AspNetCore.Builder; namespace WebApplication3 { public class Startup { public void Configure(IApplicationBuilder app) { app.UseStaticFiles(); } } Listing 3.2 Startup for a static file middleware pipeline Static file middleware Request 404 404 ! ! 1. The static file middleware handles the request by trying to return the requested file, but as it doesn’t exist, it returns a raw 404 response. 2. The 404 HTTP error code is sent back through the middleware pipeline and to the user. 3. The browser displays its default “File Not Found” error page. Figure 3.6 Returning a 404 to the browser when a file doesn’t exist. The requested file did not exist in the wwwroot folder, so the ASP.NET Core application returned a 404 response. The browser, Microsoft Edge in this case, will then show the user a default “File Not Found” error. The Startup class is very simple for this basic static file application. The Configure method is used to define the middleware pipeline. The only middleware in the pipeline
  • 100.
    72 CHAPTER 3Handling requests with the middleware pipeline TIP Remember, you can view the application code for this book in the GitHub repository at https://github.com/andrewlock/asp-dot-net-core-in-action. When the application receives a request, the ASP.NET Core web server handles it and passes it to the middleware pipeline. StaticFileMiddleware receives the request and determines whether or not it can handle it. If the requested file exists, the middleware handles the request and returns the file as the response, as shown in figure 3.7. If the file doesn’t exist, then the request effectively passes through the static file middle- ware unchanged. But wait, you only added one piece of middleware, right? Surely you can’t pass the request through to the next middleware if there isn’t another one? Luckily, ASP.NET Core effectively adds an automatic “dummy” piece of middleware to the end of the pipeline. This middleware always returns a 404 response if it’s called. TIP Remember, if no middleware generates a response for a request, the pipeline will automatically return a simple 404 error response to the browser. Static file middleware Web host/reverse proxy (IIS/NGINX/Apache) ASP.NET Core web server 1. HTTP request is made for the file Example.html. 7. The HTTP response containing the file is sent to browser. 2. Request is forwarded by IIS / NGINX / Apache to ASP .NET Core. 3. ASP .NET Core web server receives the HTTP request, builds an HttpContext object, and passes it to the middleware. Request Response 5. As the Example.html file exists, it is returned because the response to the request. 4. The static file middleware checks if the Example.html file exists in the wwwroot folder, and, if so, retrieves it. . ASP.NET Core application 6. The response is passed to the ASP .NET Core web server. wwwroot/Example.html ? 9 Figure 3.7 StaticFileMiddleware handles a request for a file. The middleware checks the wwwroot folder to see if the requested Example.html file exists. The file exists, so the middleware retrieves it and returns it as the response to the web server and, ultimately, out to the browser.
  • 101.
    73 Combining middleware ina pipeline This basic ASP.NET Core application allows you to easily see the behavior of the ASP.NET Core middleware pipeline and the static file middleware in particular, but it’s unlikely your applications will be as simple as this. It’s more likely that static files will form one part of your middleware pipeline. In the next section, we’ll look at how to combine multiple middleware, looking at a simple MVC application. 3.2.3 Simple pipeline scenario 3: An MVC web application By this point, you, I hope, have a decent grasp of the middleware pipeline, insofar as understanding that it defines your application’s behavior. In this section, you’ll see how to combine multiple middleware to form a pipeline, using a number of standard middleware components. As before, this is performed in the Configure method of Startup by adding middleware to an IApplicationBuilder object. You’ll begin by creating a basic middleware pipeline that you’d find in a typical ASP.NET Core MVC template and then extend it by adding middleware. The output when you navigate to the homepage of the application is shown in figure 3.8—identi- cal to the sample application shown in chapter 2. HTTP response status codes Every HTTP response contains a status code and, optionally, a reason phrase describ- ing the status code. Status codes are fundamental to the HTTP protocol and are a standardized way of indicating common results. A 200 response, for example, means the request was successfully answered, whereas a 404 response indicates that the resource requested couldn’t be found. Status codes are always three digits long and are grouped into five different classes, based on the first digit:  1xx—Information. Not often used, provides a general acknowledgment.  2xx—Success. The request was successfully handled and processed.  3xx—Redirection. The browser must follow the provided link, to allow the user to log in, for example.  4xx—Client error. There was a problem with the request. For example, the request sent invalid data, or the user isn’t authorized to perform the request.  5xx—Server error. There was a problem on the server that caused the request to fail. These status codes typically drive the behavior of a user’s browser. For example, the browser will handle a 301 response automatically, by redirecting to the provided new link and making a second request, all without the user’s interaction. Error codes are found in the 4xx and 5xx classes. Common codes include a 404 response when a file couldn’t be found, a 400 error when a client sends invalid data (an invalid email address for example), and a 500 error when an error occurs on the server. HTTP responses for error codes may or may not include a response body, which is content to display when the client receives the response.
  • 102.
    74 CHAPTER 3Handling requests with the middleware pipeline As you can see in the figure, creating this simple application requires only three pieces of middleware: MVC middleware to generate the HTML, static file middleware to serve the image files from the wwwroot folder, and an exception handler middle- ware to handle any errors that might occur. The configuration of the middleware pipeline for the application occurs in the Configure method of Startup, as always, and is shown in the following listing. As well as the middleware configuration, this also shows the call to AddMvc() in ConfigureServices, which is required when using MVC middleware. You’ll learn more about service configuration in chapter 10. public class Startup { public class ConfigureServices(IServiceCollection services { services.AddMvc(); } public void Configure(IApplicationBuilder app) { app.UseExceptionHandler("/Home/Error"); app.UseStaticFiles(); app.UseMvc(routes => Listing 3.3 A basic middleware pipeline for an MVC application Figure 3.8 A simple MVC application. The application uses only three pieces of middleware: an MVC middleware to serve the HTML, a static file middleware to serve the image files, and an exception handler middleware to capture any errors.
  • 103.
    75 Combining middleware ina pipeline { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); } } The addition of middleware to IApplicationBuilder to form the pipeline should be familiar to you now, but there are a couple of points worth noting in this example. First, all of the methods for adding middleware start with Use. As I mentioned earlier, this is thanks to the convention of using extension methods to extend the functional- ity of IApplicationBuilder; by prefixing the methods with Use they should be easier to discover. Another important point about this listing is the order of the Use methods in the Configure method. The order in which you add the middleware to the IApplication- Builder object is the same order in which they’re added to the pipeline. This creates a pipeline similar to that shown in figure 3.9. The exception handler middleware is called first, which passes the request on to the static file middleware. The static file handler will generate a response if the request cor- responds to a file, otherwise it will pass the request on to the MVC middleware. The impact of ordering can most obviously be seen when you have two pieces of middleware that are both listening for the same path. For example, the MVC middle- ware in the example pipeline currently responds to a request to the homepage of the application (with the "/" path) by generating the HTML response shown in figure 3.9. Error handling middleware Static file middleware MVC middleware The error handling middleware was added first, so it’s the first (and last) middleware to process the request. The static file middleware is the second middleware in the pipeline. It handles requests for static files before they get to the MVC middleware. The MVC middleware is the last in the pipeline. If it can’t handle the request, the pipeline returns a 404 reponse. Request Response Figure 3.9 The middleware pipeline for the example application in listing 3.3. The order in which you add the middleware to IApplicationBuilder defines the order of the middleware in the pipeline.
  • 104.
    76 CHAPTER 3Handling requests with the middleware pipeline Figure 3.10 shows what happens if you reintroduce a piece of middleware you saw pre- viously, WelcomePageMiddleware, and configure it to respond to the "/" path as well. As you saw in section 3.2.1, WelcomePageMiddleware is designed to return a fixed HTML response, so you wouldn’t use it in a production app, but it illustrates the point nicely. In the following listing, it’s added to the start of the middleware pipeline and configured to respond only to the "/" path. public class Startup { public class ConfigureServices(IServiceCollection services { services.AddMvc(); } public void Configure(IApplicationBuilder app) { app.UseWelcomePage("/"); app.UseExceptionHandler("/Home/Error"); app.UseStaticFiles(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); } } Listing 3.4 Adding WelcomePageMiddleware to the pipeline Figure 3.10 The Welcome page middleware response. The Welcome page middleware comes before the MVC middleware, so a request to the homepage returns the Welcome page middleware instead of the MVC response. WelcomePageMiddleware handles all requests to the "/ " path and returns a sample HTML response. Requests to "/ " will never reach the MVC middleware.
  • 105.
    77 Combining middleware ina pipeline Even though you know the MVC middleware can also handle the "/" path, Welcome- PageMiddleware is earlier in the pipeline, so it returns a response when it receives the request to "/", short-circuiting the pipeline, as shown in figure 3.11. None of the other middleware in the pipeline runs for the request, so none has an opportunity to generate a response. Figure 3.11 Overview of the application handling a request to the "/" path. The welcome page middleware is first in the middleware pipeline, so it receives the request before any other middleware. It generates an HTML response, short-circuiting the pipeline. No other middleware runs for the request. Welcome page middleware Web host/reverse proxy (IIS/NGINX/Apache) ASP.NET Core web server 1. HTTP request is made to the URL http://localhost:49392/. 6. HTTP response containing the Welcome page is sent to browser. . 2. Request is forwarded by IIS/NGINX/Apache to ASP .NET Core. 3. ASP .NET Core web server receives the HTTP request and passes it to the middleware. Request Response 5. HTML response is passed back to ASP .NET Core web server. 4. The Welcome page middleware handles the request. It returns an HTML response, short-circuiting the pipeline. ASP.NET Core application Error handler middleware MVC middleware None of the other middleware is run for the request, so the MVC middleware doesn’t get a chance to handle the request. Static file middleware
  • 106.
    78 CHAPTER 3Handling requests with the middleware pipeline If you moved WelcomePageMiddleware to the end of the pipeline, after the call to Use- Mvc, then you’d have the opposite situation. Any requests to "/" would be handled by the MVC middleware and you’d never see the Welcome page. TIP You should always consider the order of middleware when adding to the Configure method. Middleware added earlier in the pipeline will run (and potentially return a response) before middleware added later. All of the examples shown so far attempt to handle an incoming request and generate a response, but it’s important to remember that the middleware pipeline is bi-directional. Each middleware component gets an opportunity to handle both the incoming request and the outgoing response. The order of middleware is most important for those com- ponents that create or modify the outgoing response. In the previous example, I included ExceptionHandlerMiddleware at the start of the application’s middleware pipeline, but it didn’t seem to do anything. Error hand- ling middleware characteristically ignores the incoming request as it arrives in the pipeline, and instead inspects the outgoing response, only modifying it when an error has occurred. In the next section, I’ll detail the types of error handling middleware that are available to use with your application and when to use them. 3.3 Handling errors using middleware Errors are a fact of life when developing applications. Even if you write perfect code, as soon as you release and deploy your application, users will find a way to break it, whether by accident or intentionally! The important thing is that your application handles these errors gracefully, providing a suitable response to the user, and doesn’t cause your whole application to fail. The design philosophy for ASP.NET Core is that every feature is opt-in. So, as error handling is a feature, you need to explicitly enable it in your application. Many differ- ent types of errors could occur in your application and there are many different ways to handle them, but in this section I’ll focus on two: exceptions and error status codes. Exceptions typically occur whenever you find an unexpected circumstance. A typi- cal (and highly frustrating) exception you’ll no doubt have experienced before is NullReferenceException, which is thrown when you attempt to access an object that hasn’t been initialized. If an exception occurs in a middleware component, it propa- gates up the pipeline, as shown in figure 3.12. If the pipeline doesn’t handle the exception, the web server will return a 500 status code back to the user. In some situations, an error won’t cause an exception. Instead, middleware might generate an error status code. One such case you’ve already seen is when a requested path isn’t handled. In that situation, the pipeline will return a 404 error, which results in a generic, unfriendly page being shown to the user, as you saw in figure 3.6. Although this behavior is “correct,” it doesn’t provide a great experience for users of your application.
  • 107.
    79 Handling errors usingmiddleware Error handling middleware attempts to address these problems by modifying the response before the app returns it to the user. Typically, error handling middleware either returns details of the error that occurred or it returns a generic, but friendly, HTML page to the user. You should always place error handling middleware early in the middleware pipeline to ensure it will catch any errors generated in subsequent middleware, as shown in figure 3.13. Any responses generated by middleware earlier in the pipeline than the error handling middleware can’t be intercepted. The remainder of this section shows several types of error handling middleware that are available for use in your application. You can use any of them by referencing either the Microsoft.AspNetCore.All or the Microsoft.AspNetCore.Diagnostics NuGet packages in your project’s csproj file. 5. If the exception is not handled by the middleware, a raw 500 status code is sent to the browser. . 3. The MVC middleware throws an exception during execution. Welcome page middleware Error handling middleware 1. ASP .NET Core web server passes request to the middleware pipeline. 2. Each middleware component processes the request in turn. 4. The exception propagates back through the pipeline, giving each middleware the opportunity to handle it. Request 500 Authorization middleware Figure 3.12 An exception in the MVC middleware propagates through the pipeline. If the exception isn’t caught by middleware earlier in the pipeline, then a 500 “Server error” status code will be sent to the user’s browser.
  • 108.
    80 CHAPTER 3Handling requests with the middleware pipeline 3.3.1 Viewing exceptions in development: DeveloperExceptionPage When you’re developing an application, you typically want access to as much informa- tion as possible when an error occurs somewhere in your app. For that reason, Micro- soft provides DeveloperExceptionPageMiddleware, which can be added to your middleware pipeline using app.UseDeveloperExceptionPage(); Error handling middleware Static file middleware Request HTML 404 HTML Static file middleware Error handling middleware Request 404 404 Error handling middleware first in the pipeline Static file middleware first in the pipeline ! 2. For example, the static file middleware will generate a raw 404 error response if a requested file does not exist. 1. If middleware is placed after the error handling static middleware in the pipeline, then any response it generates will pass through the error handling middleware. 3. If the static file middleware generates a raw 404 status code, it will be sent directly back to the user unmodified. 2. The error handling middleware processes the response of middleware later in the pipeline, but it never sees the response generated by the static file middleware. 1. If middleware is placed early in the pipeline before the error handling static middleware, then any error codes returned by the middleware will not be modified. 4. The error handling middleware can modify the raw response to a user-friendly HTML page. 3. This raw response passes through the error handling middleware as it passes back through the pipeline. ! ! 9 9 Figure 3.13 Error handling middleware should be placed early in the pipeline to catch raw status code errors. In the first case, the error handling middleware is placed before the static file middleware, so it can replace raw status code errors with a user-friendly error page. In the second case, the error handling middleware is placed after the static file middleware, so raw error status codes can’t be modified.
  • 109.
    81 Handling errors usingmiddleware When an exception is thrown and propagates up the pipeline to this middleware, it will be captured. The middleware then generates a friendly HTML page, which it returns with a 500 status code to the user, as shown in figure 3.14. This page contains a variety of details about the request and the exception, including the exception stack trace, the source code at the line the exception occurred, and details of the request, such as any cookies or headers that had been sent. Having these details available when an error occurs is invaluable for debugging a problem, but they also represent a security risk if used incorrectly. You should never return more details about your application to users than absolutely necessary, so you should only ever use DeveloperExceptionPage when developing your application. The clue is in the name! WARNING Never use the developer exception page when running in produc- tion. Doing so is a security risk as it could publicly reveal details about your application’s code, making you an easy target for attackers. If the developer exception page isn’t appropriate for production use, what should you use instead? Luckily, there’s another general-purpose error handling middleware you can use in production, one that you’ve already seen and used: ExceptionHandler- Middleware. Title indicating the problem Buttons to click that reveal further details about the request that caused the exception Detail of the exception that occurred Full stack trace for the exception Location in the code where exception occurred Code that caused the exception Figure 3.14 The developer exception page shows details about the exception when it occurs during the process of a request. The location in the code that caused the exception, the source code line itself, and the stack trace are all shown by default. You can also click the Query, Cookies, or Headers buttons to reveal further details about the request that caused the exception.
  • 110.
    82 CHAPTER 3Handling requests with the middleware pipeline 3.3.2 Handling exceptions in production: ExceptionHandlerMiddleware The developer exception page is handy when developing your applications, but you shouldn’t use it in production as it can leak information about your app to potential attackers. You still want to catch errors though, otherwise users will see unfriendly error pages or blank pages, depending on the browser they’re using. You can solve this problem by using ExceptionHandlerMiddleware. If an error occurs in your application, the user will be presented with a custom error page that’s consistent with the rest of the application, but that only provides the necessary details about the error. For example, a custom error page, such as the one shown in figure 3.15, can keep the look and feel of the application by using the same header, displaying the currently logged-in user, and only displaying an appropriate message to the user instead of the full details of the exception. If you were to peek at the Configure method of almost any ASP.NET Core applica- tion, you’d almost certainly find the developer exception page used in combination with ExceptionHandlerMiddleware, in a similar manner to that shown here. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { Listing 3.5 Configuring exception handling for development and production Menu bar consistent with the rest of your application Error page contains appropriate details for the user details for the user. Dynamic details such as the current user can be shown on the error page. Footer consistent with the rest of your application The default error page reminds you about the developer exception page. You would change this text in your application to something more generic. Figure 3.15 A custom error page created by ExceptionHandlerMiddleware. The custom error page can keep the same look and feel as the rest of the application by reusing elements such as the header and footer. More importantly, you can easily control the error details displayed to users. Configure a different pipeline when running in development.
  • 111.
    83 Handling errors usingmiddleware app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/home/error"); } // additional middleware configuration } As well as demonstrating how to add ExceptionHandlerMiddleware to your middle- ware pipeline, this listing shows that it’s perfectly acceptable to configure different middleware pipelines depending on the environment when the application starts up. You could also vary your pipeline based on other values, such as settings loaded from configuration. NOTE You’ll see how to use configuration values to customize the middle- ware pipeline in chapter 11. When adding ExceptionHandlerMiddleware to your application, you’ll typically provide a path to the custom error page that will be displayed to the user. In the exam- ple listing, you used an error handling path of /home/error: app.UseExceptionHandler("/home/error"); ExceptionHandlerMiddleware will invoke this path after it captures an exception, in order to generate the final response. The ability to dynamically generate a response is a key feature of ExceptionHandlerMiddleware—it allows you to re-execute a middle- ware pipeline in order to generate the response sent to the user. Figure 3.16 shows what happens when ExceptionHandlerMiddleware handles an exception. It shows the flow of events when the MVC middleware generates an exception when a request is made to the /home path. The final response returns an error status code but also provides an HTML response to display to the user, using the /error path. The sequence of events when an exception occurs somewhere in the middleware pipeline after ExceptionHandlerMiddleware is as follows: 1 A piece of middleware throws an exception. 2 ExceptionHandlerMiddleware catches the exception. 3 Any partial response that has been defined is cleared. 4 The middleware overwrites the request path with the provided error handling path. 5 The middleware sends the request back down the pipeline, as though the origi- nal request had been for the error handling path. 6 The middleware pipeline generates a new response as normal. 7 When the response gets back to ExceptionHandlerMiddleware, it modifies the status code to a 500 error and continues to pass the response up the pipeline to the web server. The developer exception page should only be used when running in development mode. When in production, ExceptionHandlerMiddleware is added to the pipeline.
  • 112.
    84 CHAPTER 3Handling requests with the middleware pipeline The Exception HandlerMiddleware uses the new response, but updates the status code of the response to a 500 status code. This indicates to the browser that an error occurred, but the user sees a friendly web page indicating something went wrong. Exception handler MVC middleware Exception handler MVC middleware MVC middleware Exception handler MVC middleware Error handling middleware HTML HTML HTML HTML The ExceptionHandlerMiddleware ignores the request initially. The MVC middleware throws an exception for some reason while handling the request. The exception propagates up the pipeline and is caught by the ExceptionHandlerMiddleware. This changes the path of the request to the error path /Error and sends the request down the middleware pipeline again. The middleware pipeline executes the new error path and generates a response as usual. In this case, the MVC middleware generates an HTML response. A request is passed to the pipeline for the URL path /Home. /Home /Error /Home /Error ! ! 9 9 9 Figure 3.16 ExceptionHandlerMiddleware handling an exception to generate an HTML response. A request to the /Home path generates an exception, which is handled by the middleware. The pipeline is re-executed using the /Error path to generate the HTML response.
  • 113.
    85 Handling errors usingmiddleware The main advantage that re-executing the pipeline brings is the ability to have your error messages integrated into your normal site layout, as shown in figure 3.15. It’s certainly possible to return a fixed response when an error occurs, but you wouldn’t be able to have a menu bar with dynamically generated links or display the current user’s name in the menu. By re-executing the pipeline, you can ensure that all the dynamic areas of your application are correctly integrated, as if the page was a stan- dard page of your site. NOTE You don’t need to do anything other than add ExceptionHandler- Middleware to your application and configure a valid error handling path to enable re-executing the pipeline. The middleware will catch the exception and re-execute the pipeline for you. Subsequent middleware will treat the re- execution as a new request, but previous middleware in the pipeline won’t be aware anything unusual happened. Re-executing the middleware pipeline is a great way to keep consistency in your web application for error pages, but there are some gotchas to be aware of. First, middle- ware can only modify a response generated further down the pipeline if the response hasn’t yet been sent to the client. This can be a problem if, for example, an error occurs while ASP.NET Core is sending a static file to a client. In that case, where bytes have already begun to be sent to the client, the error handling middleware won’t be able to run, as it can’t reset the response. Generally speaking, there’s not a lot you can do about this issue, but it’s something to be aware of. A more common problem occurs when the error handling path throws an error during the re-execution of the pipeline. Imagine there’s a bug in the code that gener- ates the menu at the top of the page: 1 When the user reaches your homepage, the code for generating the menu bar throws an exception. 2 The exception propagates up the middleware pipeline. 3 When reached, ExceptionHandlerMiddleware captures it and the pipe is re- executed using the error handling path. 4 When the error page executes, it attempts to generate the menu bar for your app, which again throws an exception. 5 The exception propagates up the middleware pipeline. 6 ExceptionHandlerMiddleware has already tried to intercept a request, so it will let the error propagate all the way to the top of the middleware pipeline. 7 The web server returns a raw 500 error, as though there was no error handling middleware at all. Thanks to this problem, it’s often good practice to make your error handling pages as simple as possible, to reduce the possibility of errors occurring.
  • 114.
    86 CHAPTER 3Handling requests with the middleware pipeline WARNING If your error handling path generates an error, the user will see a generic browser error. It’s often better to use a static error page that will always work, rather than a dynamic page that risks throwing more errors. ExceptionHandlerMiddleware and DeveloperExceptionPageMiddleware are great for catching exceptions in your application, but exceptions aren’t the only sort of errors you’ll encounter. In some cases, your middleware pipeline will return an HTTP error status code in the response. It’s important to handle both exceptions and error status codes to provide a coherent user experience. 3.3.3 Handling other errors: StatusCodePagesMiddleware Your application can return a wide range of HTTP status codes that indicate some sort of error state. You’ve already seen that a 500 “server error” is sent when an exception occurs and isn’t handled and that a 404 “file not found” error is sent when a URL isn’t handled by any middleware. 404 errors, in particular, are common, often occurring when a user enters an invalid URL. TIP As well as indicating a completely unhandled URL, 404 errors are often used to indicate that a specific requested object was not found. For example, a request for the details of a product with an ID of 23 might return a 404 if no such product exists. Without handling these status codes, users will see a generic error page, such as in fig- ure 3.17, which may leave many confused and thinking your application is broken. A better approach would be to handle these error codes and return an error page that’s in keeping with the rest of your application or, at the very least, doesn’t make your application look broken. Figure 3.17 A generic browser error page. If the middleware pipeline can’t handle a request, it will return a 404 error to the user. The message is of limited usefulness to users and may leave many confused or thinking your web application is broken.
  • 115.
    87 Handling errors usingmiddleware Microsoft provides StatusCodePagesMiddleware for handling this use case in the Microsoft.AspNetCore.Diagnostics package. As with all error handling middleware, you should add it early in your middleware pipeline, as it will only handle errors gen- erated by later middleware components. You can use the middleware a number of different ways in your application. The simplest approach is to add the middleware to your pipeline without any additional configuration, using app.UseStatusCodePages(); With this method, the middleware will intercept any response that has an HTTP Sta- tus code that starts with 4xx or 5xx and has no response body. For the simplest case, where you don’t provide any additional configuration, the middleware will add a plain text response body, indicating the type and name of the response, as shown in figure 3.18. This is arguably worse than the generic method at this point, but it is a starting point for providing a more consistent experience to users! A more typical approach to using StatusCodePageMiddleware in production is to re- execute the pipeline when an error is captured, using a similar technique to ExceptionHandlerMiddleware. This allows you to have dynamic error pages that fit with the rest of your application. To use this technique, replace the call to UseStatus- CodePages with the following extension method app.UseStatusCodePagesWithReExecute("/error/{0}"); This extension method configures StatusCodePageMiddleware to re-execute the pipe- line whenever a 4xx or 5xx response code is found, using the provided error handling path. This is similar to the way ExceptionHandlerMiddleware re-executes the pipe- line, as shown in figure 3.19. Figure 3.18 Status code error pages for a 404 error. You generally won’t use this version of the middleware in production as it doesn’t provide a great user experience, but it demonstrates that the error codes are being correctly intercepted.
  • 116.
    88 CHAPTER 3Handling requests with the middleware pipeline The StatusCodePageMiddleware uses the new response, but updates the status code of the response to a 404 status code. This indicates to the browser that an error occurred, but the user sees a friendly web page indicating something went wrong. Status code middleware MVC middleware Status code middleware MVC middleware MVC middleware Status code middleware MVC middleware Status code middleware HTML HTML HTML HTML The StatusCodePageMiddleware ignores the request initially. The MVC middleware returns an error status code, in this case, a 404 response. The response propagates up the pipeline and is intercepted by the StatusCodePageMiddleware. This changes the path of the request to the error path /Error/404 and sends the request down the middleware pipeline again. The middleware pipeline executes the new error path and generates a response as usual. In this case, the MVC middleware generates an HTML response. A request is passed to the pipeline for the URL path /Home. /Home /error/404 /Home /error/404 ! ! 404 ! 9 9 Figure 3.19 StatusCodePagesMiddleware re-executing the pipeline to generate an HTML body for a 404 response. A request to the /Home path returns a 404 response, which is handled by the status code middleware. The pipeline is re-executed using the /error/404 path to generate the HTML response.
  • 117.
    89 Handling errors usingmiddleware Note that the error handling path "/error/{0}" contains a format string token, {0}. When the path is re-executed, the middleware will replace this token with the status code number. For example, a 404 error would re-execute the /error/404 path. The handler for the path (typically an MVC route) has access to the status code and can optionally tailor the response, depending on the status code. You can choose any error handling path, as long as your application knows how to handle it. NOTE You’ll learn about MVC routing in chapter 5. With this approach in place, you can create different error pages for different error codes, such as the 404-specific error page shown in figure 3.20. This technique ensures your error pages are consistent with the rest of your application, including any dynamically generated content, while also allowing you to tailor the message for common errors. WARNING As before, when re-executing the pipeline, you must be careful your error handling path doesn’t generate any errors. You can use StatusCodePagesMiddleware in combination with other exception hand- ling middleware by adding both to the pipeline. StatusCodePagesMiddleware will only modify the response if no response body has been written. So if another compo- nent, for example ExceptionHandlerMiddleware, returns a message body along with an error code, it won’t be modified. NOTE StatusCodePageMiddleware has additional overloads that let you exe- cute custom middleware when an error occurs, instead of re-executing an MVC path. Figure 3.20 An error status code page for a missing file. When an error code is detected (in this case, a 404 error), the middleware pipeline is re-executed to generate the response. This allows dynamic portions of your web page to remain consistent on error pages.
  • 118.
    90 CHAPTER 3Handling requests with the middleware pipeline Error handling is essential when developing any web application; errors happen and you need to handle them gracefully. But depending on your application, you may not always need error handling middleware, and sometimes you may need to disable it for a single request. 3.3.4 Disabling error handling middleware for Web APIs ASP.NET Core isn’t only great for creating user-facing web applications, it’s also great for creating HTTP services that can be accessed either from another server applica- tion, or from a user’s browser when running a client-side single-page application. In both of these cases, you probably won’t be returning HTML to the client, but rather XML or JSON. In that situation, if an error occurs, you probably don’t want to be sending back a big HTML page saying, “Oops, something went wrong.” Returning an HTML page to an application that’s expecting JSON could easily break it unexpectedly. Instead, the HTTP 500 status code is more descriptive and useful to a consuming application. Luckily, this is the default behavior when you don’t add error-handling middleware to your application. In the simplest case, where your whole application serves as an API to another application, you could probably get away without any error handling middleware. In reality, you may want to make sure you log the errors using middle- ware, but you certainly don’t need to change the response body in that case. But what if you have an ASP.NET Core application that takes on both roles—it serves HTML to users using standard MVC controllers and acts as an API in other cases? You’ll probably want to ensure your error handling middleware only modifies the requests to HTML endpoints and leaves the API requests to be returned as status codes only. StatusCodePagesMiddleware supports being disabled for a given request. When the middleware runs as part of the middleware pipeline, it adds a feature to the collec- tion of features on the HttpContext object called IStatusCodePagesFeature. DEFINITION Features define which capabilities the ASP.NET Core web server provides. Each feature is represented as an interface in the Features collec- tion property on HttpContext. Middleware is free to add or replace features in the collection as part of the request, thereby extending the features avail- able to an application. You may remember that the ASP.NET Core web server builds an HttpContext object representing a request, which passes up and down the middleware pipeline. By add- ing a feature to the HttpContext collection, StatusCodePagesMiddleware broadcasts its presence to the rest of the application. This allows other parts of the application to disable StatusCodePagesMiddleware if required. For example, a Web API controller could disable the feature if it doesn’t want to have error responses replaced with HTML pages, as shown in the following listing.
  • 119.
    91 Summary public class ValuesController: Controller { public string Index() { var statusCodePagesFeature = HttpContext.Features .Get<IStatusCodePagesFeature>(); if (statusCodePagesFeature != null) { statusCodePagesFeature.Enabled = false; } return StatusCode(500); } } When the Index method in this API controller is hit, it will attempt to retrieve IStatusCodePagesFeature from the feature collection on HttpContext. If Status- CodePagesMiddleware has already run in the request pipeline, then you’ll be able to set Enabled = false. If it hasn’t run, then the feature will be null, so you need to remember to check that before using the variable. The final call to return StatusCode(500) will return an HTTP 500 status code to the client. Ordinarily, StatusCodePagesMiddleware would intercept the response and replace it with a friendly HTML page, but in this case the raw status code is sent as it is, without a response body. That brings us to the end of middleware in ASP.NET Core for now. You’ve seen how to use and compose middleware to form a pipeline, as well as how to handle errors in your application. This will get you a long way when you start building your first ASP.NET Core applications. Later, you’ll learn how to build your own custom middleware, as well as how to perform complex operations on the middleware pipe- line, such as forking it in response to specific requests. In the next chapter, you’ll learn about the MVC design pattern and how it applies to ASP.NET Core. You’ve already seen several examples of MVC controllers but I’ll expand on how and when you can make use of them in your application, both for building dynamic web applications and for Web APIs. Summary  Middleware has a similar role to HTTP modules and handlers in ASP.NET but is more easily reasoned about.  Middleware is composed in a pipeline, with the output of one middleware pass- ing to the input of the next. Listing 3.6 Disabling StatusCodePagesMiddleware in a web API controller Try to get IStatusCodePagesFeature from the HttpContext. If StatusCodePagesMiddleware hasn’t been added, the feature will be null. Disable the feature for this request. Return a 500 error code—it will pass through StatusCodePagesMiddleware untouched.
  • 120.
    92 CHAPTER 3Handling requests with the middleware pipeline  The middleware pipeline is two-way: requests pass through each middleware on the way in and responses pass back through in the reverse order on the way out.  Middleware can short-circuit the pipeline by handling a request and returning a response, or it can pass the request on to the next middleware in the pipeline.  Middleware can modify a request by adding data to, or changing, the Http- Context object.  If an earlier middleware short-circuits the pipeline, not all middleware will exe- cute for all requests.  If a request isn’t handled, the middleware pipeline will return a 404 status code.  The order in which middleware is added to IApplicationBuilder defines the order in which middleware will execute in the pipeline.  The middleware pipeline can be re-executed, as long as a response’s headers haven’t been sent.  When added to a middleware pipeline, StaticFileMiddleware will serve any requested files found in the wwwroot folder of your application.  DeveloperExceptionPageMiddleware provides a lot of information about errors when developing an application but should never be used in production.  ExceptionHandlerMiddleware lets you provide user-friendly custom error handling messages when an exception occurs in the pipeline.  StatusCodePagesMiddleware lets you provide user-friendly custom error hand- ling messages when the pipeline returns a raw error response status code.  StatusCodePagesMiddleware can be disabled by accessing the Features prop- erty on HttpContext.  Microsoft provides some common middleware and there are many third-party options available on NuGet and GitHub.
  • 121.
    93 Creating web pages withMVC controllers In chapter 3, you learned about the middleware pipeline, which defines how an ASP.NET Core application responds to a request. Each piece of middleware can modify or handle an incoming request, before passing the request to the next mid- dleware in the pipeline. In ASP.NET Core web applications, the final piece of middleware in the pipe- line will normally be MvcMiddleware. This is typically where you write the bulk of your application logic, by calling various other classes in your app. It also serves as the main entry point for users to interact with your app. It typically takes one of two forms:  An HTML web application, designed for direct use by users. If the application is consumed directly by users, as in a traditional web application, then the This chapter covers  Introducing the Model-View-Controller (MVC) design pattern  Using MVC in ASP.NET Core  Creating MVC controllers for serving web pages
  • 122.
    94 CHAPTER 4Creating web pages with MVC controllers MvcMiddleware is responsible for generating the web pages that the user inter- acts with. It handles requests for URLs, it receives data posted using forms, and it generates the HTML that users use to view and navigate your app.  An API designed for consumption by another machine or in code. The other main pos- sibility for a web application is to serve as an API, to backend server processes, to a mobile app, or to a client framework for building single page applications (SPAs). The same MvcMiddleware can fulfill this role by serving data in machine-readable formats such as JSON or XML, instead of the human-focused HTML output. In this chapter, you’ll learn how ASP.NET Core uses the MvcMiddleware to serve these two requirements. You’ll start by looking at the Model-View-Controller (MVC) design pattern to see the benefits that can be achieved through its use and learn why it’s been adopted by so many web frameworks as a model for building maintainable applications. Next, you’ll learn how the MVC design pattern applies specifically to ASP.NET Core. The MVC pattern is a broad concept that can be applied in a variety of situa- tions, but the use case in ASP.NET Core is specifically as a UI abstraction. You’ll see how to add the MvcMiddleware to your application, as well as how to customize it for your needs. Once you’ve installed the middleware in your app, I’ll show how to create your first MVC controllers. You’ll learn how to define action methods to execute when your application receives a request and how to generate a result that can be used to create an HTTP response to return. For traditional MVC web applications, this will be a ViewResult that can generate HTML. I won’t cover how to create Web APIs in this chapter. Web APIs still use the Mvc- Middleware but they’re used in a slightly different way. Instead of returning web pages that are directly displayed on a user’s browser, they return data formatted for con- sumption in code. Web APIs are often used for providing data to mobile and web applications, or to other server applications. But they still follow the same general MVC pattern. You’ll see how to create a Web API in chapter 9. NOTE This chapter is the first of several on MVC in ASP.NET Core and the MvcMiddleware. As I’ve already mentioned, this middleware is often responsi- ble for handling all the business logic and UI code for your application, so, perhaps unsurprisingly, it’s large and somewhat complicated. The next five chapters all deal with a different aspect of the MVC pattern that makes up the MVC middleware. In this chapter, I’ll try to prepare you for each of the upcoming topics, but you may find that some of the behavior feels a bit like magic at this stage. Try not to become too concerned with exactly how all the pieces tie together; focus on the specific con- cepts being addressed. It should all become clear as we cover the associated details in the remainder of this first part of the book.
  • 123.
    95 An introduction toMVC 4.1 An introduction to MVC Depending on your background in software development, you may have previously come across the MVC pattern in some form. In web development, MVC is a common paradigm and is used in frameworks such as Django, Rails, and Spring MVC. But as it’s such a broad concept, you can find MVC in everything from mobile apps to rich-client desktop applications. Hopefully that is indicative of the benefits the pattern can bring if used correctly! In this section, I’ll look at the MVC pattern in general, how it applies to ASP.NET Core, and how to add the MvcMiddleware to your application. By the end of this sec- tion you should have a good understanding of the benefits of this approach and how to get started. 4.1.1 The MVC design pattern The MVC design pattern is a common pattern for designing apps that have UIs. The original MVC pattern has many different interpretations, each of which focuses on a slightly different aspect of the pattern. For example, the original MVC design pattern was specified with rich-client graphical user interface (GUI) apps in mind, rather than web applications, and so uses terminology and paradigms associated with a GUI envi- ronment. Fundamentally, though, the pattern aims to separate the management and manipulation of data from its visual representation. Before I dive too far into the design pattern itself, let’s consider a typical request. Imagine a user of your application requests a page that displays a to-do list. What hap- pens when the MvcMiddleware gets this request? Figure 4.1 shows how the MVC pat- tern is used to handle different aspects of that single page request, all of which combine to generate the final response. 1. Request for ToDoList is received from a user. Model Request Controller View 2. The ToDoList controller handles the request. 3. The controller class requests the current items on the list from the ToDoList model. The model may retrieve them from memory, a file, or a database, for instance. Response 4. The controller class finds the correct HTML template for the to-do list (also called the view) and passes it the list items from the model. 5. The ToDoList view plugs the items into the HTML template and sends the completed HTML page back to the user. List items Figure 4.1 Requesting a to-do list page for an MVC application. A different component handles each aspect of the request.
  • 124.
    96 CHAPTER 4Creating web pages with MVC controllers In general, three components make up the MVC design pattern:  Model—The data that needs to be displayed, the state of the application.  View—The template that displays the data provided by the model.  Controller—Updates the model and selects the appropriate view. Each component in an MVC application is responsible for a single aspect of the over- all system that, when combined, can be used to generate a UI. The to-do list example considers MVC in terms of a web application, but a request could also be equivalent to the click of a button in a desktop GUI application. In general, the order of events when an application responds to a user interaction or request is as follows: 1 The controller receives the request. 2 Depending on the request, the controller either fetches the requested data from the application model, or it updates the data that makes up the model. 3 The controller selects a view to display and passes the model to it. 4 The view uses the data contained in the model to generate the UI. When we describe MVC in this format, the controller serves as the entry point for the interaction. The user communicates with the controller to instigate an interaction. In web applications, this interaction takes the form of an HTTP request, so when a request to a URL is received, the controller handles it. Depending on the nature of the request, the controller may take a variety of actions, but the key point is that the actions are undertaken using the model. The model here contains all the business logic for the application, so it’s able to provide requested data or perform actions. NOTE In this description of MVC, the model is considered to be a complex beast, containing all the logic for how to perform an action, as well as any internal state. Consider a request to view a product page for an e-commerce application, for exam- ple. The controller would receive the request and would know how to contact some product service that’s part of the application model. This might fetch the details of the requested product from a database and return them to the controller. Alternatively, imagine the controller receives a request to add a product to the user’s shopping cart. The controller would receive the request, and most likely invoke a method on the model to request that the product be added. The model would then update its internal representation of the user’s cart, by adding, for example, a new row to a database table holding the user’s data. After the model has been updated, the controller needs to select a way to display the data. One of the advantages of using the MVC design pattern is that the model representing the data is decoupled from the final representation of that data, called the view.
  • 125.
    97 An introduction toMVC This separation creates the possibility for the controller to choose to display the model using a different view, based on where the original request originated, as shown in figure 4.2. If the request came from a standard web application, then the controller can display an HTML view. If the request came from another application, then the controller can choose to return the model in a format the application understands, such as JSON or XML. The other advantage of the model being independent of the view is that it improves testability. UI code is classically hard to test, as it’s dependent on the environment— anyone who has written UI tests simulating a user clicking buttons and typing in forms knows that it’s typically fragile. By keeping the model independent of the view, you can ensure the model stays easily testable, without any dependencies on UI con- structs. As the model often contains your application’s business logic, this is clearly a good thing! Once the controller has selected a view, it passes the model to it. The view can use the data passed to it to generate an appropriate UI, an HTML web page, or a simple JSON object. The view is only responsible for generating the final representation. This is all there is to the MVC design pattern in relation to web applications. Much of the confusion related to MVC seems to stem from slightly different uses of the term for slightly different frameworks and types of application. In the next section, I’ll show 1. A request is received from a user. Model Request Controller Standard web app 6. In this case, the standard web app is selected, so an HTML response is generated. 2. The controller handles the request. 3. The controller fetches data from, or updates, the model to perform the requested action. 4. The controller selects a view based on the caller that made the request and passes it the model. HTML Select a view JSON Text-based UI application Plain text 5. The selected view uses the provided model to generate an appropriate response. Model SPA/mobile application Figure 4.2 Selecting a different view using MVC depending on the caller. The final representation of the model, created by the view, is independent of the controller and business logic.
  • 126.
    98 CHAPTER 4Creating web pages with MVC controllers how the ASP.NET Core framework uses the MVC pattern, along with more examples of the pattern in action. 4.1.2 MVC in ASP.NET Core As you’ve seen in previous chapters, ASP.NET Core implements MVC using a single piece of middleware, which is normally placed at the end of the middleware pipeline, as shown in figure 4.3. Once a request has been processed by each middleware (and assuming none of them handle the request and short-circuit the pipeline), it will be received by the MVC middleware. Middleware often handles cross-cutting concerns or narrowly defined requests, such as requests for files. For requirements that fall outside of these functions, or that have many external dependencies, a more robust framework is required. The MvcMiddle- ware in ASP.NET Core can provide this framework, allowing interaction with your application’s core business logic, and the generation of a UI. It handles everything from mapping the request to an appropriate controller to generating the HTML or API response. In the traditional description of the MVC design pattern, there’s only a single type of model, which holds all the non-UI data and behavior. The controller updates this model as appropriate and then passes it to the view, which uses it to generate a UI. This simple, three-component pattern may be sufficient for some basic applications, but for more complex applications, it often doesn’t scale. Error-handling middleware Static-file middleware MVC middleware The request passes through each middleware in the pipeline. Each middleware gets an opportunity to handle the request. The MVC middleware is typically the last middleware in the pipeline. It encompasses the whole MVC design pattern. Request Response Figure 4.3 The middleware pipeline.
  • 127.
    99 An introduction toMVC One of the problems when discussing MVC is the vague and ambiguous terms that it uses, such as “controller” and “model.” Model, in particular, is such an overloaded term that it’s often difficult to be sure exactly what it refers to—is it an object, a collec- tion of objects, an abstract concept? Even ASP.NET Core uses the word “model” to describe several related, but different, components, as you’ll see shortly. DIRECTING A REQUEST TO A CONTROLLER AND BUILDING A BINDING MODEL The first step when the MvcMiddleware receives a request is routing the request to an appropriate controller. Let’s think about another page in your to-do application. On this page, you’re displaying a list of items marked with a given category, assigned to a particular user. If you’re looking at the list of items assigned to the user “Andrew” with a category of “Simple,” you’d make a request to the /todo/list/Simple/Andrew URL. Routing takes the path of the request, /todo/list/Simple/Andrew, and maps it against a preregistered list of patterns. These patterns match a path to a single con- troller class and action method. You’ll learn more about routing in the next chapter. DEFINITION An action (or action method) is a method that runs in response to a request. A controller is a class that contains a number of logically grouped action methods. Once an action method is selected, the binding model (if applicable) is generated, based on the incoming request and the method parameters required by the action When not to use the MvcMiddleware Typically, you’ll use MvcMiddleware to write most of your application logic for an app. You’ll use it to define the APIs and pages in your application, and to define how they interface with your business logic. The MvcMiddleware is an extensive framework (as you’ll see over the next six chapters) that provides a great deal of functionality to help build your apps quickly and efficiently. But it’s not suited to every app. Providing so much functionality necessarily comes with a certain degree of perfor- mance overhead. For typical apps, the productivity gains from using MVC strongly out- weigh any performance impact. But if you’re building small, lightweight apps for the cloud, then you might consider using custom middleware directly (see chapter 19). You might want to also consider Microservices in .NET Core by Christian Horsdal Gam- melgaard (Manning, 2017). Alternatively, if you’re building an app with real-time functionality, you’ll probably want to consider using WebSockets instead of traditional HTTP requests. The WebSockets protocol allows high-performance two-way communication between a server and a cli- ent, but they make some things more complicated. You can use WebSockets with ASP.NET Core, but you’ll need to handle things such as connection dropouts, server scaling issues, and old browsers yourself. For details, see the documentation at http://mng.bz/Ol13. SignalR Core is in preview at the time of writing (https://github .com/aspnet/SignalR), but will handle some of these considerations for you, so it’s worth investigating.
  • 128.
    100 CHAPTER 4Creating web pages with MVC controllers method, as shown in figure 4.4. A binding model is normally a standard class, with properties that map to the requested data. We’ll look at binding models in detail in chapter 6. DEFINITION A binding model is an object that acts as a “container” for the data provided in a request that’s required by an action method. In this case, the binding model contains two properties: Category, which is “bound” to the "Simple" value, and the User property, which is bound to the "Andrew" value. These values are provided in the request URL’s path and are used to populate a bind- ing model of the TodoModel type. This binding model corresponds to the method parameter of the ListCategory action method. This binding model is passed to the action method when it executes, so it can be used to decide how to respond. For this example, the action method uses it to decide which to-do items to display on the page. EXECUTING AN ACTION USING THE APPLICATION MODEL The role of an action method in the controller is to coordinate the generation of a response to the request it’s handling. That means it should perform only a limited number of actions. In particular, it should  Validate that the data contained in the binding model provided is valid for the request.  Invoke the appropriate actions on the application model.  Select an appropriate response to generate based on the response from the application model. 1. A request is received and handled by the middleware. Request Action 2. The routing module directs the request to a specific controller and action. Binding model Routing 3. A binding model is built from the details provided in the request. Controller 4. The action is passed to the binding model and a method parameter and is executed. URL mapped to action method model.Category = "Simple" model.User = "Andrew" ListCategory(model) TodoController.ListCategory GET URL /todo/list/Simple/Andrew Action method is executed Figure 4.4 Routing a request to a controller and building a binding model. A request to the /todo/list/Simple/Andrew URL results in the ListCategory action being executed, passing in a populated binding model.
  • 129.
    101 An introduction toMVC Figure 4.5 shows the action method invoking an appropriate method on the applica- tion model. Here, you can see that the application model is a somewhat abstract con- cept that encapsulates the remaining non-UI part of your application. It contains the domain model, a number of services, and the database interaction. DEFINITION The domain model encapsulates complex business logic in a series of classes that don’t depend on any infrastructure and can be easily tested. The action method typically calls into a single point in the application model. In our example of viewing a product page, the application model might use a variety of ser- vices to check whether the user is allowed to view the product, to calculate the display price for the product, to load the details from the database, or to load a picture of the product from a file. Assuming the request is valid, the application model will return the required details to the action method. It’s then up to the action method to choose a response to generate. GENERATING A RESPONSE USING A VIEW MODEL Once the action method has called out to the application model that contains the application business logic, it’s time to generate a response. A view model captures the details necessary for the view to generate a response. DEFINITION A view model is a simple object that contains data required by the view to render a UI. It’s typically some transformation of the data contained in the application model, plus extra information required to render the page, for example the page’s title. The action method selects an appropriate view template and passes the view model to it. Each view is designed to work with a particular view model, which it uses to generate Action 1. The action uses the category and user provided in the binding model to determine which method to invoke in the application model. Application model Domain model Services Database interaction 2. The action method calls into services that make up the application model. This might use the domain model to calculate the price of the product, for example. Controller 3. The services load the details of the product from the database and return them to the action model. Figure 4.5 When executed, an action will invoke the appropriate methods in the application model.
  • 130.
    102 CHAPTER 4Creating web pages with MVC controllers the final HTML response. Finally, this is sent back through the middleware pipeline and out to the user’s browser, as shown in figure 4.6. It’s important to note that although the action method selects which view to display, it doesn’t select what is generated. It’s the view itself that decides what the content of the response will be. PUTTING IT ALL TOGETHER: A COMPLETE MVC REQUEST Now that you’ve seen each of the steps that goes into handling a request in ASP.NET Core using MVC, let’s put it all together from request to response. Figure 4.7 shows how all of the steps combine to handle the request to display the list of to-dos for user “Andrew” and the “Simple” category. The traditional MVC pattern is still visible in ASP.NET Core, made up of the action/controller, the view, and the application model. By now, you might be thinking this whole process seems rather convoluted—so many steps to display some HTML! Why not allow the application model to create the view directly, rather than having to go on a dance back and forth with the control- ler/action method? The key benefit throughout this process is the separation of concerns:  The view is responsible only for taking some data and generating HTML.  The application model is responsible only for executing the required business logic.  The controller is responsible only for validating the incoming request and select- ing the appropriate view to display, based on the output of the application model. By having clearly defined boundaries, it’s easier to update and test each of the compo- nents without depending on any of the others. If your UI logic changes, you won’t necessarily have to modify any of your business logic classes, so you’re less likely to introduce errors in unexpected places. 1. The ListCategory action builds a view model from the data provided by the application model. Action View 2. The controller selects the ListCategory view and passes it the view model containing the list of relevant to-dos. 3. The view uses the provided view model to generate an HTML response containing the details of the to-dos to display . View model Controller HTML 4. The response is sent back through the middleware pipeline. Figure 4.6 The action method builds a view model, selects which view to use to generate the response, and passes it the view model. It’s the view that generates the response.
  • 131.
    103 An introduction toMVC 1. A request is received for the URL /todo/list/Simple/Andrew. Request Action View 2. The routing module directs the request to the ListCategory action on the ToDoController and builds a binding model. 4. The controller selects the ListCategory view and passes it the view model containing the details about the product. 5. The view uses the provided view model to generate an HTML response, which is returned to the user. Application model Binding model View model Domain model Services Database interaction Routing 3. The action method calls into services that make up the application model to fetch details about the product and to build a view model. Controller HTML Figure 4.7 A complete MVC request for the list of to-dos in the “Simple” category for user “Andrew” The dangers of tight coupling Generally speaking, it’s a good idea to reduce coupling between logically separate parts of your application as much as possible. This makes it easier to update your application without causing adverse effects or requiring modifications in seemingly unrelated areas. Applying the MVC pattern is one way to help with this goal. As an example of when coupling rears its head, I remember a case a few years ago when I was working on a small web app. In our haste, we had not properly decoupled our business logic from our HTML generation code, but initially there were no obvious problems—the code worked, so we shipped it! A few months later, someone new started working on the app, and immediately “helped” by renaming an innocuous spelling error in a class in the business layer. Unfortunately, the names of those classes had been used to generate our HTML code, so renaming the class caused the whole website to break in users’ browsers! Suffice it to say, we made a concerted effort to apply the MVC pattern after that, and ensure we had a proper separation of concerns.
  • 132.
    104 CHAPTER 4Creating web pages with MVC controllers The examples shown in this chapter demonstrate the vast majority of the MVC mid- dleware functionality. It has additional features, such as the filter pipeline, that I’ll cover later (chapter 13), and I’ll discuss binding models in greater depth in chapter 6, but the overall behavior of the system is the same. Similarly, in chapter 9, I’ll discuss how the MVC design pattern applies when you’re generating machine-readable responses using Web API controllers. The pro- cess is, for all intents and purposes, identical, with the exception of the final result generated. In the next section, you’ll see how to add the MVC middleware to your application. Most templates in Visual Studio and the .NET CLI will include the MVC middleware by default, but you’ll see how to add it to an existing application and explore the vari- ous options available. 4.1.3 Adding the MvcMiddleware to your application The MVC middleware is a foundational aspect of all but the simplest ASP.NET Core applications, so virtually all templates include it configured by default. But to make sure you’re comfortable with adding MVC to an existing project, I’ll show how to start with a basic empty application and add the MVC middleware to it from scratch. The result of your efforts won’t be exciting yet. We’ll display “Hello World” on a web page, but it’ll show how simple it is to convert an ASP.NET Core application to use MVC. It also emphasizes the pluggable nature of ASP.NET Core—if you don’t need the functionality provided by the MVC middleware, then you don’t have to include it. Here’s how you add the MvcMiddleware to your application: 1 In Visual Studio 2017, choose File > New > Project. 2 From the New Project dialog, choose .NET Core, then select ASP.NET Core Web Application. 3 Enter a Name, Location, and optionally a solution name, and click OK. 4 Create a basic template without MVC by selecting the Empty Project template in Visual Studio, as shown in figure 4.8. You can create a similar empty project using the .NET CLI with the dotnet new web command.
  • 133.
    105 An introduction toMVC Ensure Enable Docker Support is unchecked. Select Empty template. Click OK to generate the application from the selected template. Select ASP .NET Core 2.0. Figure 4.8 Creating an empty ASP.NET Core template. The empty template will create a simple ASP.NET Core application that contains a small middleware pipeline, but not the MvcMiddleware.
  • 134.
    106 CHAPTER 4Creating web pages with MVC controllers 5 Edit your project file by right-clicking the project and selecting Edit Project .csproj, where Project is the name of your project, as shown in figure 4.9. 1. Right-Click on your project’s name in Solution Explorer. 2. Choose Edit csproj. The csproj file has the same name as your project. Figure 4.9 You can edit the csproj file in Visual Studio while you have the project open. Alternatively, edit the csproj file directly in a text editor.
  • 135.
    107 An introduction toMVC 6 The default ASP.NET Core 2.0 template references the Microsoft.AspNetCore .All metapackage in its csproj file: <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" /> This includes all of the packages in ASP.NET Core, so you won’t need to refer- ence any others.1 If you’re targeting the full .NET Framework instead of .NET Core, you can’t use the metapackage, so you’ll need to add the Microsoft.Asp- NetCore.Mvc package to your project explicitly. You can add NuGet packages using the graphical NuGet Package Manager in Visual Studio by editing the csproj file to include the package, or by running the following command from the project folder (not the solution folder): dotnet add package Microsoft.AspNetCore.Mvc This adds a <PackageReference> element to your project’s csproj file: <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0" /> 7 Add the necessary MVC services (in bold) in your Startup.cs file’s Configure- Services method: public void ConfigureServices(IServiceCollection services) { services.AddMvc(); } 8 Add the MvcMiddleware to the end of your middleware pipeline with the Use- Mvc extension method (in bold). For simplicity, remove any other middleware from the Configure method of Startup.cs for now: public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); } 9 Right-click your project in Solution Explorer and choose Add > Class, as shown in figure 4.10. 1 In ASP.NET Core 2.1, a different metapackage, Microsoft.AspNetCore.App, is referenced, which includes slightly fewer packages than the All metapackage. See https://github.com/aspnet/Announcements/issues/ 287 for details.
  • 136.
    108 CHAPTER 4Creating web pages with MVC controllers 10 In the dialog box, name your class HomeController and click OK, as shown in figure 4.11. 11 Add an action called Index (in bold) to the generated class: public class HomeController { public string Index() { return "Hello world!"; } } 3. Choose Class to add a basic class to your project. 1. Right-click on your project name to bring up the context menu. 2. Click Add to open the Add submenu. Figure 4.10 Adding a new class to your project
  • 137.
    109 An introduction toMVC Once you’ve completed all these steps, you should be able to restore, build, and run your application. NOTE You can run your project by pressing F5 from within Visual Studio (or by calling dotnet run at the command line from the project folder). This will restore any referenced NuGet packages, build your project, and start your application. Visual Studio will automatically open a browser window to access your application’s homepage. When you make a request to the "/" path, the application invokes the Index method on HomeController due to the way you configured routing in the call to UseMvc. Don’t worry about this for now; we’ll go into it in detail in the next chapter. This returns the "Hello world!" string value, which is rendered in the browser as plain text. You’re returning data rather than a view here, so it’s more of a Web API controller, but you could’ve created a ViewResult to render HTML instead. You access the MVC functionality by adding the Microsoft.AspNetCore.Mvc pack- age (or more commonly the Microsoft.AspNetCore.All metapackage) to your project. The MvcMiddleware relies on a number of internal services to perform its function, which must be registered during application startup. This is achieved with the call to AddMvc in the ConfigureServices method of Startup.cs. Without this, you’ll get exceptions at runtime when the MvcMiddleware is invoked, reminding you that the call is required. Click Add to add the class to your project. Leave Class selected. Enter a name for the new controller. Figure 4.11 Creating a new MVC controller class using the Add New Item dialog box
  • 138.
    110 CHAPTER 4Creating web pages with MVC controllers The call to UseMvc in Configure registers the MvcMiddleware itself in the middle- ware pipeline. As part of this call, the routes that are used to map URL paths to con- trollers and actions are registered. We used the default convention here, but you can easily customize these to match your requirements. NOTE I’ll cover routing in detail in the next chapter. As you might expect, the MvcMiddleware comes with a large number of options for configuring how it behaves in your application. This can be useful when the default conventions and configuration don’t meet your requirements. You can modify these options by passing a configuration function to the AddMvc call that adds the MVC ser- vices. This listing shows how you could use this method to customize the maximum number of validation errors that the MVC middleware can handle. public void ConfigureServices(IServiceCollection services) { services.AddMvc(options => { options.MaxModelValidationErrors = 100; }); } You can replace many parts of the MVC middleware internals this way, thanks to the extensible design of the middleware. You won’t often need to touch the MVC options, but it’s nice to be able to customize them when the need arises. Listing 4.1 Configuring MVC options in Startup.cs AddMvc has an overload that takes a lambda function. A number of properties are available to customize the MvcMiddleware behavior. Customizing the MVC middleware internals As I’ve hinted, the MvcMiddleware exposes a large amount of its internal configura- tion through the AddMvc method, as shown in the following figure. The options object contains many different properties that you can use to extend and modify the default behavior of the middleware. Some of the customizations options available when configuring the MvcMiddleware
  • 139.
    111 An introduction toMVC The final part of adding MVC to your application is creating the controllers that are invoked when a request arises. But what makes a class act as a controller? 4.1.4 What makes a controller a controller? Controllers in ASP.NET Core are classes that contain a logical grouping of action methods. How you define them is largely up to you, but there are a number of conven- tions used by the runtime to identify controllers. MVC or Web API controllers are discovered and used by the MvcMiddleware as long as they are instantiable (they have a public constructor, aren’t static, and aren’t abstract) and either:  Have a name ending in “Controller,” for example HomeController By manipulating these options, you can control things such as how data is read from the request, how the data should be validated, and how the output data is formatted. You can even modify the way actions function across your whole application. For details on the options available, see the API reference on https://docs.microsoft.com. Convention over configuration Convention over configuration is a sometimes-controversial approach to building applications, in which a framework makes certain assumptions about the structure or naming of your code. Conforming to these assumptions reduces the amount of boilerplate code a developer must write to configure a project—you typically only need to specify the cases where your requirements don’t match the assumptions. Imagine you have a number of provider classes that can load different types of files. At runtime, you want to be able to let the user select a file and you’d automatically select the correct provider. To do this, you could explicitly register all the providers in some sort of central configuration service, or you could use a convention to rely on the runtime to “find” your classes and do the wiring up for you. Typically, in .NET, this is achieved by using reflection to search through all the types in an assembly and find those with a particular name, that derive from a base class, or that contain specific named methods. ASP.NET Core takes this approach in a number of cases, perhaps most notably with the Startup class, whose configuration methods are “discovered” at runtime, rather than by explicitly implementing an interface. This approach can sometimes result in an almost magical effect of things working for no apparent reason, which some people find confusing. It can occasionally make debugging problems tricky due to the additional level of indirection at play. On the other hand, using conventions can result in terser code, as there’s no explicit wiring- up necessary, and can provide additional flexibility, for example by allowing the sig- nature of the methods in your Startup class to vary.
  • 140.
    112 CHAPTER 4Creating web pages with MVC controllers  Inherit from the Controller or ControllerBase class (or a class that inherits from these) The MvcMiddleware will identify any class that meets these requirements at runtime and make it available to handle requests as required. Although not required, the Mic- rosoft.AspNetCore.Mvc package provides a base class, Controller, that your control- lers can inherit from. It’s often a good idea to use this class given that it contains a number of helper methods for returning results, as you’ll see later in this chapter. TIP If you’re building a Web API, you can also inherit from ControllerBase. This includes many of the same helper methods, but no helpers for creating views. Another common convention is to place all your controller files in a Controllers sub- folder in your project, as shown in figure 4.12. This can be useful for organizing some projects, but it isn’t required for the MvcMiddleware to discover them. You’re free to place your controller files anywhere you like in your project folder. Based on these requirements, it’s possible to come up with a variety of naming conven- tions and hierarchies for your controllers, all of which will be discovered at runtime. In general, it’s far better to stick to the common convention of naming your control- lers by ending them with Controller, and optionally inheriting from the Controller base class. By convention, controllers are placed in a Controllers folder. Controllers do not have to be placed in the Controllers folder — they will still be discovered. Figure 4.12 Controller location conventions in ASP.NET Core. MVC applications often place controllers in a Controllers subfolder, but they can be located anywhere in your project—the MvcMiddleware will still identify and make them available to handle requests.
  • 141.
    113 MVC controllers andaction methods public class HomeController: Controller { public ViewResult Index() { return View(); } } public class ValuesController { public string Get() { return "Hello world!"; } } It’s worth noting that although these examples have (implicit) parameterless con- structors, it’s perfectly acceptable to have dependencies in your constructor. In fact, this is one of the preferred mechanisms for accessing other classes and services from your controllers. By requiring them to be passed during the construction of the con- troller, you explicitly define the dependencies of your controller, which, among other things, makes testing easier. The dependency injection container will automatically populate any required dependencies when the controller is created. NOTE See chapter 10 for details about configuring, and using, dependency injection. The controllers you’ve seen so far all contain a single action method, which will be invoked when handling a request. In the next section, I’ll look at action methods, how to define them, how to invoke them, and how to use them to return views. 4.2 MVC controllers and action methods In the first section of this chapter, I described the MVC design pattern and how it relates to ASP.NET Core. In the design pattern, the controller receives a request and is the entry point for UI generation. In ASP.NET Core, the entry point is an action method that resides in a controller. An action, or action method, is a method that runs in response to a request. MVC controllers can contain any number of action methods. Controllers provide a mechanism to logically group actions and so apply a common set of rules to them. For example, it’s simple to require a user to be logged in when accessing any action method on a given controller by applying an attribute to the controller; you don’t need to apply the attribute to every individual action method. NOTE You’ll see how to apply authorization requirements to your actions and controllers in chapter 15. Listing 4.2 Common conventions for defining controllers Suffix your controller names with Controller Inheriting from the Controller base class allows access to utility methods like View(). If you’re not using the utility methods in your controller, you don’t need to inherit from Controller.
  • 142.
    114 CHAPTER 4Creating web pages with MVC controllers Any public method on a controller acts as an action method, and so can be invoked by a client (assuming the routing configuration allows it). The responsibility of an action method is generally threefold:  Confirm the incoming request is valid.  Invoke the appropriate business logic corresponding to the incoming request.  Choose the appropriate kind of response to return. An action doesn’t need to perform all of these actions, but at the very least it must choose the kind of response to return. For a traditional MVC application that’s return- ing HTML to a browser, action methods will typically return either a ViewResult that the MvcMiddleware will use to generate an HTML response, or a RedirectResult, which indicates the user should be redirected to a different page in your application. In Web API applications, action methods often return a variety of different results, as you’ll see in chapter 9. It’s important to realize that an action method doesn’t generate a response directly; it selects the type of response and prepares the data for it. For example, returning a ViewResult doesn’t generate any HTML at that point, it indicates which view template to use and the view model it will have access to. This is in keeping with the MVC design pattern in which it’s the view that generates the response, not the controller. TIP The action method is responsible for choosing what sort of response to send; the view engine in the MvcMiddleware uses the action result to generate the response. It’s also worth bearing in mind that action methods should generally not be perform- ing business logic directly. Instead, they should call appropriate services in the appli- cation model to handle requests. If an action method receives a request to add a product to a user’s cart, it shouldn’t directly manipulate the database or recalculate cart totals, for example. Instead, it should make a call to another class to handle the details. This approach of separating concerns ensures your code stays testable and manageable as it grows. 4.2.1 Accepting parameters to action methods Some requests made to action methods will require additional values with details about the request. If the request is for a search page, the request might contain details of the search term and the page number they’re looking at. If the request is posting a form to your application, for example a user logging in with their username and pass- word, then those values must be contained in the request. In other cases, there will be no such values, such as when a user requests the homepage for your application. The request may contain additional values from a variety of different sources. They could be part of the URL, the query string, headers, or in the body of the request itself. The middleware will extract values from each of these sources and convert them into .NET types.
  • 143.
    115 MVC controllers andaction methods If an action method definition has method arguments, the additional values in the request are used to create the required parameters. If the action has no arguments, then the additional values will go unused. The method arguments can be simple types, such as strings and integers, or they can be a complex type, as shown here. public class HomeController: Controller { private SearchService _searchService; public HomeController(SearchService searchService) { _searchService = searchService; } public ViewResult Index() { return View(); } public IActionResult Search(SearchModel searchModel) { if(ModelState.IsValid) { var viewModel = _searchService.Search(searchModel); return View(viewModel); } return Redirect("/") } } In this example, the Index action method doesn’t require any parameters, and the method is simple—it returns a view to the user. The Search action method, con- versely, accepts a SearchModel object. This could contain multiple different proper- ties that are obtained from the request and are set on the model in a process called model binding. The SearchModel object is often described as a binding model. NOTE I’ll discuss model binding in detail in chapter 6. When an action method accepts parameters, it should always check that the model provided is valid using ModelState.IsValid. The ModelState property is exposed when you inherit from the base Controller or ControllerBase class and can be used to check that the method parameters are valid. You’ll see how the process works in chapter 6 when you learn about validation. Once an action establishes that the method parameters provided to an action are valid, it can execute the appropriate business logic and handle the request. In the case Listing 4.3 Example action methods The SearchService is provided to the HomeController for use in action methods. An action without parameters requires no additional values in the request. The method doesn’t need to check if the model is valid, it just returns a response. If the model was not valid, the method indicates the user should be redirected to the path “/”. If the model is valid, a view model is created and passed to the view. The action method requires the request to have values for the properties in SearchModel.
  • 144.
    116 CHAPTER 4Creating web pages with MVC controllers of the Search action, this involves calling the provided SearchService to obtain a view model. This view model is then returned in a ViewResult by calling the base method return View(viewModel); If the model wasn’t valid, then you don’t have any results to display! In this example, the action returns a RedirectResult using the Redirect helper method. When exe- cuted, this result will send a 302 redirect response to the user, which will cause their browser to navigate to the homepage. Note that the Index method returns a ViewResult in the method signature, whereas the Search method returns an IActionResult. This is required in the Search method in order to allow the C# to compile (as the View and Redirect helper meth- ods return different types of values), but it doesn’t change the final behavior of the methods. You could just as easily have returned an IActionResult in the Index method and the behavior would be identical. TIP If you’re returning more than one type of result from an action method, you’ll need to ensure your method returns an IActionResult. 4.2.2 Using ActionResult In the previous section, I emphasized that action methods only decide what to gener- ate, and don’t perform the generation of the response. It’s the IActionResult returned by an action method which, when executed by the MvcMiddleware using the view engine, will generate the response. This approach is key to following the MVC design pattern. It separates the decision of what sort of response to send from the generation of the response. This allows you to easily test your action method logic to confirm the right sort of response is sent for a given output. You can then separately test that a given IActionResult generates the expected HTML, for example. ASP.NET Core has many different types of IActionResult:  ViewResult—Generates an HTML view.  RedirectResult—Sends a 302 HTTP redirect response to automatically send a user to a specified URL.  RedirectToRouteResult—Sends a 302 HTTP redirect response to automati- cally send a user to another page, where the URL is defined using routing.  FileResult—Returns a file as the response.  ContentResult—Returns a provided string as the response.  StatusCodeResult—Sends a raw HTTP status code as the response, optionally with associated response body content.  NotFoundResult—Sends a raw 404 HTTP status code as the response. Each of these, when executed by the MvcMiddleware, will generate a response to send back through the middleware pipeline and out to the user.
  • 145.
    117 MVC controllers andaction methods VIEWRESULT AND REDIRECTRESULT When you’re building a traditional web application and generating HTML, most of the time you’ll be using the ViewResult, which generates an HTML response using Razor (by default). We’ll look at how this happens in detail in chapter 7. You’ll also commonly use the various redirect-based results to send the user to a new web page. For example, when you place an order on an e-commerce website you typically navigate through multiple pages, as shown in figure 4.13. The web applica- tion sends HTTP redirects whenever it needs you to move to a different page, such as when a user submits a form. Your browser automatically follows the redirect requests, creating a seamless flow through the checkout process. Browser ASP.NET Core application Checkout Buy Payment Submit Order Complete! 3. The user clicks Buy on the checkout page, which sends a POST to the web application. POST to /checkout 302 REDIRECT to /payment GET to /payment 200 OK (HTML) POST to /payment 302 REDIRECT to /order-complete GET to /order-complete 200 OK (HTML) 4. The ASP .NET Core application begins the checkout process and sends a 302 redirect response to the payment page. 5. The user’s browser automatically follows the redirect to the payment page. 6. The request for the payment page is handled by the app, generating an HTML page and returning it to the browser. 7. The user fills in the the payment form and clicks Submit which sends a POST to the web application. 8. The ASP .NET Core application processes the payment and sends a 302 redirect response to the order-complete page. 9. The user’s browser automatically follows the redirect to the order-complete page. 10. The request for the order- complete page is handled by generating an HTML page and returning it to the browser. 11. The user views the HTML order- complete page. GET to /checkout 200 OK (HTML) 1. The user begins by navigating to the checkout page, which sends a GET request to the ASP .NET Core application. 2. The request for the checkout page is handled by the app, generating an HTML page and returning it to the browser. £ 9 Figure 4.13 A typical POST, REDIRECT, GET flow through a website. A user sends their shopping basket to a checkout page, which validates its contents and redirects to a payment page without the user having to manually change the URL.
  • 146.
    118 CHAPTER 4Creating web pages with MVC controllers NOTFOUNDRESULT AND STATUSCODERESULT As well as HTML and redirect responses, you’ll occasionally need to send specific HTTP status codes. If you request a page for viewing a product on an e-commerce application, and that product doesn’t exist, a 404 HTTP status code is returned to the browser and you’ll typically see a “Not found” web page. The MvcMiddleware can achieve this behavior by returning a NotFoundResult, which will return a raw 404 HTTP status code. You could achieve a similar result using the StatusCodeResult and setting the status code returned explicitly to 404. Note that the NotFoundResult doesn’t generate any HTML; it only generates a raw 404 status code and returns it through the middleware pipeline. But, as discussed in the previous chapter, you can use the StatusCodePagesMiddleware to intercept this raw 404 status code after it’s been generated and provide a user-friendly HTML response for it. CREATING ACTIONRESULT CLASSES USING HELPER METHODS ActionResult classes can be created and returned using the normal new syntax of C#: return new ViewResult() If your controller inherits from the base Controller class, then you can also use a num- ber of helper methods for generating an appropriate response. It’s common to use the View method to generate an appropriate ViewResult, the Redirect method to gener- ate a RedirectResponse, or the NotFound method to generate a NotFoundResult. TIP Most ActionResult classes have a helper method on the base Controller class. They’re typically named Type, and the result generated is called Type- Result. For example, the Content method returns a ContentResult instance. As discussed earlier, the act of returning an IActionResult doesn’t immediately gen- erate the response—it’s the execution of an IActionResult by the MvcMiddleware, which occurs outside the action method. After producing the response, the Mvc- Middleware returns it to the middleware pipeline. From there, it passes through all the registered middleware in the pipeline, before the ASP.NET Core web server finally sends it to the user. By now, you should have an overall understanding of the MVC design pattern and how it relates to ASP.NET Core. The action methods on a controller are invoked in response to given requests and are used to select the type of response to generate by returning an IActionResult. In traditional web apps, the MvcMiddleware generates HTML web pages. These can be served to a user who’s browsing your app with a web browser, as you’d see with a traditional website. It’s also possible to use the MvcMiddleware to send data in a machine-readable format, such as JSON, by returning data directly from action meth- ods, as you’ll see in chapter 9. Controllers handle both of these use cases, the only tan- gible difference being the data they return. These are typically known as MVC and Web API controllers, respectively.
  • 147.
    119 Summary It’s important toremember that the whole MVC infrastructure in ASP.NET Core is a piece of middleware that runs as part of the middleware pipeline, as you saw in the previous chapter. Any response generated, whether a ViewResult or a Redirect- Result, will pass back through the middleware pipeline, giving a potential opportu- nity for middleware to modify the response before the web server sends it to the user. An aspect I’ve only vaguely touched on is how the MvcMiddleware decides which action method to invoke for a given request. This process is handled by the routing infrastructure and is a key part of MVC in ASP.NET Core. In the next chapter, you’ll see how to define routes, how to add constraints to your routes, and how they decon- struct URLs to match a single action and controller. Summary  MVC allows for a separation of concerns between the business logic of your application, the data that’s passed around, and the display of data in a response.  Controllers contain a logical grouping of action methods.  ASP.NET Core controllers inherit from either the Controller or Controller- Base class or have a name that ends in controller.  Action methods decide what sort of response to generate; action results handle the generation.  Action methods should generally delegate to services to handle the business logic required by a request, instead of performing the changes themselves. This ensures a clean separation of concerns that aids testing and improves applica- tion structure.  Action methods can have parameters whose values are taken from properties of the incoming request.  When building a traditional web application, you’ll generally use a ViewResult to generate an HTML response.  You can send users to a new URL using a RedirectResult.  The Controller base class exposes many helper methods for creating an ActionResult.  The MVC and Web API infrastructure is unified in ASP.NET Core. The only thing that differentiates a traditional MVC controller from a Web API control- ler is the data it returns. MVC controllers normally return a ViewResult, whereas Web API controllers typically return data or a StatusCodeResult.
  • 148.
    120 Mapping URLs tomethods using conventional routing In chapter 4, you learned about the MVC design pattern, and how ASP.NET Core uses it to generate the UI for an application. Whether you’re building a traditional HTML web application or creating a Web API for a mobile application, you can use the MvcMiddleware to generate a response. This is typically placed at the end of the middleware pipeline and handles requests after all the other middleware in the pipeline have executed. In ASP.NET Core, you build MVC applications by creating controller classes that contain action methods. An action method executes in response to an appropriate request. It’s responsible for invoking the required business logic in the application model and determining what type of result to return. That might be a ViewResult indicating the template to use for HTML generation, a RedirectResult to forward the user to another URL, a StatusCodeResult if you’re writing a Web API, and so on. This chapter covers  Mapping URLs to action methods using conventions  Using constraints and default values to match URLs  Generating URLs from route parameters
  • 149.
    121 Although not explicitlypart of the classic MVC design pattern, one crucial part of the MVC pattern in ASP.NET Core is selecting the correct action method to invoke in response to a given request, as shown in figure 5.1. This process is called routing and is the focus of this chapter. This chapter begins by identifying the need for routing, why it’s useful, and the advan- tages it has over traditional layout-based mapping. You’ll see several examples of rout- ing techniques, and the separation routing can bring between the layout of your code and the URLs you expose. The bulk of this chapter focuses on how to define your routes so that the correct action method executes in response to a request to a URL. I’ll show how to build pow- erful route templates and give you a taste of the available options. In section 5.5, I’ll describe how to use the routing system to generate URLs, which you can use to create links and redirect requests for your application. One of the ben- efits of using a routing system is that it decouples your action methods from the underlying URLs that are used to execute them. This allows you to change the URLs your app uses by tweaking the routing system, leaving your action methods untouched. Using URL generation lets you avoid littering your code with hardcoded URLs like /Product/View/3, and instead lets you generate them at runtime, based on the current routing system. By the end of this chapter, you should have a much clearer understanding of how an ASP.NET Core application works. You can think of routing as the glue that ties the middleware pipeline to the MVC design strategy used by the MvcMiddleware. With middleware, MVC, and routing under your belt, you’ll be writing web apps in no time! Router Request Each request is passed to the MVC router, which inspects the request’s URL. /begin-checkout Action CheckoutController.Start() Router Request /Product/View/3 Action ProductController.View() Router Request / Action HomeController.Index() The router selects a single action method to execute, based on the incoming URL and the app’s configuration. Figure 5.1 The router is responsible for mapping incoming requests to an action method that will be executed to handle the request.
  • 150.
    122 CHAPTER 5Mapping URLs to methods using conventional routing 5.1 What is routing? In chapter 3, you saw that an ASP.NET Core application contains a middleware pipe- line, which defines the behavior of your application. Middleware is well suited to handling both cross-cutting concerns, such as logging and error handling, and nar- rowly focused requests, such as requests for images and CSS files. To handle more complex application logic, you’ll typically use the MvcMiddleware at the end of your middleware pipeline, as you saw in chapter 4. This can handle an appropriate request by invoking a method, known as an action method, and using the result to generate a response. One aspect that I glossed over was how to select an action to execute when you receive a request. What makes a request “appropriate” for a given action? The process of mapping a request to a given handler is called routing. DEFINITION Routing in ASP.NET Core is the process of mapping an incoming HTTP request to a specific handler. In MVC, the handler is an action method. When you use the MvcMiddleware in your application, you configure a router to map any incoming requests to the MVC route handler. This takes in a URL and decon- structs it to determine which controller and action method it corresponds to. A simple routing pattern, for example, might determine that the /product/view URL maps to the View action on the ProductController, as shown in figure 5.2. You can define many different routing patterns, each of which can set how a number of different URLs map to a variety of action methods. You can also have multiple pat- terns that all point to the same action method from different URLs. Alternatively, you could have a pattern that would only match a single URL, and maps to a single spe- cific action. start-checkout user/{action} {controller}/{action}/{id} 1. The request is passed to the MvcMiddleware which passes the URL to the router. /Product/View Request Router Action ProductController.View() Route templates 2. The router consults the list of configured route templates and finds the first pattern that matches. 3. The route template specifies the controller and action that should be executed to handle the request. Figure 5.2 The router compares the request URL against a list of configured route templates to determine which action method to execute.
  • 151.
    123 What is routing? Inthe MvcMiddleware, the outcome of successfully routing a request will be a single selected action and its associated controller. The middleware will then use this action to generate an appropriate response. Exactly what your URLs will look like and the action methods they map to will obvi- ously be specific to your application, but you’ll see a number of typical conventions used. These can be applied to any number of applications, as many web apps follow a similar design. In this chapter, imagine you’ve been asked to build an online currency converter for a bank. The application will have traditional pages like a homepage and a contact page, but the meat of the website will be a series of pages for viewing the exchange rates of different currencies, and for converting from one exchange rate to the next. As you’re going to be trying to attract as much traffic as possible to your web app, it’s important that the URLs you use are easy to understand and make sense to both the customer and search engines. We’re going to start with simple examples, and as Why use routing? The first version of ASP.NET MVC introduced routing, back when ASP.NET Web Forms was the de facto Microsoft web framework. Routing was introduced as a way to decouple the URLs exposed by an application from the handlers that would generate the response. Before routing, URLs typically mapped to a physical file on disk that would handle a request. For example, a request to the /product.aspx URL would map to a product.aspx file that would reside in the root folder of the application. This file would contain the logic and HTML generation code required to service the request. This had the advantage of being simple to reason about, and it was easy to see all the URLs your application exposed by viewing the project directory structure. Unfortunately, this approach has several issues. One common problem is the renam- ing of files. If you needed to rename a file—maybe the filename had a typo in it—you were intrinsically changing the public URLs exposed to your users. Any previous links to the page would be broken, including any URLs within your own application that pointed to that page. Another common complaint is the “ugly” URLs that are often required. The typical method of passing variable data when not using routing is to pass it in the query string, resulting in URLs such as /product.aspx?id=3. Compare this to equivalents using routing such as /product/3 or /product/apple-tv, and hopefully the advan- tage here is clear. You can still use query string parameters in addition to routing, but your application doesn’t have to use them to allow variable segments in the URL. Fundamentally, routing enables you to explicitly define the URLs used to navigate your application, without tying you to a particular file layout or structure for your appli- cation. There are conventions that you’ll generally use in your application to make defining the URLs easier, but you’re free to vary the two independently if required.
  • 152.
    124 CHAPTER 5Mapping URLs to methods using conventional routing we make our way through the chapter, we’ll explore ways to improve the URLs your app exposes. As a starter, you might choose to expose the URLs shown in table 5.1, which would map to the associated actions. I hope you can see that the URLs are all quite similar in structure—this is a common convention but is completely customizable, as you’ll see later. The first segment of the URL maps to the name of the selected controller, and the second segment maps to the name of the action (except in the case of List, which was not explicitly specified). Addi- tionally, the id for the action method has been automatically assigned from the URL. This approach to creating URLs, where you directly infer the controller and action method from the segments of the URL is called convention-based. This approach is often used when building traditional HTML web applications using MVC, as it makes the structure easy to reason about for users and gives hackable URLs. DEFINITION Hackable URLs refer to the ability to guess the URL for an action you want to perform, based on previous URLs you’ve seen. For example, based on the URLs in table 5.1, you might expect that the URL to view order number 4 would be /orders/view/4. This property is generally seen as desir- able when building web applications. The ability to set default values for URL segments when they aren’t explicitly provided can be useful for allowing multiple URLs that point to the same action method. You saw it in table 5.1, where the List action was inferred from the /currencies URL. A common convention used by default in most ASP.NET Core MVC applications is that the homepage of your application is invoked using the HomeController.Index() action method. With the default conventions, any of the following URL paths will invoke this action:  /  /home  /home/index Table 5.1 Possible URLs and action method mappings for the customer section of a web application Exposed URL Maps to action method Notes /currencies CurrenciesController.List() Shows the list of all currencies you support /rates/view/1 RatesController.View(id) Shows the exchange rate for the currency with id=1 /rates/edit/4 RatesController.Edit(id) Shows the form for editing the exchange rate of the currency with id=4 /orders/customer/3 OrdersController.Customer(id) Shows all the previous orders for customer with id=1
  • 153.
    125 Routing to MVCcontrollers and actions These conventions make building web applications simpler to reason about and main- tain, as it’s easy to infer which controller and action will be called. TIP Where possible, it’s often a good idea to stick close to the default rout- ing conventions. This will make your app easier to maintain in the long run, especially for other people looking at your code. Depending on your requirements, a purely convention-based approach may not be sufficient to provide the URLs you need. What if you want a specific nonstandard URL for your checkout page, or you’re only told the required URL after you’ve already built the controllers? In table 5.1, the /currencies URL is used to execute the CurrenciesController.List action; what if you want to change this so the action exe- cutes when you navigate to the /view-currencies URL instead? Luckily, the routing system is sufficiently flexible that you can easily create URLs that map to arbitrary action methods. Here are two examples:  /start-checkout—Could map to CheckoutController.BillingAddress()  /view/rates—Could map to RatesController.Index() Another use case for judicious use of routing is to make URLs more user friendly. This can be useful on sites where having a readable URL is important for search engine optimization, such as e-commerce sites, public websites, or blogs. Take another look at table 5.1. The URL to view the current exchange rate for a currency was /rates/view/1. This would work fine, but it doesn’t tell users much— which currency will this show? Will it be a historical view or the current rate? Luckily, with routing it’s easy to modify your exposed URLs without having to change your controllers and actions at all. Depending on your routing configuration, you could easily set the URL pointing to the RatesController.View action method to any of the following:  /rates/view/1  /rates/USD  /current-exchange-rate-for-USD I know which of these I’d most like to see in the URL bar of my browser, and which link I’d be most likely to click! I hope these examples have provided a glimpse of the benefits of setting up sensible routes for your application. In the next section, you’ll see how to define your routes in ASP.NET Core, and we’ll look at setting up the rout- ing for the currency converter application in more detail. 5.2 Routing to MVC controllers and actions Routing is a key part of the MVC design pattern in ASP.NET Core, as it connects the incoming request to a specific controller action. Note that this only happens if the request reaches the MvcMiddleware in the middleware pipeline. The request could be short-circuited before reaching the MvcMiddleware, either due to a previous middleware handling the request and generating a response (such as the StaticFileMiddleware,
  • 154.
    126 CHAPTER 5Mapping URLs to methods using conventional routing for example) or a previous middleware generating an error. In these two cases, the Mvc- Middleware won’t run, and so no routing will occur. If the request does make it to the MvcMiddleware, then the first step is to route the request to the required action method. You have two different ways to define these mappings in your application:  Using global, conventional routing  Using attribute routing The convention-based routes are defined globally for your application. You can use convention-based routes to map all of the controllers and actions in your application, as long as your code conforms to the conventions you define. This provides a succinct and terse way to expose your action methods at easily understood URLs. Traditional HTML-based MVC web applications typically use this approach to routing. You can also use attribute-based routes to tie a given URL to a specific action method by placing [Route] attributes on the action methods themselves. This pro- vides a lot more flexibility as you can explicitly define what a URL for a given action method should be. This approach is more verbose than the convention-based approach, as it requires applying attributes to every action method in your applica- tion. Despite this, the additional flexibility it provides can often be useful, especially when building Web APIs. Whichever technique you use, you’ll define your expected URLs using route tem- plates. These define the pattern of the URL you’re expecting, with placeholders for parts that may vary. DEFINITION Route templates define the structure of known URLs in your appli- cation. They’re strings with placeholders for variables that can contain optional values and map to controllers and actions. A single route template can match a number of different URLs. The /product/index and /product URLs would both be matched by the product/{action=index} route template, for example, and the /customer/1 and /customer/2 URLs would both be matched by the customer/{id} route template. The route template syntax is power- ful and contains many different features that are controlled by splitting a URL into multiple segments. DEFINITION A segment is a small contiguous section of a URL. It’s separated from other URL segments by at least one character, often by the / character. Routing involves matching the segments of a URL to a route template. For a single-route template, you can define  Specific, expected strings  Variable segments of the URL  Optional segments of a URL  Default values when an optional segment isn’t provided  Constraints on segments of a URL, for example, ensuring that it’s numeric
  • 155.
    127 Routing to MVCcontrollers and actions Your application will often define a number of different route templates, perhaps defining different conventions for different sections of your application or providing more hackable URLs for certain important pages. You’ll see how to define multiple conventional routes in the next section. A typical conventional route will contain placeholders for different segments of a URL, where the controller and action are often inferred from the incoming URL. For example, the {controller}/{action} single route template would map both of the following URLs to their corresponding actions:  /customer/list—To the CustomerController.List() action  /product/view—To the ProductsController.View() action These route templates are global, so you define them in Startup.cs when adding the MvcMiddleware to your pipeline, as you’ll see in the next section. When routing an incoming URL, the middleware uses the full collection of defined conventional routes and attempts to determine the appropriate controller and action they correspond to. For each request, the MvcMiddleware determines the action to execute by follow- ing the process set out in figure 5.3. For each defined route template, the router attempts to split the request’s URL into segments corresponding to the template. If the URL pattern matches, the router infers the controller and action from the URL. If this points to a valid action method, then routing is complete and the action method is executed. If the route template doesn’t match the URL, or the specified controller-action pair doesn’t exist, then the router moves on to the next defined tem- plate and attempts to match that, until no more routes are left. If none of the route templates match the incoming URL, then the MvcMiddleware is unable to handle the request, so the request will continue along the middleware pipeline. As the MvcMiddleware is typically the last middleware in the pipeline, this normally means a 404 response will be generated. The alternative to this conventional approach, in which routes are defined globally for your application, is to define the specific URL that a given action maps to. Concep- tually, this is kind of the reverse approach; instead of taking a URL and working out which action it maps to, you take an action and generate the URL it maps to. When a request arrives, the middleware checks if it corresponds to any of these URLs. As this approach is tied so closely to the action methods themselves, it’s imple- mented using attributes placed on the action methods themselves, and hence is termed attribute routing. You’re free to combine both conventional and attribute routing in your applica- tions. Typically, you’ll use conventional routing for your MVC controllers serving HTML and attribute routing for your Web API controllers, where the ability to pre- cisely control an action’s associated route template can be useful. TIP Use conventional routing for controllers returning HTML and attribute routing for Web API controllers where possible.
  • 156.
    128 CHAPTER 5Mapping URLs to methods using conventional routing In the next section, you’ll learn how to define and use conventional routing in your application, but many of the concepts described apply equally when you’re using attri- bute routing. I’ll cover attribute routing and how it interacts with conventional rout- ing in chapter 9, when I discuss building a Web API. No Execute action No Yes URL Split URL into segments Current route Move to next route Are there any more routes? Yes Unable to handle URL: request continues on middleware pipeline Does URL match route? Yes Does action and controller exist? Route values Controller Action The controller and action are extracted from the route based on the URL. The MvcMiddleware is normally the last middleware in the pipeline, so this will normally result in a 404 response. The action is executed using route values extracted from the URL. If a route does not match, the next route is inspected. Move to first route When a URL arrives, it is split into segments and compared to the first defined route. Figure 5.3 Flow chart showing how the MvcMiddleware matches a URL against multiple routes to determine which action method to execute
  • 157.
    129 Routing using conventions 5.3Routing using conventions You configure conventional routing when you add the MvcMiddleware to your middle- ware pipeline with the UseMvc method. You’ve added this middleware several times so far in this book, so the following listing should look familiar. public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddMvc(); } public void Configure(IApplicationBuilder app) { app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); } } When you call UseMvc, you also provide a lambda that defines all of the global conven- tional routes for your application. Each call to MapRoute on the provided instance of IRouteBuilder configures a new conventional route. In the listing you can see you’ve added the default route template. This is a typical default added by most MVC project templates. It was created with a name so that it can be referenced from other parts of your program (as you’ll see later in the section on URL generation), and a template, which is the route template used to match against URLs. The route template itself has a rich syntax that splits a URL into a number of seg- ments. In the next section, you’ll learn about this syntax and see how it can be used to match a variety of URLs. This example only lists a single route so all the URLs in this application would have to conform to the same general structure. To add more variety, and handle more spe- cific requirements, it’s perfectly acceptable to configure multiple global convention- based routes. For the currency converter application, imagine a requirement exists to be able to view details about a particular currency (which country uses it and so on) by navigating to a URL that contains the currency code, such as /currency/USD or /currency/GBP. In listing 5.2, you’ll add an additional route for viewing currencies by name. When requests are made to the required URLs, this route will match and execute the Cur- renciesController.View() action method. Listing 5.1 Configuring the MvcMiddleware in the middleware pipeline
  • 158.
    130 CHAPTER 5Mapping URLs to methods using conventional routing public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddMvc(); } public void Configure(IApplicationBuilder app) { app.UseMvc(routes => { routes.MapRoute( name: "currency_by_code", template: "currency/{code}", defaults: new { controller="Currencies", action="View" }); routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); } } NOTE You may see these hardcoded route templates and be tempted to make them dynamic by loading them from a configuration file or similar. Although possible, this is generally unnecessary—the routes are intimately tied to both your app’s structure and its public API, so hardcoding the values here is the norm. The order in which you add the routes to the IRouteBuilder defines the order against which the routing infrastructure will attempt to match an incoming request. It will attempt to match the first route configured, and only if that isn’t a match will it look at subsequent routes. NOTE It’s important to consider the order of your conventional routes. You should list the most specific routes first, in order of decreasing specificity, until you get to the most broad/general route. Figure 5.4 shows the consequence of not ordering your routes correctly. A set of route templates has been defined in a social networking application:  Photos/{action}/{id?}—Routes to actions on PhotoController with the {id} ID  {controller}/{action}—Default convention, will route to many actions  Person/{name}/View—Routes to PersonController. View(), to view the detailed profile of the user with the {name} name Notice the ordering here—you’ve added the default conventional router before the custom route for viewing the detailed profile information of a user. Listing 5.2 Adding multiple routes to the MvcMiddleware The name of the route can be used during URL generation, as you’ll see later. The route template defines the structure of the URL to match.
  • 159.
    131 Routing using conventions Imagineyour app receives a request to the /Person/JoeBloggs/View URL. This incoming URL is a perfect match for the final route, but because the route preceding it is more general, the final route is never tested. Consequently, the JoeBloggs() action method is executed instead of the method you might expect, View(string name), with name = "JoeBloggs". To fix this problem, you can swap the order of the last two routes. This is obviously a somewhat contrived example—it’s unlikely you’ll have an action method called JoeBloggs!—but hopefully the principle is clear. Every conventional route must define the controller and action to run when a URL matches the route. This can be done either using the route template syntax, in which the controller and action name are taken directly from the URL, or by using default values, as you’ll see in the next section. 5.3.1 Understanding route templates When you define a route during the configuration of the MvcMiddleware, you specify a route template that defines the URL pattern the route will match. The route tem- plate has a rich, flexible syntax, but a simple example is shown in figure 5.5. A router parses a route template by splitting it into a number of segments. A seg- ment is typically separated by the / character, but it can be any valid character. Each segment is either  A literal value—For example, api in figure 5.5  A route parameter—For example, {controller} and {action} in figure 5.5 1. The request is passed to the MvcMiddleware which tests the URL against each route. /Person/JoeBloggs/View Router Action PersonController.JoeBloggs() Photos/{action}/{id?} {controller}/{action}/{id?} Route templates 2. The first route does not match, but the second route does, so it is selected. 4. The action selected corresponds to the selected template, though it is not the intended one (and in a real app probably won’t exist). Person/{name}/View 3. The final route template is technically a better match for the URL, but it is never tested as it is listed last. PersonController.View(string name) PersonController.JoeBloggs() Request Figure 5.4 The order in which conventional routes are configured in your application controls the order in which they’re matched to a URL during routing.
  • 160.
    132 CHAPTER 5Mapping URLs to methods using conventional routing Literal values must be matched exactly (ignoring case) by the request URL. If you need to match a particular URL exactly, you can use a template consisting only of literals. Imagine your boss tells you the currency converter app must have a contact page, which should be at the /about/contact URL. You could achieve this using the about/contact route template. This route template consists of only literal values, and so would only match this single URL. None of the following URLs would match this route template:  /about  /about-us/contact  /about/contact/email WARNING Note that although the URL paths start with a / character when a request is made to your application, the route templates don’t! If you include the / character, you’ll get an error at runtime when routing runs. Literal segments are often more useful when used in conjunction with route parame- ters. Route parameters are sections of a URL that may vary but still be a match for the template. Route parameters are defined by giving them a name, and placing them in braces, such as {controller} or {action}. When used in this way, parameters are still required, so there must be a segment in the request URL that they correspond to, but the value can vary. This ability to vary gives you great flexibility. For example, you could use the simple {controller}/{action} route template in the currency converter application to map the following request URLs:  /currencies/list—Where controller=currencies and action=list  /rates/view—Where controller=rates and action=view But note that this template would not map the following URLs:  /currencies/—No action parameter specified  /rates/view/USD—Extra URL segment not found in route template The literal segment and route parameters are the two cornerstones of the ASP.NET Core router. With these two concepts, it’s possible to build all manner of URLs for your application. When a route template defines a route parameter, and the route matches a URL, the value associated with the parameter is captured and stored in a dictionary of api/{controller}/{action} Literal segment Required route parameter Required route parameter Figure 5.5 A simple route template showing a literal segment and required route parameters
  • 161.
    133 Routing using conventions valuesassociated with the request. These route values typically drive other behavior in the MvcMiddleware, such as model binding. DEFINITION Route values are the values extracted from a URL based on a given route template. Each route parameter in a template will have an associated route value and is stored as a string pair in a dictionary. They can be used during model binding, as you’ll see in chapter 6. The most important use of route parameters in conventional routing is for determin- ing the controller and action that a route is associated with. Specifically, a conven- tional route must specify the {controller} and {action} route parameters so that the router can map the URL to an action to execute. It does this by first attempting to find a controller in your application named the same as the controller route value, which has an action with the same name as the action route value (ignoring case sensitivity). WARNING The controller and action route values must be able to be calcu- lated for every route template so that the router knows which action to look for. As well as the basic features of literals and variable route parameter segments, route templates can use a number of additional features to build more powerful conven- tions, and hence more varied URLs. These can let you have optional URL segments, can provide default values when a segment isn’t specified, or can place additional con- straints on the value that’s valid for a given route parameter. The next section takes a look at some of these, and ways you can apply them. 5.3.2 Using optional and default values In the previous section, you saw a simple route template, with a literal segment and two required routing parameters. In figure 5.6, you can see a more complex route that uses a number of additional features. The literal api segment and the required {controller} parameter are the same as you saw in figure 5.5. The {action} parameter looks similar, but it has a default value specified for it, index. If the URL doesn’t contain a segment corresponding to the action parameter, then the router will use the index value instead. The final segment of figure 5.6, {id?}, defines an optional route parameter called id. This segment of the URL is optional—if present, the router will capture the value for the id parameter; if it isn’t there, then it won’t create a route value for id. api/{controller}/{action=index}/{id?} Literal segment Required route parameter Optional route parameter with default value if not provided Optional route parameter Figure 5.6 A more complex route template showing literal segments, named route parameters, optional parameters, and default values
  • 162.
    134 CHAPTER 5Mapping URLs to methods using conventional routing You can specify any number of route parameters in your templates, and these val- ues will be available to you when it comes to model binding. Remember that only the {controller} and {action} parameters are compulsory, and that the router uses only these values to decide which action to execute. NOTE The router uses only the {controller} and {action} parameters to determine which action to execute. Model binding can use any other route parameter values, but these don’t affect action selection. The complex route template of figure 5.6 allows you to match a greater variety of URLs by making some parameters optional and providing a default for action. Table 5.2 shows some of the possible URLs this template would match, and the correspond- ing route values the router would create. Note that there’s no way to specify a value for the optional id parameter without also specifying the action and controller parameters. You can’t put an optional parame- ter before a required parameter, as there would be no way of specifying the required parameter only. Imagine your route template had an optional controller parameter: {controller?}/{action} Now try to think of a URL that would specify the action parameter, but not the controller. It can’t be done! Using default values allows you to have multiple ways to call the same URL, which may be desirable in some cases. Remember the default MVC route template from listing 5.1? {controller=Home}/{action=Index}/{id?} The meaning of this template should be a little more obvious now. It uses default val- ues for the {controller} and {action} parameters, which means multiple URLs can get you to the homepage of the application (typically the action HomeController .Index()):  /  /Home  /Home/Index Table 5.2 URLs that would match the template of figure 5.6 and their corresponding route values URL Route values api/product/view/3 controller=product, action=view, id = 3 api/product/view controller=product, action=view api/product controller=product, action=index api/customer/list controller=customer, action=list api/order controller=order, action=index api/order/edit/O-123 controller=order, action=edit, id = O-123
  • 163.
    135 Routing using conventions Eachof these URLs will execute the same action, as they will all match the default route template. By using default values and optional constraints, you can start to add some more interesting URLs to your currency converter application. For example, you could add a route to view currencies, where if you don’t specify a currency it assumes USD using {controller}/{currency=USD}/{action=view} I’ve added two default values here, so you could map any of the following URLs:  /rates—controller=rates, action=view, currency=USD  /rates/GBP—controller=rates, action=view, currency=GBP  /rates/USD/edit—controller=rates, action=edit, currency=USD  /currencies—controller=currencies, action=view, currency=USD Adding default values allows you to use shorter and more memorable URLs in your application for common URLs, but still have the flexibility to match a variety of other routes. 5.3.3 Adding additional constraints to route parameters By defining whether a route parameter is required or optional, and whether it has a default value, you can match a broad range of URLs with a pretty terse template syn- tax. Unfortunately, in some cases this can end up being a little too broad. Routing only matches URL segments to route parameters, it doesn’t know anything about the data that you’re expecting those route parameters to contain. Considering the {controller=Home}/{action=Index}/{id?} default template, the following URLs would all match:  /Home/Edit/test  /Home/Edit/123  /1/2/3 These URLs are all perfectly valid given the template’s syntax, but some might cause problems for your application. It might surprise you initially that all of those URLs also match the currency route template you defined at the end of the last section: {controller}/{currency=USD}/{action=view} These URLs all have three segments, and so the router happily assigns route values and matches the template when you probably don’t want it to!  /Home/Edit/test—controller=Home, currency=Edit, action=test  /Home/Edit/123—controller=Home, currency=Edit, action=123  /1/2/3—controller=1, currency=2, action=3 Typically, the router passes route values other than controller and action as param- eters to action methods through a process called model binding (which we’ll discuss in detail in the next chapter). For example, an action method with the public
  • 164.
    136 CHAPTER 5Mapping URLs to methods using conventional routing IActionResult Edit(int id) signature would obtain the id parameter from the id route value. If the id route parameter ends up assigned a noninteger value from the URL, then you’ll get an exception when it’s bound to the integer id action method parameter. To avoid this problem, it’s possible to add additional constraints to a route template that must be satisfied for a URL to be considered a match. Constraints can be defined in a route template for a given route parameter using : (a colon). For example, {id:int} would add the IntRouteConstraint to the id parameter. For a given URL to be considered a match, the value assigned to the id route value must be convertible to an integer. You can apply a large number of route constraints to route templates to ensure that route values are convertible to appropriate types. You can also check more advanced constraints, for example, that an integer value has a particular minimum value, or that a string value has a maximum length. Table 5.3 describes a number of the possible constraints available, but you can find a more complete list online at http://mng.bz/U11Q. TIP As you can see from table 5.3, you can also combine multiple con- straints; place a colon between them or use an optional mark (?) at the end. Using constraints allows you to narrow down the URLs that a given route template will match. Figure 5.7 shows an enhanced version of the flow chart shown in figure 5.3. After the router matches a URL to a route template, it interrogates the constraints to check that they’re all valid. If they are, then the router will continue to attempt to find an appropriate action method. If the constraints aren’t valid, then the router will check the next defined route template instead. Table 5.3 A few route constraints and their behavior when applied Constraint Example Match examples Description int {qty:int} 123, -123, 0 Matches any integer Guid {id:guid} d071b70c-a812- 4b54-87d2- 7769528e2814 Matches any Guid decimal {cost:decimal} 29.99, 52, -1.01 Matches any decimal value min(value) {age:min(18)} 18, 20 Matches integer values of 18 or greater length(value) {name:length(6)} andrew,123456 Matches string values with a length of 6 optional int {qty:int?} 123, -123, 0, null Optionally matches any integer optional int max(value) {qty:int:max(10)?} 3, -123, 0, null Optionally matches any integer of 10 or less
  • 165.
    137 Routing using conventions WARNINGDon’t use route constraints to validate general input, for example to check that an email address is valid. Doing so will result in 404 “Page not found” errors, which will be confusing for the user. You can use a simple constraint on the currency converter route to ensure that the cur- rency route parameter only matches strings of length 3 using a simple length constraint: {controller}/{currency=USD:length(3)}/{action=view} No Execute action No Yes URL Split URL into segments Current route Move to next route Are there any more routes? Yes Unable to handle URL: request continues on middleware pipeline Does URL match route? Yes Does action and controller exist? Route Controller Action If a route does not match, the next route . is inspected Are constraints satisfied? Yes Move to first route Figure 5.7 Flow chart indicating how the MvcMiddleware matches a URL against multiple routes to determine the action method to execute when a route template uses constraints
  • 166.
    138 CHAPTER 5Mapping URLs to methods using conventional routing Although not foolproof, this should help ensure the router only maps appropriate URLs to this route. 5.3.4 Defining default values and constraints using anonymous objects All of the examples shown in this section on route templates have defined default val- ues and route constraints inline as part of the route template, using the {action= view} and {id:int} syntax. It’s also possible to specify constraints and defaults separately when creating your routes, by using different overloads of MapRoute and anonymous objects. For exam- ple, the definition of your currency converter route template, in which constraints and defaults are defined inline, looks like the following at the moment: routes.MapRoute( name: "default", template: "{controller}/}/{currency=USD:length(3)}/{action=view}"); This same route could also be defined using an alternative overload of MapRoute. routes.MapRoute( name: "default", template: "{controller}/{currency}/{action}", defaults: new { currency="USD", action="View" }, constraints: new { currency = new LengthRouteConstraint(3) }); The inline approach to specifying constraints is normally sufficient for most route templates and is generally preferable for readability. But some route templates can’t be defined using the inline approach, and you must use the anonymous type approach. You might not want users to have to specify “currencies” at the start of the URL (for example, /currencies/USD), so that the router knows which controller to use. You can use anonymous objects to avoid this by removing controller from the tem- plate and specifying a default value for it. routes.MapRoute( name: "currencies", template: "{currency}/{action}", defaults: new { controller=currencies, currency="USD", action="View" }, Listing 5.3 Defining route constraints and default values using anonymous types Listing 5.4 Using anonymous types to set default values of missing parameters Required. The raw route template, without defaults or constraints Optional. The default values to use when a parameter is missing Optional. Constraints to apply to route parameters The controller parameter is set as required, but can’t be changed as it’s no longer in the template.
  • 167.
    139 Routing using conventions constraints:new { currency = new LengthRouteConstraint(3) }); Another common case is when you want to have a highly customized URL for a partic- ularly important action method. You might want the /checkout URL to point to the PaymentController.StartProcess() action method. You could define a route to per- form this mapping in a number of ways, one of which is shown here. routes.MapRoute( name: "start_checkout", template: "checkout", defaults: new { controller="Payment", action="StartProcess" }); As we’ve already discussed, you must always define the controller and action route parameters for a given route, so the middleware can work out which action to exe- cute. By using the defaults anonymous type, you can ensure the route is valid with- out having to include the name of the controller or the action in your template definition or URL. We’re coming to the end of our look at conventional route templates, but before we move on there’s one more type of parameter to think about, the catch-all parameter. 5.3.5 Matching arbitrary URLs with the catch-all parameter You’ve already seen how route templates take URL segments and attempt to match them to parameters or literal strings. These segments normally split around the slash character, /, so the route parameters themselves won’t contain a slash. What do you do if you need them to, or you don’t know how many segments you’re going to have? Going back to the currency converter application—you’ve already defined a route template that will match the /USD/view URL and will execute the action to view the USD currency. But what if you also want the URL to contain all the currencies to show exchange rates for? Here are some examples:  /USD/convert/GBP—Show USD currency with exchange rates to GBP  /USD/convert/GBP/EUR—Show USD currency with exchange rates to GBP and EUR  /USD/convert/GBP/EUR/CAD—Show USD with rates for GBP, EUR, and CAD If you want to support showing any number of currencies, then you need a way of cap- turing everything shown after the convert segment. Listing 5.6 shows how you can capture all these segments in a single parameter called others by using a catch-all parameter. Listing 5.5 Using anonymous types to set the default values of missing parameters The template will match a single URL only: /checkout. The controller and action must be defined.
  • 168.
    140 CHAPTER 5Mapping URLs to methods using conventional routing routes.MapRoute( name: "convert_currencies", template: "{currency}/convert/{*others}", defaults: new { controller="currencies", action="View" }); Catch-all parameters can be declared using an asterisk inside the parameter defini- tion, like {*others}. This will match the remaining unmatched portion of a URL, including any slashes or other characters that aren’t part of earlier parameters. They can also match an empty string. For the USD/convert/GBP/EUR URL, the value of others would be "GBP/EUR". In the listing, I’ve specified a fixed literal segment, "convert", so that part of the URL isn’t included in the catch-all title parameter. I’ve used the anonymous type approach again for specifying the controller and action parameters. An important point to bear in mind with catch-all parameters is that they’re greedy, and more general than most other routes. This can result in a catch-all route intercepting a URL intended for a more specific route. To get around this, define the catch-all route after the more specific routes. TIP Take particular care when ordering catch-all routes. They will capture the whole unmatched portion of a URL. Counteract greedy routes by defin- ing them later in your Startup.Configure method. Convention-based routing is normally the standard approach taken when creating an HTML-based web application. Conventional routing generally makes your applica- tion’s controller structure easy to understand and browse, which is mirrored in the URL surface for the application. Conveniently, they’re also succinct to define, with a single route definition serving for many different controllers and action methods. Sometimes however, and especially when building a Web API, you might find your- self creating more and more route templates that map to a single action. In those cases, attribute routing may be a better approach. I’ll show how to use attribute rout- ing in chapter 9. 5.4 Handling multiple matching actions for a route Whether you’re using conventional routing or attribute routing, you’ll often find that you want to match a single URL to two different action methods. One of the most common situations is when you’re submitting a form. In standard web application forms, a browser makes an HTTP GET request to a page to fetch the initial form from the server and display it to the user. A user might request the login page for a website using /Account/Login. The user then fills in the form, and submits the data using an HTTP POST request to the same URL. Listing 5.6 Using catch-all parameters to include slashes in a route parameter The template consists of a parameter for currency, a literal segment, and a catch-all parameter. The template doesn’t specify controller or action; you must define them here.
  • 169.
    141 Handling multiple matchingactions for a route Up to this point, I’ve said that the router uses only the URL to determine the selected controller and action, and that only a single action may be executed. If that’s the case, how can this possibly work?! Well, without further work, it won’t. The Mvc- Middleware would throw an exception when the URL is requested saying “Multiple actions matched.” Luckily, ASP.NET Core provides a number of attributes you can use to pick a win- ning action method, when the router would normally end up selecting multiple actions. Listing 5.7 shows an AccountController with the two action methods you want to disambiguate. It’s typical for the two corresponding methods to have the same name, where the POST action takes additional parameters corresponding to the form values entered by the user. public class AccountController : Controller { public IActionResult Login() { /* method implementation*/ } [HttpPost] public IActionResult Login(string username, string password) { /* method implementation*/ } } In order to select one action over the other for a given request, you’ve decorated the second method with an HttpPostAttribute. This limits the type of requests an action can match. In this case, the [HttpPost] attribute indicates that the second Login method is a match for POST requests only, and not any other HTTP methods. For the initial GET request, there’s now only one method that’s a match—the first action in the controller—so this action executes to generate the response. The user can then enter their details, press submit, and send a POST request. Now, both methods can still handle POST requests—the first method can handle any HTTP method and the second method can handle only POST requests. But an action method with an IActionContraint attribute “trumps” an action method with- out one. The second action method “wins” the contest and executes in response to the POST request. All the common HTTP methods, such as GET, POST, PUT, and DELETE, have IActionContraint HTTP methods. NOTE Use types of IActionConstraint, such as [HttpPost] and [HttpGet], to provide precedence to one action method over another where they would match the same URL. That brings us to the end of the discussion of mapping URLs to action methods when the MvcMiddleware receives a request. One of the most important things to keep in mind Listing 5.7 Selecting an action using the HttpPostAttribute As the method has no Http attribute, it can match GET or POST requests. The method names are identical, hence both methods match the same /Account/Login URL. Indicates the method is a match for POST requests only.
  • 170.
    142 CHAPTER 5Mapping URLs to methods using conventional routing when configuring your routing is to stick to a logical set of URLs. Doing so will make it easier for you, as well as your clients, to reason about which action a URL will execute. Mapping URLs to actions is only half of the responsibilities of the routing system. It’s also used to generate URLs so that you can easily reference your actions from other parts of your application. 5.5 Generating URLs from route parameters One of the by-products of using the routing infrastructure in ASP.NET Core is that your URLs can be somewhat fluid. If you rename a controller and you’re using convention-based routing, then the URLs associated with the actions will also change. For example, renaming the List action to Index on the CurrencyController would cause the URL for the action to change from /Currency/List to /Currency/Index. Trying to manually manage these links within your app would be a recipe for heart- ache, broken links, and 404s. If your URLs were hardcoded, then you’d have to remember to do a find-and-replace with every rename! Luckily, you can use the routing infrastructure to generate appropriate URLs dynamically at runtime instead, freeing you from the burden. Conceptually, this is almost an exact reverse of the process of mapping a URL to an action method, as shown in figure 5.8. Instead of taking a URL, finding the first route satisfied and split- ting it into route values, the router takes in the route values and finds the first route it can use to build a URL with them. Routing takes in a URL, uses the collection of routes, and outputs route values. URL Router Routes Route values URL generation takes in a collection of route values and uses the collection of routes to output a URL. Route values Router Routes URL Routing URL generation Figure 5.8 A comparison between routing and URL generation. Routing takes in a URL and generates route values, but URL generation uses a route value to generate a URL.
  • 171.
    143 Generating URLs fromroute parameters 5.5.1 Generating URLs based on an action name You might need to generate URLs in a number of places in your application, and one common location is in your controllers. The following listing shows how you could generate a link to the view page for a currency, using the Url helper from the Controller base class. public class CurrencyController : Controller { public IActionResult Index() { var url = Url.Action("View", "Currency", new { code = "USD" }); return Content(${"The URL is {url}"); } public IActionResult View(string code) { /* method implementation*/ } } The Url property is an instance of IUrlHelper that allows you to easily generate URLs for your application by referencing other action methods. It exposes an Action method to which you pass the name of the action method, the name of the controller, and any additional route data. The helper will then generate a URL based on the col- lection of registered conventional routes. TIP Instead of using strings for the name of the action method, use the C# 6 nameof operator to make the value refactor-safe, for example, nameof(View). The IUrlHelper has a number of different overloads of the Action method, some of which don’t require you to specify all the routing values. If you’re routing to an action in the same controller, you can omit the controller name when generating the URL. The helper uses ambient values from the current request and overrides these with any specific values you provide. DEFINITION Ambient values are the route values for the current request. They always include controller and action but can also include any additional route values set when the action was initially located using routing. In listing 5.8, as well as providing the controller and action name, I passed in an anon- ymous object, new { code = "USD" }. This object provides additional route values when generating the URL, in this case setting the code parameter to "USD". The IUrlHelper will use these values when finding the correct route to use for generation. It will pick the first route for which it has all the required route parameters. Listing 5.8 Generating a URL using IUrlHelper and the action name Deriving from Controller gives access to the Url property. With the default convention, this will return "The URL is /Currency/View/USD". You provide the controller and action name, along with any additional route values. The URL generated will route to the View action method.
  • 172.
    144 CHAPTER 5Mapping URLs to methods using conventional routing If a selected route explicitly includes the defined route value in its definition, such as in the "{controller}/{action}/{code}" route template, then the route value will be used in the URL path, such as in the example, /currency/view/GBP. If a route doesn’t contain the route value explicitly, such as in the "{controller}/ {action}" template, then the route value is appended as additional data as part of the query string, for example /currency/view?code=GBP. DEFINITION The query string is part of a URL that contains additional data that doesn’t fit in the path. It isn’t used by the routing infrastructure for identify- ing which action to execute, but can be used for model binding, as you’ll see in chapter 6. Generating URLs based on the action you want to execute is convenient, and the usual approach taken in most cases. Unfortunately, sometimes the nature of conven- tional routing means that generating the correct URL based on route parameters can be tricky. This is often the case when you have multiple conventional routes that over- lap in the URLs they cover. In these cases, routing based on a specific route’s name can sometimes be easier. 5.5.2 Generating URLs based on a route name The IUrlHelper class exposes the RouteUrl method for generating URLs based on a specific named route. This takes the name of a route and uses the provided route val- ues to generate a URL specifically for that route. For example, if you had defined the name "view_currency" when declaring the routes in Startup.cs routes.MapRoute( name: "view_currency", template: "{controller=currency}/{action=view}/{code}"); then you could use this during URL generation. public class CurrencyController : Controller { public IActionResult Index() { var url = Url.RouteUrl("view_currency", new { code = "GBP" }); return Content("The URL is {url}"); } public IActionResult View (string code) { /* method implementation*/ } } Listing 5.9 Generating a URL using a named route You provide the name of the route to use, along with any additional route values. This will use the specified route, returning "The URL is /Currency/View/GBP". The URL generated will route to the View action method.
  • 173.
    145 Generating URLs fromroute parameters Using named routes can sometimes simplify the process of generating URLs, as it avoids issues introduced by overlapping URLs in your route templates. The URL gen- eration process uses your defined list of routes in order to determine which URL to generate—if the route you need is at the end of registered routes, then referencing it by name can often be easier. 5.5.3 Generating URLs with ActionResults Generating a URL from within your controllers is less common than you might think—it’s more usual to generate a URL as part of the action result returned by an action method. This is normally the case when you want to redirect a user’s browser to a particular action or route. Conceptually, it’s the same as generating a URL using the Url property and sending the URL back as a redirect response to the user. Listing 5.13 shows two ways to generate URLs from an ActionResult. The RedirectToAction method takes in action and controller names, as well as route parameters, and generates a URL in the same way as the Url.Action method. Simi- larly, the RedirectToRoute is equivalent to Url.RouteUrl. public class CurrencyController : Controller { public IActionResult RedirectingToAnActionMethod() { return RedirectToAction("View", "Currency", new { id = 5 }); } public IActionResult RedirectingToARoute() { return RedirectToRoute("view_currency", new { code = "GBP" }); } public IActionResult RedirectingToAnActionInTheSameController() { return RedirectToAction("Index"); } public IActionResult Index() { /* method implementation */ } } As with the IUrlHelper, you can use a number of different overloads to generate the correct URL. The listing shows an example of providing the action name to the RedirectToAction method. This will use the ambient value of the controller Listing 5.10 Generating redirect URLs from an ActionResult The RedirectToAction method generates a RedirectToActionResult with the generated URL. The RedirectToRoute method generates a RedirectToRouteResult with the generated URL. Only the action name is specified, so the current controller will be used to generate the URL.
  • 174.
    146 CHAPTER 5Mapping URLs to methods using conventional routing parameter, "Currency" in this case, when generating URLs. Consequently, it will cre- ate a URL pointing to the CurrencyController.Index action. As well as generating URLs from your controllers, you’ll find you need to generate URLs when building HTML in your views. This is necessary in order to provide naviga- tion links in your web application. You’ll see how to achieve this when we look at Razor Tag Helpers in chapter 8, but the approach is nearly the same as you’ve already seen here. Congratulations, you’ve made it all the way through this detailed discussion on routing! Routing is one of those topics that people often get stuck on when they come to building an application, which can be frustrating. You’ll revisit the other approach to routing, attribute routing, when I describe how to create Web APIs in chapter 9, but rest assured, you’ve already covered all the tricky details in this chapter! The most important thing to focus on is keeping your routes simple. If you do, then you’ll find it much easier to reason about the URLs in your application and avoid giving yourself a headache trying to work out why the wrong action methods are executing! In chapter 6, we’ll dive into the M of MVC—the various models of ASP.NET Core. You’ll see how the route values generated during routing are bound to your action method parameters, and perhaps more importantly, how to validate the values you’re provided. Summary  Routing is the process of mapping an incoming request URL to an action method that will execute to generate a response.  Route templates define the structure of known URLs in your application. They’re strings with placeholders for variables that can contain optional values and map to controllers and actions.  Routes can be defined either globally using conventional routing or can be mapped explicitly to an action using attribute routing.  An application can have many different routes. The router will attempt to find the first route where the route template matches the incoming URL.  It’s important to consider the order of your conventional routes. You should list the most specific routes first, in order of decreasing specificity, until you get to the most broad/general route.  Route parameters are variable values extracted from a request’s URL.  Route parameters can be optional and can have default values used when they’re missing.  Route parameters can have constraints that restrict the possible values allowed. If a route parameter doesn’t match its constraints, the route isn’t considered a match.
  • 175.
    147 Summary  The controllerand action route values must be able to be calculated for every route template. They can either be matched from the URL or using default values.  Don’t use route constraints as general input validators. Use them to disambigu- ate between two similar routes.  Use a catch-all parameter to capture the remainder of a URL into a route value.  The [HttpPost] and [HttpGet] attributes allow choosing between actions based on the request’s HTTP method when two actions correspond to the same URL.  You can use the routing infrastructure to generate internal URLs for your application.  The IUrlHelper can be used to generate URLs as a string based on an action name or on the name of a specific route.  You can use the RedirectToAction and RedirectToRoute methods to generate URLs while also generating a redirect response.
  • 176.
    148 The binding model: retrievingand validating user input In chapter 5, I showed you how to define a route with parameters—perhaps for the day in a calendar or the unique ID for a product page. But say a user requests a given product page—what then? Similarly, what if the request includes data from a form, to change the name of the product, for example? How do you handle that request and access the values the user provided? In the first half of this chapter, we’ll look at using a binding model to retrieve those parameters from the request so that you can use them in your action meth- ods. You’ll see how to take the data posted in the form or in the URL and bind them This chapter covers  Using request values to create a binding model  Customizing the model binding process  Validating user input using DataAnnotations attributes
  • 177.
    149 Understanding the Min MVC to C# objects. These objects are passed to your action methods as method parameters so you can do something useful with them—return the correct diary entry or change a product’s name, for instance. Once your code is executing in an action method, you might be forgiven for think- ing that you can happily use the binding model without any further thought. Hold on now, where did that data come from? From a user—you know they can’t be trusted! The second half of the chapter focuses on how to make sure that the values provided by the user are valid and make sense for your app. You can think of the binding model as the input model to an action method, taking the user’s raw HTTP request and making it available to your code by populating a “plain old CLR object” (POCO). Once your action method has run, you’re all set up to use the output models in ASP.NET Core’s implementation of MVC—the view mod- els and API models. These are used to generate a response to the user’s request. We’ll cover them in chapters 7 and 9. Before we go any further, let’s recap the MVC design pattern and how binding models fit into ASP.NET Core. 6.1 Understanding the M in MVC MVC is all about the separation of concerns. The premise is that by isolating each aspect of your application to focus on a single responsibility, it reduces the interde- pendencies in your system. This makes it easier to make changes without affecting other parts of your application. The classic MVC design pattern has three independent components:  Controller—Calls methods on the model and selects a view.  View—Displays a representation of data that makes up the model.  Model—The data to display and the methods for updating itself. In this representation, there’s only one model, which represents all the business logic for the application as well as how to update and modify its internal state. ASP.NET Core has multiple models, which takes the single responsibility principle one step fur- ther than some views of MVC. In chapter 4, we looked at an example of a to-do list application that can show all the to-do items for a given category and username. With this application, you make a request to a URL that’s routed using todo/list/{category}/{username}. This will then return a response showing all the relevant to-do items, as shown in figure 6.1. The application uses the same MVC constructs you’ve already seen, such as routing to an action method, as well as a number of different models. Figure 6.2 shows how a request to this application maps to the MVC design pattern and how it generates the final response, including additional details around the model binding and validation of the request. ASP.NET Core MVC has a number of different models, most of which are POCOs, and the application model, which is more of a concept around a collection of services.
  • 178.
    150 CHAPTER 6The binding model: retrieving and validating user input Each of the models in ASP.NET Core MVC is responsible for handling a different aspect of the overall request:  Binding model—This includes information that’s explicitly provided by the user when making a request, as well as additional contextual data. This includes things like route parameters parsed from the URL, the query string, and form or JSON data in the request body. The binding model itself is one or more .NET objects that you define. They’re passed to a controller’s action method as parameters when it’s executed. For this example, the binding model would include the name of the cate- gory, open, and the username, Andrew. The MvcMiddleware inspects the binding model before the action method executes to check whether the provided values are valid, though the method will execute even if they’re not.  Application model—This is typically a whole group of different services and classes—anything needed to perform some sort of business action in your appli- cation. It may include the domain model (which represents the thing your app is trying to describe) and database models (which represent the data stored in a database), as well as any other additional services. In the to-do list application, the application model would contain the com- plete list of to-do items, probably stored in a database, and would know how to find only those to-do items in the open category assigned to Andrew. Modeling your domain is a huge topic, with many different possible approaches, so it’s outside the scope of this book, but we’ll touch briefly on cre- ating database models in chapter 11. The category and username are provided in the URL. The category and username are used to filter the list of to-do task items. The category and username can also be shown in the view model. Figure 6.1 A basic to-do list application that displays to-do list items. A user can filter the list of items by changing the category and username parameters in the URL.
  • 179.
    151 Understanding the Min MVC  View model—This contains the data needed by the view to generate a response, such as the list of to-dos in the open category assigned to Andrew. It often also contains extra data, such as the total number of to-dos in all categories.  API model—A variation on the view model, in which the whole model represents the data to return. Web API controllers use this to generate the final response to return to the caller, normally in the form of a JSON or XML response. You’ll see how to create and use API models in chapter 9. The main difference between API models and view models is that API models contain only the data to return, whereas view models often contain 1. A request is received for the URL /todo/list/open/Andrew. Request Action View 2. The routing module matches the request to the ListCategory action and derives the parameters category=open and username=Andrew. 7. The controller selects the ListCategory view and passes it the view model containing the list of to-do items, as well as other details, such as the category and username. 8. The view uses the provided view model to generate an HTML response, which is returned to the user. Application model View model Domain model Services Database interaction Routing 6. The action method calls into services that make up the application model to fetch the list of to-do items and to build a view model. Controller HTML Binding model Model binding Model validation Binding model Model state 3. The parameters parsed from the request are used to build a binding model, setting the Username and Category properties on the ToDoBindingModel. 4. After binding, the model is validated to check that it has acceptable values. The result of the validation is stored in ModelState. 5. The model (ToDoBindingModel) is passed to the ListCategory action as a method parameter, while the ModelState is available as a property on the controller. Figure 6.2 MVC in ASP.NET Core handling a request to view a subset of items in a to-do list application
  • 180.
    152 CHAPTER 6The binding model: retrieving and validating user input additional information related to generating the view. You define both types of model, and both can be POCOs. These four types of models make up the bulk of any MVC application, handling the input, business logic, and output of each controller action. Imagine you have an e- commerce application that allows users to search for clothes by sending requests to the URL /search/{query} URL, where {query} holds their search term:  Binding model—Would take the {query} route parameter from the URL and any values posted in the request body (maybe a sort order, or number of items to show), and bind them to a C# class, which acts as a throwaway data transport class. This would be passed as a method parameter to the action method when it’s invoked.  Application model—The services and classes that perform the logic. When invoked by the action method, this would load all the clothes that match the query, applying the necessary sorting and filters, and return the results back to the controller.  View model—The values provided by the application model would be added to the view model, along with other metadata, such as the total number of items available, or whether the user can currently check out. The important point about all these models is that their responsibilities are well- defined and distinct. Keeping them separate and avoiding reuse helps to ensure your application stays agile and easy to update. Now that you’ve been properly introduced to the various models in ASP.NET Core MVC, it’s time to focus on how to use them. This chapter looks at the binding models that are built from incoming requests—how are they created, and where do the values come from? 6.2 From request to model: making the request useful By now, you should be familiar with the MvcMiddleware handling a request by execut- ing an action method on a controller. You’ve also already seen a number of action methods, such as public IActionResult EditProduct(ProductModel product) Action methods are normal C# methods (there’s nothing particularly special about them), so the ASP.NET Core framework needs to be able to call them in the usual way. When action methods accept parameters as part of their method signature, such as the preceding product, the framework needs a way to generate those objects. Where exactly do they come from, and how are they created? I’ve already hinted that in most cases, these values come from the request itself. But the HTTP request that the server receives is a series of strings—how does the Mvc- Middleware turn that into a .NET object? This is where model binding comes in.
  • 181.
    153 From request tomodel: making the request useful DEFINITION Model binding extracts values from a request and uses them to cre- ate .NET objects that are passed as method parameters to the action being executed. The model binder is responsible for looking through the request that comes in and finding values to use. It then creates objects of the appropriate type and assigns these values to your model in a process called binding. These objects are then provided to your action methods as method parameters. This binding is a one-way population of an object from the request, not the two- way data-binding that desktop or mobile development sometimes uses. NOTE Some method parameters can also be created using dependency injec- tion, instead of from the request. For more details on dependency injection, see chapter 10. The MvcMiddleware can automatically populate your binding models for you using properties of the request, such as the URL they used, any headers sent in the HTTP request, any data explicitly POSTed in the request body, and so on. These models are then passed to your action methods as method parameters. By default, MVC will use three different binding sources when creating your binding models. It will look through each of these in order and will take the first value it finds (if any) that matches the name of the parameter:  Form values—Sent in the body of an HTTP request when a form is sent to the server using a POST.  Route values—Obtained from URL segments or through default values after matching a route.  Query string values—Passed at the end of the URL, not used during routing. The model binding process is shown in figure 6.3. The model binder checks each binding source to see if it contains a value that could be set on the model. Alterna- tively, the model can also choose the specific source the value should come from, as you’ll see later in this section. Once each property is bound, the model is validated and is passed to the action method to execute it. You’ll learn about the validation pro- cess in the second half of this chapter. Figure 6.4 shows an example of a request creating the ProductModel method argu- ment using model binding for the initial example, the EditProduct action method: public IActionResult EditProduct(ProductModel product) The Id property has been bound from a URL route parameter, but the Name and SellPrice have been bound from the request body. The big advantage of using model binding is that you don’t have to write the code to parse requests and map the data yourself. This sort of code is typically repetitive and error-prone, so using the built-in conventional approach lets you focus on the important aspects of your appli- cation: the business requirements.
  • 182.
    154 CHAPTER 6The binding model: retrieving and validating user input TIP Model binding is great for reducing repetitive code. Take advantage of it whenever possible and you’ll rarely find yourself having to access the Request object directly. If you need to, the capabilities are there to let you completely customize the way model binding works, but it’s relatively rare that you’ll find yourself needing to dig too deep into this. For the vast majority of cases it works as is, as you’ll see in the remain- der of this section. class ProductModel { public int Id {get; set;} public string Name {get; set;} } Form values Route values Query string values Form values Route values Query string values Each of the properties on the binding model tries to bind to a value from a binding source. The binding sources are checked in order, and the first value that matches is used. In this case, the Id property is bound to a route value. Because there is a form value that matches the name property, the route values and query string values are not checked in this case. Figure 6.3 Model binding involves mapping values from binding sources, which correspond to different parts of a request. An HTTP request is received and directed to the EditProduct action by routing. This method requires a ProductModel object. The model binder builds a new ProductModel object using values taken from the request. This includes route values from the URL, as well as data sent in the body of the request. Figure 6.4 Using model binding to create an instance of a model that’s used to execute an action method
  • 183.
    155 From request tomodel: making the request useful 6.2.1 Binding simple types You’ll start your journey into model binding by considering a simple action method. The next listing shows a simple calculator action method that takes one number as a method parameter and squares it by multiplying it by itself. public CalculatorController : Controller { public IActionResult Square(int value) { var result = value * value; var viewModel = new ResultViewModel(result); return View(viewModel); } } In the last chapter, you learned about routing and how this selects which action method to execute. The number of route templates that would match the action method you’ve defined is endless, but use the following simple template: {controller}/{action}/{value} When a client requests a URL, for example /Calculator/Square/5, the MvcMiddleware uses routing to parse it for route parameters. With the preceding template, this would produce the following route values:  controller=Calculator  action=Square  value=5 The router only uses the controller and action parameters for routing, but all the values are stored in a collection as name-value pairs. The router will look for the Calculator- Controller.Square action method, it will find the method defined in the previous list- ing and will then attempt to execute it. This action method contains a single parameter—an integer called value—which is your binding model. When the MvcMiddleware executes this action method, it will spot the expected parameter, flick through the route values associated with the request, and find the value=5 pair. It can then bind the value parameter to this route value and execute the method. The action method itself doesn’t care about where this value came from; it goes along its merry way, creating a view model and returning a ViewResult. The key thing to appreciate is that you didn’t have to write any extra code to try to extract the value from the URL when the method executed. All you needed to do was create a method parameter with the right name and let model binding do its magic. Listing 6.1 An action method accepting a simple parameter The method parameter is the binding model. A more complex example would do this work in an external service, in the application model. The result is passed to the view to generate a response using a view model.
  • 184.
    156 CHAPTER 6The binding model: retrieving and validating user input Route values aren’t the only values the model binder can use to create your action method parameters. As you saw previously, MVC will look through three default bind- ing sources to find a match for your action parameters:  Form values  Route values  Query string values Each of these binding sources store values as name-value pairs. If none of the binding sources contain the required value, then the method parameter receives a new instance of the type instead. The exact value the method parameter will have in this case depends on the type of the variable:  For value types, the value will be default(T). For an int parameter this would be 0, and for a bool it would be false.  For reference types, the type is created using the default constructor. For cus- tom types like ProductModel, that will create a new object. For nullable types like int? or bool?, the value will be null.  For string types, the value will be null. WARNING It’s important to consider the behavior of your action method if model binding fails to bind your method parameters. If none of the binding sources contain the value, the value passed to the method could be null, or have an unexpected default value. Listing 6.1 shows how to bind a single method parameter. Let’s take the next logical step and look at how you’d bind multiple method parameters. In the previous chapter, we looked at configuring routing while building a currency converter application. As the next step in your development, your boss asks you to cre- ate a method in which the user provides a value in one currency and you must convert it to another. You first create an action method that accepts the three values you need, as shown next, and configure a specific route template using "{currencyIn}/ {currencyOut}". public ConverterController { public IActionResult ConvertCurrency( string currencyIn, string currencyOut, int qty) { /* method implementation */ } } As you can see, there are three different parameters to bind. The question is, where will the values come from and what will they be set to? The answer is, it depends! Table 6.1 Listing 6.2 An action method accepting multiple binding parameters
  • 185.
    157 From request tomodel: making the request useful shows a whole variety of possibilities. All these examples use the same route and action method, but depending on the data sent, different values can be bound from what you might expect, as the available binding sources offer conflicting values. For each example, be sure you understand why the bound values have the values that they do. In the first example, the qty value isn’t found in the form data, in the route values, or in the query string, so it has the default value of 0. In each of the other examples, the request contains one or more duplicated values; in these cases, it’s important to bear in mind the order in which the model binder consults the binding sources. By default, form values will take precedence over other binding sources, including route values! NOTE The default model binder isn’t case sensitive, so a binding value of QTY=50 will happily bind to the qty parameter. Although this may seem a little overwhelming, it’s relatively unusual to be binding from all these different sources at once. It’s more common to have your values all come from the request body as form values, maybe with an ID from URL route values. This scenario serves as more of a cautionary tale about the knots you can twist yourself into if you’re not sure how things work under the hood! In these examples, you happily bound the qty integer property to incoming val- ues, but as I mentioned earlier, the values stored in binding sources are all strings. What types can you convert a string to? The model binder will convert pretty much any primitive .NET type such as int, float, decimal (and string obviously), plus any- thing that has a TypeConverter.1 There are a few other special cases that can be con- verted from a string, such as Type, but thinking of it as primitives only will get you a long way there! Table 6.1 Binding request data to action method parameters from two binding sources URL (route values) HTTP body data (form values) Parameter values bound /GBP/USD currencyIn=GBP, currencyOut=USD qty=0 /GBP/USD?currencyIn=CAD QTY=50 currencyIn=GBP, currencyOut=USD qty=50 /GBP/USD?qty=100 qty=50 currencyIn=GBP, currencyOut=USD qty=50 /GBP/USD?qty=100 currencyIn=CAD& currencyOut=EUR& qty=50 currencyIn=CAD, currencyOut=EUR qty=50 1 TypeConverters can be found in the System.ComponentModel.TypeConverter package. You can view the source code at http://mng.bz/CShQ.
  • 186.
    158 CHAPTER 6The binding model: retrieving and validating user input 6.2.2 Binding complex types If it seems like only being able to bind simple primitive types is a bit limiting, then you’re right! Luckily, that’s not the case for the model binder. Although it can only convert strings directly to those primitive types, it’s also able to bind complex types by traversing any properties your binding model exposes. In case this doesn’t make you happy straight off the bat, let’s look at how you’d have to bind your actions if simple types were your only option. Imagine a user of your currency converter application has reached a checkout page and is going to exchange some currency. Great! All you need now is to collect their name, email, and phone number. Unfortunately, your action method would have to look something like this: public IActionResult Checkout(string firstName, string lastName, string phoneNumber, string email) Yuck! Four parameters might not seem that bad right now, but what happens when the requirements change and you need to collect other details? The method signature will keep growing. The model binder will bind the values quite happily, but it’s not exactly clean code. SIMPLIFYING METHOD PARAMETERS BY BINDING TO COMPLEX OBJECTS A common pattern when you have many method parameters is to extract a class that encapsulates the data the method requires. If extra parameters need to be added, you can add a new property to this class. This class becomes your binding model and might look something like this. public UserBindingModel { public string FirstName { get; set; } public string LastName { get; set; } public string Email { get; set; } public string PhoneNumber { get; set; } } With this model, you can now update your action’s method signature to public IActionResult Checkout(UserBindingModel model) Functionally, the model binder treats this new complex type a little differently. Rather than looking for parameters with a value that matches the parameter name (model), it attempts to create a new instance of the model using new UserBindingModel(). NOTE You don’t have to use custom classes for your methods; it depends on your requirements. If your action method needs only a single integer, then it makes more sense to bind to the simple parameter. Next, the model binder loops through all the properties your model exposes, such as FirstName and LastName. For each of these properties, it consults the collection of Listing 6.3 A binding model for capturing a user’s details
  • 187.
    159 From request tomodel: making the request useful binding sources and attempts to find a name-value pair that matches. If it finds one, it sets the value on the property and moves on to the next. TIP Although the name of the model isn’t necessary in this example, the model binder will also look for properties prefixed with the name of the model, such as model.FirstName and model.LastName. This can be useful if you have multiple complex arguments to an action method, as it ensures the cor- rect properties are bound on each type. Once all the properties that can be bound on the binding model are set, the model is passed to the action method, which is executed as usual. The behavior from this point is identical to when you have lots of individual parameters—you’ll end up with the same values set on your binding model—the code is cleaner and easier to work with. TIP For a class to be model-bound, it must have a default public constructor. You can only bind properties which are public and settable. With this technique you can bind complex hierarchical models, whose properties are themselves complex models. As long as each property exposes a type that can be model-bound, the binder can traverse it with ease. BINDING COLLECTIONS AND DICTIONARIES As well as ordinary custom classes and primitives, you can bind to collections, lists, and dictionaries. Imagine you had a page in which a user selected all the currencies they were interested in; you’d display the rates for all those selected, as shown in figure 6.5. To achieve this, you could create an action method that accepted a List<string> type such as IActionResult ShowRates(List<string> currencies); Figure 6.5 The select list in the currency converter application will send a list of selected currencies to the application. Model binding can bind the selected currencies and customize the view for the user to show the equivalent cost in the selected currencies.
  • 188.
    160 CHAPTER 6The binding model: retrieving and validating user input You could then POST data to this method by providing values in a number of differ- ent formats:  currencies[index]—Where currencies is the name of the parameter to bind and index is the index of the item to bind, for example, currencies[0]= GBR&currencies[1]=USD.  [index]—If there’s only a single list, you can omit the name of the parameter, for example, [0]=GBR&[1]=USD.  currencies—Alternatively, you can omit the index and send currencies as the key value for every value, for example, currencies=GBR&currencies=USD. The key values can come from route values and query values, but it’s far more com- mon to POST them in a form. Dictionaries can use similar binding, where the dictio- nary key replaces the index both where the parameter is named and where it’s omitted. If this all seems a bit confusing, don’t feel too alarmed. If you’re building a tradi- tional web application, and using Razor views to generate HTML, then it will take care of most of the naming for you. As you’ll see in chapter 8, the view will ensure that any form data you POST will be generated in the correct format. BINDING FILE UPLOADS WITH IFORMFILE A common feature of many websites is the ability to upload files. This could be a rela- tively infrequent activity, for example if a user uploads a profile picture for their Stack Overflow profile, or it may be integral to the application, like uploading photos to Facebook. ASP.NET Core supports uploading files by exposing the IFormFile interface. You can use this interface as a method parameter to your action method and it will be popu- lated with the details of the file upload: public IActionResult UploadFile(IFormFile file); Letting users upload files to your application Uploading files to websites is a pretty common activity, but you should carefully con- sider whether your application needs that ability. Whenever files can be uploaded by users the road is fraught with danger. You should be careful to treat the incoming files as potentially malicious, don’t trust the filename provided, take care of large files being uploaded, and don’t allow the files to be executed on your server. Files also raise questions as to where the data should be stored—should they go in a database, in the filesystem, or some other storage? None of these questions has a straightforward answer and you should think hard about the implications of choos- ing one over the other. Better yet, if you can avoid it, don’t let users upload files!
  • 189.
    161 From request tomodel: making the request useful You can also use an IEnumerable<IFormFile> if your action method accepts multiple files: public IActionResult UploadFiles(IEnumerable<IFormFile> file); The IFormFile object exposes several properties and utility methods for reading the contents of the uploaded file, some of which are shown here: public interface IFormFile { string ContentType { get; } long Length { get; } string FileName { get; } Stream OpenReadStream(); } As you can see, this interface exposes a FileName property, which returns the filename that the file was uploaded with. But you know not to trust users, right? You should never use the filename directly in your code—always generate a new filename for the file before you save it anywhere. WARNING Never use posted filenames in your code. Users can use them to attack your website and access files they shouldn’t be able to. The IFormFile approach is fine if users are only going to be uploading small files. When your method accepts an IFormFile instance, the whole content of the file is buffered in memory and on disk before you receive it. You can then use the Open- ReadStream method to read the data out. If users post large files to your website, you may find you start to run out of space in memory or on disk, as it buffers each of the files. In that case, you may need to stream the files directly to avoid saving all the data at once. Unfortunately, unlike the model binding approach, streaming large files can be complex and error-prone, so it’s outside the scope of this book. For details, see the documentation at http://mng.bz/SH7X. TIP Don’t use the IFormFile interface to handle large file uploads as you may see performance issues. For the vast majority of your action methods, the default configuration of model bind- ing for simple and complex types works perfectly well, but you may find some situa- tions where you need to take a bit more control. Luckily, that’s perfectly possible, and you can completely override the process if necessary by replacing the ModelBinders used in the guts of the MvcMiddleware. It’s rare to need that level of customization—a far more common requirement is to be able to specify which binding source to use to bind an action method parameter.
  • 190.
    162 CHAPTER 6The binding model: retrieving and validating user input 6.2.3 Choosing a binding source As you’ve already seen, by default the ASP.NET Core model binder will attempt to bind all action method parameters from three different binding sources: form data, route data, and the query string. Occasionally, you may find it necessary to specifically declare which binding source to bind to, but in other cases, these three sources won’t be sufficient. The most com- mon scenarios are when you want to bind a method parameter to a request header value, or when the body of a request contains JSON-formatted data that you want to bind to a parameter. In these cases, you can decorate your action method parameters (or binding model class properties) with attributes that say where to bind from, as shown here. public class PhotosController { public IActionResult TagPhotosWithUser( [FromHeader] string userId, [FromBody] List<Photo> photos) { /* method implementation */ } } In this example, an action method updates a collection of photos with a user. You’ve got method parameters for the ID of the user to tag in the photos, userId, and a list of Photo objects to tag, photos. Rather than binding these action methods using the standard binding sources, you’ve added attributes to each parameter, indicating the binding source to use. The [FromHeader] attribute has been applied to the userId parameter. This tells the model binder to bind the value to an HTTP request header value called userId. You’re also binding a list of photos to the body of the HTTP request by using the [FromBody] attribute. This will read JSON from the body of the request and will bind it to the List<Photo> method parameter. WARNING Developers familiar with the previous version of ASP.NET should take note that the [FromBody] attribute is explicitly required when binding to JSON requests. This differs from previous ASP.NET behavior, in which no attribute was required.2 You aren’t limited to binding JSON data from the request body—you can use other formats too, depending on which InputFormatters you configure the MvcMiddleware Listing 6.4 Choosing a binding source for model binding 2 ASP.NET Core 2.1 introduces the [ApiController] attribute, which allows you to add model binding con- ventions designed for Web API controllers. This means [FromBody] is automatically used for complex parameters. For details see http://mng.bz/FYH1. The userId will be bound from an HTTP header in the request. The list of photos will be bound to the body of the request, typically in JSON format.
  • 191.
    163 From request tomodel: making the request useful to use. By default, only a JSON input formatter is configured. You’ll see how to add an XML formatter in chapter 9, when I discuss Web APIs. You can use a few different attributes to override the defaults and to specify a bind- ing source for each method parameter:  [FromHeader]—Bind to a header value  [FromQuery]—Bind to a query string value  [FromRoute]—Bind to route parameters  [FromForm]—Bind to form data posted in the body of the request  [FromBody]—Bind to the request’s body content You can apply each of these to any number of method parameters, as you saw in listing 6.4, with the exception of the [FromBody] attribute—only one parameter may be dec- orated with the [FromBody] attribute. Also, as form data is sent in the body of a request, the [FromBody] and [FromForm] attributes are effectively mutually exclusive. TIP Only one parameter may use the [FromBody] attribute. This attribute will consume the incoming request as HTTP request bodies can only be safely read once. As well as these attributes for specifying binding sources, there are a few other attri- butes for customizing the binding process even further:  [BindNever]—The model binder will skip this parameter completely.3  [BindRequired]—If the parameter was not provided, or was empty, the binder will add a validation error.  [FromServices]—Used to indicate the parameter should be provided using dependency injection (see chapter 10 for details). In addition, you’ve got the [ModelBinder] attribute, which puts you into “God mode” with respect to model binding. With this attribute, you can specify the exact binding source, override the name of the parameter to bind to, and specify the type of binding to perform. It’ll be rare that you need this one, but when you do, at least it’s there! By combining all these attributes, you should find you’re able to configure the model binder to bind to pretty much any data your action method wants to receive. If you’re building a traditional web app, then you’ll probably find you rarely need to use them; the defaults should work well for you in most cases. This brings us to the end of this section on model binding. If all has gone well, you should have a populated binding model, and the middleware can execute the action method. It’s time to handle the request, right? Nothing to worry about? Not so fast! How do you know that the data you received was valid? That you haven’t been sent malicious data attempting a SQL injection attack, or a phone num- ber full of letters? 3 You can use the [BindNever] attribute to prevent mass assignment, as discussed in this post: http://mng.bz/QvfG.
  • 192.
    164 CHAPTER 6The binding model: retrieving and validating user input The binder is relatively blindly assigning values sent in a request, which you’re hap- pily going to plug into your own methods? What’s to stop nefarious little Jimmy from sending malicious values to your application? Except for basic safeguards, there’s nothing stopping him, which is why it’s import- ant that you always validate the input coming in. ASP.NET Core provides a way to do this in a declarative manner out of the box, which is the focus of the second half of this chapter. 6.3 Handling user input with model validation Validation in general is a pretty big topic, and one that you’ll need to consider in every app you build. ASP.NET Core makes it relatively easy to add validation to your applications by making it an integral part of the framework. 6.3.1 The need for validation Data can come from many different sources in your web application—you could load it from files, read it from a database, or you could accept values that a user typed into a form in requests. Although you might be inclined to trust that the data already on your server is valid (though this is sometimes a dangerous assumption!), you definitely shouldn’t trust the data sent as part of a request. Validation occurs in the MvcMiddleware after model binding, but before the action executes, as you saw in figure 6.2. Figure 6.6 shows a more compact view of where model validation fits in this process, demonstrating how a request to a checkout page that requests a user’s personal details is bound and validated. 1. A request is received for the URL /checkout/saveuser .. Request Action 2. The routing module directs the request to the SaveUser action on the CheckoutController. Binding model Routing 3. Once an action method has been selected, a UserBindingModel is built from the details provided in the request. Controller The UserBindingModel and the validation model state resul t are passed to the SaveUser action as arguments when the action is executed. Model binding Model validation Binding model 4. The UserBindingModel is validated according to the DataAnnotation attributes on its properties. Model state Figure 6.6 Validation occurs after model binding but before the action method executes. The action executes whether or not validation is successful.
  • 193.
    165 Handling user inputwith model validation You should always validate data provided by users before you use it in your methods. You have no idea what the browser may have sent you. The classic example of “little Bobby Tables” (https://xkcd.com/327/) highlights the need to always validate any data sent by a user. The need for validation isn’t only to check for security threats, though; it’s also needed to check for nonmalicious errors, such as:  Data should be formatted correctly (email fields have a valid email format).  Numbers might need to be in a particular range (you can’t buy -1 copies of this book!).  Some values may be required but others are optional (name may be required for a profile but phone number is optional).  Values must conform to your business requirements (you can’t convert a cur- rency to itself, it needs to be converted to a difference currency). It might seem like some of these can be dealt with easily enough in the browser—for example, if a user is selecting a currency to convert to, don’t let them pick the same currency; and we’ve all seen the “please enter a valid email address” messages. Unfortunately, although this client-side validation is useful for users, as it gives them instant feedback, you can never rely on it, as it will always be possible to bypass these browser protections. It’s always necessary to validate the data as it arrives at your web application, using server-side validation. WARNING In your application, always validate user input on the server side. If that feels a little redundant, like you’ll be duplicating logic and code, then I’m afraid you’re right. It’s one of the unfortunate aspects of web development; the dupli- cation is a necessary evil. Thankfully, ASP.NET Core provides several features to try to reduce this burden. If you had to write this validation code fresh for every app, it would be tedious and likely error-prone. Luckily, you can simplify your validation code significantly using a set of attributes provided by .NET Core and the .NET Framework. 6.3.2 Using DataAnnotations attributes for validation Validation attributes, or more precisely DataAnnotations attributes, allow you to spec- ify rules that the properties in your model should conform to. They provide metadata about your model by describing the sort of data the model should contain, as opposed to the data itself. DEFINITION Metadata describes other data, specifying the rules and character- istics the data should adhere to. You can apply DataAnnotations attributes directly to your binding models to indicate the type of data that’s acceptable. This allows you to, for example, check that required fields have been provided, that numbers are in the correct range, and that email fields are valid email addresses.
  • 194.
    166 CHAPTER 6The binding model: retrieving and validating user input As an example, let’s consider the checkout page for your currency converter appli- cation. You need to collect details about the user before you can continue, so you ask them to provide their name, email and, optionally, a phone number. The following list- ing shows the UserBindingModel decorated with validation attributes that represent the validation rules for the model. This expands on the example you saw in listing 6.3. Public class UserBindingModel { [Required] [StringLength(100)] [Display(Name = "Your name")] public string FirstName { get; set; } [Required] [StringLength(100)] [Display(Name = "Last name")] public string LastName { get; set; } [Required] [EmailAddress] public string Email { get; set; } [Phone] [Display(Name = "Phone number")] public string PhoneNumber { get; set; } } Suddenly your binding model contains a whole wealth of information where previ- ously it was pretty sparse on details. For example, you’ve specified that the FirstName property should always be provided, that it should have a maximum length of 100 characters, and that when it’s referred to (in error messages, for example) it should be called "Your name" instead of "FirstName". The great thing about these attributes is that they clearly declare the expected state of the model. By looking at these attributes, you know what the properties will/should contain. They also provide hooks for the ASP.NET Core framework to validate that the data set on the model during model binding is valid, as you’ll see shortly. You’ve got a plethora of attributes to choose from when applying DataAnnotations to your models. I’ve listed some of the common ones here, but you can find more in the System.ComponentModel.DataAnnotations namespace. For a more complete list, I recommend using IntelliSense in Visual Studio/Visual Studio Code, or you can always look at the source code directly on GitHub (http://mng.bz/7N7s).  [CreditCard]—Validates that a property has a valid credit card format  [EmailAddress]—Validates that a property has a valid email address format Listing 6.5 Adding DataAnnotations to a binding model to provide metadata Values marked Required must be provided. Customizes the name used to describe the property The StringLengthAttribute sets the maximum length for the property. The StringLengthAttribute sets the maximum length for the property. Validates the value of Email is a valid email address
  • 195.
    167 Handling user inputwith model validation  [StringLength(max)]—Validates that a string has at most the max amount of characters  [MinLength(min)]—Validates that a collection has at least the min amount of items  [Phone]—Validates that a property has a valid phone number format  [Range(min, max)]—Validates that a property has a value between min and max  [RegularExpression(regex)]—Validates that a property conforms to the regex regular expression pattern  [Url]—Validates that a property has a valid URL format  [Required]—Indicates the property that must be provided  [Compare]—Allows you to confirm that two properties have the same value (for example, Email and ConfirmEmail) WARNING The [EmailAddress] and other attributes only validate that the format of the value is correct. They don’t validate that the email address exists. The DataAnnotations attributes aren’t a new feature—they have been part of the .NET Framework since version 3.5—and their usage in ASP.NET Core is almost the same as in the previous version of ASP.NET. They’re also used for other purposes, in addition to validation. Entity Framework Core (among others) uses DataAnnotations to define the types of column and rule to use when creating database tables from C# classes. You can read more about Entity Framework Core in chapter 12, and in Jon P Smith’s Entity Framework Core in Action (Manning, 2018). If the DataAnnotation attributes provided out of the box don’t cover everything you need, then it’s also possible to write custom attributes by deriving from the base ValidationAttribute. You’ll see how to create a custom attribute for your currency converter application in chapter 19. Alternatively, if you’re not a fan of the attribute-based approach, the MvcMiddleware is flexible enough that you can replace the validation infrastructure with your preferred technique. For example, you could use the popular FluentValidation library (https://github.com/JeremySkinner/FluentValidation) in place of the Data- Annotations attributes if you prefer. TIP DataAnnotations are good for input validation of properties in isola- tion, but not so good for validating business rules. You’ll most likely need to perform this validation outside the DataAnnotations framework. Whichever validation approach you use, it’s important to remember that these tech- niques don’t protect your application by themselves. The MvcMiddleware will ensure the validation occurs, but it doesn’t automatically do anything if validation fails. In the next section, we’ll look at how to check the validation result on the server and handle the case where validation has failed.
  • 196.
    168 CHAPTER 6The binding model: retrieving and validating user input 6.3.3 Validating on the server for safety Validation of the binding model occurs before the action executes, but note that the action always executes, whether the validation failed or succeeded. It’s the responsibil- ity of the action method to handle the result of the validation. NOTE Validation happens automatically but handling validation failures is the responsibility of the action method. The MvcMiddleware stores the output of the validation attempt in a property on the ControllerBase base class called ModelState. This object is a ModelStateDictionary, which contains a list of all the validation errors that occurred after model binding, as well as some utility properties for working with it. As an example, listing 6.6 shows the SaveUser action method being invoked with the UserBindingModel shown in the previous section. This action doesn’t do anything with the data currently, but the pattern of checking ModelState early in the method is the key takeaway here. public class CheckoutController : Controller { public IActionResult SaveUser(UserBindingModel model) { if(!ModelState.IsValid) { return View(model); } /* Save to the database, update user, return success * return RedirectToAction("Success"); } } If the ModelState indicates an error occurred, the method immediately calls the View helper method, passing in the model. This will return a ViewResult that will ultimately generate HTML to return to the user, as you saw in chapter 4. By passing in the model, the view can repopulate the values the user provided in the form when it’s dis- played, as shown in figure 6.7. Also, helpful messages to the user can be added using the validation errors in the ModelState. If the request is successful, the action method returns a RedirectToAction result that will redirect the user to the Success action on the CheckoutController. This pat- tern of returning a redirect response after a successful POST is called the POST- REDIRECT-GET pattern. Listing 6.6 Checking model state to view the validation result Deriving from Controller makes the ModelState property available. The model parameter contains the model- bound data. If there were validation errors, IsValid will be false. Validation failed, so redisplay the form with errors, and finish the method early. Validation passed, so it’s safe to use the data provided in model.
  • 197.
    169 Handling user inputwith model validation NOTE The error messages displayed on the form are the default values for each validation attribute. You can customize the message by setting the ErrorMessage property on any of the validation attributes. For example, you could customize a [Required] attribute using [Required(ErrorMessage= "Required")]. Figure 6.7 When validation fails, you can redisplay the form to display ModelState validation errors to the user. Note the “Your name” field has no associated validation errors, unlike the other fields. POST-REDIRECT-GET The POST-REDIRECT-GET design pattern is a web development pattern that prevents users from accidently submitting the same form multiple times. Users typically sub- mit a form using the standard browser POST mechanism, sending data to the server. This is the normal way by which you might take a payment, for example. If a server takes the naive approach and responds with a 200 OK response and some HTML to display, then the user will still be on the same URL. If the user refreshes their browser, they will be making an additional POST to the server, potentially making another payment! Browsers have some mechanisms to avoid this, such as in the fol- lowing figure, but the user experience isn’t desirable.
  • 198.
    170 CHAPTER 6The binding model: retrieving and validating user input You might be wondering why validation isn’t handled automatically—if validation has occurred, and you have the result, why does the action method get executed at all? Isn’t there a risk that you might forget to check the validation result? This is true, and in some cases the best thing is to make the generation of the vali- dation check and response automatic—this is often the case for Web APIs, and can be made automatic using filters, which we’ll cover in chapters 9 and 13. For traditional web apps however, you typically still want to generate an HTML response, even when validation failed. This allows the user to see the problem, and potentially correct it. This is often harder to make automatic, especially as the view model you use to generate your view and the binding model that’s validated are often different classes. By including the IsValid check explicitly in your action methods, it’s easier to control what happens when other validation fails. If the user tries to update a product, then you won’t know whether the requested product ID exists as part of the Data- Annotations validation, only whether the ID has the correct format. By moving the validation to the action method, you can treat data and business rule validation fail- ures similarly. (continued) The POST-REDIRECT-GET pattern says that in response to a successful POST, you should return a REDIRECT response to a new URL, which will be followed by the browser making a GET to the URL. If the user refreshes their browser now, then they’ll be refreshing the final GET call. No additional POST is made, so no additional pay- ments or side effects should occur. This pattern is easy to achieve in ASP.NET Core MVC applications using the pattern shown in listing 6.6. By returning a RedirectToActionResult after a successful POST, your application will be safe if the user refreshes the page in their browser. Refreshing a browser window after a POST causes warnings to be shown to the user
  • 199.
    171 Handling user inputwith model validation You might find you need to load additional data into the view model before you can regenerate the view—a list of available currencies, for example. That all becomes simpler and more explicit with the IsValid pattern. I hope I’ve hammered home how important it is to validate user input in ASP.NET Core, but just in case: VALIDATE! There, we’re good. Having said that, just perform- ing validation on the server can leave users with a slightly poor experience. How many times have you filled out a form online, submitted it, gone to get a snack, and come back to find out you mistyped something and have to redo it. Wouldn’t it be nicer to have that feedback immediately? 6.3.4 Validating on the client for user experience You can add client-side validation to your application in a couple of different ways. HTML5 has several built-in validation behaviors that many browsers will use. If you display an email address field on a page and use the “email” HMTL input type, then the browser will automatically stop you from submitting an invalid format, as shown in figure 6.8. Your application doesn’t control this validation, it’s built into modern HTML5 brows- ers.4 The alternative approach is to perform client-side validation by running Java- Script on the page and checking the values the user has entered before submitting the form. This is the default approach used in ASP.NET Core MVC when you’re building a traditional web application. I’ll go into detail on how to generate the client-side validation helpers in the next chapter, where you’ll see the DataAnnotations attributes come to the fore once again. By decorating a view model with these attributes, you provide the necessary metadata to the Razor engine for it to generate the appropriate HTML. With this approach, the user sees any errors with their form immediately, even before the request is sent to the server, as shown in figure 6.9. This gives a much shorter feedback cycle, providing a much better user experience. If you’re building an SPA, then the onus is on the client-side framework to validate the data on the client side before posting it to the Web API. The Web API will still val- idate the data when it arrives at the server, but the client-side framework is responsible for providing the smooth user experience. 4 HTML5 constraint validation support varies by browser. For details on the available constraints, see http://mng.bz/daX3. Figure 6.8 By default, modern browsers will automatically validate fields of the email type before a form is submitted.
  • 200.
    172 CHAPTER 6The binding model: retrieving and validating user input When you use the Razor templating engine to generate your HTML, you get much of this validation for free. It automatically configures the validation for the built-in attri- butes without requiring any additional work. Unfortunately, if you’ve used custom ValidationAttributes, then these will only run on the server by default; you need to do some wiring up of the attribute to make it work on the client side too. Despite this, custom validation attributes can be useful for handling common scenarios in your application, as you’ll see in chapter 19. That concludes this look at the binding models of MVC. You saw how the ASP.NET Core framework uses model binding to simplify the process of extracting values from a request and turning them into normal .NET objects you can quickly work with. The most important aspect of this chapter is the focus on validation—this is a common concern for all web applications, and the use of DataAnnotations can make it easy to add validation to your models. In the next chapter, we continue our journey through MVC by looking at how to create views. In particular, you’ll learn how to generate HTML in response to a request using the Razor templating engine. Figure 6.9 With client-side validation, clicking Submit will trigger validation to be shown in the browser before the request is sent to the server. As shown in the right-hand pane, no request is sent.
  • 201.
    173 Summary Summary  ASP.NET CoreMVC has three distinct models, each responsible for a different aspect of a request. The binding model encapsulates data sent as part of a request, the application model represents the state of the application, and the view model contains data used by the view to generate a response.  Model binding extracts values from a request and uses them to create .NET objects to pass as method parameters to the action being executed.  By default, there are three binding sources: POSTed form values, route values, and the query string. The binder will interrogate these in order when trying to bind an action method parameter.  When binding values to parameters, the names of the parameters aren’t case sensitive.  You can bind to simple method parameter types or to the properties of complex types.  To bind complex types, they must have a default constructor and public, setta- ble properties.  Simple types must be convertible to strings to be bound automatically, for example numbers, dates, and Boolean values.  Collections and dictionaries can be bound using the [index]=value and [key]=value syntax, respectively.  You can customize the binding source for an action parameter using [From*] attributes applied to the method, such as [FromHeader] and [FromBody]. These can be used to bind to nondefault binding sources, such as headers or JSON body content.  In contrast to the previous version of ASP.NET, the [FromBody] attribute is required when binding JSON properties (previously it was not required).  Validation is necessary to check for security threats. Check that data is format- ted correctly, confirm it conforms to expected values and that it meets your business rules.  ASP.NET Core provides DataAnnotations attributes to allow you to declara- tively define the expected values.  Validation occurs automatically after model binding, but you must manually check the result of the validation and act accordingly in your action method by interrogating the ModelState property.  Client-side validation provides a better user experience than server-side valida- tion alone, but you should always use server-side validation.  Client-side validation uses JavaScript and attributes applied to your HTML ele- ments to validate form values.
  • 202.
    174 Rendering HTML using Razorviews In the previous four chapters, I’ve covered a whole cross-section of MVC, including the MVC design pattern itself, controllers and actions, routing, and binding mod- els. This chapter shows how to create your views—the UI to your application. For desktop apps, that might be WinForms or WPF. For Web APIs, that might be a non- visual view, such as a JSON or XML response, which is subsequently used to gener- ate HTML in the client’s browser. For traditional web applications, the UI is the HTML, CSS, and JavaScript that’s delivered to the user’s browser directly, and that they interact with. This chapter covers  Creating views to display HTML to a user  Using C# and the Razor markup syntax to generate HTML dynamically  Reusing common code with layouts and partial views
  • 203.
    175 Views: rendering theuser interface in MVC In ASP.NET Core, views are normally created using the Razor markup syntax (sometimes described as a templating language), which uses a mixture of HTML and C# to generate the final result. This chapter covers some of the features of Razor and how to use it to build the view templates for your application. Generally speaking, users will have two sorts of interactions with your app: they read data that your app displays, and they send data or commands back to it. The Razor language contains a number of constructs that make it simple to build both types of applications. When displaying data, you can use the Razor language to easily combine static HTML with values from your view model. Razor can use C# as a control mechanism, so adding conditional elements and loops is simple, something you couldn’t achieve with HTML alone. The normal approach to sending data to web applications is with HTML forms. Virtually every dynamic app you build will use forms; some applications will be pretty much nothing but forms! ASP.NET Core and the Razor templating language include a number of helpers that make generating HTML forms easy, called Tag Helpers. NOTE You’ll get a brief glimpse of Tag Helpers in the next section, but I’ll explore them in detail in the next chapter. In this chapter, we’ll be focusing primarily on displaying data and generating HTML using Razor, rather than creating forms. You’ll see how to render values from your view model to the HTML, and how to use C# to control the generated output. Finally, you’ll learn how to extract the common elements of your views into sub-views called layouts and partial views, and how to compose them to create the final HTML page. 7.1 Views: rendering the user interface in MVC As you know from earlier chapters on the MVC design pattern, it’s the job of the con- troller and action method to choose which view to return to the client. For example, if you’re developing a to-do list application, imagine a request to view a particular to-do item, as shown in figure 7.1. A typical request follows the steps shown in figure 7.1: 1 The request is received by Kestrel and uses routing to determine the action to invoke—the ViewToDo method on ToDoController. 2 The model binder uses the request to build the action method’s binding model, as you saw in the last chapter. This is passed to the action method as a method parameter when it’s executed. The action method checks that you have passed a valid id for the to-do item, making the request valid. 3 Assuming all looks good, the action method calls out to the various services that make up the application model. This might load the details about the to-do from a database, or from the filesystem, returning them to the controller. As part of this process, either the application model or the controller itself gener- ates a view model.
  • 204.
    176 CHAPTER 7Rendering HTML using Razor views The view model is a custom class, typically a POCO, that contains all the data required to render a view. In this example, it contains details about the to-do itself, but it might also contain other data: how many to-dos you have left, whether you have any to-dos scheduled for today, your username, and so on, anything that controls how to generate the end UI for the request. 4 The final task for the action method is to choose a view template to render. In most cases, an action method will only need to render a single type of template, but there are situations when you might like to render a different one depend- ing on the data you get back from the application model. For example, you may have one template for to-do items that contain a list of tasks, and a different template when the to-do item contains a picture, as in figure 7.2. 5 The selected view template uses the view model to generate the final response, and returns it back to the user via the middleware pipeline. A common thread throughout this discussion of MVC is the separation of concerns it brings, and this is no different when it comes to your views. It would be easy enough to directly generate the HTML in your application model or in your controller actions, but instead you’ll delegate that responsibility to a single component, the view. 1. A request is received to the URL /todo/View/3. Request Action View 2. The routing module directs the request to the ViewToDo action on the ToDoController and builds a binding model. 4. The controller selects an appropriate view and passes it the view model containing the details about the to-do item. 5. The view uses the provided view model to generate an HTML response that is returned to the user. Application model Binding model View model Domain model Services Database interaction Routing 3. The action method calls into services that make up the application model to fetch details about the to-do item and to build a view model. Controller HTML id = 3 Figure 7.1 Handling a request for a to-do list item using ASP.NET Core MVC
  • 205.
    177 Views: rendering theuser interface in MVC But even more than that, you’ll also separate the data required to build the view from the process of building it, by using a view model. The view model should contain all the dynamic data needed by the view to generate the final output. An action method selects a template and requests that a view be rendered by returning a ViewResult object that contains the view model. You saw this type in chap- ter 4, but we’ll look closer at how you they’re typically used in the following sections. When a ViewResult executes, it locates the requested Razor template and exe- cutes the content. The use of C# in the template means you can dynamically generate the final HTML sent to the browser. This allows you to, for example, display the name of the current user in the page, hide links the current user doesn’t have access to, or render a button for every item in a list. Imagine your boss asks you to add a page to your application that displays a list of the application’s users. You should also be able to view a user from the page, or create a new one, as shown in figure 7.3. With Razor templates, generating this sort of dynamic content is simple. For exam- ple, the following listing shows the template that could be used to generate the inter- face in figure 7.3. It combines standard HTML with C# statements, and uses Tag Helpers to generate the form elements. Figure 7.2 The controller is responsible for selecting the appropriate template to render the view model. For example, a list of to-do items can be displayed using a different template to a to-do item consisting of a picture.
  • 206.
    178 CHAPTER 7Rendering HTML using Razor views @model IndexViewModel <div class="row"> <div class="col-md-12"> <form asp-action="Index"> <div class="form-group"> <label asp-for="NewUser"></label> <input class="form-control" asp-for="NewUser" /> <span asp-validation-for="NewUser"></span> </div> <div class="form-group"> <button type="submit" class="btn btn-success">Add</button> </div> </form> </div> </div> <h4>Number of users: @Model.ExistingUsers.Count</h4> <div class="row"> <div class="col-md-12"> <ul> @foreach (var user in Model.ExistingUsers) Listing 7.1 A Razor template to list users and a form for adding a new user Form elements can be used to send values back to the application. The view model contains the data you wish to display on the page. Razor markup describes how to display this data using a mixture of HTML and C#. By combining the data in your view model with the Razor markup, HTML can be generated dynamically, instead of being fixed at compile time. @foreach(var user in Model.ExistingUsers) { <li> <button>View</button> <span>@user</span> </li> } Model.ExistingUsers = new[] { 'Andrew', 'Robbie', 'Jimmy', 'Bart' }; Figure 7.3 The use of C# in Razor lets you easily generate dynamic HTML that varies at runtime. Normal HTML is sent to the browser unchanged. Tag Helpers attach to HTML elements to create forms. Values can be written from C# objects to the HTML. C# constructs like for loops can be used in Razor.
  • 207.
    179 Creating Razor views { <li> <aclass="btn btn-default" asp-action="ViewUser" asp-route-userName="@user">View</a> <span>@user</span> </li> } </ul> </div> </div> This example demonstrates a variety of Razor features. There’s a mixture of HTML that’s written unmodified to the response output, and various C# constructs that are used to dynamically generate HTML. In addition, you can see a number of Tag Help- ers. These look like normal HTML attributes that start asp-, but they’re part of the Razor language. They can customize the HTML element they’re attached to, chang- ing how it’s rendered. They make building HTML forms much simpler than they would be otherwise. Don’t worry if this template is a bit overwhelming at the moment; we’ll break it all down as you progress through this chapter and the next. You add Razor templates to your project as cshtml files. They’re sometimes copied directly to the server when you deploy your application and are compiled at runtime by your application. If you modify one of these files, then your app will automatically recompile it on the fly. If you don’t need that capability and would rather take the one-time performance hit at compile time, then you can also precompile these files when you build your app. Precompilation is the default in ASP.NET Core 2.0. NOTE Like most things in ASP.NET Core, it’s possible to swap out the Razor templating engine and replace it with your own server-side rendering engine. You can’t replace Razor with a client-side framework like AngularJS or React. If you want to take this approach, you’d use Web APIs instead. I’ll discuss Web APIs in detail in chapter 9. In the next section, you’ll learn how to choose a view template to invoke from your controller and how to pass your view model to it to build the HTML response. 7.2 Creating Razor views With ASP.NET Core, whenever you need to display an HTML response to the user, you should use a view to generate it. Although it’s possible to directly return a string from your action methods that would be rendered as HTML in the browser, this approach doesn’t adhere to the MVC separation of concerns and will quickly leave you tearing your hair out. NOTE Some middleware, such as the WelcomePageMiddleware you saw in chap- ter 3, may generate HTML responses without using a view, which can make sense in some situations. But your MVC controllers should always generate HTML using views. Tag Helpers can also be used outside of forms to help in other HTML generation.
  • 208.
    180 CHAPTER 7Rendering HTML using Razor views This chapter focuses on the final part of the ASP.NET Core MVC pattern, the view generation. Figure 7.4 shows a zoomed-in view of this process, right after the action has invoked the application model and received some data back. Some of this figure should be familiar—it’s similar to the lower half of figure 7.1. It shows that the action method uses a ViewResult object to indicate that a view should be created. This ViewResult contains the view model and the name of the view tem- plate to render. At this point, the control flow passes back to the MVC middleware, which uses a series of heuristics (as you’ll see shortly) to locate the view, based on the template name provided. Once a template has been located, the Razor engine passes it the view model from the ViewResult and invokes it to generate the final HTML. You saw how to create controllers in chapter 4, and in this section, you’ll see how to create views and ViewResult objects and how to specify the template to render. You can add a new view template to your application in Visual Studio by right-clicking in your application in Solution Explorer and choosing Add > New Item, and selecting MVC View Page from the dialog, as shown in figure 7.5. If you aren’t using Visual Stu- dio, create a blank new file with the cshtml file extension. Action View 1. The final step taken by MVC action method is to generate a view model and select the name of the template to render. 4. Once located, the View template is passed the view model and invoked to generate the final HTML output. View model Controller HTML Template name ViewResult 2. These values are encapsulated in a ViewResult object, which is returned from the action method. Locate template View model 3. The MVC middleware uses the template name to find the specific view template to render. 5. The generated HTML is passed back through the middleware pipeline and back to the user’s browser. Figure 7.4 The process of generating HTML from an MVC controller using a view
  • 209.
    181 Creating Razor views Withyour view template created, you now need to invoke it. In the remainder of this section you’ll see how to create a ViewResult and provide it a view model so that your view can render the data it contains. 7.2.1 Selecting a view from a controller In most cases, you won’t create a ViewResult directly in your action methods. Instead, you’ll use one of the View helper methods on the Controller base class. These helper methods simplify passing in a view model and selecting an action method, but there’s nothing magic about them—all they do is simplify creating ViewResult objects. In the simplest case, you can call the View method without any arguments, as shown next. This helper method returns a ViewResult that will use conventions to find the view template to render. public class HomeController : Controller { public IActionResult Index() { return View(); } } Listing 7.2 Returning ViewResult from an action method using default conventions Figure 7.5 The Add New Item dialog. Choosing MVC View Page will add a new Razor template view file to your application. Inheriting from the Controller base class makes the View helper methods available. The View helper method returns a ViewResult.
  • 210.
    182 CHAPTER 7Rendering HTML using Razor views In this example, the View helper method returns a ViewResult without specifying the name of a template to run. Instead, the name of the template to use is based on the name of the action method. Given that the controller is called HomeController and the method is called Index, by default the Razor template engine will look for a tem- plate at the Views/Home/Index.cshtml location. This is another case of using conventions in MVC to reduce the amount of boiler- plate you have to write. As always, the conventions are optional. You can also explicitly pass the name of the template to run as a string to the View method. For example, if the Index method instead returned View("ListView");, then the templating engine would look for a template called ListView.cshtml instead. You can even specify the complete path to the view file, relative to your application’s root folder, such as View ("Views/global.cshtml");,which would look for the template at the Views/global .chtml location. NOTE When specifying the absolute path to a view, you must include both the top-level Views folder and the cshtml file extension in the path. Figure 7.6 shows the complete process used by the default Razor templating engine to try to locate the correct View template to execute. It’s possible for more than one tem- plate to be eligible, for example if an Index.chstml file exists in both the Home and Shared folders. In a manner analogous to routing, the engine will use the first tem- plate it finds. You may find it tempting to be explicit by default and provide the name of the view file you want to render in your controller; if so, I’d encourage you to fight that urge. You’ll have a much simpler time if you embrace the conventions as they are and go with the flow. That extends to anyone else who looks at your code; if you stick to the standard conventions, then there’ll be a comforting familiarity when they look at your app. That can only be a good thing! Now you know where to place your view templates, and how to direct the templat- ing engine to them. It’s about time to take a closer look at a template, so you can see what you’re working with! 7.2.2 Introducing Razor templates Razor templates contain a mixture of HTML and C# code interspersed with one another. The HTML markup lets you easily describe exactly what should be sent to the browser, whereas the C# code can be used to dynamically change what is rendered. For example, the listing 7.3 shows an example of Razor rendering a list of strings, rep- resenting to-do items.
  • 211.
    183 Creating Razor views @{ vartasks = new List<string> { "Buy milk", "Buy eggs", "Buy bread" }; } Listing 7.3 Razor template for rendering a list of strings Has an absolute view path been provided? Yes No Does a view exist in views/controller/action? Controller name Action name Yes Use provided absolute path Use views/controller/action Use views/shared/action Does a view exist in views/shared/action? Yes No Throw exception: InvalidOperationException MVC action creates a ViewResult Action name No Does a view exist at the provided path? No Throw exception: InvalidOperationException Yes Figure 7.6 A flow chart describing how the Razor templating engine locates the correct view template to execute Arbitrary C# can be executed in a template. Variables remain in scope throughout the page.
  • 212.
    184 CHAPTER 7Rendering HTML using Razor views <h1>Tasks to complete</h1> <ul> @for(var i=0; i< tasks.Count; i++) { var task = tasks[i]; <li>@i - @task</li> } </ul> The pure HTML sections in this template are the angle brackets. The Razor engine copies this HTML directly to the output, unchanged, as though you were writing a normal HTML file. As well as HTML, you can also see a number of C# statements in there. The advan- tage of being able to, for example, use a for loop rather than having to explicitly write out each <li> element should be self-evident. I’ll dive a little deeper into more of the C# features of Razor in the next section. When rendered, this template would pro- duce the HTML shown here. <h1>Tasks to complete</h1> <ul> <li>0 - Buy milk</li> <li>1 - Buy eggs</li> <li>2 - Buy bread</li> </ul> As you can see, the final output of a Razor template after it’s been rendered is simple HTML. There’s nothing complicated left, just straight markup that can be sent to the browser and rendered. Figure 7.7 shows how a browser would render it. Listing 7.4 HTML output produced by rendering a Razor template Standard HTML markup will be rendered to the output unchanged. Mixing C# and HTML allows you to dynamically create HTML at runtime. HTML from the Razor template is written directly to the output. The <li> elements are generated dynamically based on the data. HTML from the Razor template is written directly to the output. The data to display is defined in C# . Razor markup describes how to display this data using a mixture of HTML and C#. By combining the C# object data with the Razor markup, HTML can be generated dynamically, instead of being fixed at compile time. <h1>Tasks to complete</h1> <ul> @for(var i=0; i<tasks.Count; i++) { var task = tasks[i]; <li>@i - @task</li> } </ul> var tasks = new List<string> { 'Buy milk', 'Buy eggs', 'Buy bread' } Figure 7.7 Razor templates can be used to generate the HTML dynamically at runtime from C# objects.
  • 213.
    185 Creating Razor views Inthis example, I hardcoded the list values for simplicity—there was no dynamic data provided. This is often the case on simple action methods like you might have on your homepage—you need to display an almost static page. For the rest of your applica- tion, it will be far more common to have some sort of data you need to display, encap- sulated by a view model. 7.2.3 Passing data to views In ASP.NET Core, you have a number of ways of passing data from an action method in a controller to a view. Which approach is best will depend on the data you’re trying to pass through, but in general you should use the mechanisms in the following order:  View model—The view model should be used to encapsulate any data that needs to be displayed, which is specific to that view. It’s a strongly typed class that can be any sort of object you like. The view model is available in the view when it’s rendered, as you’ll see shortly.  ViewData—This is a dictionary of objects with string keys that can be used to pass arbitrary data from the controller to the view.  ViewBag—A version of the ViewData that uses C# dynamic objects instead of string keys. It’s a wrapper around the ViewData object, so you can use it in place of ViewData if you prefer dynamic to Dictionary. I’ll only refer to View- Data in this book, as they’re almost the same thing.  HttpContext—Technically the HttpContext object is available in both the con- troller and view, so you could use it to transfer data between them. But don’t— there’s no need for it with the other methods and abstractions available to you. Far and away the best approach for passing data from a controller to a view is to use a view model. There’s nothing special about a view model; it’s a custom class that you create to hold the data you require. NOTE Many frameworks have the concept of a data context for binding UI components. The view model is a similar concept, in that it contains values to display in the UI, but the binding is only one-directional; the view model pro- vides values to the UI, and once the UI is built and sent as a response, the view model is destroyed. You can make a view model available by passing it to the View helper method from your controller, as shown in the following listing. In this example, I’ve created a ToDoItemViewModel to hold the values I want to display in the view—the Title and its Tasks. public class ToDoController : Controller { public IActionResult ViewTodo(int id) { Listing 7.5 Passing a view model to a view for a controller Inheriting from the Controller-based class provides the helper View methods
  • 214.
    186 CHAPTER 7Rendering HTML using Razor views var viewModel = new ToDoItemViewModel { Title = "Tasks for today", Tasks = new List<string>{ { "Get fuel", "Check oil", "Check tyre pressure" } }; return View(viewModel); } } By passing it to the View method, the viewModel instance will be available in the tem- plate, allowing you to access the values it contains from the view template. All that’s required is to add an @model directive at the top of your template so that it knows the Type of the view model it should expect: @model ToDoItemViewModel DEFINITION A directive is a statement in a Razor file that changes the way the template is parsed or compiled. Another common directive is the @using newNamespace directive, which would make objects in the newNamespace name- space available. Once you’ve added this directive, you can access any of the data on the todoModel you provided, using the Model property. For example, to display the Title property from the ToDoItemViewModel, you’d use <h1>@Model.Title</h1>. This would render the string provided on the original viewModel object, producing the <h1>Tasks for today</h1> HTML. TIP Note that the @model directive should be at the top of your view, and has a lowercase m. The Model property can be accessed anywhere in the view and has an uppercase M. In the vast majority of cases, using a view model is the way to go; it’s the standard mech- anism for passing data between the controller and the view. But in some circumstances, a view model may not be the best fit. This is often the case when you want to pass data between view layouts (you’ll see how this works in the last section of this chapter). A common example is the title of the page. You need to provide a title for every page, so you could create a base class with a Title property and make all your view models inherit from it. But that’s a little cumbersome, so a common approach for this situation is to use the ViewData collection to pass data around. In fact, the standard MVC templates use this approach by default rather than using view models by setting values in the ViewData dictionary from within the action methods: public IActionResult Contact() { ViewData["Message"] = "Your contact page."; Building a view model: this would normally call out to a database or filesystem to load the data. Creates a ViewResult that looks for a view called ViewTodo, and passes it the viewModel
  • 215.
    187 Creating dynamic webpages with Razor return View(); } They’re then displayed in the template using <h3>@ViewData["Message"]</h3> NOTE Personally, I don’t agree with the default approach in the templates— the message being presented is integral to the page, so it should probably be part of a view model. You can also set values on the ViewData dictionary from within the view itself: @{ ViewData["Title"] = "About"; } <h2>@ViewData["Title"].</h2> This template sets the value of the "Title" key in the ViewData dictionary to "About" and then fetches the key to render in the template. This might seem superfluous, but as the ViewData dictionary is shared throughout the request, it makes the title of the page available in layouts, as you’ll see later. When rendered, this would produce the following output: <h2>About.</h2> TIP Create a set of global, static constants for any ViewData keys, and refer- ence those instead of typing "Title" repeatedly. You’ll get IntelliSense for the values, they’ll be refactor-safe, and you’ll avoid hard-to-spot typos. As I mentioned previously, there are other mechanisms besides view models and ViewData that you can use to pass data around, but these two are the only ones I use personally, as you can do everything you need with them. You’ve had a small taste of the power available to you in Razor templates, but in the next section, I’d like to dive a little deeper into some of the available C# capabilities. 7.3 Creating dynamic web pages with Razor You might be glad to know that pretty much anything you can do in C# is possible in Razor syntax. Under the covers, the cshtml files are compiled into normal C# code (with string for the raw HTML sections), so whatever weird and wonderful behavior you need can be created. Having said that, just because you can do something doesn’t mean you should. You’ll find it much easier to work with, and maintain, your files if you keep them as simple as possible. This is true of pretty much all programming, but I find especially so with Razor templates. This section covers some of the more common C# constructs you can use. If you find you need to achieve something a more exotic, refer to the Razor syntax docu- mentation at https://docs.microsoft.com/en-us/aspnet/core/mvc/views/razor.
  • 216.
    188 CHAPTER 7Rendering HTML using Razor views 7.3.1 Using C# in Razor templates One of the most common requirements when working with Razor templates is to ren- der a value you’ve calculated in C# to the HTML. For example, you might want to print the current year to use with a copyright statement in your HTML, to give <p>Copyright 2018 ©</p> or you might want to print the result of a calculation, for example <p>The sum of 1 and 2 is <i>3</i><p> You can do this in two ways, depending on the exact C# code you need to execute. If the code is a single statement, then you can use the @ symbol to indicate you want to write the result to the HTML output, as shown in figure 7.8. You’ve already seen this used to write out values from the view model or from ViewData. If the C# you want to execute is something that needs a space, then you need to use parentheses to demarcate the C#, as shown in figure 7.9. These two approaches, in which C# is evaluated and written directly to the HTML out- put, are called Razor expressions. Sometimes you want to execute some C#, but you don’t need to output the values. I used this technique when we were setting values in ViewData: @{ ViewData["Title"] = "About"; } <p>Copyright @DateTime.Now.Year &copy;</p> @ indicates start of C# expression Whitespace indicates end of C# expression HTML HTML C# expression. The result will be written to the HTML output. Figure 7.8 Writing the result of a C# expression to HTML. The @ symbol indicates where the C# code begins and the expression ends at the end of the statement, in this case at the space. <p>The sum of 1 and 2 is <i>@(1 + 2)</i></p> HTML The whole C# expression within @() is evaluated and written to the HTML output. HTML Figure 7.9 When a C# expression contains whitespace, you must wrap it in parentheses using @() so the Razor engine knows where the C# stops and HTML begins.
  • 217.
    189 Creating dynamic webpages with Razor This example demonstrates a Razor code block, which is normal C# code, identified by the @{} structure. Nothing is written to the HTML output here, it’s all compiled as though you’d written it in any other normal C# file. TIP When you execute code within code blocks, it must be valid C#, so you need to add semicolons. Conversely, when you’re writing values directly to the response using Razor expressions, you don’t need them. If your output HTML breaks unexpectedly, keep an eye out for missing or rogue extra semicolons! Razor expressions are one of the most common ways of writing data from your view model to the HTML output. You’ll see the other approach, using Tag Helpers, in the next chapter. Razor’s capabilities extend far further than this, however, as you’ll see in the next section where you’ll learn how to include traditional C# structures in your templates. 7.3.2 Adding loops and conditionals to Razor templates One of the biggest advantages of using Razor templates over static HTML is the ability to dynamically generate the output. Being able to write values from your view model to the HTML using Razor expressions is a key part of that, but another common use is loops and conditionals. With these, you could hide sections of the UI, or produce HTML for every item in a list, for example. Loops and conditionals include constructs such as if and for loops, and using them in Razor templates is almost identical to C#, but you need to prefix their usage with the @ symbol. In case you’re not getting the hang of Razor yet, when in doubt, throw in another @! One of the big advantages of Razor in the context of ASP.NET Core is that it uses lan- guages you’re already familiar with: C# and HTML. There’s no need to learn a whole new set of primitives for some other templating language: it’s the same if, foreach, and while constructs you already know. And when you don’t need them, you’re writing raw HTML, so you can see exactly what the user will be getting in their browser. In listing 7.6 , I've applied a number of these different techniques in the template for displaying a to-do item. You can see the @model directive indicates I’ve provided a ToDoItemViewModel view model, which is used to generate the final HTML. The view model has a bool IsComplete property, as well as a List<string> property called Tasks, which contains any outstanding tasks. @model ToDoItemViewModel <div> @if (Model.IsComplete) { <strong>Well done, you’re all done!</strong> } else { Listing 7.6 Razor template for rendering a ToDoItemViewModel The @model directive indicates the type of view model in Model. The if control structure checks the value of the view model’s IsComplete property at runtime.
  • 218.
    190 CHAPTER 7Rendering HTML using Razor views <strong>The following tasks remain:</strong> <ul> @foreach (var task in Model.Tasks) { <li>@task</li> } </ul> } </div> This code definitely lives up to the promise of mixing C# and HTML! There are tradi- tional C# control structures, like if and foreach, that you’d expect in any normal pro- gram, interspersed with the HTML markup that I want to send to the browser. As you can see, the @ symbol is used to indicate when I’m starting a control statement, but I generally let the Razor template infer when I’m switching back and forth between HTML and C#. The template shows how to generate dynamic HTML at runtime, depending on the exact data provided. If the view model has outstanding Tasks, then the HTML will generate a list item for each task, producing output something like that shown in fig- ure 7.10. The foreach structure will generate the <li> elements once for each task in Tasks. A razor expression is used to write the task to the HTML output. @if (Model.IsComplete) { <p>Well done, you're all done!</p> } else { <p>The following tasks remain:</p> <ul> @foreach(var task in Model.Tasks) { <li>@task</li> } </ul> } The data to display is defined in the model. Razor markup can include C# constructs such as if statements and for loops. Only the relevant if block is rendered to the HTML, and the content within a foreach loop is rendered once for every item. Model.IsComplete = false; Model.Tasks = new List<string> { “Get gas”, “Check oil”, “Check tire pressure” }; Figure 7.10 The Razor template generates a <li> item for each remaining task, depending on the view model passed to the view at runtime.
  • 219.
    191 Creating dynamic webpages with Razor A common trope of the ASP.NET Core team is that they try to ensure you “fall into the pit of success” when building an application. This refers to the idea that, by default, the easiest way to do something should be the correct way of doing it. This is a great phi- losophy, as it means you shouldn’t get burned by, for example, security problems if you follow the standard approaches. Occasionally, however, you may need to step beyond the safety rails; a common use case is when you need to render some HTML contained in a C# object to the output, as you’ll see in the next section. 7.3.3 Rendering HTML with Raw In the previous example, I rendered the list of tasks to HTML by writing the string task using the @task Razor expression. But what if the task variable contains HTML you want to display, so instead of "Check oil" it contains "<strong>Check oil</strong>"? If you use a Razor expression to output this as you did previously, then you might hope to get this: <li><strong>Check oil</strong></li> IntelliSense and tooling support The mixture of C# and HTML might seem hard to read in the book, and that’s a rea- sonable complaint. It’s also another valid argument for trying to keep your Razor tem- plates as simple as possible. Luckily, if you’re using Visual Studio or Visual Studio Code, then the tooling can help somewhat. As you can see in this figure, the C# portions of the code are shaded to help distinguish them from the surrounding HTML. Although the ability to use loops and conditionals is powerful—they’re one of the advantages of Razor over static HTML—they also add to the complexity of your view. Try to limit the amount of logic in your views to make them as easy to understand and maintain as possible. Visual Studio shades the C# regions of code and highlights @ symbols where C# transitions to HTML. This makes the Razor templates easier to read.
  • 220.
    192 CHAPTER 7Rendering HTML using Razor views But that’s not the case. The HTML generated comes out like this: <li>&lt;strong&gt;Check oil&lt;/strong&gt;</li> Hmm, looks odd, right? What’s happened here? Why did the template not write your variable to the HTML, like it has in previous examples? If you look at how a browser displays this HTML, like in figure 7.11, then hopefully it makes more sense. Razor templates encode C# expressions before they’re written to the output stream. This is primarily for security reasons; writing out arbitrary strings to your HTML could allow users to inject malicious data and JavaScript into your website. Consequently, the C# variables you print in your Razor template get written as HTML-encoded values. In some cases, you might need to directly write out HTML contained in a string to the response. If you find yourself in this situation, first, stop. Do you really need to do this? If the values you’re writing have been entered by a user, or were created based on values provided by users, then there’s a serious risk of creating a security hole in your website. If you really need to write the variable out to the HTML stream, then you can do so using the Html property on the view page and calling the Raw method: <li>@Html.Raw(task)</li> With this approach, the string in task will be directly written to the output stream, pro- ducing the HTML you originally wanted, <li><strong>Check oil</strong></li>, which renders as shown in figure 7.12. Figure 7.11 The second item, <strong>Check oil<strong> has been HTML-encoded, so the <strong> elements are visible to the user as part of the task. Figure 7.12 The second item, "<strong>Check oil<strong>" has been output using Html.Raw(), so it hasn’t been HTML encoded. The <strong> elements result in the second item being shown in bold instead.
  • 221.
    193 Layouts, partial views,and _ViewStart WARNING Using Html.Raw on user input creates a security risk that users could use to inject malicious code into your website. Avoid using Html.Raw if possible! The C# constructs shown in this section can be useful, but they can make your tem- plates harder to read. It’s generally easier to understand the intention of Razor templates that are predominantly HTML markup rather than C#. In the previous version of ASP.NET, these constructs, and in particular the Html helper property, were the standard way to generate dynamic markup. You can still use this approach in ASP.NET Core by using the various HtmlHelper1 methods on the Html property, but these have largely been superseded by a cleaner technique: Tag Helpers. NOTE I’ll discuss Tag Helpers, and how to use them to build HTML forms, in the next chapter. Tag Helpers are a useful feature that’s new to Razor in ASP.NET Core, but a number of other features have been carried through from the previous version of ASP.NET. In the final section of this chapter, you’ll see how you can create nested Razor templates and use partial views to reduce the amount of duplication in your views. 7.4 Layouts, partial views, and _ViewStart Every HTML document has a certain number of elements that are required: <html>, <head>, and <body>. As well, there are often common sections that are repeated on every page of your application, such as the header and footer, as shown in figure 7.13. Each page on your application will also probably reference the same CSS and Java- Script files. All of these different elements add up to a maintenance nightmare. If you had to manually include these in every view, then making any changes would be a laborious, error-proneprocess.Instead, Razor letsyouextractthese common elementsinto layouts. DEFINITION A layout in Razor is a template that includes common code. It can’t be rendered directly, but it can be rendered in conjunction with normal Razor views. By extracting your common markup into layouts, you can reduce the duplication in your app. This makes changes easier, makes your views easier to manage and main- tain, and is generally good practice! In this section, you’ll see how to create a layout and reference it from your normal Razor views. 1 HTML Helpers are almost obsolete, though they’re still available if you prefer to use them.
  • 222.
    194 CHAPTER 7Rendering HTML using Razor views 7.4.1 Using layouts for shared markup Layout files are, for the most part, normal Razor templates that contain markup com- mon to more than one page. An ASP.NET Core app can have multiple layouts, and layouts can reference other layouts. A common use for this is to have different layouts for different sections of your application. For example, an e-commerce website might use a three-column view for most pages, but a single-column layout when you come to the checkout pages, as shown in figure 7.14. You’ll often use layouts across many different action methods, so they’re typically placed in the Views/Shared folder. You can name them anything you like, but there’s a common convention to use _Layout.cshtml as the filename for the base layout in your application. This is the default name used by the MVC templates in Visual Studio and the .NET CLI. TIP A common convention is to prefix your layout files with an underscore (_) to distinguish them from standard Razor templates in your Views folder. Header common to every page in the app Sidebar and sub-header common to some views in the app Body content specific to this view only Figure 7.13 A typical web application has a block-based layout, where some blocks are common to every page of your application. The header block will likely be identical across your whole application, but the sub-header and sidebar are probably identical for all the pages in one section. The body content will differ for every page in your application.
  • 223.
    195 Layouts, partial views,and _ViewStart A layout file looks similar to a normal Razor template, with one exception: every lay- out must call the @RenderBody() function. This tells the templating engine where to insert the content from the child views. A simple layout is shown in the following list- ing. Typically, your application will reference all your CSS and JavaScript files in the layout, as well as include all the common elements such as headers and footers, but this example includes pretty much the bare minimum HTML. <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>@ViewData["Title"]</title> <link rel="stylesheet" href="~/css/site.css" /> </head> <body> @RenderBody() </body> </html> As you can see, the layout file includes the required elements, such as <html> and <head>, as well as elements you need on every page, such as <title> and <link>. This example also shows the benefit of storing the page title in ViewData; the layout can render it in the <title> element so that it shows in the browser’s tab, as shown in fig- ure 7.15. Listing 7.7 A basic Layout.cshtml file calling RenderBody Three-column layout Single-column layout Figure 7.14 The https://manning.com website uses different layouts for different parts of the web application. The product pages use a three-column layout, but the cart page uses a single-column layout. ViewData is the standard mechanism for passing data to a layout from a view. Elements common to every page, such as your CSS, are typically found in the layout. Tells the templating engine where to insert the child view’s content
  • 224.
    196 CHAPTER 7Rendering HTML using Razor views Views can specify a layout file to use by setting the Layout property inside a Razor code block. @{ Layout = "_Layout"; ViewData["Title"] = "Home Page"; } <h1>@ViewData[“Title”]</h1> <p>This is the home page</p> Any contents in the view will be rendered inside the layout, where the call to @Render- Body() occurs. Combining the two previous listings will result in the following HTML being generated and sent to the user. <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Home Page</title> <link rel="stylesheet" href="/css/site.css" /> </head> <body> <h1>Home Page</h1> <p>This is the home page</p> </body> <html> Judicious use of layouts can be extremely useful in reducing the duplication on a page. By default, layouts only provide a single location where you can render content from the view, at the call to @RenderBody. In cases where this is too restrictive, you can render content using sections. 7.4.2 Overriding parent layouts using sections A common requirement when you start using multiple layouts in your application is to be able to render content from child views in more than one place in your layout. Consider the case of a layout that uses two columns. The view needs a mechanism for Listing 7.8 Setting the Layout property from a view Listing 7.9 Rendered output from combining a view with its layout Figure 7.15 The contents of the <title> element is used to name the tab in the user’s browser, in this case Home Page. Set the layout for the page to _Layout.cshtml. ViewData is a convenient way of passing data from a view to the layout. The content in the view to render inside the layout ViewData set in the view is used to render the layout. The RenderBody call renders the contents of the view.
  • 225.
    197 Layouts, partial views,and _ViewStart saying “render this content in the left column” and “render this other content in the right column”. This is achieved using sections. NOTE Remember, all of the features outlined in this chapter are specific to Razor, which is a server-side rendering engine. If you’re using a client-side SPA framework to build your application, you’ll likely handle these require- ments in other ways, either within the client code or by making multiple requests to a Web API endpoint. Sections provide a way of organizing where view elements should be placed within a layout. They’re defined in the view using an @section definition, as shown in the fol- lowing listing, which defines the HTML content for a sidebar separate from the main content, in a section called Sidebar. The @section can be placed anywhere in the file, top or bottom, wherever is convenient. @{ Layout = "_TwoColumn"; } @section Sidebar { <p>This is the sidebar content</p> } <p>This is the main content </p> The section is rendered in the parent layout with a call to @RenderSection(). This will render the content contained in the child section into the layout. Sections can be either required or optional. If they’re required, then a view must declare the given @section; if they’re optional then they can be omitted, and the layout will skip it. Skipped sections won’t appear in the rendered HTML. This listing shows a layout that has a required section called Sidebar, and an optional section called Scripts. @{ Layout = "_Layout"; } <div class="main-content"> @RenderBody() </div> <div class="side-bar"> @RenderSection("Sidebar", required: true) </div> @RenderSection("Scripts", required: false) Listing 7.10 Defining a section in a view template Listing 7.11 Rendering a section in a layout file, _TwoColumn.cshtml All content inside the braces is part of the Sidebar section, not the main body content. Any content not inside an @section will be rendered by the @RenderBody call. This layout is nested inside a layout itself. Renders all the content from a view that isn’t part of a section Renders the Sidebar section; if the Sidebar section isn’t defined in the view, throws an error Renders the Scripts section; if the Scripts section isn’t defined in the view, ignore it.
  • 226.
    198 CHAPTER 7Rendering HTML using Razor views TIP It’s common to have an optional section called Scripts in your layout pages. This can be used to render additional JavaScript that’s required by some views, but that isn’t needed on every view. A common example is the jQuery Unobtrusive Validation scripts for client-side validation. If a view requires the scripts, it adds the appropriate @section Scripts to the Razor markup. You may notice that the previous listing defines a Layout property, even though it’s a layout itself, not a view. This is perfectly acceptable, and lets you create nested hierar- chies of layouts, as shown in figure 7.16. Layout files and sections provide a lot of flexibility to build sophisticated UIs, but one of their most important uses is in reducing the duplication of code in your applica- tion. They’re perfect for avoiding duplication of content that you’d need to write for every view. But what about those times when you find you want to reuse part of a view somewhere else? For those cases, you have partial views. 7.4.3 Using partial views to encapsulate markup Partial views are exactly what they sound like—they’re part of a view. They provide a means of breaking up a larger view into smaller, reusable chunks. This can be useful for both reducing the complexity in a large view by splitting it into multiple partial views, or for allowing you to reuse part of a view inside another. _Layout.cshtml defines the HTML in the header and footer. The main content of the view is rendered in _TwoColumn.cshtml by RenderBody. _TwoColumn.cshtml is rendered inside _Layout.cshtml. The sidebar content of the view is rendered in _TwoColumn.cshtml by RenderSection(Sidebar). Figure 7.16 Multiple layouts can be nested to create complex hierarchies. This allows you to keep the elements common to all views in your base layout and extract layout common to multiple views into sub-layouts.
  • 227.
    199 Layouts, partial views,and _ViewStart Most web frameworks that use server-side rendering have this capability—Ruby on Rails has partial views, Django has inclusion tags, and Zend has partials. All of these work in the same way, extracting common code into small, reusable templates. Even client-side templating engines such as Mustache and Handlebars used by client-side frameworks like AngularJs and Ember have similar “partial view” concepts. Consider a to-do list application. You might find you have an action method that displays a single to-do with a given id using a view called ViewToDo.cshtml. Later on, you create a new action method that displays the five most recent to-do items using a view called RecentToDos.cshtml. Instead of copying and pasting the code from one view to the other, you could create a partial view, called _ToDo.cshtml. @model ToDoItemViewModel <h2>@Model.Title</h2> <ul> @foreach (var task in Model.Tasks) { <li>@task</li> } </ul> Both the ViewToDo.cshtml and RecentToDos.cshtml views can now render the _ToDo .cshtml partial view. Partial views are rendered using the @await Html.PartialAsync method and can be passed a view model to render. For example, the RecentTo- Dos.cshtml view could achieve this using @model List<ToDoItemViewModel> @foreach(var todo in Model) { @await Html.PartialAsync("_ToDo", todo) } You can call the partial in a number of other ways, such as Html.Partial and Html.RenderPartial, but Html.PartialAsync is recommended.2 NOTE Like layouts, partial views are typically named with a leading under- score. The Razor code contained in a partial view is almost identical to a standard view. The main difference is the fact that partial views are typically called from other views rather than as the result of an action method. The only other difference is that partial views don’t run _ViewStart.cshtml when they execute, which you’ll see shortly. Listing 7.12 Partial view _ToDo.cshtml for displaying a ToDoItemViewModel 2 You should use the asynchronous partial methods to render partials where possible, as the synchronous meth- ods can lead to subtle performance issues. Partial views can use a view model, just like normal views. The content of the partial view, which previously existed in the ViewToDo.cshtml file
  • 228.
    200 CHAPTER 7Rendering HTML using Razor views Partial views aren’t the only way to reduce duplication in your view templates. Razor also allows you to pull common elements such as namespace declarations and layout configuration into centralized files. In the final section of this chapter, you’ll see how to wield these files to clean up your templates. 7.4.4 Running code on every view with _ViewStart and _ViewImports Due to the nature of views, you’ll inevitably find yourself writing certain things repeat- edly. If all of your views use the same layout, then adding the following code to the top of every page feels a little redundant: @{ Layout = "_Layout"; } Similarly, if you’ve declared your view models in a different namespace to your app’s default, for example the WebApplication1.Models namespace, then having to add @using WebApplication1.Models to the top of the page can get to be a chore. Thankfully, ASP.NET Core includes two mechanisms for handling these common tasks: _ViewImports.cshtml and _ViewStart.cshtml. IMPORTING COMMON DIRECTIVES WITH _VIEWIMPORTS The _ViewImports.cshtml file contains directives that will be inserted at the top of every view. This includes things like the @using and @model statements that you’ve already seen—basically any Razor directive. To avoid adding a using statement to every view, you can include it in here instead. Child actions in ASP.NET Core In the previous version of ASP.NET MVC, there was the concept of a child action. This was an action method that could be invoked from inside a view. This was the main mechanism for rendering discrete sections of a complex layout that had nothing to do with the main action method. For example, a child action method might render the shopping cart on an e-commerce site. This approach meant you didn’t have to pollute every page’s view model with the view model items required to render the shopping cart, but it fundamentally broke the MVC design pattern, by referencing controllers from a view. In ASP.NET Core, child actions are no more. View components have replaced them. These are conceptually quite similar in that they allow both the execution of arbitrary code and the rendering of HTML, but they don’t directly invoke controller actions. You can think of them as a more powerful partial view that you should use anywhere a partial view needs to contain significant code or business logic. You’ll see how to build a small view component in chapter 19.
  • 229.
    201 Layouts, partial views,and _ViewStart @using WebApplication1 @using WebApplication1.Models @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers The _ViewImports.cshtml file can be placed in any folder, and it will apply to all views and sub-folders in that folder. Typically, it’s placed in the root Views folder so that it applies to every view in your app. It’s important to note that you should only put Razor directives in _ViewImports .cshtml—you can’t put any old C# in there. As you can see in the previous listing, this is limited to things like @using or the @addTagHelper directive that you’ll learn about in the next chapter. If you want to run some arbitrary C# at the start of every view in your application, for example to set the Layout property, then you should use the _ViewStart.cshtml file instead. RUNNING CODE FOR EVERY VIEW WITH _VIEWSTART You can easily run common code at the start of every view by adding a _ViewStart file to the Views folder in your application. This file can contain any Razor code, but it’s typically used to set the Layout for all the views in your application, as shown in the following listing. You can then omit the Layout statement from all views that use the default layout. If a view needs to use a nondefault layout then you can override it by setting the value in the view itself. @{ Layout = "_Layout"; } Any code in the _ViewStart.cshtml file runs before the view executes. Note that _View- Start.cshtml only runs for full views—it doesn’t run for layouts or partials views. Also, note that the names for these special Razor files are enforced rather than conventions. WARNING You must use the names _ViewStart.cshtml and _ViewImports .cshtml for the Razor engine to locate and execute them correctly. To apply them to all your app’s views, add them to the root of the Views folder, not to the Shared subfolder. If you only want to run _ViewStart.cshtml or _ViewImports.cshtml for some of your views, you can include additional _ViewStart.cshtml or _ViewImports.cshtml files in a controller-related subfolder. The controller-specific files will run after the root Views folder files. Listing 7.13 A typical _ViewImports.cshtml file importing additional namespaces Listing 7.14 A typical _ViewStart.cshtml file setting the default layout The default namespace of your application Add this directive to avoid placing in every view Makes Tag Helpers available in your views
  • 230.
    202 CHAPTER 7Rendering HTML using Razor views That concludes our first look at rendering HTML using the Razor templating engine. You saw how to select a view, pass a view model to it, and render the data to the HTML. Finally, you saw how to build complex layouts and reduce duplication in your views using partial views. In the next chapter, you’ll learn about Tag Helpers and how to use them to build HTML forms, a staple of modern web applications. Tag Helpers are one of the biggest improvements to Razor in ASP.NET Core, so getting to grips with them will make edit- ing your views an overall more pleasant experience! Summary  In MVC, views are responsible for generating the UI for your application.  Razor is a templating language that allows you to generate dynamic HTML using a mixture of HTML and C#.  HTML forms are the standard approach for sending data from the browser to the server. You can use Tag Helpers to easily generate these forms.  By convention, views are named the same as the action that invokes them, and reside either in a folder with the same name as the action method’s controller or in the Shared folder.  Controllers can override the default view template by passing the template name when returning a ViewResult using the View helper method on the Controller base class. Partial views, layouts, and AJAX This chapter describes using Razor to render full HTML pages server-side, which are then sent to the user’s browser in traditional web apps. A common alternative approach when building web apps is to use a JavaScript client-side framework to build a Single Page Application (SPA), which renders the HTML client-side in the browser. One of the technologies SPAs typically use is AJAX (Asychronous JavaScript and XML), in which the browser sends requests to your ASP.NET Core app without reload- ing a whole new page. It’s also possible to use AJAX requests with apps that use server-side rendering. To do so, you’d use JavaScript to request an update for part of a page. If you want to use AJAX with an app that uses Razor, you should consider making extensive use of partial views. You can then expose these via additional actions. Using AJAX can reduce the overall amount of data that needs to be sent back and forth between the browser and your app, and it can make your app feel smoother and more responsive, as it requires fewer full-page loads. But using AJAX with Razor can add complexity, especially for larger apps. If you foresee yourself making extensive use of AJAX to build a highly dynamic web app, you might want to consider using Web API controllers and a client-side framework (see chapter 9) instead.
  • 231.
    203 Summary  Controllers canpass strongly typed data to a view using a view model. To access the properties on the view model, the view should declare the model type using the @model directive.  Controllers can pass key-value pairs to the view using the ViewData dictionary.  Razor expressions render C# values to the HTML output using @ or @(). You don’t need to include a semicolon after the statement when using Razor expressions.  Razor code blocks, defined using @{}, execute C# without outputting HTML. The C# in Razor code blocks must be complete statements, so it must include semicolons.  Loops and conditionals can be used to easily generate dynamic HTML in tem- plates, but it’s a good idea to limit the number of if statements in particular, to keep your views easy to read.  If you need to render a string as raw HTML you can use Html.Raw, but do so sparingly—rendering raw user input can create a security vulnerability in your application.  Tag Helpers allow you to bind your data model to HTML elements, making it easier to generate dynamic HTML while staying editor friendly.  You can place HTML common to multiple views in a layout. The layout will ren- der any content from the child view at the location @RenderBody is called.  Encapsulate commonly used snippets of Razor code in a partial view. A partial view can be rendered using @await Html.PartialAsync().  _ViewImports.cshtml can be used to include common directives, such as @using statements, in every view.  _ViewStart.cshtml is called before the execution of each main view and can be used to execute code common to all views, such as a default layout page. It doesn’t execute for layouts or partial views.  _ViewImports.cshtml and _ViewStart.cshtml are hierarchical—files in the root folder execute first, followed by files in controller-specific view folders.
  • 232.
    204 Building forms with TagHelpers In chapter 7, you learned about Razor templates and how to use them to generate the views for your MVC application. By mixing HTML and C#, you can create dynamic applications that can display different data based on the request, the logged-in user, or any other data you can access. Displaying dynamic data is an important aspect of many web applications, but it’s typically only one half of the story. As well as displaying data to the user, you often need the user to be able to submit data back to your application. You can use data to customize the view, or to update the application model by saving it to a data- base, for example. For traditional web applications, this data is usually submitted using an HTML form. This chapter covers  Building forms easily with Tag Helpers  Generating URLs with the Anchor Tag Helper  Using Tag Helpers to add functionality to Razor
  • 233.
    205 In chapter 6,you learned about model binding, which is how you accept the data sent by a user in a request and convert it into a C# object that you can use in your action methods. You also learned about validation, and how important it is to validate the data sent in a request. You used DataAnnotations to define the rules associated with your models, as well as other associated metadata like the display name for a property. The final aspect we haven’t yet looked at is how to build the HTML forms that users use to send this data in a request. Forms are one of the key ways users will interact with your application in the browser, so it’s important they’re both correctly defined for your application and also user friendly. ASP.NET Core provides a feature to achieve this, called Tag Helpers. Tag Helpers are new to ASP.NET Core. They’re Razor components that you can use to customize the HTML generated in your templates. Tag Helpers can be added to an otherwise standard HTML element, such as an <input>, to customize its attri- butes based on your view model, saving you from having to write boilerplate code. They can also be standalone elements and can be used to generate completely cus- tomized HTML. NOTE Remember, Razor, and therefore Tag Helpers, are for server-side HTML rendering. You can’t use Tag Helpers directly in frontend frameworks like Angular or React. If you’ve used the previous version of ASP.NET, then Tag Helpers may sound reminis- cent of HTML Helpers, which could also be used to generate HTML based on your view model. Tag Helpers are the logical successor to HTML Helpers, as they provide a more streamlined syntax than the previous, C#-focused helpers. HTML Helpers are still available in ASP.NET Core, so if you’re converting some old templates to ASP.NET Core, or want to, you can use them in your templates, but I won’t be covering them in this book. In this chapter, you’ll primarily learn how to use Tag Helpers when building forms. They simplify the process of generating correct element names and IDs so that model binding can occur seamlessly when the form is sent back to your application. To put them into context, you’re going to carry on building the currency converter applica- tion that you’ve seen in previous chapters. You’ll add the ability to submit currency exchange requests to it, validate the data, and redisplay errors on the form using Tag Helpers to do the leg work for you, as shown in figure 8.1. As you develop the application, you’ll meet the most common Tag Helpers you’ll encounter when working with forms. You’ll also see how you can use Tag Helpers to simplify other common tasks, such as generating links, conditionally displaying data in your application, and ensuring users see the latest version of an image file when they refresh their browser. To start, I’ll talk a little about why you need Tag Helpers when Razor can already generate any HTML you like by combining C# and HTML in a file.
  • 234.
    206 CHAPTER 8Building forms with Tag Helpers 8.1 Catering to editors with Tag Helpers One of the common complaints about the mixture of C# and HTML in Razor tem- plates is that you can’t easily use standard HTML editing tools with them; all the @ and {} symbols in the C# code tend to confuse the editors. Reading the templates can be similarly difficult for people; switching paradigms between C# and HTML can be a bit jarring sometimes. This arguably wasn’t such a problem when Visual Studio was the only supported way to build ASP.NET websites, as it could obviously understand the templates without any issues, and helpfully colorize the editor. But with ASP.NET Core going cross- platform, the desire to play nicely with other editors reared its head again. This was one of the biggest motivations for Tag Helpers. They integrate seamlessly into the standard HTML syntax by adding what look to be attributes, typically starting with asp-*. They’re most often used to generate HTML forms, as shown in the follow- ing listing. This listing shows a view from the first iteration of the currency converter application, in which you choose the currencies to convert and the quantity. Figure 8.1 The currency converter application forms, built using Tag Helpers. The labels, dropdowns, input elements, and validation messages are all generated using Tag Helpers.
  • 235.
    207 Catering to editorswith Tag Helpers @model CurrencyConverterModel <form asp-action="Convert" asp-controller="Currency"> <div class="form-group"> <label asp-for="CurrencyFrom"></label> <input class="form-control" asp-for="CurrencyFrom" /> <span asp-validation-for="CurrencyFrom"></span> </div> <div class="form-group"> <label asp-for="Quantity"></label> <input class="form-control" asp-for="Quantity" /> <span asp-validation-for="Quantity"></span> </div> <div class="form-group"> <label asp-for="CurrencyTo"></label> <input class="form-control" asp-for="CurrencyTo" /> <span asp-validation-for="CurrencyTo"></span> </div> <button type="submit" class="btn btn-default">Submit</button> </form> At first glance, you might not even spot the Tag Helpers, they blend in so well with the HTML! This makes it easy to edit the files with any standard HTML text editor. But don’t be concerned that you’ve sacrificed readability in Visual Studio—as you can see in figure 8.2, elements with Tag Helpers are clearly distinguishable from the standard HTML <div> element and the standard HTML class attribute on the <input> ele- ment. The C# properties of the view model being referenced (CurrencyFrom, in this case) are also still shaded, as with other C# code in Razor files. And of course, you get IntelliSense, as you’d expect. Listing 8.1 User registration form using Tag Helpers asp-for on Labels generates the caption for labels based on the view model. Use @model to describe the view model type for Tag Helpers. Tag Helpers on Forms are used to generate the action URL. asp-for on Inputs generate the correct type, value, and validation attributes for the model. Validation messages are written to a span using Tag Helpers. Validation messages are written to a span using Tag Helpers. Validation messages are written to a span using Tag Helpers. Figure 8.2 In Visual Studio, Tag Helpers are distinguishable from normal elements by being bold and a different color, C# is shaded, and IntelliSense is available.
  • 236.
    208 CHAPTER 8Building forms with Tag Helpers TIP Visual Studio 2017 currently doesn’t have native IntelliSense support for the asp-* Tag Helpers themselves, though it will be enabled in an update. In the meantime, install the Razor Language Services extension.1 Unfortunately, IntelliSense for Tag Helpers isn’t available in Visual Studio Code. Tag Helpers are extra attributes on standard HTML elements (or new elements entirely) that work by modifying the HTML element they’re attached to. They let you easily integrate your server-side values, such as those in your view model, with the gen- erated HTML. Notice that listing 8.1 didn’t specify the captions to display in the labels. Instead, you declaratively used asp-for="CurrencyFrom" to say, “for this <label>, use the Currency- From property to work out what caption to use.” Similarly, for the <input> elements, Tag Helpers are used to  Automatically populate the values from the view model  Choose the correct input type to display (for example, a number input for the Quantity property)  Display any validation errors, as shown in figure 8.32 1 You can install Visual Studio extensions by clicking Tools > Extensions and Updates, or by viewing on the Visual Studio Marketplace at http://mng.bz/LcNX. 2 To learn more about the internals of Tag Helpers, read the documentation at http://mng.bz/Idb0. Figure 8.3 Tag Helpers hook into the metadata provided by DataAnnotations, as well as the property types themselves. The Validation Tag Helper can even populate error messages based on the ModelState, as you saw in the last chapter on validation. Label caption calculated from [Display] attribute Validation error message populated from ModelState Input types determined from DataAnnotations and property type
  • 237.
    209 Creating forms usingTag Helpers Tag Helpers can perform a variety of functions by modifying the HTML elements they’re applied to. This chapter introduces a number of the common Tag Helpers and how to use them, but it’s not an exhaustive list. I don’t cover all of the helpers that come out of the box in ASP.NET Core (there are more coming with every release!), and you can easily create your own, as you’ll see in chapter 18. Alternatively, you could use those published by others on NuGet or GitHub.3 As with all of ASP.NET Core, Microsoft is developing Tag Helpers in the open on GitHub, so you can always take a look at the source to see how they’re implemented. 8.2 Creating forms using Tag Helpers The Tag Helpers for working with forms are some of the most useful and are the ones you’ll probably use most often. You can use them to generate HTML markup based on properties of your view model, creating the correct id and name attributes and setting the value of the element to the model property’s value (among other things). This capability significantly reduces the amount of markup you need to write manually. Imagine you’re building the checkout page for the currency converter application, and you need to capture the user’s details on the checkout page. In chapter 6, you built a UserBindingModel binding model (shown in listing 8.2), added DataAnnotation 3 I’ve published a few custom Tag Helpers on GitHub at http://mng.bz/FYH1 WebForms flashbacks For those who remember ASP.NET back in the day of WebForms, before the advent of the MVC pattern for web development, Tag Helpers may be triggering your PTSD. Although the asp- prefix is somewhat reminiscent of ASP.NET Web Server control definitions, never fear—the two are different beasts. Web Server controls were directly added to a page’s backing C# class, and had a broad scope that could modify seemingly unrelated parts of the page. Coupled with that, they had a complex lifecycle that was hard to understand and debug when things weren’t working. The perils of trying to work with that level of complexity haven’t been forgotten, and Tag Helpers aren’t the same. Tag Helpers don’t have a lifecycle—they participate in the rendering of the element to which they’re attached, and that’s it. They can modify the HTML element they’re attached to, but they can’t modify anything else on your page, making them concep- tually much simpler. An additional capability they bring is the ability to have multiple Tag Helpers acting on a single element—something Web Server controls couldn’t easily achieve. Overall, if you’re writing Razor templates, you’ll have a much more enjoyable experi- ence if you embrace Tag Helpers as integral to its syntax. They bring a lot of benefits without obvious downsides, and your cross-platform-editor friends will thank you!
  • 238.
    210 CHAPTER 8Building forms with Tag Helpers attributes for validation, and saw how to model bind it in your action method. In this chapter, you’ll see how to create the view for it, using the UserBindingModel as a view model. WARNING For simplicity, I’m using the same object for both my binding model and view model, but in practice you should use two separate objects to avoid mass-assignment attacks on your app.4 public class UserBindingModel { [Required] [StringLength(100, ErrorMessage = "Maximum length is {1}")] [Display(Name = "Your name")] public string FirstName { get; set; } [Required] [StringLength(100, ErrorMessage = "Maximum length is {1}")] [Display(Name = "Last name")] public string LastName { get; set; } [Required] [EmailAddress] public string Email { get; set; } [Phone(ErrorMessage = "Not a valid phone number.")] [Display(Name = "Phone number")] public string PhoneNumber { get; set; } } The UserBindingModel is decorated with a number of DataAnnotations attributes. In the previous chapter, you saw that these attributes are used during model validation when your binding model is bound, before the action method is executed. These attributes are also used by the Razor templating language to provide the metadata required to generate the correct HTML when you use Tag Helpers. With the help of the UserBindingModel, Tag Helpers, and a little HTML, you can create a Razor view that lets the user enter their details, as shown in figure 8.4. The Razor template to generate this page is shown in listing 8.3. This code uses a variety of tag helpers, including  A Form Tag Helper on the <form> element  Label Tag Helpers on the <label>  Input Tag Helpers on the <input>  Validation Message Tag Helpers on <span> validation elements for each prop- erty in the UserBindingModel Listing 8.2 UserBindingModel for creating a user on a checkout page 4 You can read about techniques for working with separate binding and view models at http://mng.bz/QvfG.
  • 239.
    211 Creating forms usingTag Helpers @model UserBindingModel @{ ViewData["Title"] = "Checkout"; } <h1>@ViewData["Title"]</h1> <form asp-action="Index" asp-controller="Checkout"> <div class="form-group"> <label asp-for="FirstName"></label> <input class="form-control" asp-for="FirstName" /> <span asp-validation-for="FirstName"></span> </div> <div class="form-group"> <label asp-for="LastName"></label> <input class="form-control" asp-for="LastName" /> <span asp-validation-for="LastName"></span> </div> <div class="form-group"> <label asp-for="Email"></label> <input class="form-control" asp-for="Email" /> <span asp-validation-for="Email"></span> </div> Listing 8.3 Razor template for binding to UserBindingModel on the checkout page Figure 8.4 The checkout page for an application. The HTML is generated based on a UserBindingModel, using Tag Helpers to render the required element values, input types, and validation messages. The @model directive describes the view model for the page. Form Tag Helpers use routing to determine the URL the form will be posted to. The Label Tag Helper uses DataAnnotations on a property to determine the caption to display. The Input Tag Helper uses DataAnnotations to determine the type of input to generate.
  • 240.
    212 CHAPTER 8Building forms with Tag Helpers <div class="form-group"> <label asp-for="PhoneNumber"></label> <input class="form-control" asp-for="PhoneNumber" /> <span asp-validation-for="PhoneNumber"></span> </div> <button type="submit" class="btn btn-default">Submit</button> </form> You can see the HTML markup that this template produces in listing 8.4. This Razor markup and the resulting HTML produces the results you saw in figure 8.4. You can see that each of the HTML elements with a Tag Helper have been customized in the output: the <form> element has an action attribute, the <input> elements have an id and name based on the name of the referenced property, and both the <input> and <span> have data-* elements for validation. <form action="/Checkout" method="post"> <div class="form-group"> <label for="FirstName">Your name</label> <input class="form-control" type="text" id="FirstName" name="FirstName" value="" data-val="true" data-val-length-max="100" data-val-length="Maximum length is 100." data-val-required="The Your name field is required." /> <span class="field-validation-valid" data-valmsg-for="FirstName" data-valmsg-replace="true"></span> </div> <div class="form-group"> <label for="LastName">Last name</label> <input class="form-control" type="text" id="LastName" name="LastName" value="" data-val="true" data-val-length-max="100" data-val-length="Maximum length is 100." data-val-required="The Last name field is required." /> <span class="field-validation-valid" data-valmsg-for="LastName" data-valmsg-replace="true"></span> </div> <div class="form-group"> <label for="Email">Email</label> <input class="form-control" type="email" id="Email" name="Email" value="" data-val="true" data-val-length-max="100" data-val-email="The Email field is not a valid e-mail address." data-val-required="The Email field is required." /> <span class="field-validation-valid" data-valmsg-for="Email" data-valmsg-replace="true"></span> </div> <div class="form-group"> <label for="PhoneNumber">Phone number</label> <input class="form-control" type="tel" id="PhoneNumber" name="PhoneNumber" value="" Listing 8.4 HTML generated by the Razor template on the checkout page The Validation Tag Helper displays error messages associated with the given property.
  • 241.
    213 Creating forms usingTag Helpers data-val="true" data-val-length-max="100" data-val-phone="Not a valid phone number." /> <span class="field-validation-valid" data-valmsg-for="PhoneNumber" data-valmsg-replace="true"></span> </div> <button type="submit" class="btn btn-default">Submit</button> <input name="__RequestVerificationToken" type="hidden" value="CfDJ8PkYhAINFx1JmYUVIDWbpPyy_TRUNCATED" /> </form> Wow, that’s a lot of markup! If you’re new to working with HTML, this might all seem a little overwhelming, but the important thing to notice is that you didn’t have to write most of it! The Tag Helpers took care of the vast majority of the plumbing for you. That’s basically Tag Helpers in a nutshell; they simplify the fiddly mechanics of build- ing HTML forms, leaving you to concentrate on the overall design of your application instead of writing boilerplate markup. NOTE If you’re using Razor to build your views, Tag Helpers will make your life easier, but they’re entirely optional. You’re free to write raw HTML with- out them, or to use the previous HTML Helpers. Tag Helpers simplify and abstract the process of HTML generation, but they generally try to do so without getting in your way. If you need the final generated HTML to have a particular attribute, then you can add it to your markup. You can see that in the pre- vious listings where class attributes are defined on <input> elements, such as <input class="form-control" asp-for="FirstName" />, they pass untouched through from Razor to the HTML output. TIP This is different from the way HTML Helpers worked in the previous ver- sion of ASP.NET; they often required jumping through hoops to set attributes in the generated markup. Even better than this, you can also set attributes that are normally generated by a Tag Helper, like the type attribute on an <input> element. For example, if the Favorite- Color property on your view model was a string, then by default, Tag Helpers would generate an <input> element with type="text". Updating your markup to use the HTML5 color picker type is trivial; set the type in the markup: <input type="color" asp-for="FavoriteColor" /> TIP HTML5 adds a huge number of features, including lots of form ele- ments that you may not have come across before, such as range inputs and color pickers. We’re not going to cover them in this book, but you can read about them on the Mozilla Developer Network website at http://mng.bz/qOc1. In this section, you’ll build the currency calculator Razor templates from scratch, add- ing Tag Helpers as you find you need them. You’ll probably find you use most of the
  • 242.
    214 CHAPTER 8Building forms with Tag Helpers common form Tag Helpers in every application you build, even if it’s on a simple login page. 8.2.1 The Form Tag Helper The first thing you need to start building your HTML form is, unsurprisingly, the <form> element. In the previous example, the <form> element was augmented with two Tag Helper attributes: asp-action and asp-controller: <form asp-action="Index" asp-controller="Checkout"> This resulted in the addition of action and method attributes to the final HTML, indi- cating the URL that the form should be sent to when submitted: <form action="/Checkout" method="post"> Setting the asp-action and asp-controller attributes allows you to specify the action method in your application that the form will be posted to when it’s submitted. The asp-action and asp-controller attributes are added by a FormTagHelper. This Tag Helper uses the values provided to generate a URL for the action attribute using the URL generation features of routing that I described at the end of chapter 5. NOTE Tag Helpers can make multiple attributes available on an element. Think of them like properties on a Tag Helper configuration object. Adding a single asp- attribute activates the Tag Helper on the element. Adding addi- tional attributes lets you override further default values of its implementation. The Form Tag Helper makes a number of other attributes available on the <form> ele- ment that you can use to customize the generated URL. You’ve already seen the asp- action and asp-controller attributes for setting the controller and action route parameters. Hopefully you’ll remember from chapter 5 that you can set other route parameters too, all of which will be used to generate the final URL. For example, the default route template looks something like {controller}/{action}/{id?}. As you can see, there’s an optional id route parameter that will be used during URL generation. How can you set this value for use during URL generation? The Form Tag Helper defines an asp-route-* wildcard attribute that you can use to set arbitrary route parameters. Set the * in the attribute to the route parameter name. For the example, to set the id route parameter, you’d set the asp-route-id value, so <form asp-action="View" asp-controller="Product" asp-route-id="5"> would generate the following markup, using the default route template to generate the URL: <form action="/Product/View/5" method="post">
  • 243.
    215 Creating forms usingTag Helpers You can add as many asp-route-* attributes as necessary to your <form> to generate the correct action URL, but sometimes that’s still not enough. In chapter 5, I described using a route name when generating URLs as being another option. You can achieve this with the Form Tag Helper by using the asp-route attribute. NOTE Use the asp-route attribute to specify the route name to use, and the asp-route-* attributes to specify the route parameters to use during URL generation. Imagine you’re building a social networking application, and you’ve defined a custom route called view_posts for viewing the posts of a user with a given username: routes.MapRoute( name: "view_posts", template: "{username}/posts", defaults: new {controller="User", action="ViewPosts"); You can ensure the URL generated for a <form> uses this route by employing the fol- lowing combination of asp-route and asp-route-username, where the current user- name is stored in the Username property on the view model. <form asp-route="view_posts" asp-route-username="@Model.Username"> TIP You can use values from your view model (and C# in general) in Tag Helpers like you would in normal Razor. See chapter 7 for details. The main job of the Form Tag Helper is to generate the action attribute, but it per- forms one additional, important function: generating a hidden <input> field needed to prevent cross-site request forgery (CSRF) attacks. DEFINITION Cross-site request forgery (CSRF) attacks are a website exploit that can be used to execute actions on your website by an unrelated malicious website. You’ll learn about them in detail in chapter 18. You can see the generated hidden <input> at the bottom of the generated <form> in listing 8.4; it’s named __RequestVerificationToken and contains a seemingly ran- dom string of characters. This field won’t protect you on its own, but I’ll describe in chapter 18 how it’s used to protect your website. The Form Tag Helper generates it by default, so generally speaking you won’t need to worry about it, but if you need to dis- able it, you can do so by adding asp-antiforgery="false" to your <form> element. The Form Tag Helper is obviously useful for generating the action URL, but it’s time to move on to more interesting elements, those that you can see in your browser! 8.2.2 The Label Tag Helper Every <input> field in your currency converter application needs to have an associ- ated label so the user knows what the <input> is for. You could easily create those yourself, manually typing the name of the field and setting the for attribute as appro- priate, but luckily there’s a Tag Helper to do that for us.
  • 244.
    216 CHAPTER 8Building forms with Tag Helpers The Label Tag Helper is used to generate the caption (the visible text) and the for attribute for a <label> element, based on the properties in the view model. It’s used by providing the name of the property in the asp-for attribute: <label asp-for="FirstName"></label> The Label Tag Helper uses the [Display] DataAnnotations attribute that you saw in chapter 6 to determine the appropriate value to display. If the property you’re gener- ating a label for doesn’t have a [Display] attribute, the Label Tag Helper will use the name of the property instead. So, for the model public class UserBindingModel { [Display(Name = "Your name")] public string FirstName { get; set; } public string Email { get; set; } } in which the FirstName property has an [Display] attribute, but the Email property doesn’t; the following Razor <label asp-for="FirstName"></label> <label asp-for="Email"></label> would generate the HTML <label for="FirstName">Your Name</label> <label for="Email">Email</label> The caption text inside the <label> element uses the value set in the [Display] attri- bute, or the property name in the case of the Email property. Also note that the for attribute has been generated with the name of the property. This is a key bonus of using Tag Helpers—it hooks in with the element IDs generated by other Tag Helpers, as you’ll see shortly. NOTE The for attribute is important for accessibility. It specifies the ID of the element to which the label refers. As is typical with Tag Helpers, the Label Tag Helper won’t override values you set your- self. If, for example, you don’t want to use the caption generated by the helper, you could insert your own manually. The following code <label asp-for="Email">Please enter your Email</label> would generate the HTML <label for="Email">Please enter your Email</label> As ever, you’ll generally have an easier time with maintenance if you stick to the stan- dard conventions and don’t override values like this, but the option is there. Right, next up is a biggie: the Input and Textarea Tag Helpers.
  • 245.
    217 Creating forms usingTag Helpers 8.2.3 The Input and Textarea Tag Helpers Now you’re getting into the meat of your form—the <input> elements that handle user input. Given that there’s such a wide array of possible input types, there’s a vari- ety of different ways they can be displayed in the browser. For example, Boolean val- ues are typically represented by a checkbox type <input> element, whereas integer values would use a number type <input> element, and a date would use the date type, shown in figure 8.5. To handle this diversity, the Input Tag Helper is one of the most powerful Tag Help- ers. It uses information based on both the type of the property (bool, string, int, and so on) and any DataAnnotations attributes applied to it ([EmailAddress] and [Phone], among others) to determine the type of the input element to generate. The DataAnnotations are also used to add data-val-* client-side validation attributes to the generated HTML. Consider the Email property from listing 8.2 that was decorated with the [Email- Address] attribute. Adding an <input> is as simple as using the asp-for attribute: <input asp-for="Email" /> The property is a string, so ordinarily, the Input Tag Helper would generate an <input> with type="text". But the addition of the [EmailAddress] attribute provides additional metadata about the property. Consequently, the Tag Helper generates an HTML5 <input> with type="email": Figure 8.5 Various input element types. The exact way in which each type is displayed varies by browser.
  • 246.
    218 CHAPTER 8Building forms with Tag Helpers <input type="email" id="Email" name="Email" value="test@example.com" data-val="true" data-val-email="The Email Address field is not a valid e-mail address." data-val-required="The Email Address field is required." /> You can take a whole host of things away from this example. First, the id and name attri- butes of the HTML element have been generated from the name of the property. These are the same as the value referenced by the Label Tag Helper in its for attribute. Also, the initial value of the field has been set to the value currently stored in the property ("test@example.com", in this case). The type of the element has also been set to the HTML5 email type, instead of using the default text type. Perhaps the most striking addition is the swath of data-val-* attributes. These can be used by client-side JavaScript libraries such as jQuery to provide client-side valida- tion of your DataAnnotations constraints. Client-side validation provides instant feed- back to users when the values they enter are invalid, providing a smoother user experience than can be achieved with server-side validation alone, as I described in chapter 6. Client-side validation In order to enable client-side validation in your application, you need to add more jQuery libraries to your HTML pages. In particular, you need to include the jQuery, jQuery-validation, and jQuery-validation-unobtrusive JavaScript libraries. You can do this in a number of ways, but the simplest is to include the script files at the bottom of your view using <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0. ➥min.js"></script> <script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/ ➥jquery.validate.min.js"></script> <script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/ ➥3.2.6/jquery.validate.unobtrusive.min.js"></script> The default MVC templates include these scripts for you, in a handy partial template that you can add to your page in a Scripts section. If you’re using the default layout and need to add client-side validation to your view, add the following section some- where on your view: @section Scripts{ @Html.Partial("_ValidationScriptsPartial") } This will ensure the appropriate scripts are added using a best-practice approach, by serving from a content delivery network (CDN) for speed and using your application as a fallback if there are network issues.
  • 247.
    219 Creating forms usingTag Helpers The Input Tag Helper tries to pick the most appropriate template for a given property based on DataAnnotation attributes or the type of the property. Whether this gener- ates the exact <input> type you need may depend, to an extent, on your application. As always, you can override the generated type by adding your own type attribute to the Razor. Table 8.1 shows how some of the common data types are mapped to <input> types, and how the data types themselves can be specified. The Input Tag Helper has one additional attribute that can be used to customize the way data is displayed: asp-format. HTML forms are entirely string-based, so when the value of an <input> is set, the Input Tag Helper must take the value stored in the property and convert it to a string. Under the covers, this performs a string .Format() on the property’s value, passing in a format string. The Input Tag Helper uses a default format string for each different data type, but with the asp-format attribute, you can set the specific format string to use. For exam- ple, you could ensure a decimal property, Dec, is formatted to three decimal places with the following code: <input asp-for="Dec" asp-format="0.000" /> If the Dec property had a value of 1.2, this would generate HTML similar to <input type="text" id="Dec" name="Dec" value="1.200"> Table 8.1 Common data types, how to specify them, and the input element type they map to Data type How it’s specified Input element type byte, int, short, long, uint Property type number decimal, double, float Property type text string Property type, [DataType(DataType.Text)] attribute text HiddenInput [HiddenInput] attribute hidden Password [Password] attribute password PhoneAttribute [Phone] attribute tel EmailAddress [EmailAddress] attribute email Url [Url] attribute url Date DateTime property type, [DataType(DataType.Date)] attribute date
  • 248.
    220 CHAPTER 8Building forms with Tag Helpers NOTE You may be surprised that decimal and double types are rendered as text fields and not as number fields. This is due to a number of technical rea- sons, predominantly related to the way some cultures render numbers with commas and spaces. Rendering as text avoids errors that would only appear in certain browser-culture combinations. In addition to the Input Tag Helper, ASP.NET Core provides the Textarea Tag Helper. This works in a similar way, using the asp-for attribute, but is attached to a <textarea> element instead: <textarea asp-for="Multiline"></textarea> This would generate HTML similar to the following. Note that the property value is rendered inside the Tag, and data-val-* validation elements are attached as usual: <textarea data-val="true" id="Multiline" name="Multiline" data-val-length="Maximum length 200." data-val-length-max="200" data-val-required="The Multiline field is required." >This is some text, I'm going to display it in a text area</textarea> Hopefully, this section has hammered home how much typing Tag Helpers can cut down on, especially when using them in conjunction with DataAnnotations for gener- ating validation attributes. But this is more than reducing the number of keystrokes required; Tag Helpers ensure that the markup generated is correct, and has the correct name, id, and format to automatically bind your binding models when they’re sent to the server. With <form>, <label>, and <input> under your belt, you’re able to build the major- ity of your currency converter forms. Before we look at displaying validation messages, there’s one element I’d like to take a look at: the <select>, or drop-down, input. 8.2.4 The Select Tag Helper As well as <input> fields, a common element you’ll see on web forms is the <select> element, or dropdowns and list boxes. Your currency converter application, for exam- ple, could use a <select> element to let you pick which currency to convert from a list. By default, this element shows a list of items and lets you select one, but there are a number of variations, as shown in figure 8.6. As well as the normal select box, you could show a list box, add multiselection, and display your list items in groups. To use <select> elements in your Razor code, you’ll need to include two proper- ties in your view model: one property for the list of options to display and one to hold the value (or values) selected. For example, listing 8.5 shows the view model used to create the three left-most select lists you saw in figure 8.6. Displaying groups requires a slightly different setup, as you’ll see shortly.
  • 249.
    221 Creating forms usingTag Helpers public class SelectListsViewModel { public string SelectedValue1 { get; set; } public string SelectedValue2 { get; set; } public IEnumerable<string> MultiValues { get; set; } public IEnumerable<SelectListItem> Items { get; set; } = new List<SelectListItem> { new SelectListItem{Value= "csharp", Text="C#"}, new SelectListItem{Value= "python", Text= "Python"}, new SelectListItem{Value= "cpp", Text="C++"}, new SelectListItem{Value= "java", Text="Java"}, new SelectListItem{Value= "js", Text="JavaScript"}, new SelectListItem{Value= "ruby", Text="Ruby"}, }; } This listing demonstrates a number of aspects of working with <select> lists:  SelectedValue1/SelectedValue2—Used to hold the values selected by the user. They’re model-bound to the selected values and used to preselect the cor- rect item when rendering the form.  MultiValues—Used to hold the selected values for a multiselect list. It’s an IEnumerable, so it can hold more than one selection per <select> element.  Items—Provides the list of options to display in the <select> elements. Note that the element type must be SelectListItem, which exposes the Value and Text properties, to work with the Select Tag Helper. Listing 8.5 View model for displaying select element dropdowns and list boxes Figure 8.6 Some of the many ways to display <select> elements using the Select Tag Helper. These properties will hold the values selected by the single- selection select boxes. To create a multiselect list box, use an IEnumerable<>. The list of items to display in the select boxes
  • 250.
    222 CHAPTER 8Building forms with Tag Helpers NOTE The Select Tag Helper only works with SelectListItem elements. That means you’ll normally have to convert from an application-specific list set of items (for example, a List<string> or List<MyClass>) to the UI- centric List<SelectListItem>. But hey, that’s why we have view models! The Select Tag Helper exposes the asp-for and asp-items attributes that you can add to <select> elements. As for the Input Tag Helper, the asp-for attribute speci- fies the property in your view model to bind to. The asp-items attribute is provided the IEnumerable<SelectListItem> to display the available <option>. TIP It’s common to want to display a list of enum options in a <select> list. This is so common that ASP.NET Core ships with a helper for generating a SelectListItem for any enum. If you have an enum of the TEnum type, you can generate the available options in your View using asp-items="Html .GetEnumSelectList<TEnum>()". The following listing shows how to display a drop-down list, a single-selection list box, and a multiselection list box. It uses the view model from the previous listing, binding each <select> list to a different property, but reusing the same Items list for all of them. @model SelectListsViewModel <select asp-for="SelectedValue1" asp-items="Model.Items"></select> <select asp-for="SelectedValue2" asp-items="Model.Items" size="@Model.Items.Count()"></select> <select asp-for="MultiValues" asp-items="Model.Items"></select> Hopefully, you can see that the Razor for generating a drop-down <select> list is almost identical to the Razor for generating a multiselect <select> list. The Select Tag Helper takes care of adding the multiple HTML attribute to the generated out- put, if the property it’s binding to is an IEnumerable. WARNING The asp-for attribute must not include the Model. prefix. The asp- items attribute, on the other hand, must include it if referencing a property on the view model. The asp-items attribute can also reference other C# items, such as objects stored in ViewData, but using the view model is the best approach. You’ve seen how to bind three different types of select list so far, but the one I haven’t yet covered from figure 8.6 is how to display groups in your list boxes using <optgroup> Listing 8.6 Razor template to display a select element in three different ways Creates a standard drop-down select list by binding to a standard property in asp-for Creates a single-select list box by providing the standard HTML size attribute Creates a multiselect list box by binding to an IEnumerable property in asp-for
  • 251.
    223 Creating forms usingTag Helpers elements. Luckily, nothing needs to change in your Razor code, you just have to update how you define your SelectListItems. The SelectListItem object defines a Group property that specifies the Select- ListGroup the item belongs to. The following listing shows how you could create two groups and assign each list item to either a “dynamic” or “static” group, using a view model similar to that shown in listing 8.5. The final list item, C#, isn’t assigned to a group, so it will be displayed as normal, outside of the grouping. public class SelectListsViewModel { public IEnumerable<string> SelectedValues { get; set; } public IEnumerable<SelectListItem> Items { get; set; } public SelectListsViewModel() { var dynamic = new SelectListGroup { Name = "Dynamic" }; var stat = new SelectListGroup { Name = "Static" }; ItemsWithGroups = new List<SelectListItem> { new SelectListItem { Value= "js", Text="Javascript", Group = dynamic }, new SelectListItem { Value= "cpp", Text="C++", Group = stat }, new SelectListItem { Value= "python", Text="Python", Group = dynamic }, new SelectListItem { Value= "csharp", Text="C#", }, } } With this in place, the Select Tag Helper will generate <optgroup> elements as neces- sary when rendering the Razor to HTML. The preceding view model would be ren- dered to HTML as <select id="SelectedValues" name="SelectedValues" multiple="multiple"> <optgroup label="Dynamic"> <option value="js">JavaScript</option> <option value="python">Python</option> Listing 8.7 Adding Groups to SelectListItems to create optgroup elements Initializes the list items in the constructor Creates single instance of each group to pass to SelectListItems Sets the appropriate group for each SelectListItem If a SelectListItem doesn’t have a Group, it won’t be added to an optgroup.
  • 252.
    224 CHAPTER 8Building forms with Tag Helpers </optgroup> <optgroup label="Static Languages"> <option value="cpp">C++</option> </optgroup> <option value="csharp">C#</option> </select> Another common requirement when working with <select> elements is to include an option in the list that indicates “no value selected,” as shown in figure 8.7. Without this extra option, the default <select> dropdown will always have a value and default to the first item in the list. You can achieve this in one of two ways: you could either add the “not selected” option to the available SelectListItems, or you could manually add the option to the Razor, for example by using <select asp-for="Model.SelectedValue" asp-items="Model.Items"> <option Value = "">**Not selected**</option> </select> This will add an extra <option> at the top of your <select> element, with a blank Value attribute, allowing you to provide a “no selection” option for the user. TIP Adding a “no selection” option to a <select> element is so common, you might want to create a partial view to encapsulate this logic, as you saw in the previous chapter. I’ll leave it as an exercise for the reader, but you’d need to create a common interface as the view model for your partial view. With the Input Tag Helper and Select Tag Helper under your belt, you should be able to create most of the forms that you’ll need. You have all the pieces you need to create the currency converter application now, with one exception. Remember, whenever you accept input from a user, you should always validate the data. The Validation Tag Helpers provide a way to display model validation errors to the user on your form, without having to write a lot of boilerplate markup. Figure 8.7 Without a “no selection” option, the <select> element will always have a value. This may not be the behavior you desire if you don’t want an <option> to be selected by default.
  • 253.
    225 Creating forms usingTag Helpers 8.2.5 The Validation Message and Validation Summary Tag Helpers In section 8.2.3 you saw that the Input Tag Helper generates the necessary data-val- * validation attributes on form input elements themselves. But you also need some- where to display the validation messages. This can be achieved for each property in your view model using the Validation Message Tag Helper applied to a <span> by using the asp-validation-for attribute: <span asp-validation-for="Email"></span> When an error occurs during client-side validation, the appropriate error message for the referenced property will be dis- played in the <span>, as shown in figure 8.8. This <span> element will also be used to show appropriate validation messages if server-side validation fails, and the form is being redisplayed. Any errors associated with the Email property stored in ModelState will be ren- dered in the element body, and the element will have appropriate attributes to hook into jQuery validation: <span class="field-validation-valid" data-valmsg-for="Email" data-valmsg-replace="true">The Email Address field is required.</span> The validation error shown in the element will be replaced when the user updates the Email <input> field and client-side validation is performed. NOTE For further details on model validation, see chapter 6. As well as displaying validation messages for individual properties, you can also display a summary of all the validation messages in a <div> by using the Validation Summary Tag Helper, shown in figure 8.9. This renders a <ul> containing a list of the Model- State errors. The Validation Summary Tag Helper is applied to a <div> using the asp-validation- summary attribute and providing a ValidationSummary enum value, such as <div asp-validation-summary="All"></div> The ValidationSummary enum controls which values are displayed, and has three pos- sible values:  None—Don’t display a summary. (I don’t know why you’d use this.)  ModelOnly—Only display errors that are not associated with a property.  All—Display errors either associated with a property or with the model. The Validation Summary Tag Helper is particularly useful if you have errors associated with your view model that aren’t specific to a property. These can be added to the model state by using a blank key, as shown in listing 8.8. In this example, the property validation Figure 8.8 Validation messages can be shown in an associated <span> by using the Validation Message Tag Helper.
  • 254.
    226 CHAPTER 8Building forms with Tag Helpers passed, but we provide additional model-level validation that we aren’t trying to convert a currency to itself. public class CurrencyController : Controller { [HttpPost] public IActionResult Convert( CurrencyConverterModel model) { if(model.CurrencyFrom == model.CurrencyTo) { ModelState.AddModelError( string.Empty, "Cannot convert currency to itself"); } if (!ModelState.IsValid) { return View(model); } Listing 8.8 Adding model-level validation errors to the ModelState Figure 8.9 Form showing validation errors. The Validation Message Tag Helper is applied to <span>, close to the associated input. The Validation Summary Tag Helper is applied to a <div>, normally at the top or bottom of the form. Validation Message Tag Helpers Validation Summary Tag Helper Can’t convert currency to itself Adds model-level error by using empty key If there are any property- level or model-level errors, display them.
  • 255.
    227 Creating forms usingTag Helpers //store the valid values somewhere etc return RedirectToAction("Index", "Checkout"); } } Without the Validation Summary Tag Helper, the model-level error would still be added if the user used the same currency twice, and the form would be redisplayed. Unfortunately, there would have been no visual cue to the user indicating why the form did not submit—obviously that’s a problem! By adding the Validation Summary Tag Helper, the model-level errors are shown to the user so they can correct the prob- lem, as shown in figure 8.10. NOTE For simplicity, I added the validation check to the controller action. A better approach might be to create a custom validation attribute to achieve this instead. That way, your controller stays lean and sticks to the single responsibility principle. You’ll see how to achieve this in chapter 19. Identical currencies cause model-level validation error . Without Tag Helper , the user has no idea why the form has been redisplayed. Identical currencies cause model-level validation error . Tag Helper shows model-level errors. Figure 8.10 Model-level errors are only displayed by the Validation Summary Tag Helper. Without one, users won’t have any indication that there were errors on the form, and so won’t be able to correct them.
  • 256.
    228 CHAPTER 8Building forms with Tag Helpers This section has covered most of the common Tag Helpers available for working with forms, including all the pieces you need to build the currency converter forms. They should also be enough to get you going on your own applications, building out your Razor views using your view models. But forms aren’t the only area in which Tag Help- ers are useful; they’re generally applicable any time you need to mix server-side logic with HTML generation. One such example is generating links to other pages in your application using routing-based URL generation. Given that routing is designed to be fluid as you refac- tor your application, keeping track of the exact URLs the links should point to would be a bit of a maintenance nightmare if you had to do it “by hand.” As you might expect, there’s a Tag Helper for that: the Anchor Tag Helper. 8.3 Generating links with the Anchor Tag Helper At the end of chapter 5, I showed how you could generate URLs for links to other pages in your application from inside your controllers and by using ActionResults. Views are the other common place where you often need to link to other actions in your application, normally by way of an <a> element with an href attribute pointing to the appropriate URL. The Anchor Tag Helper can be used to generate the URL for a given action using routing. Conceptually, this is almost identical to the way the Form Tag Helper gener- ates the action URL, as you saw in section 8.2.1. For the most part, using the Anchor Tag Helper is identical too; you provide asp-controller and asp-action attributes, along with asp-route-* attributes as necessary. The default MVC templates use the Anchor Tag Helper to generate the links shown in the navigation bar using the code. <ul class="nav navbar-nav"> <li><a asp-area="" asp-controller="Home" asp-action="Index">Home</a></li> <li><a asp-area="" asp-controller="Home" asp-action="About">About</a></li> <li><a asp-area="" asp-controller="Home" asp-action="Contact">Contact</a></li> </ul> As you can see, each <a> element has an asp-action and asp-controller attribute. These use the routing system to generate an appropriate URL for the <a>, resulting in the following markup: <ul class="nav navbar-nav"> <li><a href="/">Home</a></li> <li><a href="/Home/About">About</a></li> <li><a href="/Home/Contact">Contact</a></li> </ul> Listing 8.9 Using the Anchor Tag Helper to generate URLs in _Layout.cshtml
  • 257.
    229 Cache-busting with theAppend Version Tag Helper The URLs use default values where possible, so the Index action on the HomeController generates the simple "/" URL instead of "/Home/Index". If you need more control over the URL generated, the Anchor Tag Helper exposes a number of additional properties you can set, which will be used during URL genera- tion. The most commonly used are  asp-action—Sets the action route parameter.  asp-controller—Sets the controller route parameter.  asp-area—Sets the area route parameter to use. Areas can be used to provide an additional layer of organization to your application.5  asp-host—If set, the link will point to the provided host and will generate an absolute URL instead of a relative URL.  asp-protocol—Sets whether to generate an http or https link. If set, it will gen- erate an absolute URL instead of a relative URL.  asp-route—Uses the named route to generate the URL.  asp-route-*—Sets the route parameters to use during generation. Can be added multiple times for different route parameters. By using the Anchor Tag Helper and its attributes, you generate your URLs using the routing system as described in chapter 5, ensuring that you won’t break your applica- tion if you rename something. This reduces the duplication in your code by removing the hardcoded URLs you’d otherwise need to embed in all your views. If you find yourself writing repetitive code in your markup, chances are someone has written a Tag Helper to help with it. The Append Version Tag Helper in the fol- lowing section is a great example of using Tag Helpers to reduce the amount of fiddly code required. 8.4 Cache-busting with the Append Version Tag Helper A common problem with web development, both when developing and when an application goes into production, is ensuring that browsers are all using the latest files. For performance reasons, browsers often cache files locally and reuse them for subsequent requests, rather than calling your application every time a file is requested. Normally, this is great—most of the static assets in your site rarely change, so cach- ing them significantly reduces the burden on your server. Think of an image of your company logo—how often does that change? If every page shows your logo, then caching the image in the browser makes a lot of sense. But what happens if it does change? You want to make sure users get the updated assets as soon as they’re available. A more critical requirement might be if the Java- Script files associated with your site change. If users end up using cached versions of 5 I won’t cover areas in this book. They’re an optional aspect of MVC that are often only used on large projects. You can read about them here: http://mng.bz/3X64.
  • 258.
    230 CHAPTER 8Building forms with Tag Helpers your JavaScript then they might see strange errors, or your application might appear broken to them. This conundrum is a common one in web development, and one of the most com- mon ways for handling it is to use a cache-busting query string. DEFINITION A cache-busting query string adds a query parameter to a URL, such as ?v=1. Browsers will cache the response and use it for subsequent requests to the URL. When the resource changes, the query string is also changed, for example to ?v=2. Browsers will see this is a request for a new resource, and will make a fresh request. The biggest problem with this approach is that it requires you to update a URL every time an image, CSS, or JavaScript file changes. This is a manual step that requires updating every place the resource is referenced, so it’s inevitable that mistakes are made. Tag Helpers to the rescue! When you add a <script>, <img>, or <link> ele- ment to your application, you can use Tag Helpers to automatically generate a cache- busting query string: <script src="~/js/site.js" asp-append-version="true"></script> The asp-append-version attribute will load the file being referenced and generate a unique hash based on its contents. This is then appended as a unique query string to the resource URL: <script src="/js/site.js?v=EWaMeWsJBYWmL2g_KkgXZQ5nPe"></script> As this value is a hash of the file contents, it will remain unchanged as long as the file isn’t modified, so the file will be cached in users’ browsers. But if the file is modified, then the hash of the contents will change and so will the query string. This ensures browsers are always served the most up-to-date files for your application without your having to worry about manually updating every URL whenever you change a file. The default MVC templates in Visual Studio and the .NET CLI make use of Tag Helpers for both forms and cache busting. They also use Tag Helpers to condition- ally render different markup depending on the current environment using a tech- nique you haven’t seen yet, where the Tag Helper is declared as a completely separate element. 8.5 Using conditional markup with the Environment Tag Helper In many cases, you want to render different HTML in your Razor templates depend- ing if your website is running in a development or production environment. For example, in development, you typically want your JavaScript and CSS assets to be ver- bose and easy to read, but in production you’d process these files to make them as small as possible. Another example might be the desire to apply a banner to the appli- cation when it’s running in a testing environment, which is removed when you move to production, shown in figure 8.11.
  • 259.
    231 Using conditional markupwith the Environment Tag Helper NOTE You’ll learn about configuring your application for multiple environ- ments in chapter 11. You’ve already seen how to use C# to add if statements to your markup, so it would be perfectly possible to use this technique to add an extra div to your markup when the current environment has a given value. If we assume that the env variable contains the current environment, then you could use something like @if(env == "Testing" || env == "Staging") { <div class="warning">You are currently on a testing environment</div> } There’s nothing wrong with this, but a better approach would be to use the Tag Helper paradigm to keep your markup clean and easy to read. Luckily, ASP.NET Core comes with the EnvironmentTagHelper, which can be used to achieve the same result in a slightly clearer way: <environment include="Testing,Staging"> <div class="warning">You are currently on a testing environment</div> </environment> This Tag Helper is a little different from the others you’ve seen before. Instead of aug- menting an existing HTML element using an asp- attribute, the whole element is the Tag Helper. This Tag Helper is completely responsible for generating the markup, and it uses an attribute to configure it. Functionally, this Tag Helper is identical to the C# markup (although for now I’ve glossed over how the env variable could be found), but it’s more declarative in its func- tion than the C# alternative. You’re obviously free to use either approach, but person- ally I like the HTML-like nature of Tag Helpers. Either way, the EnvironmentTagHelper is used in the default MVC templates, so at least you’ll know what it’s up to now! We’ve reached the end of this chapter on Tag Helpers, and with it, our first look at building traditional web applications that display HTML to users. In the last part of the book, we’ll revisit Razor templates, and you’ll learn how to build custom compo- nents like custom Tag Helpers and View Components. For now, you have everything Figure 8.11 The warning banner will be shown whenever you’re running in a testing environment, to make it easy to distinguish from production.
  • 260.
    232 CHAPTER 8Building forms with Tag Helpers you need to build complex Razor layouts—the custom components can help tidy up your code down the line. This chapter, along with the previous four, has been a whistle-stop tour of how to build MVC applications with ASP.NET Core. You now have the basic building blocks to start making simple ASP.NET Core applications. In the second part of this book, I’ll show you some of the additional features you’ll need to understand to build complete applications. But before we get to that, I’ll take a chapter to discuss building Web APIs. I’ve mentioned the Web API approach previously, in which your application serves data using the MVC framework, but instead of returning user-friendly HTML, it returns machine-friendly JSON or XML. In the next chapter, you’ll see why and how to build a Web API, take a look at an alternative routing system designed for APIs, and learn how to generate JSON responses to requests. Summary  Tag Helpers let you bind your data model to HTML elements, making it easier to generate dynamic HTML while remaining editor friendly.  As with Razor in general, Tag Helpers are for server-side rendering of HTML only. You can’t use them directly in frontend frameworks, such as Angular or React.  Tag Helpers can be standalone elements or can attach to existing HTML using attributes.  Tag Helpers can customize the elements they’re attached to, add additional attributes, and customize how they’re rendered to HTML. This can greatly reduce the amount of markup you need to write.  Tag Helpers can expose multiple attributes on a single element.  You can add the asp-action and asp-controller attributes to the <form> ele- ment to set the action URL using the URL generation feature of the MVC middleware’s router.  You specify route values to use during routing with the Form Tag Helper using asp-route-* attributes.  The Form Tag Helper also generates a hidden field that you can use to prevent CSRF attacks.  You can attach the Label Tag Helper to a <label> using asp-for. It generates an appropriate for attribute and caption based on the [Display] DataAnnotations attribute and the view model property name.  The Input Tag Helper sets the type attribute of an <input> element to the appropriate value based on a bound property’s Type and any DataAnnotations applied to it. It also generates the data-val-* attributes required for client-side validation.  To enable client-side validation, you must add the necessary JavaScript files to your view for jQuery validation and unobtrusive validation.
  • 261.
    233 Summary  The SelectTag Helper can generate drop-down <select> elements as well as list boxes, using the asp-for and asp-items attributes.  To generate a multiselect <select> element, bind the element to an IEnumerable property on the view model.  The items supplied in asp-for must be an IEnumerable<SelectListItem>.  You can generate an IEnumerable<SelectListItem> for an enum TEnum using the Html.GetEnumSelectList<TEnum>() helper method.  The Select Tag Helper will generate <optgroup> elements if the items supplied in asp-for have an associated SelectListGroup on the Group property.  Any extra additional <option> elements added to the Razor markup will be passed through to the final HTML. You can use these additional elements to easily add a “no selection” option to the <select> element.  The Validation Message Tag Helper is used to render the client- and server-side validation error messages for a given property. Use the asp-validation-for attribute to attach the Validation Message Tag Helper to a <span>.  The Validation Summary Tag Helper is used to display validation errors for the model, as well as for individual properties. Use the asp-validation-summary attribute to attach the Validation Summary Tag Helper to a <div>.  You can generate <a> URLs using the Anchor Tag Helper. This Helper uses routing to generate the href URL using asp-action, asp-controller, and asp-route-* attributes.  You can add the asp-append-version attribute to <link>, <script>, and <img> elements to provide cache-busting capabilities based on the file’s contents.  You can use the Environment Tag Helper to conditionally render different HTML based on the app’s current execution environment.
  • 262.
    234 Creating a WebAPI for mobile and client applications using MVC In the previous five chapters, you’ve worked through each layer of a traditional ASP.NET Core MVC application, using Razor views to render HTML to the browser. In this chapter, you’ll see a slightly different take on an MVC application. We’ll explore Web APIs, which serve as the backend for client-side SPAs and mobile apps. You can apply much of what you’ve learned to Web APIs; they use the same MVC design pattern, and the concepts of routing, model binding, and validation This chapter covers  Creating a Web API controller to return JSON to clients  Using attribute routing to customize your URLs  Generating a response using content negotiation  Enabling XML formatting
  • 263.
    235 What is aWeb API and when should you use one? all carry through. The differentiation from traditional web applications is entirely in the view part of MVC. Instead of returning HTML, they return data as JSON or XML, which client applications use to control their behavior or update the UI. In this chapter, you’ll learn how to define controllers and actions and see how sim- ilar they are to the controllers you already know. You’ll learn how to create an API model to return data and HTTP status codes in response to a request, in a way that cli- ent apps can understand. The subsequent section looks at an alternative approach to routing often used with Web APIs, called attribute routing. This approach uses the same route templates con- cept from chapter 5 but applies them to your action methods in a way that’s more suited to the customization needs of Web API applications. You’ll also learn how to format the API models returned by your action methods using content negotiation, to ensure you generate a response that the calling client can understand. As part of this, you’ll learn how to add support for additional format types, such as XML, so that you can both generate XML responses and receive XML data POSTed to your app. One of the great aspects of ASP.NET Core is the variety of applications you can cre- ate with it. The ability to easily build a generalized HTTP Web API presents the possi- bility of using ASP.NET Core in a greater range of situations than can be achieved with traditional web apps alone. But should you build a Web API and why? In the first sec- tion of this chapter, I’ll go over some of the reasons why you might or might not want to create a Web API. 9.1 What is a Web API and when should you use one? Traditional web applications handle requests by returning HTML to the user, which is displayed in a web browser. You can easily build applications of this nature using Mvc- Middleware to generate HTML with Razor templates, as you’ve seen in recent chap- ters. This approach is common and well understood, but the modern application developer also has a number of other possibilities to consider, as shown in figure 9.1. Client-side single-page applications (SPAs) have become popular in recent years with the development of frameworks such as Angular, React, and Ember. These frame- works use JavaScript that runs in a user’s web browser to generate the HTML they see and interact with. The server sends this initial JavaScript to the browser when the user first reaches the app. The user’s browser loads the JavaScript and initializes the SPA, before loading any application data from the server. Once the SPA is loaded, communication with a server still occurs over HTTP, but instead of sending HTML directly to the browser in response to requests, the server- side application sends data (normally in a format such as JSON or XML) to the client- side application. The SPA then parses the data and generates the appropriate HTML to show to a user, as shown in figure 9.2. The server-side application endpoint that the client communicates with is sometimes called a Web API.
  • 264.
    236 CHAPTER 9Creating a Web API for mobile and client applications using MVC DEFINITION A Web API exposes a number of URLs that can be used to access or change data on a server. It’s typically accessed using HTTP. These days, mobile applications are common and are, from the server application’s point of view, similar to client-side SPAs. A mobile application will typically communi- cate with a server application using an HTTP Web API, receiving data in a common format, such as JSON, just like an SPA. It then modifies the application’s UI depend- ing on the data it receives. One final use case for a Web API is where your application is designed to be par- tially or solely consumed by other services. Imagine you’ve built a web application to send emails. By creating a Web API, you can allow other application developers to use your email service by sending you an email address and a message. Virtually all lan- guages and platforms have access to an HTTP library they could use to access your ser- vice from code. This is all there is to a Web API. It exposes a number of endpoints (URLs) that cli- ent applications can send requests to and retrieve data from. These are used to power the behavior of the client apps, as well as to provide all the data they need to display the correct interface to a user. Browser Traditional web application Server Client Synchronous request via HTTP Response: HTML web page SPA web application REST API Asynchronous request via HTTP Response: partial page data as JSON or XML Client application RPC service Synchronous or asynchronous request via HTTP Response: data as JSON, XML or binary Figure 9.1 Modern developers have to consider a number of different consumers of their applications. As well as traditional users with web browsers, these could be SPAs, mobile applications, or other apps.
  • 265.
    237 What is aWeb API and when should you use one? Whether you need or want to create a Web API for your ASP.NET Core application depends on the type of application you want to build. If you’re familiar with client- side frameworks, will need to develop a mobile application, or already have an SPA build-pipeline configured, then you’ll most likely want to add Web APIs for them to be able to access your application. One of the selling points of using a Web API is that it can serve as a generalized backend for all of your applications. For example, you could start by building a client- side application that uses a Web API. Later, you could add a mobile app that uses the same Web API, with little or no modification required to your ASP.NET Core code. If you’re new to web development, have no need to call your application from out- side a web browser, or don’t want/need the effort involved in configuring a client-side application, then you probably won’t need Web APIs initially. You can stick to generat- ing your UI using Razor and will no doubt be highly productive! NOTE Although there has definitely been a shift toward client-side frame- works, server-side rendering using Razor is still relevant. Which approach you choose will depend largely on your preference for building HTML applica- tions in the traditional manner versus using JavaScript on the client. Having said that, adding Web APIs to your application isn’t something you have to worry about ahead of time. Adding them later is simple, so you can always ignore them initially and add them in as the need arises. In many cases, this will be the best approach. Initial requests fetch client-side JavaScript application. Subsequent requests fetch data in JSON format. Figure 9.2 A sample client-side SPA using Angular. The initial requests load the SPA JavaScript into the browser, and subsequent requests fetch data from a Web API, formatted as JSON.
  • 266.
    238 CHAPTER 9Creating a Web API for mobile and client applications using MVC Once you’ve established that you need a Web API for your application, creating one is easy, as it’s built into ASP.NET Core. In the next section, you’ll see how to create a Web API controller in an existing MVC application. 9.2 Creating your first Web API Controller The MVC design pattern is sometimes only thought of in relation to applications that directly render their UI, like the Razor views you’ve seen in previous chapters. In ASP.NET Core, the MVC pattern applies equally well when building a Web API; but the view part of MVC involves generating a machine-friendly response rather than a user -friendly response. As a parallel to this, you create Web API controllers in ASP.NET Core in the very same way you create traditional MVC controllers. The only thing that differentiates them from a code perspective is the type of data they return—MVC controllers typi- cally return a ViewResult; Web API controllers generally return raw .NET objects from the action methods, or an IActionResult such as StatusCodeResult, as you saw in chapter 4. NOTE This is different from the previous version of ASP.NET, where the MVC and Web API stacks were completely independent. ASP.NET Core uni- fies the two stacks into a single approach, which makes using both in a project painless! To give you an initial taste of what you’re working with, figure 9.3 shows the result of calling a Web API endpoint from your browser. Instead of a friendly HTML UI, you SPAs with ASP.NET Core The cross-platform and lightweight design of ASP.NET Core means it lends itself well to acting as a backend for your SPA framework of choice. Given the focus of this book and the broad scope of SPAs in general, I won’t be looking at Angular, React, or other SPAs here. Instead, I suggest checking out the resources appropriate to your chosen SPA. Books are available from Manning for all the common client-side frameworks:  React in Action by Mark Tielens Thomas (Manning, 2018) https://livebook .manning.com/#!/book/react-in-action/.  Angular in Action by Jeremy Wilken (Manning, 2018) https://livebook.manning .com/#!/book/angular-in-action/.  Vue.js in Action by Erik Hanchett with Benjamin Listwon (Manning, 2018) https://livebook.manning.com/#!/book/vue-js-in-action/. If you want to get started using ASP.NET Core with an SPA, Microsoft provides a num- ber of templates you can install for use with the .NET CLI. See https://docs .microsoft.com/en-us/ aspnet/core/spa/ for details. Additionally, these use Micro- soft JavaScriptServices to provide features such as server-side pre-rendering and hot- module replacement. See http://mng.bz/O4bd for details on integrating JavaScript- Services into your own apps.
  • 267.
    239 Creating your firstWeb API Controller receive data that can be easily consumed in code. In this example, the Web API returns a list of string fruit names as JSON when you request the URL /Fruit/Index. TIP Web APIs are normally accessed from code by SPAs or mobile apps, but by accessing the URL in your web browser directly, you can view the data the API is returning. Listing 9.1 shows the code that was used to create the Web API demonstrated in figure 9.3. This is obviously a trivial example, but it highlights the similarity to traditional MVC controllers. You can add a Web API controller to your project in exactly the same way as you saw in chapter 4, using the New Item dialog in Visual Studio, or by creating a new .cs file when you use the .NET CLI or another IDE. public class FruitController : Controller { List<string> _fruit = new List<string> { "Pear", "Lemon", "Peach" }; public IEnumerable<string> Index() { return _fruit; } } There’s nothing particularly special about this controller; it returns a list of strings when the action method executes. The only real difference from the equivalent MVC controller in this case is the return type of the action. Instead of returning a View- Result or an IActionResult, it directly returns the list of strings. When you return data directly from an action method, you’re providing the API model for the request. The client will receive this data. It’s formatted into an appropriate Listing 9.1 A simple Web API controller Figure 9.3 Testing a Web API by accessing the URL in the browser. A GET request is made to the /Fruit/Index URL, which returns a List<string> that has been JSON-encoded into an array of strings. The Web API controller inherits from the Controller base class, as per conventions The data returned would typically be fetched from the application model in a real app. The controller exposes a single action method, Index, which returns the list of fruit.
  • 268.
    240 CHAPTER 9Creating a Web API for mobile and client applications using MVC response, in this case a JSON representation of the list, and sent back to the browser with a 200 (OK) status code. TIP ASP.NET Core will format returned data as JSON by default. You’ll see how to format the returned data in other ways later in this chapter. The URL at which a Web API controller is exposed is handled in the same way as for traditional MVC controllers—using routing. The routing module directs a request to a particular controller and action, which is then invoked and returns a result. This result is then used to generate an appropriate response. Web API controllers don’t have to return data directly. You’re free to return an IActionResult instead, and often this is required. Depending on the desired behav- ior of your API, you may sometimes want to return data, and other times you may want to return a raw HTTP status code, indicating whether the request was successful. If an API call is made requesting details of a product that does not exist, you might want to return a 404 (Not Found) status code, as you’ll see shortly.1 Although the primary goal of a Web API is to return data, typically only the most trivial of cases will be able to return data directly from the action method in the man- ner you’ve seen. For example, if the request represented a command, such as “Delete the user with ID 9,” then there might not be any data to return, other than a success/ failure status code. Listing 9.2 shows an example of a case where you’d want to return an IAction- Result instead of returning the data directly. It shows another method on the same FruitController as before. This method exposes a way for clients to fetch a specific fruit by an id, which we’ll assume is its index in the list of _fruit you defined in the previous listing. Model binding is used to set the value of the id parameter from the request. public IActionResult View(int id) { if (id >= 0 && id < _fruit.Count) { return Ok(_fruit[id]); } return NotFound(); } 1 ASP.NET Core 2.1 introduces IActionResult<T>, which can simplify generating OpenAPI specifications for your Controllers. See http://mng.bz/FYH1 for details. Listing 9.2 A Web API action returning IActionResult to handle error conditions The action method returns an IActionResult as it can no longer always return List<string>. An element can only be returned if the id value is a valid _fruit element index. Using Ok to return data will format the data passed to it and send a 200 status code. NotFound returns a NotFoundResult, which will send a 404 status code.
  • 269.
    241 Creating your firstWeb API Controller In the successful path for the action method, the id parameter has a value greater than zero and less than the number of elements in _fruit. When that’s true, the value of the element can be returned to the caller. This is achieved by creating an OkResult, using the Ok helper method1 on the Controller base class. This generates a 200 status code and returns the element in the response body, as shown in figure 9.4, as if you had returned the _fruit[id] object from the method directly. If the id is outside the bounds of the _fruit list, then the method calls NotFound to create a NotFoundResult. When executed, this method generates a raw 404 HTTP sta- tus code response, which will show the default “not found” page for your browser, as shown in figure 9.5. The fact that you’re returning two different types depending on the code path taken means that you must mark the action method as returning an IActionResult in order to make the code compile. 1 Some people get uneasy when they see the phrase “helper method,” but there’s nothing magic about the Controller helpers—they’re shorthand for creating a new IActionResult of a given type. You don’t have to take my word for it though, you can always view the source code for the base class on GitHub at http://mng.bz/NZDt. Figure 9.4 Returning data using the Ok helper method. The Ok method uses the data passed to it to create the response body, then sets the status code to 200. Figure 9.5 A request that generates a raw 404 response without any content, as is typical for a Web API, will show the default “not found” page in a browser.
  • 270.
    242 CHAPTER 9Creating a Web API for mobile and client applications using MVC You’re free to return any type of IActionResult from your Web API controllers, but you’ll commonly return StatusCodeResult instances, which return a specific status code with or without associated data. NotFoundResult and OkResult both derive from StatusCodeResult, for example. Another commonly used response is a 400 (bad request), which is normally returned when the data provided in the request fails validation. This can be generated using a BadRequestResult, often using the Bad- Request helper method. TIP You learned about various IActionResults in chapter 4. BadRequest- Result, OkResult, and NotFoundResult all inherit from StatusCodeResult, and set the appropriate status code for their type (200, 404, and 400, respec- tively). Using these wrapper classes makes the intention of your code clearer than relying on other developers to understand the significance of the various status code numbers. Once you’ve returned an IActionResult (or other object) from your controller, it’s serialized to an appropriate response. This works in several ways, depending on  The formatters that your app supports  The data you return from your method  The data formats the requesting client can handle You’ll learn more about formatters and serializing data at the end of this chapter, but before we go any further, it’s worth zooming out a little, and exploring the parallels between traditional MVC applications and Web API endpoints. The two are similar, so it’s important to establish the patterns that they share and where they differ. 9.3 Applying the MVC design pattern to a Web API In the previous version of ASP.NET, Microsoft commandeered the generic term “Web API” to create the ASP.NET Web API framework. This framework, as you might expect, was used to create HTTP endpoints that could return formatted JSON or XML in response to requests. The ASP.NET Web API framework was completely separate from the MVC frame- work, even though it used similar objects and paradigms. The underlying web stacks for them were completely different beasts and couldn’t interoperate. In ASP.NET Core, that has all changed. You now have a single framework, unified in MvcMiddleware, which you can use to build both traditional web applications and Web APIs. The names MVC and Web API are often still used to differentiate between the different ways the MvcMiddleware can be applied, but there are few differences in the framework itself. You’ve already seen this yourself; the Web API FruitController you created in the last section derived from the same Controller base class as the examples you’ve seen in previous chapters. Consequently, even if you’re building an application that consists entirely of Web APIs, using no server-side rendering of HTML with Razor templates, the MVC design
  • 271.
    243 Applying the MVCdesign pattern to a Web API pattern still applies. Whether you’re building traditional web applications or Web APIs, you can structure your application virtually identically. After five chapters of it, you’re, I hope, nice and familiar with how ASP.NET Core handles an MVC request. But just in case you’re not, figure 9.6 shows how Mvc- Middleware handles a typical request after passing through the middleware pipeline. This example shows how a request to view the available fruit on a traditional grocery store website might look. The router routes the request to view all the fruit listed in the apples category to the View action method on the FruitController. The middleware then constructs a binding model, validates it, and passes it to the action. The action method interacts with the application model by calling into services, talking to a database, and return- ing the necessary data to the action. 1. A request is received to the URL /fruit/apples. Request Action View 2. The routing module matches the request to FruitController.View action and derives the route parameter category=apples. 5. The controller selects the view template and passes it the view model containing the list of apples, as well as other details, such as suggested items, favorites, or previously purchased apples. 6. The view uses the provided view model to generate an HTML response which is returned to the user via the middleware pipeline. Application model View model Domain model Services Database interaction Routing 4. The action method calls into services that make up the application model to fetch the apples and to build a view model. MVC controller HTML Binding model Model binding Model validation Binding model Model state 3. The method parameters for the View action method are bound and validated, and the action method is executed. Figure 9.6 Handling a request to a traditional MVC application, in which the view generates an HTML response that’s sent back to the user.
  • 272.
    244 CHAPTER 9Creating a Web API for mobile and client applications using MVC Finally, the action method selects a View to render, creates a view model, and executes the view to generate the HTML response. The MvcMiddleware returns the response to the middleware pipeline and back to the user’s browser. How would this change if the request came from a client-side or mobile applica- tion? If you want to serve machine-readable JSON instead of HTML, how does the MVC process change? As shown in figure 9.7, the answer is “very little.” This shows how a Web API endpoint handles a similar request. As before, routing handles the request by selecting a controller and action to invoke. You’ll normally use a slightly different routing system when building a Web API, as you’ll see later, but that’s completely optional. You can use the same conven- tional routing as with traditional MVC apps if you prefer. 1. A request is received to the URL /fruit/apples. Request JSON formatter 2. The routing module matches the request to the View action on the ApiFruitController and derives the route parameter category=apples. 5. The controller selects the JSON formatter and passes it the API model containing the list of apples. 6. The formatter uses the provided API model to generate a JSON response which is returned to the SPA / mobile app via the middleware pipeline. Application model API model Database interaction Routing 4. The action method calls into services that make up the application model to fetch the apples and to build a view model. Web API controller JSON Binding model Model binding Model validation Binding model Model state 3. The method parameters for the View action method are bound and validated, and the action method is executed. Services Action Domain model Figure 9.7 A call to a Web API endpoint in an e-commerce ASP.NET Core web application. The ghosted portion of the diagram is identical to figure 9.6.
  • 273.
    245 Applying the MVCdesign pattern to a Web API In this example, routing directs the request to a controller described as a Web API controller, but there’s nothing special about this class compared to the MVC control- ler from figure 9.6. The only difference is that the Web API controller will return data in a machine-readable format, rather than as HTML. TIP In ASP.NET Core, there’s no difference between MVC controllers and Web API controllers. The naming refers only to the difference in the data returned by their methods and the purpose for which they’re used.1 After routing comes model binding, in which the binder creates a binding model and populates it with values from the request, exactly as in the MVC request. Validation occurs in the same way. The action executes in the same way as for the MVC controller, by interacting with the application model—the very same application model as is used by the MVC con- troller. This is an important point; by separating the behavior of your app into an application model, instead of incorporating it into the controllers themselves, you’re able to reuse the business logic of your application. After the application model has returned the data necessary to service the request—the fruit objects in the apples category—you see the first difference to the traditional HTML app. Instead of building a view model, the action method creates an API model. This is analogous to the view model used previously, but rather than con- taining data used to generate an HTML view, it contains the data that will be sent back in the response. DEFINITION View models contain both data required to build a response and metadata about how to build the response. API Models only contain the data to be returned in the response. When we looked at the traditional MVC app, we used the view model in conjunction with a Razor view template to build the final response. With the Web API app, we use the API model in conjunction with a formatter. A formatter, as the name suggests, seri- alizes the API model into a machine-readable response, such as JSON or XML. The formatter forms the “V” in the Web API version of MVC, by choosing an appropriate representation for the data to return. Finally, as with the traditional web app, the generated response is then sent back through the middleware pipeline, passing through each of the configured middle- ware components, and back to the original caller. Hopefully, the parallels between traditional web applications and Web APIs are clear; the majority of behavior is identical, only the response varies. Everything from when the request comes in to the interaction with the application model is similar between the paradigms. 1 ASP.NET Core 2.1 introduces the [ApiController] attribute. You can use this attribute to opt-in to specific Web API conventions. To learn more about this attribute, see http://mng.bz/FYH1.
  • 274.
    246 CHAPTER 9Creating a Web API for mobile and client applications using MVC Some of the minor differences are down to convention in how Web APIs are used rather than hard and fast rules. These differences aren’t specific to Web APIs—you’re free to use them with MVC controllers too. You’ll tend to use them with Web APIs, pri- marily due to the differing use cases for traditional MVC apps and Web API apps. The first of these differences relates to the routing infrastructure and how the router selects action methods for execution based on an incoming request. You’ve already seen that MVC controllers typically use conventional routing, which selects an action method based on a handful of globally defined route templates. In contrast, Web API controllers tend to use attribute routing, where each action method is tied directly to one or more specific URLs. 9.4 Attribute routing: taking fine-grained control of your URLs Chapter 5 described the conventional routing scheme, used by default in most tradi- tional web applications. This involves defining a number of global routes that Mvc- Middleware compares to an incoming request’s URL. A route consists of a name, a route template, and, optionally, defaults and constraints to control whether the middleware con- siders a given request URL to be a match for the route. This listing shows an example of adding the MVC middleware to an application using the default MVC route. public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddMvc(); } public void Configure(IApplicationBuilder app) { app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); } } With the configuration shown in the listing, the routes are global; they’re applied to every request and they can select any action on a controller to be executed, depend- ing on the incoming URL. These routes are terse and defined in one place, so they’re often a good choice to use for your MVC applications. Where they fall down is when you need more control over the exact URLs you expose. When building a Web API application, you sometimes find you want, or need, to make minor changes to the URL a particular action exposes. These URLs form the Listing 9.3 Defining conventional routing when configuring MvcMiddleware
  • 275.
    247 Attribute routing: takingfine-grained control of your URLs public API of your application, so it’s important that they’re easy to consume. Imagine you have a commonly called action on the CategoriesController that returns a list of all the current categories on your e-commerce site that contain available products: public IActionResult ListAllCurrentCategoriesWithProducts(); Using conventional routing, that would map to an excessively long URL, /Categories/ListAllCurrentCategoriesWithProducts. The team decides to shorten it to /CurrentCategories, so users can easily understand which section of your app they’re in. With conventional routing you’d need to add a new, highly specific, route in the global configuration, to handle this outlier method. Over time, you might add other similarly specific routes, giving the potential for clashes and breaking other routes in your application. Conventional routing is not as useful when you need to have this level of fine-grained control over your routes. Instead, a more common approach is to use attribute routing for your Web APIs. Attribute routing, as the name suggests, involves applying attributes to your action methods to specify the URL that they should match. public class HomeController: Controller { [Route("")] public IActionResult Index() { /* method implementation*/ } [Route("contact")] public IActionResult Contact() { /* method implementation*/ } } By applying [Route] attributes to your controller, you bypass conventional routing for that action method. Each [Route] attribute defines a specific URL that corresponds to the associated action method. In the example provided, the "/" URL maps directly to the Index method and the "/contact" URL maps to the Contact method. TIP If you use [Route] attributes on all your action methods, then you don’t need to set up any conventional routes when you call UseMvc() in Startup.Configure(). Conceptually, attribute routing takes the opposite approach to conventional routing. The MVC-style conventional routing looks at the incoming URL, checks it against your route templates to fill in the blanks for the controller and action, and then checks to see if the requested controller and action exist in your application. Listing 9.4 Attribute routing example The Index action will be executed when the "/" URL is requested. The Contact action will be executed when the "/home/contact" URL is requested.
  • 276.
    248 CHAPTER 9Creating a Web API for mobile and client applications using MVC Attribute routing, on the other hand, starts by looking for all the controllers and action methods in your application that have [Route] attributes. It uses these to calcu- late the URL that needs to be called to invoke a given action, building a dictionary where the key is the URL and the value is the action method. When a request arrives, the router can check if the key is in the dictionary—if it is, then it knows which action to execute, if it isn’t, then the action isn’t attribute-routed. Attribute routing maps URLs to a specific action method, but a single action method can still have multiple URLs. Each URL must be declared with its own Route- Attribute, as shown in this listing, which shows the skeleton of the Web API for a car- racing game. public class CarController { [Route("car/start")] [Route("car/ignition")] [Route("start-car")] public IActionResult Start() { /* method implementation*/ } [Route("car/speed/{speed}")] [Route("set-speed/{speed}")] public IActionResult SetCarSpeed(int speed) { /* method implementation*/ } } The listing shows two different action methods, both of which can be accessed from multiple URLs. For example, the Start method will be executed when any of the fol- lowing URLS are requested:  /car/start  /car/ignition  /start-car These URLs are completely independent of the controller and action method names; only the value in the RouteAttribute matters. NOTE The controller and action name have no bearing on the URLs or route templates when RouteAttributes are used. The templates used by the route attributes are standard route templates, the same as you used in chapter 5. You can use literal segments and you’re free to define route parameters that will extract values from the URL, as shown by the SetCarSpeed Listing 9.5 Attribute routing with multiple attributes The Start method will be executed when any of these URLs are reached. The name of the action method has no effect on the RouteAttribute or URL. The RouteAttributes can contain route parameters, in this case speed.
  • 277.
    249 Attribute routing: takingfine-grained control of your URLs method in the previous listing. That method defines two route templates, both of which define a route parameter, {speed}. TIP I’ve used multiple [Route] attributes on each action in this example, but it’s best practice to expose your action at a single URL. This will make your API easier to understand and consume by other applications. Route parameters are handled in the very same way as for conventional routing—they represent a segment of the URL that can vary. The only difference with attribute rout- ing is that the controller and action name are already known, so you’re not allowed to use the {controller} and {action} parameters—these are already set to the control- ler and action that corresponds to the decorated action method. As before, when defining conventional routes, the route parameters in your Route- Attribute templates can  Be optional  Have default values  Use route constraints For example, you could update the SetCarSpeed method in the previous listing to constrain {speed} to an integer and to default to 20 like so: [Route("car/speed/{speed=20:int}")] [Route("set-speed/{speed=20:int}")] public IActionResult SetCarSpeed(int speed) It’s also possible to give your route attributes a name by providing one to Route- Attribute, for example: [Route("set-speed/{speed}", Name = "set_speed")] public IActionResult SetCarSpeed(int speed) Route names are optional, and aren’t used when matching URLs, but they can be use- ful for URL generation. You saw in chapter 5 how to create URLs in your action meth- ods using the Url helper and the RedirectToRoute method, and how to use them with Tag Helpers in chapter 8. Although you may only have half a dozen conventional routes, you effectively have a route for every attribute-routed action method that has been given a name. Each route name must be unique in the application, so be sure to keep track of them if you do use them! If you managed to get your head around routing in chapter 5, then attribute rout- ing shouldn’t hold any dangers for you. You’re writing a custom route template for every action method in your application. When combining both conventional and attribute routing in your application, you need to bear in mind how these two approaches interact and what it means for your URLs. 9.4.1 Ordering of conventional and attribute routes In chapter 5, you saw that the order in which you define conventional routes in Startup.cs controls the order in which those routes are checked when MvcMiddleware
  • 278.
    250 CHAPTER 9Creating a Web API for mobile and client applications using MVC receives a request. A route template defined earlier will be tested first, so gets first dibs on handling the request. Because of that, it’s important to define the most specific routes first, and the most general routes at the end. With attribute routing, ordering doesn’t happen in quite the same way. On startup, MvcMiddleware scans your app using reflection and locates all the routes applied using RouteAttributes. It then automatically orders the routes from most specific to least specific, as though you had defined the routes in the most optimum way. Having said that, RouteAttributes do have an Order property. You can use this to explicitly ensure a particular route attribute match is attempted before other routes, but I’d strongly advise against using it. Doing so adds another layer of complexity that you need to manage and could be indicative of excessive complexity in the URLs your application exposes. WARNING If you find yourself relying on a route order, then your URLs are probably confusing. This will make it difficult for clients to consume your API. Instead of using the Order property, look at ways to reduce the overlap between your routes. What does ordering routes in the most optimum way look like? Well, it obviously depends on your application, and if you’ve defined your [Route] attributes using a confusing URL space, then there may not be a truly optimum order. In those cases, falling back to route naming can sometimes be an easier solution than trying to coerce the correct order. An aspect I haven’t touched on yet is what happens when you have an application with both conventional and attribute routing. For example, the standard conventional routing for the Index action method public class HomeController { [Route("view”)] public IActionResult Index() { } } would suggest that the /home/index URL would invoke the action. On the other hand, attribute routing suggests the /view URL would invoke the action. Who wins? Either? Both? Well, applying attribute routing to an action means that it can never be matched using conventional routing. Even though the action theoretically matches the /home/index route, the conventional router can’t even see it, so it will never match. You’re free to use both conventional routing and attribute routing in a single application, but you need to bear this conflict between the two approaches in mind. Even if a convention seems like it should match a particular action, the presence of an attribute route will mean the action won’t be matched by the convention. TIP Attribute routing on an action (or a controller, as you’ll see) will make the action unreachable by conventional routing.
  • 279.
    251 Attribute routing: takingfine-grained control of your URLs This fact lends credence to the convention of sticking to conventional routing for tra- ditional MVC controllers that return HTML using Razor templates and using attribute routing for your Web API controllers. Doing so will make your life a lot simpler and can help avoid conflicts if you’re consistent with your naming.1 TIP Use conventional routing for MVC controllers, and attribute routing for Web API controllers. Adding a prefix such as "api" to your Web API route templates helps to separate the Web API URL space from the MVC URL space. One thing you might begin noticing when you start using attribute routing is how much more verbose it is than conventional routing. Where with conventional routing you had only a handful of routes, you now have one for every single action method! This is largely a symptom of the greater control attribute routing provides, but there are a few features available to make your life a little easier. In particular, combin- ing route attributes and token replacement can help reduce duplication in your code. 9.4.2 Combining route attributes to keep your route templates DRY Adding route attributes to all of your API controllers can get a bit tedious, especially if you’re mostly following conventions where your routes have a standard prefix such as "api" or the controller name. Generally, you’ll want to ensure you don’t repeat your- self (DRY) when it comes to these strings. The following listing shows two action meth- ods with a number of [Route] attributes. (This is for demonstration purposes only. Stick to one per action if you can!) public class CarController { [Route("api/car/start")] [Route("api/car/ignition")] [Route("/start-car")] public IActionResult Start() { /* method implementation*/ } [Route("api/car/speed/{speed}")] [Route("/set-speed/{speed}")] public IActionResult SetCarSpeed(int speed) { /* method implementation*/ } } 1 ASP.NET Core 2.1 introduces the [ApiController] attribute. Applying this attribute to your controller means it must only use [Route] attributes; it will never match conventional routes. Listing 9.6 Duplication in RouteAttribute templates
  • 280.
    252 CHAPTER 9Creating a Web API for mobile and client applications using MVC There’s quite a lot of duplication here—you’re adding "api/car" to most of your routes. Presumably, if you decided to change this to "api/vehicles", you’d have to go through each attribute and update it. Code like that is asking for a typo to creep in! To alleviate this pain, it’s also possible to apply RouteAttributes to controllers, in addition to action methods. When a controller and an action method both have a route attribute, the overall route template for the method is calculated by combining the two templates. [Route("api/car")] public class CarController { [Route("start")] [Route("ignition")] [Route("/start-car")] public IActionResult Start() { /* method implementation*/ } [Route("speed/{speed}")] [Route("/set-speed/{speed}")] public IActionResult SetCarSpeed(int speed) { /* method implementation*/ } } Combining attributes in this way can reduce some of the duplication in your route templates and makes it easier to add or change the prefixes (such as switching "car" to "vehicle") for multiple action methods. To ignore the RouteAttribute on the controller and create an absolute route template, start your action method route tem- plate with a slash (/). You can combine multiple RouteAttribute on controllers too, if you use inheri- tance. If all your controllers inherit from a base class, you can use this approach to add a global prefix to all your attribute routes. [Route("api")] public class BaseController : Controller { } [Route("car")] public class CarController : BaseController { [Route("start")] [Route("ignition")] Listing 9.7 Combining RouteAttribute templates Listing 9.8 Using a base class to add a global prefix to RouteAttribute templates Combines to give the"api/car/start" template Combines to give the "api/car/ignition" template Does not combine as starts with /, gives the "start-car" template Combines to give the "api/car/speed/{speed}" template Does not combine as starts with /, gives the "set- speed/{speed}" template The BaseController defines the "api" global prefix The CarController inherits the RouteAttribute from the BaseController and adds its own prefix Combines all attributes to give the "api/car/start" template Combines all attributes to give the "api/car/ignition" template
  • 281.
    253 Attribute routing: takingfine-grained control of your URLs [Route("/start-car")] public IActionResult Start() { /* method implementation*/ } } As you can see, the [Route] attribute from the “most-base” controller is used first, fol- lowed by the action controller, and finally, the action method, combining to give a sin- gle route for the action method. Once again, specifying a leading "/" to the route will prevent route combination and will use only the final [Route] attribute to define the route. This has reduced a lot of the duplication, but you can do one better by using token replacement. 9.4.3 Using token replacement to reduce duplication in attribute routing The ability to combine attribute routes is handy, but you’re still left with some duplica- tion if you’re prefixing your routes with the name of the controller, or your route tem- plates use the action name. Luckily, you can simplify even further! Attribute routes support the automatic replacement of the [action] and [controller] tokens in your attribute routes. These will be replaced with the name of the action and the controller (without the “Controller” suffix), respectively. The tokens are replaced after all attributes have been combined, so this is useful when you have controller inheritance hierarchies. This listing shows how you can simplify the previous route attribute hierarchy. [Route("api/[controller]")] public abstract class BaseController { } public class CarController : BaseController { [Route("[action]")] [Route("ignition")] [Route("/start-car")] public IActionResult Start() { /* method implementation*/ } } The router will also replace tokens in the Name property of RouteAttributes, which can make generating unique route names easier. If you use a RouteAttribute on a base controller with a tokenized name [Route("api/[controller]", Name = "[controller]_[action]")] Listing 9.9 Token replacement in RouteAttributes Does not combine with base attributes as it starts with /, so remains as "start-car" Token replacement happens last, so [controller] is replaced with "car" not "base" Combines and replaces tokens to give the "api/car/start" template Combines and replaces tokens to give the "api/car/ignition" template Does not combine with base attributes as it starts with /, so remains as "start-car"
  • 282.
    254 CHAPTER 9Creating a Web API for mobile and client applications using MVC then a unique route name will be generated for each action. You need to be careful if you have multiple RouteAttributes applied to a single action, as in the previous list- ing, as then the route names will no longer be unique! TIP Avoid using multiple [Route] attributes on your Web API action meth- ods. Having multiple URLs invoke the same action can be confusing for con- sumers of your app and complicates routing. We’ve covered pretty much everything there is to know about attribute routing now, with one exception: handling different HTTP request types like GET and POST. 9.4.4 Handling multiple matching actions with attribute routing In chapter 5, you saw that you could discriminate between two action methods that match the same URL by using HTTP verb attributes such as [HttpPost]. With Web API controllers, the need to distinguish between identical URLs becomes more preva- lent, due to their typical design. Imagine you’re building an API to manage your calendar. You want to be able to list and create appointments. Well, a traditional HTTP REST service might define the following URLs and HTTP verbs to achieve this:  GET /appointments—List all your appointments  POST /appointments—Create a new appointment Note that these two endpoints define the same URL, only the HTTP verb differs. This is common when building Web APIs and, luckily, is easy to model in ASP.NET Core. As with conventional routing, you use attributes such as [HttpPost] to define the HTTP verb an action method corresponds to. public class AppointmentController { [HttpGet("/appointments")] public IActionResult ListAppointments() { /* method implementation */ } [HttpPost("/appointments")] public IActionResult CreateAppointment() { /* method implementation */ } } These HTTP verb attributes are the same ones you saw used in chapter 5 with conven- tional routing, but in this case, they also contain the route template themselves. [HttpGet("/appointments")] effectively combines [Route("/appointments")] and Listing 9.10 Using HTTP verb attributes with attribute routing Only executed in response to GET /appointments Only executed in response to POST /appointments
  • 283.
    255 Enabling additional inputformatters: binding to XML data [HttpGet] into a more compact representation. For that reason, this is the preferred approach for defining your attribute routes. TIP Define routes using attributes such as [HttpGet], [HttpPost], and [HttpDelete]. This makes the action methods more specific and easier to reason about. And with that, you’ll probably be glad to hear we’re finished with routing for Web APIs and, in fact, for the whole book! With the route template details you have in chapter 5 and in this section, you have everything you need to customize the URLs in your application, whether you’re using conventional routes or attribute routing. Conceptually, people often find attribute routing a bit easier to grok, as you’re nor- mally mapping a URL one-to-one with an action method. Although having the free- dom to define the exact URL for a particular action method is useful, try not to get too power-crazed; always try to think about the users and clients consuming your API. Having a simple and well-defined set of URLs for your application should be your main goal. With routing checked off the list, it’s time to consider the next step in an MVC request—model binding—and how you can customize it for Web APIs. The process is identical for both MVC and Web API, but Web APIs often run into an additional requirement, especially when interoperating with legacy systems—the ability to post XML to a Web API action method. By default, this isn’t possible but, luckily, enabling it is easy. 9.5 Enabling additional input formatters: binding to XML data In chapter 6, I introduced model binding as a way of mapping an incoming request to the method parameters of an action. After the router selects an action method, the model binder is responsible for taking the data posted in a request and mapping it to the method parameters of the action method. In particular, you saw that by using the [FromBody] attribute on a method parame- ter, you could bind a parameter to the body of a request. These days, with mobile applications and SPAs becoming increasingly popular, the vast majority of data posted to Web APIs is JSON. The default model binder supports this out of the box, so all that’s required to have the JSON data bound to your method parameter is to add the [FromBody] attribute.1 There was a time, however, when XML was king. It’s still used for configuration (good old MSBuild and .csproj files) and many communication protocols (SOAP, RSS). Consequently, it’s quite likely you’ll need to be able to accept XML data to a Web API action at some point. 1 ASP.NET Core 2.1 introduces the [ApiController] attribute. If you decorate your Web API controller with this attribute, you don’t need to decorate your binding models with [FromBody]. The MVC middleware will infer that complex types should be bound using [FromBody] automatically.
  • 284.
    256 CHAPTER 9Creating a Web API for mobile and client applications using MVC By default, MvcMiddleware only accepts POST data in JSON format. In order to be able to accept XML data and have it bind automatically to your models, you’ll need additional services. Before you dig into how to add the formatters, it’s worth considering what happens if you try to send XML to an ASP.NET Core application that you haven’t yet configured to accept it. In this example, I’ve created a simple Web API Controller that parses an object form the request body and returns a 200 OK response: [HttpPost] public IActionResult Add([FromBody] Car car) { return Ok(); } Figure 9.8 shows a screenshot of Postman (www.getpostman.com), which you can use to create requests for testing your application. I created a request with XML in the body and POSTed it to the preceding action. As you can see, the application returns a 415 response code, which means “Unsupported Media Type”, indicating that Mvc- Middleware was unable to parse the XML in the body of the request. Clients include the content-type header as part of POST requests to tell the server what sort of data it’s sending. In this case, the request is sent with a content-type of text/xml. When the request arrives, it checks this header and looks for an input for- matter that can deserialize the request. TIP The input formatter is selected based on the content-type header of the request. Customizable by default The ability to customize each aspect of ASP.NET Core is one of the features that sets it apart from the previous version of ASP.NET. ASP.NET Core configures the vast majority of its internal components using one of two mechanisms—dependency injec- tion or by configuring an Options object when you add the service to your application, as you’ll see in chapters 10 (dependency injection) and 11 (Options object). As an adjunct to this, much of ASP.NET Core starts from the assumption that you want nothing in your application, and lets you add in the things you need. This means you can easily create small, stripped-back web applications, when compared to the monolithic System.Web-based previous version of ASP.NET. Your application has only the features you need. In counterpoint to this, this philosophy means you’ll run into many more situations where the default settings won’t fit your requirements. Luckily, the ability to easily customize all the components of ASP.NET Core means you’ll normally be able to add additional features without too much difficulty.
  • 285.
    257 Enabling additional inputformatters: binding to XML data You’ve already seen what happens when you make a request with a content-type your app can’t deserialize. ASP.NET Core doesn’t include an XML input formatter by default when you add MVC to your project, but you can add a NuGet package to pro- vide the functionality. 1 If you’re not using the ASP.NET Core 2.0 metapackage, add <PackageReference> for the Microsoft.AspNetCore.Mvc.Formatters.Xml package to your csproj file: <PackageReference Include="Microsoft.AspNetCore.Mvc.Formatters.Xml" Version="2.0.0" /> You can add the preceding line directly, use the Visual Studio package manager, or run the following command from inside your project’s folder: dotnet add package Microsoft.AspNetCore.Mvc.Formatters.Xml 2 Update the ConfigureServices method in Startup to add the formatters to MVC public void ConfigureServices(IServiceCollection services) { services.AddMvc() .AddXmlSerializerFormatters(); } That’s it! That simple change registers the XML input formatter with MvcMiddleware. Whenever your app receives a request with a content-type of text/xml or application/ A request is sent to the web API with a content-type of text/xml. The body of the request contains XML. The ASP .NET Core app is not configured to handle text/xml so it returns 4 5 Unsupported Media Type. 1 . Figure 9.8 When an application can’t handle the format of the data sent to a Web API action, it returns a 415 Unsupported Media Type response.
  • 286.
    258 CHAPTER 9Creating a Web API for mobile and client applications using MVC xml, the input formatter will handle it, parse the request, and allow the model binder to bind your data, so the application can return a 200 response, shown in figure 9.9. Accepting XML is a common requirement and, luckily, it’s easy to enable in your Web API (and MVC) controllers with the XML formatters package. Similarly, you often need to customize the format of the data returned by your Web API control- lers—whether that’s JSON, XML, or a different, custom format. In the next section, you’ll see how you can control this both at the application level and for individual action methods. 9.6 Generating a response from a model This brings us to the final section in this chapter: formatting a response. You’ve seen how to tweak your application for a Web API using attribute routing and adding dif- ferent input formatters, but the output formatting is where Web APIs differ from tradi- tional MVC controllers. Consider this scenario: You’ve created a Web API action method for returning a list of cars you’ve owned, as in the following listing. It invokes a method on your appli- cation model, which hands back the list of data to the controller. Now you need to for- mat the response and return it to the caller. A request is sent to the web API with a content-type of text/xml. The body of the request contains XML. The XML formatter package has been added, so the request is accepted, and the action method returns a 200 OK response. Figure 9.9 Once the XML formatters have been configured, the application can parse the text/xml content-type and returns a 200 OK response code.
  • 287.
    259 Generating a responsefrom a model public class CarsController : Controller { [HttpGet("api/cars")] public IEnumerable<string> ListCars() { return new string[] { "Nissan Micra", "FordFocus" }; } } You’ve already seen that it’s possible to return data directly from an action method, in which case, the middleware formats it and returns the formatted data to the caller. But how does the middleware know which format to use? After all, you could serialize it as JSON, as XML, even with a simple ToString() call. The process of determining the format of data to send to clients is known gener- ally as content negotiation (conneg). At a high level, the client sends a header indicating the types of content it can understand—the Accept header—and the server picks one of these, formats the response, and sends a content-type header in the response, indicating which it chose. You’re not forced into only sending a content-type the client expects and, in some cases, you may not even be able to handle the types it requests. What if a request stipu- lates it can only accept Excel spreadsheets? It’s unlikely you’d support that, even if that’s the only content-type the request contains! When you return an API model from an action method, whether directly (as in the previous listing) or via an OkResult or other StatusCodeResult, ASP.NET Core will Listing 9.11 A Web API controller to return a list of cars The action is executed with a request to GET /api/cars. The API Model containing the data is an IEnumerable<string>. This data would normally be fetched from the application model. The accept and content-type headers The accept header is sent by a client as part of a request to indicate the type of con- tent that the client can handle. It consists of a number of MIME types,a with optional weightings (from 0 to 1) to indicate which type would be preferred. For example, the application/json,text/xml;q=0.9,text/plain;q=0.6 header indicates that the client can accept JSON, XML, and plain text, with weightings of 1.0, 0.9, and 0.6, respectively. JSON has a weighting of 1.0, as no explicit weighting was provided. The weightings can be used during content negotiation to choose an optimal representa- tion for both parties. The content-type header describes the data sent in a request or response. It con- tains the MIME type of the data, with an optional character encoding. For example, the application/json; charset=utf-8 header would indicate that the body of the request or response is JSON, encoded using UTF-8. a For more on MIME types see http://mng.bz/D3UB.
  • 288.
    260 CHAPTER 9Creating a Web API for mobile and client applications using MVC always return something. If it can’t honor any of the types stipulated in the Accept header, it will fall back to returning JSON by default. Figure 9.10 shows that even though XML was requested, MvcMiddleware formatted the response as JSON. NOTE In the previous version of ASP.NET, objects were serialized to JSON using PascalCase, where properties start with a capital. In ASP.NET Core, objects are serialized using camelCase by default, where properties start with a lowercase letter. Whichever way the data is sent, it’s serialized by an IOutputFormatter implementa- tion. ASP.NET Core ships with a limited number of output formatters out of the box, but as always, it’s easy to add additional ones, or change the way the defaults work. 9.6.1 Customizing the default formatters: adding XML support As with most of ASP.NET Core, the Web API formatters are completely customizable. By default, only formatters for plain text (text/plain), HTML (text/html), and JSON (application/json) are configured. A request is sent to the web API with an accept header type of text/xml. The ASP .NET Core app is not configured to return text/xml, so it returns JSON by default instead. Figure 9.10 Even though the request was made with an Accept header of text/xml, the response returned was JSON, as the server was not configured to return XML.
  • 289.
    261 Generating a responsefrom a model Given the common use case of SPAs and mobile applications, this will get you a long way. But as you saw in section 9.5, you often need to be able to return data in another format, such as XML. You’ve already seen how to add XML output-formatting support—it’s the same process as adding input-formatter support in section 9.5! Technically, you can add the output and input formatters independently, but it’s unlikely you’ll need one and not the other. Not many clients are going to send you JSON but demand an XML response! As you saw in section 9.5, add the Microsoft.AspNetCore.Mvc.Formatters.Xml package, and update the call to AddMvc()to add the output formatters: services.AddMvc() .AddXmlSerializerFormatters(); With this simple change, the middleware can now format responses as XML. Running the same request as shown in figure 9.10 with XML support enabled means the app will respect the text/xml accept header. The formatter serializes the string array to XML, as shown in figure 9.11, instead of the JSON returned previously. A request is sent to the web API with an accept header type of text/xml. With the XML formatters added, the accept header can be honored, so text/xml is returned instead of the default JSON response. Figure 9.11 With the XML output formatters added, the text/xml Accept header is respected and the response can be serialized to XML.
  • 290.
    262 CHAPTER 9Creating a Web API for mobile and client applications using MVC This is an example of content negotiation, where the client has specified what formats it can handle and the server selects one of those, based on what it can produce. This approach is part of the HTTP protocol, but there are some quirks to be aware of when relying on it in ASP.NET Core. You won’t often run into these, but if you’re not aware of them when they hit you, they could have you scratching your head for hours! 9.6.2 Choosing a response format with content negotiation Content negotiation is where a client says which types of data it can accept using the accept header and the server picks the best one it can handle. Generally speak- ing, this works as you’d hope: the server formats the data using a type the client can understand. The ASP.NET Core implementation has some special cases that are worth bearing in mind:  By default, the middleware will only return application/json, text/plain and text/html MIME types. You can add additional IOutputFormatters to make other types available, as you saw in the previous section for text/xml.  By default, if you return null as your API model, whether from an action method, or by passing null in StatusCodeResult, the middleware will return a 204 No Content response.  When you return a string as your API model, if no Accept header is set, the middleware will format the response as text/plain.  When you use any other class as your API model and there’s either no Accept header or none of the supported formats were requested, the first formatter that can generate a response will be used (typically JSON by default).  If the middleware detects that the request is probably from a browser (the accept header contains */*), then it will not use conneg. Instead, it will format the response as though no accept header was provided, using the default for- matter (typically JSON). These defaults are relatively sane, but they can certainly bite you if you’re not aware of them. The last point in particular, where the response to a request from a browser is virtually always formatted as JSON has certainly caught me out when trying to test XML requests locally! As you should expect by now, all of these rules are configurable; you can easily change the default behavior in your application if it doesn’t fit your requirements. In chapter 4, I showed how to customize MVC by modifying the maximum number of validation errors in your application. The following listing shows a similar approach, in which you can force the middleware to respect the browser’s Accept header when adding the MVC services in Startup.cs.
  • 291.
    263 Summary public void ConfigureServices(IServiceCollectionservices) { services.AddMvc(options => { options.RespectBrowserAcceptHeader = true; }); } In most cases, conneg should work well for you out of the box, whether you’re build- ing a SPA or a mobile application. In some cases, you may find you need to bypass the usual conneg mechanisms for certain actions, and there are a number of ways to achieve this, but I won’t cover them in this book as I’ve found I rarely need to use them. For details, see the documentation at http://mng.bz/tnu9. That brings us to the end of this chapter on Web APIs and, with it, part 1 of this book! It’s been a pretty intense tour of ASP.NET Core, with a heavy focus on the MVC middleware. By making it this far, you now have all the knowledge you need to start building simple MVC applications using Razor view templates, or to create a Web API server for your SPA or mobile app. In part 2, you’ll get into some juicy topics, in which you’ll learn the details needed to build complete apps, like adding users to your application, saving data to a data- base, and how to deploy your application. In chapter 10, we’ll look at dependency injection in ASP.NET Core and how it helps create loosely coupled applications. You’ll learn how to register the ASP.NET Core framework services with a container and set up your own classes as dependency- injected services. Finally, you’ll see how to replace the built-in container with a third- party alternative. Summary  A Web API exposes a number of methods or endpoints that can be used to access or change data on a server. It’s typically accessed using HTTP.  Web API action methods can return data directly or can use IActionResult to generate an arbitrary response.  Web APIs follow the same MVC design pattern as traditional web applications. The formatters which generate the final response form the view.  Unlike the previous version of ASP.NET, there’s no difference between MVC controllers and Web API controllers in ASP.NET Core. The naming refers only to the difference in the data returned by their methods and the purpose for which they’re used.  The data returned by a Web API action is called an API model. It contains the data the middleware will serialize and send back to the client. This differs from Listing 9.12 Customizing MVC to respect the browser Accept header in Web APIs AddMvc has an overload that takes a lambda function False by default, a number of other properties are also available to be set
  • 292.
    264 CHAPTER 9Creating a Web API for mobile and client applications using MVC view models, as view models contain both data and metadata about how to gen- erate the response.  Attribute routing is an alternative way of defining the routes in your application by applying RouteAttributes to your action methods. Web API controllers often use this approach, as it allows tighter control over the URL generated for each action method.  The controller and action name have no bearing on the URLs or route tem- plates when you use attribute routing.  Route attributes applied to a controller combine with attributes on action methods to form the final template. These are also combined with attributes on inherited base classes.  If an action or controller uses attribute routing, it can no longer be reached via conventional routing.  Use the "[controller]" and "[action]" tokens in your route templates to reduce repetition. They’ll be replaced with the current controller and action name.  The [HttpPost] and [HttpGet] attributes allow choosing between actions based on the request’s HTTP method when two actions correspond to the same URL.  You can model bind requests sent in the XML format using the Microsoft .AspNetCore.Mvc.Formatters.Xml package. Add the XML formatters by calling services.AddMvc().AddXmlSerializerFormatters() in your Startup class. This will also enable XML responses.  By default, ASP.NET Core will format the API model returned from a Web API controller as JSON.  If you return more than one type of result from an action method, the method signature must return IActionResult.  In contrast to the previous version of ASP.NET, JSON data is serialized using camelCase rather than PascalCase.  Content negotiation occurs when the client specifies the type of data it can han- dle and the server chooses a return format based on this.  By default, ASP.NET Core can return text/plain, text/html, and application/ json, but you can add additional formatters.  Content negotiation isn’t used when the accept header contains */*, such as in most browsers. You can disable this option by modifying the RespectBrowser- AcceptHeader option when adding MVC services in Startup.cs.
  • 293.
    Part 2 Building completeapplications We covered a lot of ground in part 1. You saw how an ASP.NET Core appli- cation is composed of middleware and we focused heavily on the MVC middle- ware. You saw how to use it to build traditional server-side-rendered apps using Razor and how to build web APIs for mobile and client-side apps. In part 2, we dive deeper into the framework and look at a variety of compo- nents that you’ll inevitably need to build more complex apps. By the end of this part, you’ll be able to build dynamic applications, customized to specific users, that can be deployed to multiple environments, each with a different configuration. ASP.NET Core uses dependency injection (DI) throughout its libraries, so it’s important that you understand how this design pattern works. In chapter 10, I introduce DI, why it is used, and how to configure the services in your applica- tions to use DI. Chapter 11 looks at the ASP.NET Core configuration system, which lets you pass configuration values to your app from a range of sources—JSON files, envi- ronment variables, and many more. You’ll learn how to configure your app to use different values depending on the environment in which it is running, and how to bind strongly typed objects to your configuration to help reduce runtime errors. Most web applications require some sort of data storage, so in chapter 12, I introduce Entity Framework Core (EF Core). This is a new, cross-platform library that makes it easier to connect your app to a database. EF Core is worthy of a book in and of itself, so I only provide a brief introduction. I show you how to create a database and how to insert, update, and query simple data.
  • 294.
    266 CHAPTER In chapters13 through 15, we look at how to build more complex applications. You’ll see how you can add ASP.NET Core Identity to your apps so that users can log in and enjoy a customized experience. You’ll learn how to protect your app using authorization to ensure only certain users can access certain action methods, and you’ll see how to refactor your app to extract common code out of your action meth- ods in filters. In the final chapter of this part, I cover the steps required to make an app live, including how to publish your app to IIS, how to configure the URLs your app listens on, and how to optimize your client-side assets for improved performance.
  • 295.
    267 Service configuration with dependencyinjection In part 1 of this book, you saw the bare bones of how to build applications with ASP.NET Core. You learned how to compose middleware to create your application and how to use the MVC pattern to build traditional web applications and web APIs. This gave you the tools to start building simple applications. In this chapter, you’ll see how to use dependency injection (DI) in your ASP.NET Core applications. DI is a design pattern that helps you develop loosely coupled code. ASP.NET Core uses the pattern extensively, both internally in the framework This chapter covers  Understanding the benefits of dependency injection  How ASP.NET Core uses dependency injection  Configuring your services to work with dependency injection  Choosing the correct lifetime for your services
  • 296.
    268 CHAPTER 10Service configuration with dependency injection and in the applications you build, so you’ll need to use it in all but the most trivial of applications. You may have heard of DI before, and possibly even used it in your own applica- tions. If so, this chapter shouldn’t hold many surprises for you. If you haven’t used DI before, never fear, I’ll make sure you’re up to speed by the time the chapter is done! This chapter will start by introducing DI in general, the principles it drives, and why you should care about it. You’ll see how ASP.NET Core has embraced DI throughout its implementation and why you should do the same when writing your own applications. Once you have a solid understanding of the concept, you’ll see how to apply DI to your own classes. You’ll learn how to configure your app so that the ASP.NET Core framework can create your classes for you, removing the pain of having to create new objects manually in your code. Toward the end of the chapter, you’ll learn how to con- trol how long your objects are used for and some of the pitfalls to be aware of when you come to write your own applications. In chapter 19, we’ll revisit some of the more advanced ways to use DI, including how to wire up a third-party DI container. For now though, let’s get back to basics: what is DI and why should you care about it? 10.1 Introduction to dependency injection The ASP.NET Core framework has been designed from the ground up to be modular and to adhere to “good” software engineering practices. As with anything in software, what is considered best practice varies over time, but for object-oriented program- ming, the SOLID1 principles have stood the test of time. On that basis, ASP.NET Core has dependency injection (sometimes called dependency inversion, DI, or inversion of control2 ) baked in to the heart of the framework. Whether or not you want to use it within your own application code, the framework libraries themselves depend on it as a concept. This section aims to give you a basic understanding of what dependency injection is, why you should care about it, and how ASP.NET Core uses it. The topic itself extends far beyond the reach of this single chapter. If you want a deeper background, I highly recommend checking out Martin Fowler’s articles online.3 TIP For a more directly applicable read with many examples in C#, I recom- mend picking up Dependency Injection in .NET by Steven van Deursen and Mark Seemann (Manning, 2017). 1 SOLID is a mnemonic for Single responsibility, Open-closed, Liskov substitution, Interface segregation, and Dependency inversion: https://en.wikipedia.org/wiki/SOLID_(object-oriented_design). 2 Although related, dependency injection and dependency inversion are two different things. I cover both in a general sense in this chapter, but for a good explanation of the differences, see this post by Derick Bailey: http://mng.bz/vU7N. 3 Martin Fowler’s website at https://martinfowler.com is a gold mine of best-practice goodness. One of the most applicable articles to this chapter can be found at www.martinfowler.com/articles/injection.html.
  • 297.
    269 Introduction to dependencyinjection I’ll begin this section by starting with a common scenario: a class in your application depends on a different class, which in turn depends on another. You’ll see how depen- dency injection can help alleviate this chaining of dependencies for you and provide a number of extra benefits. 10.1.1 Understanding the benefits of dependency injection When you first started programming, the chances are you didn’t immediately use a DI framework. That’s not surprising, or even a bad thing; DI adds a certain amount of extra wiring that’s not warranted in simple applications or when you’re getting started. But when things start to get more complex, DI comes into its own as a great tool to help keep that complexity in check. Let’s consider a simple example, written without any sort of DI. Imagine a user has registered on your web app and you want to send them an email. This listing shows how you might approach this initially. public class UserController : Controller { public IActionResult RegisterUser(string username) { var emailSender = new EmailSender(); emailSender.SendEmail(username); return View(); } } In this example, the RegisterUser action on UserController executes when a new user registers on your app. This creates a new instance of an EmailSender class, and calls SendEmail() to send the email. The EmailSender class is the one that does the sending of the email. For the purposes of this example, you can imagine it will look something like this: public class EmailSender { public void SendEmail(string username) { Console.WriteLine($"Email sent to {username}!"); } } Console.Writeline will perform the sending of the email. NOTE Although I’m using sending email as a simple example, in practice you might want to move this code out of your Controller classes entirely. This sort of task is well suited to using message queues and a background process, but that’s outside the scope of this book. Listing 10.1 Sending an email without DI when there are no dependencies The action method is called when a new user is created. Creates a new instance of EmailSender Uses the new instance to send the email
  • 298.
    270 CHAPTER 10Service configuration with dependency injection If the EmailSender class is as simple as the previous example and it has no dependen- cies, then you might not see any need to adopt a different approach to creating objects. And to an extent, you’d be right. But what if you later update your implementation of EmailSender so that it doesn’t implement the whole email-sending logic itself? In practice, EmailSender would need to do a number of things to send an email. It would need to  Create an email message  Configure the settings of the email server  Send the email to the email server Doing all of that in one class would go against the single responsibility principle (SRP), so you’d likely end up with EmailSender depending on other services. Figure 10.1 shows how this web of dependencies might look. UserController wants to send an email using EmailSender, but in order to do so, it also needs to create the MessageFactory, NetworkClient, and EmailServerSettings objects that EmailSender depends on. Each class has a number of dependencies, so the “root” class, in this case User- Controller, needs to know how to create every class it depends on, as well as every class its dependencies depend on. This is sometimes called the dependency graph. DEFINITION The dependency graph is the set of objects that must be created in order to create a specific requested “root” object. EmailSender MessageFactory EmailServerSettings UserController NetworkClient EmailSender depends on MessageFactory and NetworkClient. UserController depends on EmailSender. NetworkClient depends on EmailServerSettings. To use EmailSender, UserController must create all of the dependencies. Figure 10.1 Dependency diagram without dependency injection. UserController indirectly depends on all the other classes; it also creates them all.
  • 299.
    271 Introduction to dependencyinjection EmailSender depends on the MessageFactory and NetworkClient objects, so they’re provided via the constructor, as shown here. public class EmailSender { private readonly NetworkClient _client; private readonly MessageFactory _factory; public EmailSender(MessageFactory factory, NetworkClient client) { _factory = factory; _client = client; } public void SendEmail(string username) { var email = _factory.Create(username); _client.SendEmail(email); Console.WriteLine($"Email sent to {username}!"); } } On top of that, the NetworkClient class that EmailSender depends on also has a dependency on an EmailServerSettings object: public class NetworkClient { private readonly EmailServerSettings _settings; public NetworkClient(EmailServerSettings settings) { _settings = settings; } } This might feel a little contrived, but it’s common to find this sort of chain of depen- dencies. In fact, if you don’t have this in your code, it’s probably a sign that your classes are too big and aren’t following the single responsibility principle. So, how does this affect the code in UserController? The following listing shows how you now have to send an email, if you stick to new-ing up objects in the controller. public IActionResult RegisterUser(string username) { var emailSender = new EmailSender( new MessageFactory(), new NetworkClient( new EmailServerSettings ( host: "smtp.server.com", port: 25 )) Listing 10.2 A service with multiple dependencies Listing 10.3 Sending email without DI when you manually create dependencies The EmailSender now depends on two other classes. Instances of the dependencies are provided in the constructor. The EmailSender coordinates the dependencies to create and send an email. To create EmailSender, you also have to create all of its dependencies. You need a new MessageFactory. The NetworkClient also has dependencies. You’re already two layers deep, but there could feasibly be more.
  • 300.
    272 CHAPTER 10Service configuration with dependency injection ); emailSender.SendEmail(username); return View(); } This is turning into some gnarly code. Improving the design of EmailSender to sepa- rate out the different responsibilities has made calling it from UserController a real chore. This code has a number of issues, among them:  Not obeying the single responsibility principle—Our code is now responsible for both creating an EmailSender object and using it to send an email.  Considerable ceremony—Of the 11 lines of code in the RegisterUser method, only the last two are doing anything useful. This makes it harder to read and harder to understand the intent of the method.  Tied to the implementation—If you decide to refactor EmailSender and add another dependency, you’d need to update every place it’s used. UserController has an implicit dependency on the EmailSender class, as it manually creates the object itself as part of the RegisterUser method. The only way to know that UserController uses EmailSender is to look at its source code. In contrast, EmailSender has explicit dependencies on NetworkClient and MessageFactory, which must be provided in the constructor. Similarly, NetworkClient has an explicit depen- dency on the EmailServerSettings class. TIP Generally speaking, any dependencies in your code should be explicit, not implicit. Implicit dependencies are hard to reason about and difficult to test, so you should avoid them wherever you can. DI is useful for guiding you along this path. Dependency injection aims to solve the problem of building a dependency graph by inverting the chain of dependencies. Instead of the UserController creating its dependencies manually, deep inside the implementation details of the code, an already-created instance of EmailSender is injected via the constructor. Now, obviously something needs to create the object, so the code to do that has to live somewhere. The service responsible for creating an object is called a DI container or an IoC container, as shown in figure 10.2. DEFINITION The DI container or IoC container is responsible for creating instances of services. It knows how to construct an instance of a service by cre- ating all of its dependencies and passing these to the constructor. I’ll refer to it as a DI container throughout this book. The term dependency injection is often used interchangeably with inversion of control (IoC). IoC describes the way that the control of how EmailSender is created has been reversed: instead of the UserController controlling how to create an instance, it’s provided one instead. Finally, you can send the email.
  • 301.
    273 Introduction to dependencyinjection NOTE There are many existing DI containers available for .NET: Autofac, StructureMap, Unity, Ninject, Simple Injector . . . The list goes on! In chap- ter 19, you’ll see how to replace the default ASP.NET Core container with one of these alternatives. The advantage of adopting this pattern becomes apparent when you see how much it simplifies using dependencies. The following listing shows how UserController would look if you used DI to create EmailSender instead of doing it manually. All of the new cruft has gone and you can focus purely on what the controller is doing—call- ing EmailSender and returning a View. public class UserController : Controller { private readonly EmailSender _emailSender; public UserController(EmailSender emailSender) Listing 10.4 Sending an email using DI to inject dependencies EmailSender MessageFactory EmailServerSettings UserController NetworkClient The EmailSender depends on the MessageFactory and the NetworkClient. The UserController depends on the EmailSender. The NetworkClient depends on the EmailServerSettings. Dependency Injection Container The DI Container creates the complete dependency graph and provides an instance of the EmailSender to the User Controller. Figure 10.2 Dependency diagram using dependency injection. UserController indirectly depends on all the other classes but doesn’t need to know how to create them. UserController declares that it requires EmailSender and the container provides it. Instead of creating the dependencies implicitly, they’re injected via the constructor.
  • 302.
    274 CHAPTER 10Service configuration with dependency injection { _emailSender = emailSender; } public IActionResult RegisterUser(string username) { _emailSender.SendEmail(username); return View(); } } One of the advantages of a DI container is that it has a single responsibility: creating objects or services. You ask a container for an instance of a service and it takes care of figuring out how to create the dependency graph, based on how you configure it. NOTE It’s common to refer to services when talking about DI containers, which is slightly unfortunate as it’s one of the most overloaded terms in soft- ware engineering! In this context, a service refers to any class or interface that the DI container creates when required. The beauty of this approach is that by using explicit dependencies, you never have to write the mess of code you saw in listing 10.3. The DI container can inspect your ser- vice’s constructor and work out how to write the majority of the code itself. DI con- tainers are always configurable, so if you want to describe how to manually create an instance of a service you can, but by default you shouldn’t need to. TIP You can inject dependencies into a service in other ways; for example, by using property injection. But constructor injection is the most common and is the only one supported out of the box in ASP.NET Core, so I’ll only be using that in this book. Hopefully, the advantages of using DI in your code are apparent from this quick example, but DI provides additional benefits that you get for free. In particular, it helps keep your code loosely coupled by coding to interfaces. 10.1.2 Creating loosely coupled code Coupling is an important concept in object-oriented programming. It refers to how a given class depends on other classes to perform its function. Loosely coupled code doesn’t need to know a lot of details about a particular component to use it. The initial example of UserController and EmailSender was an example of tight coupling; you were creating the EmailSender object directly and needed to know exactly how to wire it up. On top of that, the code was difficult to test. Any attempts to test UserController would result in an email being sent. If you were testing the con- troller with a suite of unit tests, that seems like a surefire way to get your email server blacklisted for spam! Taking EmailSender as a constructor parameter and removing the responsibility of creating the object helps reduce the coupling in the system. If the EmailSender Instead of creating the dependencies implicitly, they’re injected via the constructor. The action method is easy to read and understand again.
  • 303.
    275 Introduction to dependencyinjection implementation changes so that it has another dependency, you no longer have to update UserController at the same time. One issue that remains is that UserController is still tied to an implementation rather than an interface. Coding to interfaces is a common design pattern that helps fur- ther reduce the coupling of a system, as you’re not tied to a single implementation. This is particularly useful in making classes testable, as you can create “stub” or “mock” implementations of your dependencies for testing purposes, as shown in figure 10.3. TIP You can choose from many different mocking frameworks. My favorite is Moq, but NSubstitute and FakeItEasy are also popular options. As an example, you might create an IEmailSender interface, which EmailSender would implement: public interface IEmailSender { public void SendEmail(string username); } UserController could then depend on this interface instead of the specific EmailSender implementation, as shown here. That would allow you to use a different implementation during unit tests, a DummyEmailSender for example. public class UserController : Controller { private readonly IEmailSender _emailSender; public UserController(IEmailSender emailSender) { _emailSender = emailSender; } Listing 10.5 Using interfaces with dependency injection IEmailSender EmailSender UserController Instead of depending on a specific implementation, the UserController depends on the interface IEmailSender. MockEmailSender HtmlEmailSender At runtime, we can choose a specific implementation to use. We can even use “stub” or “mock” implementations for unit tests. Figure 10.3 By coding to interfaces instead of an explicit implementation, you can use different IEmailSender implementations in different scenarios, for example a MockEmailSender in unit tests. You now depend on IEmailSender instead of the specific EmailSender implementation.
  • 304.
    276 CHAPTER 10Service configuration with dependency injection public IActionResult RegisterUser(string username) { _emailSender.SendEmail(username); return View(); } } The key point here is that the consuming code, UserController, doesn’t care how the dependency is implemented, only that it implements the IEmailSender interface and exposes a SendEmail method. The application code is now independent of the implementation. Hopefully, the principles behind DI seem sound—by having loosely coupled code, it’s easy to change or swap out implementations completely. But this still leaves you with a question: how does the application know to use EmailSender in production instead of DummyEmailSender? The process of telling your DI container, “when you need IEmailSender, use EmailSender” is called registration. DEFINITION You register services with a DI container so that it knows which implementation to use for each requested service. This typically takes the form of, “for interface X, use implementation Y.” Exactly how you register your interfaces and types with a DI container can vary depending on the specific DI container implementation, but the principles are gener- ally all the same. ASP.NET Core includes a simple DI container out of the box, so let’s look at how it’s used during a typical request. 10.1.3 Dependency injection in ASP.NET Core ASP.NET Core was designed from the outset to be modular and composable, with an almost plugin-style architecture, which is generally complemented by DI. Conse- quently, ASP.NET Core includes a simple DI container that all the framework libraries use to register themselves and their dependencies. This container is used, for example, to register all of the MVC infrastructure—the formatters, the view engine, the validation system, and so on. It’s only a basic con- tainer, so it only exposes a few methods for registering services, but you can also replace it with a third-party DI container. This can give you extra capabilities, such as auto-registration or setter injection. The DI container is built into the ASP.NET Core hosting model, as shown in figure 10.4. The hosting model pulls dependencies from the DI container when they’re needed. If the framework determines that UserController is required due to the incoming URL/route, the controller activator responsible for creating a Controller instance will ask the DI container for an IEmailSender implementation. You don’t care what the implementation is, as long as it implements IEmailSender.
  • 305.
    277 Introduction to dependencyinjection NOTE This approach, where a class calls the DI container directly to ask for a class is called the service locator pattern. Generally speaking, you should try to avoid this pattern in your code; include your dependencies as constructor arguments directly and let the DI container provide them for you.1 The DI container needs to know what to create when asked for IEmailSender, so you must have registered an implementation, such as EmailSender, with the container. Once an implementation is registered, the DI container can inject it anywhere. That means you can inject framework-related services into your own custom services, as long as they are registered with the container. It also means you can register alterna- tive versions of framework services and have the framework automatically use those in place of the defaults. The flexibility to choose exactly how and which components you combine in your applications is one of the selling points of DI. In the next section, you’ll learn how to configure DI in your own ASP.NET Core application, using the default, built-in container. 1 You can read about the Service Locator antipattern in Dependency Injection in .NET by Steven van Deursen and Mark Seemann (Manning, 2017), http://mng.bz/i995. Dependency Injection Container UserController 1. A request is received to the URL /RegisterUser. Request 2. The routing module determines that the request should be directed to the RegisterUser action on the UserController. Routing EmailSender 3. The controller activator calls the DI container to create an instance of the UserController, including all of its dependencies. Controller activator 4. The RegisterUser method on the UserController instance is invoked, passing in the binding model. Binding Model Figure 10.4 The ASP.NET Core hosting model uses the DI container to fulfill dependencies when creating controllers.
  • 306.
    278 CHAPTER 10Service configuration with dependency injection 10.2 Using the dependency injection container In previous versions of ASP.NET, using dependency injection was entirely optional. In contrast, to build all but the most trivial ASP.NET Core apps, some degree of DI is required. As I’ve mentioned, the underlying framework depends on it, so things like using MVC require you to configure the required services. In this section, you’ll see how to register these framework services with the built-in container, as well as how to register your own services. Once services are registered, you can use them as dependencies and inject them into any of the services in your application. 10.2.1 Adding ASP.NET Core framework services to the container As I described earlier, ASP.NET Core uses DI to configure its internal components as well as your own custom services. In order to be able to use these components at run- time, the DI container needs to know about all the classes it will need. You register these in the ConfigureServices method of your Startup class. NOTE The dependency injection container is set up in the Configure- Services method of your Startup class in Startup.cs. Now, if you’re thinking, “Wait, I have to configure the internal components myself?” then don’t panic. Although true in one sense—you do need to explicitly register the components with the container in your app—all the libraries you’ll use expose handy extension methods that you need to ensure you call. For example, the MVC middleware exposes the AddMvc() extension method that you saw in chapters 2, 3, and 4. Invoke the extension method in ConfigureServices of Startup. public void ConfigureServices(IServiceCollection services) { services.AddMvc(); } It’s as simple as that. Under the hood, this call is registering multiple components with the DI container, using the same APIs you’ll see shortly for registering your own services. Most nontrivial libraries that you add to your application will have services that you need to add to the DI container. By convention, each library that has necessary services should expose an Add*() extension method that you can call in ConfigureServices. There’s no way of knowing exactly which libraries will require you to add services to the container, it’s generally a case of checking the documentation for any libraries you use. If you forget to add them, then you may find the functionality doesn’t work, or you might get a handy exception like the one shown in figure 10.5. Keep an eye out for these and be sure to register any services that you need! Listing 10.6 Registering the MVC services with the DI container The AddMvc extension method adds all necessary services to the IServiceCollection.
  • 307.
    279 Using the dependencyinjection container It’s also worth noting that some of the Add*() extension methods allow you to specify additional options when you call them, often by way of a lambda expression. You can think of these as configuring the installation of a service into your application. The MVC middleware, for example, provides a wealth of options for fine-tuning its behavior if you want to get your fingers dirty, as shown by the IntelliSense snippet in figure 10.6. Once you’ve added the required framework services, you can get down to business and register your own services, so you can use DI in your own code. 10.2.2 Registering your own services with the container In the first section of this chapter, I described a system for sending emails when a new user registers on your application. Initially, UserController was manually creating an instance of EmailSender, but you subsequently refactored this, so you inject an instance of IEmailSender into the constructor instead. Figure 10.5 If you fail to call AddMvc in the ConfigureServices of Startup, you’ll get a friendly exception message at runtime. Figure 10.6 Configuring services when adding them to the service collection. The AddMvc() function allows you to configure a wealth of the internals of the MVC middleware.
  • 308.
    280 CHAPTER 10Service configuration with dependency injection The final step to make this refactoring work is to configure your services with the DI container. This will let it know what to use to fulfill the IEmailSender dependency. If you don’t register your services, then you’ll get an exception at runtime, like the one in figure 10.7. Luckily, this exception is useful, letting you know which service wasn’t registered (IEmailSender) and which service needed it (UserController). That won’t always be the case! In order to completely configure the application, you need to register EmailSender and all of its dependencies with the DI container, as shown in figure 10.8. Configuring DI consists of making a series of statements about the services in your app. For example:  When a service requires IEmailSender, use an instance of EmailSender  When a service requires NetworkClient, use an instance of NetworkClient  When a service requires MessageFactory, use an instance of MessageFactory NOTE You’ll also need to register the EmailServerSettings object with the DI container—you’ll do that slightly differently, in the next section. Dependency Injection Container UserController 1. A request is received to the URL /RegisterUser. Request Routing IEmailSender 2. The Controller activator calls the DI container to create an instance of the UserController, including its dependencies. Controller activator 4. The exception passes back up the middleware pipeline and out to the user. Here, the DeveloperExceptionPageMiddleware displays the error in the browser. 3. As the IEmailSender has not been registered, the DI container can’t create the UserController, so throws an InvalidOperationException. 500 ! Figure 10.7 If you don’t register all of your required dependencies in ConfigureServices, you’ll get an exception at runtime, telling you which service wasn’t registered.
  • 309.
    281 Using the dependencyinjection container These statements are made by calling various Add* methods on IServiceCollection in the ConfigureServices method. Each method provides three pieces of informa- tion to the DI container:  Service type—TService. What class or interface will be requested as a depen- dency. Often an interface, such as IEmailSender, but sometimes a concrete type, such as NetworkClient or MessageFactory.  Implementation type—TService or TImplementation. The class the container should create to fulfill the dependency. Must be a concrete type, such as EmailSender. May be the same as the service type, as for NetworkClient and MessageFactory.  Lifetime—Transient, Singleton, or Scoped. How long an instance of the service should be used for. I’ll discuss lifetimes in section 10.3. The following listing shows how you can configure EmailSender and its dependencies in your application using three different methods: AddScoped<TService>, Add- Singleton<TService>, and AddScoped<TService, TImplementation>. This tells the DI container how to create each of the TService instances when they’re required. public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddScoped<IEmailSender, EmailSender>(); Listing 10.7 Registering services with the DI container Dependency Injection Container EmailSender IEmailSender NetworkClient MessageFactory NetworkClient MessageFactory You register services and implementations in pairs. For each pair, you indicate the type of service that will be requested, and what type of implementation to create. Alternatively, the service and implementation can be the same type, such as for NetworkClient and MessageFactory. These can be the different types, where the implementation implements the service, such as the EmailSender that implements IEmailSender. Figure 10.8 Configuring the DI container in your application involves telling it what type to use when a given service is requested; for example, “Use EmailSender when IEmailSender is required.” You’re using MVC, so must call AddMvc. Whenever you require an IEmailSender, use EmailSender.
  • 310.
    282 CHAPTER 10Service configuration with dependency injection services.AddScoped<NetworkClient>(); services.AddSingleton<MessageFactory>(); } And that’s all there is to dependency injection! It may seem a little bit like magic,1 but you’re just giving the container instructions on how to make all of the constituent parts. You give it a recipe for how to cook the chili, shred the lettuce, and grate the cheese, so that when you ask for a burrito, it can put all the parts together and hand you your meal! The service type and implementation type are the same for NetworkClient and MessageFactory, so there’s no need to specify the same type twice in the AddScoped method, hence the slightly simpler signature. These generic methods aren’t the only way to register services with the container, you can also provide objects directly, or by using lambdas, as you’ll see in the next section. 10.2.3 Registering services using objects and lambdas As I mentioned earlier, I didn’t quite register all the services required by UserController. In all my previous examples, NetworkClient depends on EmailServerSettings, which you’ll also need to register with the DI container for your project to run without exceptions. I avoided registering this object in the preceding example because you have to use a slightly different approach. The preceding Add* methods use generics to specify the Type of the class to register, but they don’t give any indication of how to construct an instance of that type. Instead, the container makes a number of assumptions that you have to adhere to:  The class must be a concrete type.  The class must have only a single “valid” constructor that the container can use.  For a constructor to be “valid,” all constructor arguments must be registered with the container, or must be an argument with a default value. NOTE These limitations apply to the simple built-in DI container. If you choose to use a third-party container in your app, then they may have a differ- ent set of limitations. The EmailServerSettings class doesn’t meet these requirements, as it requires you to provide a host and port in the constructor, which are strings without default values: 1 Under the hood, the built-in ASP.NET Core DI container uses reflection to create dependencies, but differ- ent DI containers may use other approaches. Whenever you require a NetworkClient, use NetworkClient. Whenever you require a MessageFactory, use MessageFactory.
  • 311.
    283 Using the dependencyinjection container public class EmailServerSettings { public EmailServerSettings(string host, int port) { Host = host; Port = port; } public string Host { get; } public int Port { get; } } You can’t register these primitive types in the container; it would be weird to say, “For every string constructor argument, in any type, use the "smtp.server.com" value!” Instead, you can create an instance of the EmailServerSettings object yourself and provide that to the container, as shown next. The container will use the con- structed object whenever an instance of the EmailServerSettings object is required. public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddScoped<IEmailSender, EmailSender>(); services.AddSingleton<NetworkClient>(); services.AddScoped<MessageFactory>(); services.AddSingleton( new EmailServerSettings ( host: "smtp.server.com", port: 25 )); } This works fine if you only want to have a single instance of EmailServerSettings in your application—the same object will be shared everywhere. But what if you want to cre- ate a new object each time one is requested? NOTE When the same object is used whenever it’s requested, it’s known as a singleton. I’ll discuss singletons along with other lifetimes in detail in the next section. The lifetime is how long the DI container should use a given object to fulfill a service’s dependencies. Instead of providing a single instance that the container will always use, you can also provide a function that the container will invoke when it needs an instance of the type, shown in figure 10.9. Listing 10.8 Providing an object instance when registering services This instance of EmailServerSettings will be used whenever an instance is required.
  • 312.
    284 CHAPTER 10Service configuration with dependency injection The easiest way to do this is to use a lambda function (an anonymous delegate), in which the container will create a new EmailServerSettings object when it’s needed. public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddScoped<IEmailSender, EmailSender>(); services.AddSingleton<NetworkClient>(); services.AddScoped<MessageFactory>(); services.AddScoped( provider=> new EmailServerSettings ( host: "smtp.server.com", port: 25 )); } In this example, I’ve changed the lifetime of the created EmailServerSettings object to be scoped instead of singleton and provided a factory lambda function that returns a new EmailServerSettings object. Every time the container requires a new Email- ServerSettings, it executes the function and uses the new object it returns. NOTE I’ll discuss lifetimes shortly, but it’s important to notice that there are two concepts of a singleton here. If you create an object and pass it to the con- tainer, it’s always registered as a singleton. You can also register any arbitrary class as a singleton and the container will only use one instance throughout your application. When you use a lambda to register your services, you’re provided with an IService- Provider instance at runtime, called provider in listing 10.9. This is the public API of the DI container itself, which you can access using GetService(). If you need to Listing 10.9 Using a lambda factory function to register a dependency Dependency Injection Container EmailServerSettings EmailServerSettings An instance of the EmailServerSettings is required by the DI container to create an instance of NetworkClient. Instead of creating the EmailServerSettings instance using the constructor, the DI container invokes the function, and uses the instance it returns. provider => new EmailServerSettings ( host: "smtp.server.com", port: 25 ); Figure 10.9 You can register a function with the DI container that will be invoked whenever a new instance of a service is required. Because you’re providing a function to create the object, you aren’t restricted to singleton. The lambda is provided an instance of IServiceProvider. The constructor is now called every time an object is requested instead of only once.
  • 313.
    285 Using the dependencyinjection container obtain dependencies to create an instance of your service, you can reach into the con- tainer at runtime in this way, but you should avoid doing so if possible. TIP Avoid calling GetService() in your factory functions if possible. Instead, favor constructor injection—it’s more performant, as well as being simpler to reason about. At this point, all your dependencies are registered. But ConfigureServices is starting to look a little messy, isn’t it? It’s entirely down to personal preference, but I like to group my services into logical collections and create extension methods for them, as in the following listing, to create an equivalent of the AddMvc() extension method. As you add more and more features to your app, I think you’ll appreciate it too. public static class EmailSenderServiceCollectionExtensions { public static IServiceCollection AddEmailSender( this IServiceCollection services) { services.AddScoped<IEmailSender, EmailSender>(); services.AddSingleton<NetworkClient>(); services.AddScoped<MessageFactory>(); services.AddSingleton( new EmailServerSettings ( host: "smtp.server.com", port: 25 )); return services; } } Listing 10.10 Creating an extension method to tidy up adding multiple services Open generics and dependency injection As well as dependencies that have primitive dependencies in their constructor, you also can’t use the generic registration methods to register open generics. Open generics are types that contain a generic type parameter, such as Repository <T>. The normal use case for this sort of type is to define a base behavior that you can use with multiple generic types. In the Repository<T> example, you might inject IRepository<Customer> into your services, which should inject an instance of DbRepository<Customer>, for example. To register these types, you must use a different overload of the Add* methods. For example, services.AddScoped(typeof(IRespository<>), typeof(DbRepository<>)); This will ensure that whenever a service constructor requires IRespository<T>, the container will inject an instance of DbRepository<T>. Extend the IServiceCollection that’s provided in the ConfigureServices method. Cut and paste your registration code from ConfigureServices. By convention, return the IServiceCollection to allow method chaining.
  • 314.
    286 CHAPTER 10Service configuration with dependency injection With the preceding extension method created, the ConfigureServices method is much easier to grok! public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddEmailSender(); } So far, you’ve seen how to register the simple DI cases where you have a single imple- mentation. In some scenarios, you might find you have multiple implementations of an interface. In the next section, you’ll see how to register these with the container to match your requirements. 10.2.4 Registering a service in the container multiple times One of the advantages of coding to interfaces is that you can create multiple imple- mentations of a service. For example, imagine you want to create a more generalized version of IEmailSender so that you can send messages via SMS or Facebook, as well as by email. You create an interface for it public interface IMessageSender { public void SendMessage(string message); } as well as several implementations: EmailSender, SmsSender, and FacebookSender. But how do you register these implementations in the container? And how can you inject these implementations into your UserController? The answer varies slightly depend- ing on if you want to use all of the implementations or only one of them. INJECTING MULTIPLE IMPLEMENTATIONS OF AN INTERFACE Imagine you want to send a message using each of the IMessageSender implementa- tions whenever a new user registers, so they get an email, an SMS, and a Facebook message, as shown in figure 10.10. Welcome! Welcome! 1. A new user registers with your app and enters their details, POSTing to the RegisterUser action method. Welcome! 2. Your app sends them a welcome message by email, SMS, and Facebook using the IMessageSender implementations. RegisterUser UserController Figure 10.10 When a user registers with your application, they call the RegisterUser method. This sends them an email, an SMS, and a Facebook message using the IMessageSender classes.
  • 315.
    287 Using the dependencyinjection container The easiest way to achieve this is to register all of the implementations in your DI con- tainer and have it inject one of each type into UserController. UserController can then use a simple foreach loop to call SendMessage() on each implementation, as in figure 10.11. You register multiple implementations of the same service with a DI container in exactly the same way as for single implementations, using the Add* extension meth- ods. For example: public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddScoped<IMessageSender, EmailSender>(); services.AddScoped<IMessageSender, SmsSender>(); services.AddScoped<IMessageSender, FacebookSender>(); } You can then inject IEnumerable<IMessageSender> into UserController, as shown in the following listing. The container will inject an array of IMessageSender containing one of each of the implementations you have registered, in the same order as you reg- istered them. You can then use a standard foreach loop in the RegisterUser method to call SendMessage on each implementation. 3. The RegisterUser method on the UserController loops over the IMessageSender instances and calls SendMessage on each. 1. During Startup, multiple implementations of IMessageSender are registered with the DI container using the normal Add* methods. Dependency Injection Container EmailSender SmsSender FacebookSender IMessageSender IMessageSender IMessageSender UserController IEnumerable<IMessageSender> EmailSender SmsSender FacebookSender foreach (var messageSender in _messageSenders) { messageSender.SendMessage(username); } 2. The DI container creates one of each IMessageSender implementation, and injects them into the UserController as an IEnumerable<IMessageSender>. _messageSenders Figure 10.11 You can register multiple implementations of a service with the DI container, such as IEmailSender in this example. You can retrieve an instance of each of these implementations by requiring IEnumerable<IMessageSender> in the UserController constructor.
  • 316.
    288 CHAPTER 10Service configuration with dependency injection public class UserController : Controller { private readonly IEnumerable<IMessageSender> _messageSenders; public UserController( IEnumerable<IMessageSender> messageSenders) { _messageSenders = messageSenders; } public IActionResult RegisterUser(string username) { foreach (var messageSender in _messageSenders) { messageSender.SendMessage(username); } return View(); } } WARNING You must use IEnumerable<T> as the constructor argument to inject all of the registered types of a service, T. Even though this will be injected as a T[] array, you can’t use T[] or ICollection<T> as your construc- tor argument. Doing so will cause an InvalidOperationException, as you saw in figure 10.7. It’s simple enough to inject all of the registered implementations of a service, but what if you only need one? How does the container know which one to use? INJECTING A SINGLE IMPLEMENTATION WHEN MULTIPLE SERVICES ARE REGISTERED Imagine you’ve already registered all of the IMessageSender implementations, what happens if you have a service that requires only one of them? For example public class SingleMessageSender { private readonly IMessageSender _messageSender; public SingleMessageSender(IMessageSender messageSender) { _messageSender = messageSender; } } The container needs to pick a single IMessageSender to inject into this service, out of the three implementations available. It does this by using the last registered imple- mentation—the FacebookSender from the previous example. NOTE The DI container will use the last registered implementation of a ser- vice when resolving a single instance of the service. Listing 10.11 Injecting multiple implementations of a service into a consumer Requesting an IEnumerable will inject an array of IMessageSender. Each IMessageSender in the IEnumerable is a different implementation.
  • 317.
    289 Using the dependencyinjection container This can be particularly useful for replacing built-in DI registrations with your own services. If you have a custom implementation of a service that you know is registered within a library’s Add* extension method, you can override that registration by regis- tering your own implementation afterwards. The DI container will use your imple- mentation whenever a single instance of the service is requested. The main disadvantage with this approach is that you end up with multiple imple- mentations registered—you can inject an IEnumerable<T> as before. Sometimes you want to conditionally register a service, so you only ever have a single registered imple- mentation. CONDITIONALLY REGISTERING SERVICES USING TRYADD Sometimes, you’ll only want to add an implementation of a service if one hasn’t already been added. This is particularly useful for library authors; they can create a default implementation of an interface and only register it if the user hasn’t already registered their own implementation. You can find a number of extension methods for conditional registration in the Microsoft.Extensions.DependencyInjection.Extensions namespace, such as Try- AddScoped. This checks to make sure a service hasn’t been registered with the con- tainer before calling AddScoped on the implementation. The following listing shows you conditionally adding SmsSender, only if there are no existing IMessageSender implementations. As you just registered EmailSender, the container will ignore the SmsSender registration, so it won’t be available in your app. public void ConfigureServices(IServiceCollection services) { services.AddScoped<IMessageSender, EmailSender>(); services.TryAddScoped<IMessageSender, SmsSender>(); } Code like this often doesn’t make a lot of sense at the application level, but it can be useful if you’re building libraries for use in multiple apps. The ASP.NET Core frame- work, for example, uses TryAdd* in many places, which lets you easily register alterna- tive implementations of internal components in your own application if you want. That pretty much covers registering dependencies. Before we look in more depth at the “lifetime” aspect of dependencies, we’ll take a quick detour and look at two ways other than a constructor to inject dependencies in your app. 10.2.5 Injecting services into action methods and view templates I mentioned in section 10.1 that the ASP.NET Core DI container only supports con- structor injection, but there are two additional locations where you can use depen- dency injection: Listing 10.12 Conditionally adding a service using TryAddScoped EmailSender is registered with the container. There’s already an IMessageSender implementation, so SmsSender isn’t registered.
  • 318.
    290 CHAPTER 10Service configuration with dependency injection 1 Action methods 2 View templates In this section, I’ll briefly discuss these two situations, how they work, and when you might want to use them. INJECTING SERVICES DIRECTLY INTO ACTION METHODS USING [FROMSERVICES] MVC controllers typically contain multiple action methods that logically belong together. You might group all the action methods related to managing user accounts into the same controller, for example. This allows you to apply filters and authoriza- tion to all of the action methods collectively, as you’ll see in chapter 13. As you add additional action methods to a controller, you may find the controller needs additional services to implement new action methods. With constructor injec- tion, all of these dependencies are provided via the constructor. That means the DI container must create all of the services for every action method in a controller, even if none of them are required by the action method being called. Consider listing 10.13 for example. This shows UserController with two stub methods: RegisterUser and SignInUser. Each action method requires a different dependency, so both dependencies will be created and injected, whichever action method is called by the request. If ISignInManager or IMessageSender have lots of dependencies themselves, the DI container may have to create lots of objects for a ser- vice that is often not used. public class UserController : Controller { private readonly IMessageSender _messageSender; private readonly ISignInService _signInService; public UserController( IMessageSender messageSender, ISignInService signInService) { messageSenders = messageSender; _signInService = signInService; } public IActionResult RegisterUser(string username) { _messageSender.SendMessage(username); return View(); } public IActionResult SignInUser(string username, string password) { _signInService.SignUserIn(username, password); return View(); } } Listing 10.13 Injecting services into a controller via the constructor Both IMessageSender and ISignInService are injected into the constructor every time. Only the RegisterUser method uses IMessageSender. Only the SignInUser method uses ISignInService.
  • 319.
    291 Using the dependencyinjection container If you know a service is particularly expensive to create, you can choose to inject it as a dependency directly into the action method, instead of into the controller’s construc- tor. This ensures the DI container only creates the dependency when the specific action method is invoked, as opposed to when any action method on the controller is invoked. NOTE Generally speaking, your controllers should be sufficiently cohesive that this approach isn’t necessary. If you find you have a controller that’s dependent on many services, each of which is used by a single action method, you might want to consider splitting up your controller. You can directly inject a dependency into an action method by passing it as a parame- ter to the method and using the [FromServices] attribute. During model binding, MvcMiddleware will resolve the parameter from the DI container, instead of from the request values, like you saw in chapter 6. This listing shows how you could rewrite list- ing 10.13 to use [FromServices] instead of constructor injection. public class UserController : Controller { public IActionResult RegisterUser( [FromServices] IMessageSender messageSender, string username) { messageSender.SendMessage(username); return View(); } public IActionResult SignInUser( [FromServices] ISignInService signInService string username, string password) { signInService.SignUserIn(username, password); return View(); } } You might be tempted to use the [FromServices] attribute in all of your action meth- ods, but I’d encourage you to use standard constructor injection most of the time. Having the constructor as a single location that declares all the dependencies of a class can be useful, so I only use [FromServices] where creating an instance of a dependency is expensive and is only used in a single action method. INJECTING SERVICES INTO VIEW TEMPLATES Injecting dependencies into the constructor is recommended, but what if you don’t have a constructor? In particular, how do you go about injecting services into a Razor view template when you don’t have control over how the template is constructed? Listing 10.14 Injecting services into a controller using the [FromServices] attribute The [FromServices] attribute ensures IMessageSender is resolved from the DI container. IMessageSender is only available in RegisterUser. ISignInService is resolved from the DI container and injected as a parameter. Only the SignInUser method can use ISignInService.
  • 320.
    292 CHAPTER 10Service configuration with dependency injection Imagine you have a simple service, HtmlGenerator, to help you generate HTML in your view templates. The question is, how do you pass this service to your view tem- plates, assuming you’ve already registered it with the DI container? One option is to inject HtmlGenerator into your controller using constructor injec- tion or the [FromServices] attribute. Then you can pass the service via ViewData or the view model, as you saw in chapter 7. In some cases, that approach may make sense, but other times, you might not want to have references to the HtmlGenerator service in your controller or view model at all. In those cases, you can directly inject Html- Generator into your view templates. NOTE Some people take offense at injecting services into views in this way. You definitely shouldn’t be injecting services related to business logic into your views, but I think it makes sense for services that are related to HTML generation. You can inject a service into a Razor template with the @inject directive by providing the type to inject and a name for the injected service in the template. @inject HtmlGenerator helper <h1>The page title</h1> <footer> @helper.Copyright() </footer> Injecting services directly into views can be a useful way of exposing UI-related ser- vices to your view templates without having to closely couple your views to your con- trollers and actions. You shouldn’t find you need to rely on it too much, but it’s a useful tool to have. That pretty much covers registering and using dependencies, but there’s one important aspect I’ve only vaguely touched on: lifetimes, or, when does the container create a new instance of a service? Understanding lifetimes is crucial to working with DI containers, so it’s important to pay close attention to them when registering your services with the container. 10.3 Understanding lifetimes: when are services created? Whenever the DI container is asked for a particular registered service, for example an instance of IMessageSender, it can do one of two things:  Create and return a new instance of the service  Return an existing instance of the service The lifetime of a service controls the behavior of the DI container with respect to these two options. You define the lifetime of a service during DI service registration. This Listing 10.15 Injecting a service into a Razor view template with @inject Injects an instance of HtmlGenerator into the view, named helper Uses the injected service by calling the helper instance
  • 321.
    293 Understanding lifetimes: whenare services created? dictates when a DI container will reuse an existing instance of the service to fulfill ser- vice dependencies, and when it will create a new one. DEFINITION The lifetime of a service is how long an instance of a service should live in a container before it creates a new instance. It’s important to get your head around the implications for the different lifetimes used in ASP.NET Core, so this section looks at each available lifetime option and when you should use it. In particular, you’ll see how the lifetime affects how often the DI container creates new objects. In section 10.3.4, I’ll show you a pattern of lifetimes to look out for, where a short-lifetime dependency is “captured” by a long-lifetime dependency. This can cause some hard-to-debug issues, so it’s important to bear in mind when configuring your app! In ASP.NET Core, you can specify three different lifetimes when registering a ser- vice with the built-in container:  Transient—Every single time a service is requested, a new instance is created. This means you can potentially have different instances of the same class within the same dependency graph.  Scoped—Within a scope, all requests for a service will give you the same object. For different scopes you’ll get different objects. In ASP.NET Core, each web request gets its own scope.  Singleton—You’ll always get the same instance of the service, no matter which scope. NOTE These concepts align well with most other DI containers, but the ter- minology often differs. If you’re familiar with a third-party DI container, be sure you understand how the lifetime concepts align with the built-in ASP.NET Core DI container. To illustrate the behavior of the different lifetimes available to you, in this section, I’ll use a simple representative example. Imagine you have DataContext, which has a con- nection to a database, as shown in listing 10.13. It has a single property, RowCount, which displays the number of rows in the Users table of a database. For the purposes of this example, fix the number of rows in the constructor, so it will display the same value every time you call RowCount, but different instances of the service will display a different number. public class DatabaseContext { static readonly Random _rand = new Random(); public DatabaseContext() { RowCount = _rand.Next(1, 1_000_000_000); } Listing 10.16 DataContext generating a random RowCount in its constructor Generates a random number between 1 and 1,000,000,000
  • 322.
    294 CHAPTER 10Service configuration with dependency injection public int RowCount { get; } } You also have a Repository class that has a dependency on the DataContext, as shown in the next listing. This also exposes a RowCount property, but this property del- egates the call to its instance of DataContext. Whatever value DataContext was cre- ated with, the Repository will display the same value. public class Repository { private readonly DatabaseContext _dataContext; public Repository(DatabaseContext dataContext) { _dataContext = dataContext; } public int RowCount => _dataContext.RowCount; } Finally, you have RowCountController, which takes a dependency on both Repository and on DataContext directly. When the controller activator creates an instance of RowCountController, the DI container will inject an instance of DataContext and an instance of Repository. To create Repository, it will also inject an additional instance of DataContext. Over the course of two consecutive requests, a total of four instances of DataContext will be required, as shown in figure 10.12. RowCountController records the value of RowCount from Repository and Data- Context in a view model. This is then rendered using a Razor template (not shown). public class RowCountController : Controller { private readonly Repository _repository; private readonly DataContext _dataContext; public RowCountController( Repository repository, DataContext dataContext) { _repository = repository; _dataContext = dataContext; } public IActionResult Index() { var viewModel = new RowCountViewModel { DataContextCount = _dataContext.RowCount, RepositoryCount = _repository.RowCount, }; Listing 10.17 Repository service that depends on an instance of DataContext Listing 10.18 RowCountController depends on DataContext and Repository Read-only properly set in the constructor, so always returns the same value. An instance of DataContext is provided using DI. RowCount returns the same value as the current instance of DataContext. DataContext and Repository are passed in using DI. When invoked, the action retrieves RowCount from both dependencies.
  • 323.
    295 Understanding lifetimes: whenare services created? return View(viewModel); } } The purpose of this example is to explore the relationship between the four Data- Context instances, depending on the lifetimes you use to register the services with the container. I’m generating a random number in DataContext as a way of uniquely identifying a DataContext instance, but you can think of this as being a point-in-time snapshot of the number of users logged in to your site, for example, or the amount of stock in a warehouse. I’ll start with the shortest-lived lifetime, move on to the common scoped lifetime, and then take a look at singletons. Finally, I’ll show an important trap you should be on the lookout for when registering services in your own apps. 10.3.1 Transient: everyone is unique In the ASP.NET Core DI container, transient services are always created new, when- ever they’re needed to fulfill a dependency. You can register your services using the AddTransient extension methods: services.AddTransient<DataContext>(); services.AddTransient<Repository>(); The view model is passed to the Razor view and rendered to the screen. Dependency Injection Container Repository RowCountController DataContext DataContext For each request, two instances of DataContext are required to build the RowCountController instance. First request Dependency Injection Container Repository RowCountController DataContext DataContext A total of four DataContext instances is required for two requests. Second request Figure 10.12 The DI Container uses two instances of DataContext for each request. Depending on the lifetime with which the DataContext type is registered, the container might create one, two, or four different instances of DataContext.
  • 324.
    296 CHAPTER 10Service configuration with dependency injection When registered in this way, every time a dependency is required, the container will create a new one. This applies both between requests but also within requests; the DataContext injected into the Repository will be a different instance to the one injected into the RowCountController. NOTE Transient dependencies can result in different instances of the same type within a single dependency graph. Figure 10.13 shows the results you get from two consecutive requests when you use the transient lifetime for both services. Note that, by default, Controller instances are also transient and are created new with every request. Transient lifetimes can result in a lot of objects being created, so they make the most sense for lightweight services with little or no state. It’s equivalent to calling new every time you need a new object, so bear that in mind when using it. You probably won’t use the transient lifetime too often; the majority of your services will probably be scoped instead. 10.3.2 Scoped: let’s stick together, guys The scoped lifetime states that a single instance of an object will be used within a given scope, but a different instance will be used between different scopes. In ASP.NET Core, a scope maps to a request, so within a single request the container will use the same object to fulfill all dependencies. For the row count example, that means that, within a single request (a single scope), the same DataContext will be used throughout the dependency graph. The DataContext injected into the Repository will be the same instance as that injected into RowCountController. Figure 10.13 When registered using the transient lifetime, all four DataContext objects are different. That can be seen by the four different numbers displayed over two requests.
  • 325.
    297 Understanding lifetimes: whenare services created? In the next request, you’ll be in a different scope, so the container will create a new instance of DataContext, as shown in figure 10.14. A different instance means a differ- ent RowCount for each request, as you can see. You can register dependencies as scoped using the AddScoped extension methods. In this example, I registered DataContext as scoped and left Repository as transient, but you’d get the same results if they were both scoped: services.AddScoped<DataContext>(); Due to the nature of web requests, you’ll often find services registered as scoped dependencies in ASP.NET Core. Database contexts and authentication services are common examples of services that should be scoped to a request—anything that you want to share across your services within a single request, but that needs to change between requests. Generally speaking, you’ll find a lot of services registered using the scoped life- time—especially anything that uses a database or is dependent on a specific request. But some services don’t need to change between requests, such as a service that calcu- lates the area of a circle, or that returns the current time in different time zones. For these, a singleton lifetime might be more appropriate. 10.3.3 Singleton: there can be only one The singleton is a pattern that came before dependency injection; the DI container provides a robust and easy-to-use implementation of it. The singleton is conceptually simple: an instance of the service is created when it’s first needed (or during registra- tion, as in section 10.2.3) and that’s it: you’ll always get the same instance injected into your services. The singleton pattern is particularly useful for objects that are either expensive to create or that don’t hold state. The latter point is important—any service registered as a singleton should be thread safe. Figure 10.14 Scoped dependencies use the same instance of DataContext within a single request, but a new instance for a separate request. Consequently, the RowCounts are identical within a request.
  • 326.
    298 CHAPTER 10Service configuration with dependency injection WARNING Singleton services must be thread safe in a web application, as they’ll typically be used by multiple threads during concurrent requests. Let’s consider what using singletons means for the row count example. I can update the registration of DataContext to be a singleton in ConfigureServices, using services.AddSingleton<DataContext>(); I then call the RowCountController.Index action method twice and observe the results shown in figure 10.15. You can see that every instance has returned the same value, indicating that all four instances of DataContext are the same single instance. Singletons are convenient for objects that need to be shared or that are immutable and expensive to create. A caching service should be a singleton, as all requests need to share it. It must be thread safe though. Similarly, you might register a settings object loaded from a remote server as a singleton, if you loaded the settings once at startup and reused them through the lifetime of your app. On the face of it, choosing a lifetime for a service might not seem too tricky, but there’s an important “gotcha” that can come back to bite you in subtle ways, as you’ll see shortly. 10.3.4 Keeping an eye out for captured dependencies Imagine you’re configuring the lifetime for the DataContext and Repository exam- ples. You think about the suggestions I’ve provided and decide on the following life- times:  DataContext—Scoped, as it should be shared for a single request  Repository—Singleton, as it has no state of its own and is thread safe, so why not? Figure 10.15 Any service registered as a singleton will always return the same instance. Consequently, all the calls to RowCount return the same value, both within a request and between requests.
  • 327.
    299 Understanding lifetimes: whenare services created? WARNING This lifetime configuration is to explore a bug—don’t use it in your code or you’ll experience a similar problem! Unfortunately, you’ve created a captured dependency because you’re injecting a scoped object, DataContext, into a singleton, Repository. As it’s a singleton, the same Repository instance is used throughout the lifetime of the app, so the DataContext that was injected into it will also hang around, even though a new one should be used with every request. Figure 10.16 shows this scenario, where a new instance of Data- Context is created for each scope, but the instance inside Repository hangs around for the lifetime of the app. Captured dependencies can cause subtle bugs that are hard to root out, so you should always keep an eye out for them. These captured dependencies are relatively easy to introduce, so always think carefully when registering a singleton service. WARNING A service should only use dependencies with a lifetime longer than or equal to the lifetime of the service. A service registered as a singleton can only use singleton dependencies. A service registered as scoped can use scoped or singleton dependencies. A transient service can use dependencies with any lifetime. Dependency Injection Container Repository RowCountController DataContext DataContext First request Dependency Injection Container Repository RowCountController DataContext DataContext Second request As the repository has been registered as a singleton, the DataContext it uses will act also as a singleton, even though it is registered as scoped. The DataContext dependency has been captured by the repository, breaking the scoped lifetime. Figure 10.16 DataContext is registered as a scoped dependency, but Repository is a singleton. Even though you expect a new DataContext for every request, Repository captures the injected DataContext and causes it to be reused for the lifetime of the app.
  • 328.
    300 CHAPTER 10Service configuration with dependency injection At this point, I should mention that there’s one glimmer of hope in this cautionary tale. ASP.NET Core 2.0 includes a new option that lets you check for these kinds of captured dependencies and will throw an exception on application startup if it detects them, as shown in figure 10.17. This scope validation check has a performance impact, so by default it’s only enabled when your app is running in a development environment, but it should help you catch most issues of this kind. You can enable or disable this check regardless of envi- ronment by setting the ValidateScopes option when creating your WebHostBuilder in Program.cs, as shown in the following listing. public class Program { public static void Main(string[] args) { BuildWebHost(args).Run(); } public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() .UseDefaultServiceProvider(options => { options.ValidateScopes = true; }) .Build(); } Listing 10.19 Setting the ValidateScopes property to always validate scopes In ASP .NET Core 2.0, you will get an Exception when the DI container detects a captured dependency. The exception message describes which service was captured... ...and which service captured the dependency . Figure 10.17 In ASP.NET Core 2.0, the DI container will throw an exception when it creates a service with a captured dependency. By default, this check is only enabled for development environments. The default builder sets ValidateScopes to validate only in development environments. You can override the validation check with the UseDefaultService- Provider extension. Setting to true will validate scopes in all environments. This has performance implications.
  • 329.
    301 Summary With that, you’vereached the end of this introduction to DI in ASP.NET Core. You now know how to add framework services to your app using Add* extension methods like AddMvc(), as well as how to register your own service with the DI container. Hope- fully, that will help you keep your code loosely coupled and easy to manage. In the next chapter, we’ll look at the ASP.NET Core configuration model. You’ll see how to load settings from a file at runtime, how to store sensitive settings safely, and how to make your application behave differently depending on which machine it’s running on. We’ll even use a bit of DI; it gets everywhere in ASP.NET Core! Summary  Dependency injection is baked into the ASP.NET Core framework. You need to ensure your application adds all the framework's dependencies in Startup.  The dependency graph is the set of objects that must be created in order to cre- ate a specific requested “root” object.  You should aim to use explicit dependencies over implicit dependencies in most cases. ASP.NET Core uses constructor arguments to declare explicit dependencies.  The DI or IoC container is responsible for creating instances of services. It knows how to construct an instance of a service by creating all of its dependen- cies and passing these to the constructor.  When discussing DI, the term service is used to describe any class or interface registered with the container.  You register services with a DI container so it knows which implementation to use for each requested service. This typically takes the form of, “for interface X, use implementation Y.”  The default built-in container only supports constructor injection. If you require other forms of DI, such as property injection, you can use a third-party container.  You can register services with the container by calling Add* extension methods on IServiceCollection in ConfigureServices in Startup.  If you forget to register a service that’s used by the framework or in your own code, you’ll get an InvalidOperationException at runtime.  When registering your services, you describe three things: the service type, the implementation type, and the lifetime. The service type defines which class or interface will be requested as a dependency. The implementation type is the class the container should create to fulfill the dependency. The lifetime is how long an instance of the service should be used for.  You can register a service using generic methods if the class is concrete and all its constructor arguments must be registered with the container or have default values.
  • 330.
    302 CHAPTER 10Service configuration with dependency injection  You can provide an instance of a service during registration, which will register that instance as a singleton.  You can provide a lambda factory function that describes how to create an instance of a service with any lifetime you choose.  Avoid calling GetService() in your factory functions if possible. Instead, favor constructor injection—it’s more performant, as well as being simpler to reason about.  You can register multiple implementations for a service. You can then inject IEnumerable<T> to get access to all of the implementations at runtime.  If you inject a single instance of a multiple-registered service, the container injects the last implementation registered.  You can use the TryAdd* extension methods to ensure that an implementation is only registered if no other implementation of the service has been registered.  You define the lifetime of a service during DI service registration. This dictates when a DI container will reuse an existing instance of the service to fulfill ser- vice dependencies and when it will create a new one.  The transient lifetime means that every single time a service is requested, a new instance is created.  The scoped lifetime means that within a scope, all requests for a service will give you the same object. For different scopes, you’ll get different objects. In ASP.NET Core, each web request gets its own scope.  You’ll always get the same instance of a singleton service, no matter which scope.  A service should only use dependencies with a lifetime longer than or equal to the lifetime of the service.
  • 331.
    303 Configuring an ASP.NET Coreapplication In part 1 of this book, you learned the basics of getting an ASP.NET Core app up and running and how to use the MVC design pattern to create a traditional web app or a Web API. Once you start building real applications, you will quickly find that you want to tweak various settings at runtime, without having to recompile your application. This chapter looks at how you can achieve this in ASP.NET Core using configuration. I know. Configuration sounds boring, right? But I have to confess, the configu- ration model is one of my favorite parts of ASP.NET Core. It’s so easy to use, and so This chapter covers  Loading settings from multiple configuration providers  Storing sensitive settings safely  Using strongly typed settings objects  Using different settings in different hosting environments
  • 332.
    304 CHAPTER 11Configuring an ASP.NET Core application much more elegant than the previous version of ASP.NET. In section 11.2, you’ll learn how to load values from a plethora of sources—JSON files, environment variables, and command-line arguments—and combine them into a unified configuration object. On top of that, ASP.NET Core brings the ability to easily bind this configuration to strongly typed options objects. These are simple, POCO classes that are populated from the configuration object, which you can inject into your services, as you’ll see in section 11.3. This lets you nicely encapsulate settings for different features in your app. In the final section of this chapter, you’ll learn about the ASP.NET Core hosting environments. You often want your app to run differently in different situations such as when running on your developer machine compared to when you deploy it to a pro- duction server. These different situations are known as environments. By letting the app know in which environment it’s running, it can load a different configuration and vary its behavior accordingly. Before we get to that, let’s go back to basics: what is configuration, why do we need it, and how does ASP.NET Core handle these requirements? 11.1 Introducing the ASP.NET Core configuration model Configuration is the set of external parameters provided to an application that con- trols the application’s behavior in some way. It typically consists of a mixture of settings and secrets that the application will load at runtime. DEFINITION A setting is any value that changes the behavior of your applica- tion. A secret is a special type of setting that contains sensitive data, such as a password, an API key for a third-party service, or a connection string. The obvious question before we get started is to consider why you need app configura- tion, and what sort of things you need to configure. You should normally move any- thing that you can consider a setting or a secret out of your application code. That way, you can easily change these values at runtime, without having to recompile your application. You might, for example, have an application that shows the locations of your brick- and-mortar stores. You could have a setting for the connection string to the database in which you store the details of the stores, but also settings such as the default loca- tion to display on a map, the default zoom level to use, maybe even the locations of the stores, as shown in figure 11.1. You wouldn’t necessarily want to have to recompile your app to tweak these, so you can store them in external configuration sources instead. There’s also a security aspect to this; you don’t want to hardcode secret values like API keys or passwords into your code, where they could be committed to source con- trol and made publicly available. Even values embedded in your compiled application can be extracted, so it’s best to externalize them whenever possible. Virtually every web framework provides a mechanism for loading configuration and the previous version of ASP.NET was no different. It used the <appsettings>
  • 333.
    305 Introducing the ASP.NETCore configuration model element in a web.config file to store key-value configuration pairs. At runtime, you’d use the static (*wince*) ConfigurationManager to load the value for a given key from the file. You could do more advanced things using custom configuration sections, but this was painful, and so was rarely used, in my experience. ASP.NET Core gives you a totally revamped experience. At the most basic level, you’re still specifying key-value pairs at strings, but instead of getting those values from a single file, you can now load them from multiple sources. You can load values from files, but they can now be any format you like: JSON, XML, YAML, and so on. On top of that, you can load values from environment variables, from command-line argu- ments, from a database or from a remote service. Or you could create your own cus- tom configuration provider. DEFINITION ASP.NET Core uses configuration providers to load key-value pairs from a variety of sources. Applications can use many different configuration providers. The ASP.NET Core configuration model also has the concept of overriding settings. Each configuration provider can define its own settings, or it can overwrite settings from a previous provider. You’ll see this super-useful feature in action in the next section. ASP.NET Core makes it simple to bind these key-value pairs, which are defined as strings, to POCO-setting classes you define in your code. This model of strongly typed configuration makes it easy to logically group settings around a given feature and lends itself well to unit testing. Figure 11.1 You can store the default map location, zoom level, and store locations in configuration and load them at runtime.
  • 334.
    306 CHAPTER 11Configuring an ASP.NET Core application Before we get into the details of loading configuration from providers, we’ll take a step back and look at where this process happens—inside WebHostBuilder. For ASP.NET Core 2.0 apps built using the default templates, that’s invariably inside the WebHost.CreateDefaultBuilder() method. 11.2 Configuring your application with CreateDefaultBuilder As you saw in chapter 2, the default templates in ASP.NET Core 2.0 use the Create- DefaultBuilder method. This is an opinionated helper method that sets up a num- ber of defaults for your app. public class Program { public static void Main(string[] args) { BuildWebHost(args).Run(); } public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() .Build(); } In chapter 2, I glossed over this method, as you will only rarely need to change it for simple apps. But as your application grows, and if you want to change how configura- tion is loaded for your application, you may find you need to break it apart. This listing shows an overview of the CreateDefaultBuilder method, so you can see how WebHostBuilder is constructed. public static IWebHostBuilder CreateDefaultBuilder(string[] args) { var builder = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .ConfigureAppConfiguration((hostingContext, config) => { // Configuration provider setup }) Listing 11.1 Using CreateDefaultBuilder to set up configuration Listing 11.2 The WebHost.CreateDefaultBuilder method The entry point for your application creates IWebHost and calls Run. CreateDefaultBuilder sets up a number of defaults, including configuration. Creating an instance of WebHostBuilder Kestrel is the default HTTP server in ASP.NET Core. The content root defines the directory where configuration files can be found. Configures application settings, the topic of this chapter
  • 335.
    307 Configuring your applicationwith CreateDefaultBuilder .ConfigureLogging((hostingContext, logging) => { logging.AddConfiguration( hostingContext.Configuration.GetSection("Logging")); logging.AddConsole(); logging.AddDebug(); }) .UseIISIntegration() .UseDefaultServiceProvider((context, options) => { options.ValidateScopes = context.HostingEnvironment.IsDevelopment(); }); return builder; } The first method called on WebHostBuilder is UseKestrel. This extension method con- figures the ASP.NET application to use the Kestrel HTTP server, the new high- performance, cross-platform HTTP server that was created specifically for ASP.NET Core. The call to UseContentRoot tells the application in which directory it can find any configuration or view files it will need later. This is typically the folder in which the application is running, hence the call to GetCurrentDirectory. TIP ContentRoot is not where you store static files that the browser can access directly—that’s the WebRoot, typically wwwroot. ConfigureLogging is where you can specify the logging settings for your application. We’ll look at logging in detail in chapter 17; for now, it’s enough to know that Create- DefaultBuilder sets this up for you. When you’re running ASP.NET Core applications in production, you need to run them behind a reverse proxy. The call to UseIISIntegration ensures that if you’re running your application on Windows, IIS can talk to your application, monitor its health, and forward requests to it. When running the application on Linux or macOS, this method call has no effect. The last method call in CreateDefaultBuilder, UseDefaultServiceProvider, configures your app to use the built-in DI container. It also sets the ValidateScopes option based on the current HostingEnvironment. When running the application in the development environment, the app will automatically check for captured depen- dencies, as you learned about in chapter 10. You’ll learn more about hosting environ- ments in section 11.5. The ConfigureAppConfiguration() method is the focus of section 11.3. It’s where you load the settings and secrets for your app, whether they’re in JSON files, environment variables, or command-line arguments. In the next section, you’ll see Sets up the logging infrastructure When running on windows, IIS integration is automatically configured. Configures the DI container. The ValidateScopes option checks for captured dependencies. Returns WebHostBuilder for further configuration before calling Build()
  • 336.
    308 CHAPTER 11Configuring an ASP.NET Core application how to use this method to load configuration values from various configuration pro- viders using the ASP.NET Core ConfigurationBuilder. 11.3 Building a configuration object for your app The ASP.NET Core configuration model centers on two main constructs: Configuration- Builder and IConfigurationRoot. NOTE ConfigurationBuilder describes how to construct the final configura- tion representation for your app, and IConfigurationRoot holds the config- uration values themselves. You describe your configuration setup by adding a number of IConfiguration- Providers to IConfigurationBuilder. These describe how to load the key-value pairs from a particular source; for example, a JSON file or from environment variables, as shown in figure 11.2. Calling Build() on ConfigurationBuilder queries each of these providers for the values they contain to create the IConfigurationRoot instance. ASP.NET Core ships with a number of configuration providers, each provided by a different NuGet package. In ASP.NET Core 2.0, all of these packages are included by 1. You start by creating an instance of the ConfigurationBuilder. 2. You add providers to the builder using various extension methods. These providers can pull configuration values from multiple sources. ConfigurationBuilder IConfigurationRoot Build() JsonConfigurationProvider EnvironmentVariablesProvider CommandLineProvider AddJsonFile() AddEnvironmentVariables() Add CommandLine() 3. Calling Build() on the ConfigurationBuilder loads the values from each provider and stores them in the IConfigurationRoot. key:value key:value key:value Figure 11.2 Using ConfigurationBuilder to create an instance of IConfigurationRoot. Configuration providers are added to the builder with extension methods. Calling Build() queries each provider to create IConfigurationRoot.
  • 337.
    309 Building a configurationobject for your app default, thanks to the Microsoft.AspNetCore.All metapackage. Some of the common providers are:  JSON files—Microsoft.Extensions.Configuration.Json  XML files—Microsoft.Extensions.Configuration.Xml  Environment variables—Microsoft.Extensions.Configuration.EnvironmentVariables  Command-line arguments—Microsoft.Extensions.Configuration.CommandLine  Azure Key Vault—Microsoft.Extensions.Configuration.AzureKeyVault If none of these fits your requirements, you can find a whole host of alternatives on GitHub and NuGet, and it’s not difficult to create your own custom provider. For example, I wrote a simple configuration provider to read YAML files, available on NuGet or GitHub.1 In most cases, the default providers will be sufficient. In particular, most templates start with an appsettings.json file, which will contain a variety of settings, depending on the template you choose. This listing shows the default file generated by the ASP.NET Core 1.1 Web template without authentication. { "Logging": { "IncludeScopes": false, "LogLevel": { "Default": "Warning" } } } As you can see, this file only contains settings to control logging to start with, but you can add additional configuration for your app here too. WARNING Don’t store sensitive values, such as production passwords, API keys, or connection strings, in this file. You’ll see how to store these securely in section 11.3.4. Adding your own configuration values involves adding a key-value pair to the JSON. It’s a good idea to “namespace” your settings by creating a base object for related set- tings, as in the MapDisplay object shown here. { "Logging": { "IncludeScopes": false, "LogLevel": { "Default": "Warning" 1 You can find my YAML provider on GitHub at https://github.com/andrewlock/NetEscapades.Configuration. Listing 11.3 Default appsettings.json file created by an ASP.NET Core Web template Listing 11.4 Adding additional configuration values to an appsettings.json file
  • 338.
    310 CHAPTER 11Configuring an ASP.NET Core application } }, "MapDisplay": { "DefaultZoomLevel": 9, "DefaultLocation": { "latitude": 50.500, "longitude": -4.000 } } } I’ve nested the map settings inside the MapDisplay parent key; this creates a section which will be useful later when it comes to binding your values to a POCO object. I also nested the latitude and longitude keys under the DefaultLocation key. You can create any structure of values that you like; the configuration provider will read them just fine. Also, you can store them as any data type—numbers, in this case—but be aware that the provider will read and store them internally as strings. TIP The configuration keys are not case-sensitive, so bear that in mind when loading from providers in which the keys are case-sensitive. Now that you have a configuration file, it’s time for your app to load it using ConfigurationBuilder! For this, return to the ConfigureAppConfiguration() method exposed by WebHostBuilder in Program.cs. 11.3.1 Adding a configuration provider in Program.cs The default templates in ASP.NET Core use the CreateDefaultBuilder helper method to bootstrap WebHostBuilder for your app, as you saw in section 11.2. As part of this configuration, the CreateDefaultBuilder method calls ConfigureApp- Configuration and sets up a number of default configuration providers, which we’ll look at in more detail throughout this chapter:  JSON file provider—Loads settings from an optional JSON file called appsettings .json.  JSON file provider—Loads settings from an optional environment-specific JSON file called appsettings.ENVIRONMENT.json. I’ll show how to use environment- specific files in section 11.5.2.  User Secrets—Loads secrets that are stored safely in development.  Environment variables—Loads environment variables as configuration variables. Great for storing secrets in production.  Command-line arguments—Uses values passed as arguments when you run your app. Using the default builder ties you to this default set, but it’s entirely optional. If you want to use different configuration providers, you can use your own WebHostBuilder instance instead. You can set up configuration using the ConfigureAppConfiguration Nest all the configuration under the MapDisplay key. Values can be numbers in the JSON file, but they’ll be converted to strings when they’re read. You can create deeply nested structures to better organize your configuration values.
  • 339.
    311 Building a configurationobject for your app method, as shown in the following listing, in which you configure a JSON provider to load the appsettings.json file. For brevity, I’ve omitted the logging and service provider configuration that you saw in listing 11.2, but you’d need to include this in practice. public class Program { public static void Main(string[] args) { BuildWebHost(args).Run(); } public static IWebHost BuildWebHost(string[] args) => new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .ConfigureAppConfiguration(AddAppConfiguration) .ConfigureLogging( (hostingContext, logging) => { /* Detail not shown */ }) .UseIISIntegration() .UseDefaultServiceProvider( (context, options) =>{ /* Detail not shown */ }) .UseStartup<Startup>() .Build(); public static void AddAppConfiguration( WebHostBuilderContext hostingContext, IConfigurationBuilder config) { config.AddJsonFile("appsettings.json", optional: true); } } TIP In listing 11.5, I extracted the configuration to a static helper method, AddAppConfiguration, but you can also provide this inline as a lambda method. In ASP.NET Core 2.0, WebHostBuilder creates an IConfigurationBuilder instance before invoking the ConfigureAppConfiguration method. All you need to do is add the configuration providers for your application. In this example, you’ve added a single JSON configuration provider by calling the AddJsonFile extension method and providing a filename. You’ve also set the value of optional to true. This tells the configuration provider to skip over files it can’t find at runtime, instead of throwing FileNotFoundException. Note that the extension method just registers the provider at this point, it doesn’t try to load the file yet. And that’s it! The WebHostBuilder instance takes care of calling Build(), which generates IConfigurationRoot, which represents your configuration object. This is then registered as an IConfiguration instance with the DI container, so you can Listing 11.5 Loading appsettings.json using a custom WebHostBuilder Adds the configuration setup function to WebHostBuilder WebHostBuilder provides a hosting context and an instance of ConfigurationBuilder. Adds a JSON configuration provider, providing the filename of the configuration file
  • 340.
    312 CHAPTER 11Configuring an ASP.NET Core application inject it into your classes. You’d commonly inject this into the constructor of your Startup class, so you can use it in the Configure and ConfigureServices methods: public class Startup { public Startup(IConfiguration config) { Configuration = config } public IConfiguration Configuration { get; } } NOTE The ASP.NET Core 2.0 WebHostBuilder registers the configuration object as an IConfiguration in the DI container, not an IConfiguration- Root. In ASP.NET Core 1, you have to manually register the configuration with DI yourself. At this point, at the end of the Startup constructor, you have a fully loaded configura- tion object! But what can you do with it? The IConfigurationRoot stores configuration as a set of key-value string pairs. You can access any value by its key, using standard dic- tionary syntax. For example, you could use var zoomLevel = Configuration["MapDisplay:DefaultZoomLevel"]; to retrieve the configured zoom level for your application. Note that I used a : to des- ignate a separate section. Similarly, to retrieve the latitude key, you could use Configuration["MapDisplay:DefaultLocation:Latitude"]; You can also grab a whole section of the configuration using the GetSection(section) method. This grabs a chunk of the configuration and resets the namespace. Another way of getting the latitude key would be Configuration.GetSection("MapDisplay”)["DefaultLocation:Latitude"]; Accessing setting values like this is useful in the ConfigureServices and Configure methods of Startup, when you’re defining your application. When setting up your application to connect to a database, for example, you’ll often load a connection string from the Configuration object (you’ll see a concrete example of this in the next chapter, when we look at Entity Framework Core). If you need to access the configuration object like this from classes other than Startup, you can use DI to take it as a dependency in your service’s constructor. But accessing configuration using string keys like this isn’t particularly convenient; you should try to use strongly typed configuration instead, as you’ll see in section 11.4. WARNING ASP.NET Core 1 doesn’t register the configuration object with the DI container by default. If you need this functionality, you can register it your- self as a singleton by calling services.AddSingleton(Configuration) in Startup.ConfigureServices.
  • 341.
    313 Building a configurationobject for your app So far, this is probably all feeling a bit convoluted, and run-of-the-mill to load settings from a JSON file, and I’ll grant you, it is. Where the ASP.NET Core configuration sys- tem shines is when you have multiple providers. 11.3.2 Using multiple providers to override configuration values You’ve seen that ASP.NET Core uses the builder pattern to construct the configura- tion object, but so far, you’ve only configured a single provider. When you add provid- ers, it’s important to consider the order in which you add them, as that defines the order in which the configuration values are added to the underlying dictionary. Con- figuration values from later providers will overwrite values with the same key from ear- lier providers. NOTE This bears repeating: the order you add configuration providers to ConfigurationBuilder is important. Later configuration providers can over- write the values of earlier providers. Think of the configuration providers as adding a layer of configuration values to a stack, where each layer may overlap with some or all of the layers below, as shown in fig- ure 11.3. When you call Build(), ConfigurationBuilder collapses these layers down into one, to create the final set of configuration values stored in IConfigurationRoot. Update your code to load configuration from three different configuration provid- ers—two JSON providers and an environment variable provider—by adding them to ConfigurationBuilder. I’ve only shown the AddAppConfiguration method in this list- ing for brevity. Each configuration provider adds a layer of configuration values to the stack. Calling Build() collapses the stack, taking the “top-most” configuration values, as viewed from above. The values at the top of a vertical slice are the final values found in the IConfigurationRoot. sharedsettings.json appsettings.json Environment variables Figure 11.3 Each configuration provider adds a “layer” of values to ConfigurationBuilder. Calling Build() collapses that configuration. Later providers will overwrite configuration values with the same key as earlier providers.
  • 342.
    314 CHAPTER 11Configuring an ASP.NET Core application public class Program { /* Additional Program configuration*/ public static void AddAppConfiguration( WebHostBuilderContext hostingContext, IConfigurationBuilder config) { config .AddJsonFile("sharedSettings.json", optional: true) .AddJsonFile("appsettings.json", optional: true) .AddEnvironmentVariables(); } } This design can be useful for a number of things. Fundamentally, it allows you to aggregate configuration values from a number of different sources into a single, cohe- sive object. To cement this in place, consider the configuration values in figure 11.4. Listing 11.6 Loading from multiple providers in Startup.cs using ASP.NET Core 2.0 Loads configuration from a different JSON configuration file before the appsettings.json file Adds the machine’s environment variables as a configuration provider "StoreDetails:" - "" "StoreDetails:Name" - "Head office" "StoreDetails:Location:Latitiude" - "50.5" "StoreDetails:Location:Longitude" - "-4.0" "MyAppConnString" - "localDB;" "MapDisplay:" - "" "MapDisplay:DefaultZoomLevel" - 5 "MapDisplay:DefaultLocation:Latitude" - "50.5" "MapDisplay:DefaultLocation:Longitude" - "-4.0" "MyAppConnString - "productionDB;" "GoogleMapsApiKey - "123456ABCD" { "MyAppConnString": "localDB;", "MapDisplay": { "DefaultZoomLevel": 5, "DefaultLocation: { "Latitude": 50.5, "Longitude": -4.0 } } } appsettings.json { "StoreDetails": { "Name": "Head office", "Location: { "Latitude": 50.5, "Longitude": -4.0 } } } sharedsettings.json Environment variables MyAppConnString: "productionDB;" GoogleMapsApiKey: "123456ABCD" IConfigurationRoot Each configuration provider adds a number of configuration values to the final IConfigurationRoot. They are added in the order the configuration providers were added to the ConfigurationBuilder. The Environment variables are loaded after appsettings.json, so the “localDB” MyAppConString value is overwritten with the “productionDB” value. Figure 11.4 The final IConfigurationRoot includes the values from each of the providers. Both appsettings.json and the environment variables include the MyAppConnString key. As the environment variables are added later, that configuration value is used.
  • 343.
    315 Building a configurationobject for your app Most of the settings in each provider are unique and added to the final IConfiguration- Root. But the "MyAppConnString" key appears both in appsettings.json and as an envi- ronment variable. Because the environment variable provider is added after the JSON providers, the environment variable configuration value is used in IConfiguration- Root. The ability to collate configuration from multiple providers is a handy trait on its own, but this design is also useful when it comes to handling sensitive configuration values, such as connection strings and passwords. The next section shows how to deal with this problem, both locally on your development machine and on production servers. 11.3.3 Storing configuration secrets safely As soon as you build a nontrivial app, you’ll find you have a need to store some sort of sensitive data as a setting somewhere. This could be a password, connection string, or an API key for a remote service, for example. Storing these values in appsettings.json is generally a bad idea as you should never commit secrets to source control; the number of secret API keys people have commit- ted to GitHub is scary! Instead, it’s much better to store these values outside of your project folder, where they won’t get accidentally committed. You can do this in a number of ways, but the easiest and most commonly used approaches are to use environment variables for secrets on your production server and User Secrets locally. Neither approach is truly secure, in that they don’t store values in an encrypted format. If your machine is compromised, attackers will be able to read the stored val- ues as they’re stored in plaintext. They’re intended to help you avoid committing secrets to source control. TIP Azure Key Vault1 is a secure alternative, in that it stores the values encrypted in Azure. But you will still need to use the following approach for storing the Azure Key Value connection details! Whichever approach you choose to store your application secrets, make sure you aren’t storing them in source control, if possible. Even private repositories may not stay private forever, so it’s best to err on the side of caution! STORING SECRETS IN ENVIRONMENT VARIABLES IN PRODUCTION You can add the environment variable configuration provider using the AddEnvironment- Variables extension method, as you’ve already seen in listing 11.6. This will add all of the environment variables on your machine as key-value pairs in the configuration object. 1 The Azure Key Vault configuration provider is available as a Microsoft.Extensions.Configuration.AzureKey- Vault NuGet package. For details on using it in your application, see the documentation at http:// mng.bz/96aQ.
  • 344.
    316 CHAPTER 11Configuring an ASP.NET Core application You can create the same hierarchical sections that you typically see in JSON files by using a colon, :, or a double underscore, __, to demarcate a section, for example: MapSettings:MaxNumberOfPoints or MapSettings__MaxNumberOfPoints. TIP Some environments, such as Linux, don’t allow “:” in environment vari- ables, you must use the double underscore approach on these instead. The environment variable approach is particularly useful when you’re publishing your app to a self-contained environment, such as a dedicated server, Azure, or a Docker container. You can set environment variables on your production machine, or on your Docker container, and the provider will read them at runtime, overriding the defaults specified in your appsettings.json files.1 For a development machine, environment settings are less useful, as all your apps would be using the same variables. For example, if you set the Connection- Strings__DefaultConnection environment variable, then that would be added for every app you run locally. That sounds like more of a hassle than a benefit! For development scenarios, you can use the User Secrets Manager. This effectively adds per-app environment variables, so you can have different settings for each app, but store them in a different location from the app itself. STORING SECRETS WITH THE USER SECRETS MANAGER IN DEVELOPMENT The idea behind User Secrets is to simplify storing per-app secrets outside of your app’s project tree. This is similar to environment variables, but you use a unique key per-app to keep the secrets segregated. WARNING The secrets aren’t encrypted, so shouldn’t be considered secure. They’re an improvement on storing them in your project folder. Setting up User Secrets takes a bit more effort than using environment variables, as you need to configure a tool to read and write them, add the User Secrets configura- tion provider, and define a unique key for your application: 1 ASP.NET Core 2.0 includes the User Secrets NuGet package by default as part of the Microsoft.AspNetCore.All metapackage. If you’re not using the meta- package, you’ll need to explicitly add the Microsoft.Extensions.Configuration .UserSecrets package to your project file: <PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="2.0.0" /> 2 Add the SecretManager NuGet tools to your csproj file. Note, you need to do this manually by editing the csproj file—you can’t use the NuGet UI in Visual Studio.2 1 For instructions on how to set environment variables for your operating system, see the documentation at http://mng.bz/R052. 2 ASP.NET Core 2.1 (in preview at the time of writing) introduces the concept of global tools so you can install the tools globally for all your applications. For preliminary details, see http://mng.bz/jGs6.
  • 345.
    317 Building a configurationobject for your app <ItemGroup> <DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="2.0.0" /> </ItemGroup> 3 Restore the NuGet package using dotnet restore. Visual Studio will do this automatically for you after you save your edits to the csproj file. 4 If you’re using Visual Studio, right-click your project and choose Manage User Secrets. This opens an editor for a secrets.json file in which you can store your key-value pairs, as if it were an appsettings.json file, as shown in figure 11.5. 5 Add a unique identifier to your csproj file. Visual Studio does this automatically when you click Manage User Secrets, but if you’re using the command line, you’ll need to add it yourself. Typically, you’d use a unique ID, like a GUID: <PropertyGroup> <UserSecretsId>96eb2a39-1ef9-4d8e-8b20-8e8bd14038aa</UserSecretsId> </PropertyGroup> 6 If you aren’t using Visual Studio, you can add User Secrets using the command line: dotnet user-secrets set GoogleMapsApiKey F5RJT9GFHKR7F Figure 11.5 Click Manage User Secrets to open an editor for the User Secrets app.
  • 346.
    318 CHAPTER 11Configuring an ASP.NET Core application or you can edit the secret.json file directly using your favorite editor. The exact location of this file depends on your operating system and may vary. Check the documentation1 for details. Phew, that’s a lot of setup, and you’re not done yet! You still need to update your app to load the User Secrets at runtime. Thankfully, this is a lot simpler; you can use the extension method included as part of the UserSecrets NuGet package to add them to ConfigurationBuilder: if(env.IsDevelopment()) { configBuilder.AddUserSecrets<Startup>(); } NOTE You should only use the User Secrets provider in development, not in production, so you’re conditionally adding the provider to Configuration- Builder. In production, you should use environment variables or Azure Key Vault, as discussed earlier. This method has a number of overloads, but the simplest is a generic method that you can call using your application’s Startup class. The User Secrets provider needs to read the UserSecretsId property that you (or Visual Studio) added to the csproj. The Startup class acts as a simple marker to indicate which assembly contains this property. NOTE If you’re interested, the User Secrets package uses the UserSecretsId property in your csproj file to generate an assembly-level UserSecretsId- Attribute. The provider then reads this attribute at runtime to determine the UserSecretsId of the app, and hence generates the path to the secrets.json file. And there you have it, safe storage of your secrets outside your project folder during development. This might seem like overkill, but if you have anything that you con- sider remotely sensitive that you need to load into configuration, then I strongly urge you to use environment variables or User Secrets. It’s almost time to leave configuration providers behind, but before we do, I’d like to show you the ASP.NET Core configuration system’s party trick: reloading files on the fly. 11.3.4 Reloading configuration values when they change Besides security, not having to recompile your application every time you want to tweak a value is one of the advantages of using configuration and settings. In the pre- vious version of ASP.NET, changing a setting by editing web.config would cause your 1 The Secret Manager Tool 2.0.0 stores the secrets.json file in the user profile, but is subject to change in later versions. See http://mng.bz/1B2v for details.
  • 347.
    319 Building a configurationobject for your app app to have to restart. This beat having to recompile, but waiting for the app to start up before it could serve requests was a bit of a drag. In ASP.NET Core, you finally get the ability to edit a file and have the configura- tion of your application automatically update, without having to recompile or restart. A common scenario where you might find this useful is when you’re trying to debug an app you have in production. You typically configure logging to one of a number of levels, for example:  Error  Warning  Information  Debug Each of these settings is more verbose than the last, but also provides more context. By default, you might configure your app to only log warning- and error-level logs in pro- duction, so you don’t generate too many superfluous log entries. Conversely, if you’re trying to debug a problem, you want as much information as possible, so you might want to use the debug log level. Being able to change configuration at runtime means you can easily switch on extra logs when you encounter an issue and switch them back afterwards by editing your appsettings.json file. NOTE Reloading is generally only available for file-based configuration pro- viders, as opposed to the environment variable or User Secrets provider. You can enable the reloading of configuration files when you add any of the file-based providers to your ConfigurationBuilder. The Add*File extension methods include an overload with a reloadOnChange parameter. If this is set to true, the app will moni- tor the filesystem for changes to the file and will trigger a rebuild of IConfiguration- Root, if needs be. This listing shows how to add configuration reloading to the appsettings.json file loaded inside the AddAppConfiguration method. public class Program { /* Additional Program configuration*/ public static void AddAppConfiguration( WebHostBuilderContext hostingContext, IConfigurationBuilder config) { config.AddJsonFile( "appsettings.json", optional: true reloadOnChange: true); } } Listing 11.7 Reloading appsettings.json when the file changes IConfigurationRoot will be rebuilt if the appsettings.json file changes.
  • 348.
    320 CHAPTER 11Configuring an ASP.NET Core application With that in place, any changes you make to the file will be mirrored in IConfiguration- Root. But as I said at the start of this chapter, IConfigurationRoot isn’t the preferred way to pass settings around in your application. Instead, as you’ll see in the next sec- tion, you should favor strongly typed POCO objects. 11.4 Using strongly typed settings with the options pattern Most of the examples I’ve shown so far have been how to get values into IConfiguration- Root, as opposed to how to use them. You’ve seen that you can access a key using the Configuration["key"] dictionary syntax, but using string keys like this feels messy and prone to typos. Instead, ASP.NET Core promotes the use of strongly typed settings. These are POCO objects that you define and create and represent a small collection of settings, scoped to a single feature in your app. The following listing shows both the settings for your store locator component and display settings to customize the homepage of the app. They’re separated into two separate objects with "MapDisplay" and "HomePageSettings" keys corresponding to the different areas of the app they impact. { "MapDisplay": { "DefaultZoomLevel": 6, "DefaultLocation": { "latitude": 50.500, "longitude": -4.000 } }, "HomePageSettings": { "Title": "Acme Store Locator", "ShowCopyright": true } } The simplest approach to making the homepage settings available to HomeController would be to inject IConfiguration and access the values using the dictionary syntax; for example: public class HomeController : Controller { public HomeController(IConfiguration config) { var title = config["HomePageSettings:Title"]; var showCopyright = bool.Parse( config["HomePageSettings:ShowCopyright"]); } } Listing 11.8 Separating settings into different objects in appsettings.json Settings related to the store locator section of the app General settings related to the app’s homepage
  • 349.
    321 Using strongly typedsettings with the options pattern But you don’t want to do this; too many strings for my liking! And that bool.Parse? Yuk! Instead, you can use custom strongly typed objects, with all the type safety and IntelliSense goodness that brings. public class HomeController : Controller { public HomeController(IOptions<HomePageSettings> options) { HomePageSettings settings = options.Value; var title = settings.Title; var showCopyright = settings.ShowCopyright; } } The ASP.NET Core configuration system includes a binder, which can take a collection of configuration values and bind them to a strongly typed object, called an options class. This is similar to the concept of binding from chapter 6, where request values were bound to the arguments of your application model. This section shows how to set up the binding of configuration values to a POCO options class and how to make sure it reloads when the underlying configuration val- ues change. We’ll also have a look at the different sorts of objects you can bind. 11.4.1 Introducing the IOptions interface ASP.NET Core introduced strongly typed settings as a way of letting configuration code adhere to the single responsibility principle and to allow injecting configuration classes as explicit dependencies. They also make testing easier; instead of having to create an instance of IConfiguration to test a service, you can create an instance of the POCO options class. For example, the HomePageSettings class shown in the previous example could be simple, exposing just the values related to the homepage: public class HomePageSettings { public string Title { get; set; } public bool ShowCopyright { get; set; } } Your options classes need to be non-abstract and have a public parameterless con- structor to be eligible for binding. The binder will set any public properties that match configuration values, as you’ll see shortly. Listing 11.9 Injecting strongly typed options into a controller using IOptions<T> You can inject a strongly typed options class using the IOptions<> wrapper interface. The Value property exposes the POCO settings object. The binder can also convert string values directly to primitive types. The settings object contains properties that are bound to configuration values at runtime.
  • 350.
    322 CHAPTER 11Configuring an ASP.NET Core application To help facilitate the binding of configuration values to your custom POCO options classes, ASP.NET Core introduces the IOptions<T> interface. This is a simple interface with a single property, Value, which contains your configured POCO options class at runtime. Options classes are set up in the ConfigureServices section of Startup, as shown here. public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.Configure<MapDisplaySettings>( Configuration.GetSection("MapDisplay")); services.Configure<HomePageSettings>( Configuration.GetSection("HomePageSettings")); } TIP If you get a compilation error with the preceding overload of Configure<>, add the Microsoft.Extensions.Options.ConfigurationExtensions NuGet pack- age to your app. ASP.NET Core 2.0 includes this by default as part of the metapackage, so it’s not often necessary. Each call to Configure<T> sets up the following series of actions internally:  Creates an instance of ConfigureOptions<T>, which indicates that IOptions<T> should be configured based on configuration. If Configure<T> is called multiple times, then multiple Configure- Options<T> objects will be used, all of which can be applied to create the final object, in much the same way as the IConfigurationRoot is built from multiple layers.  Each ConfigureOptions<T> instance binds a section of IConfigurationRoot to an instance of the T POCO class. This sets any public properties on the options class based on the keys in the provided ConfigurationSection. Note that the section name ("MapDisplay" in listing 11.10) can have any value; it doesn’t have to match the name of your options class.  Registers the IOptions<T> interface in the DI container as a singleton, with the final bound POCO object in the Value property. This last step lets you inject your options classes into controllers and services by inject- ing IOptions<T>, as you’ve seen. This gives you encapsulated, strongly typed access to your configuration values. No more magic strings, woo-hoo! WARNING If you forget to call Configure<T> and inject IOptions<T> into your services, you won’t see any errors, but the T options class won’t be bound to anything and will only have default values in its properties. Listing 11.10 Configuring the options classes using Configure<T> in Startup.cs Binds the MapDisplay section to the POCO options class MapDisplaySettings Binds the HomePageSettings section to the POCO options class HomePageSettings
  • 351.
    323 Using strongly typedsettings with the options pattern The binding of the T options class to ConfigurationSection happens when you first request IOptions<T>. The object is registered in the DI container as a singleton, so it’s only bound once. There’s one catch with this setup: you can’t use the reloadOnChange parameter I described in section 11.2.3 to reload your strongly typed options classes when using IOptions<T>. IConfigurationRoot will still be reloaded if you edit your appset- tings.json files, but it won’t propagate to your options class. If that seems like a step backwards, or even a deal-breaker, then don’t worry. IOptions<T> has a cousin, IOptionsSnapshot<T>, for such an occasion. 11.4.2 Reloading strongly typed options with IOptionsSnapshot In the previous section, you used IOptions<T> to provide strongly typed access to con- figuration. This provided a nice encapsulation of the settings for a particular service, but with a specific drawback: the options class never changes, even if you modify the underlying configuration file from which it was loaded, for example appsettings.json. This is often not a problem (you shouldn’t be modifying files on live production servers anyway!), but if you need this functionality, you can use the IOptions- Snapshot<T> interface introduced with ASP.NET Core 1.1. WARNING IOptionsSnapshot is only available in ASP.NET Core 1.1 and above. If you’re using ASP.NET Core 1.0, you won’t be able to reload the IOptions<T> options classes when the underlying configuration changes. Conceptually, IOptionsSnaphot<T> is identical to IOptions<T> in that it’s a strongly typed representation of a section of configuration. The difference is when, and how often, the POCO options objects are created when each of these are used.  IOptions<T>—The instance is created once, when first needed. It always con- tains the configuration when the object instance was first created.  IOptionsSnapshot<T>—A new instance is created when needed, if the underly- ing configuration has changed since the last instance was created. IOptionsSnaphot<T> is automatically set up for your options classes at the same time as IOptions<T>, so you can use it in your services in exactly the same way! This listing shows how you could update your HomeController so that you always get the latest configuration values in your strongly typed HomePageSettings options class. public class HomeController : Controller { public HomeController( IOptionsSnapshot<HomePageSettings> options) { HomePageSettings settings = options.Value; var title = settings.Title; } } Listing 11.11 Injecting reloadable options using IOptionsSnapshot<T> IOptionsSnapshot<T> will update if the underlying configuration values change. The Value property exposes the POCO settings object, the same as for IOptions<T>. The settings object will match the configuration values at some point, instead of at first run.
  • 352.
    324 CHAPTER 11Configuring an ASP.NET Core application Whenever you edit the settings file and cause IConfigurationRoot to be reloaded, IOptionsSnapshot<HomePageSettings> will be rebound. A new HomePageSettings object is created with the new configuration values and will be used for all future dependency injection. Until you edit the file again, of course! It’s as simple as that; update your code to use IOptionsSnapshot<T> instead of IOptions<T> wherever you need it. The final thing I want to touch on in this section is the design of your POCO options classes themselves. These are typically simple collections of properties, but there are a few things to bear in mind so that you don’t get stuck debugging why the binding seemingly hasn’t worked! 11.4.3 Designing your options classes for automatic binding I’ve touched on some of the requirements for your POCO classes for the IOptions<T> binder to be able to populate them, but there are a few rules to bear in mind. The first key point is that the binder will be creating instances of your options classes using reflection, so your POCO options classes need to:  Be non-abstract  Have a default (public parameterless) constructor If your classes satisfy these two points, the binder will loop through all the properties on your class and bind any it can. In the broadest sense, the binder can bind any prop- erty which  Is public  Has a getter—the binder won’t write set-only properties  Has a setter or a non-null value  Is not an indexer This listing shows an extensive options class, with a whole host of different types of properties, some of which are valid to bind, and some of which aren’t. public class TestOptions { public string String { get; set; } public int Integer { get; set; } public SubClass Object { get; set; } public SubClass ReadOnly { get; } = new SubClass(); public Dictionary<string, SubClass> Dictionary { get; set; } public List<SubClass> List { get; set; } public IDictionary<string, SubClass> IDictionary { get; set; } public IEnumerable<SubClass> IEnumerable { get; set; } public ICollection<SubClass> IEnumerable { get; } = new List<SubClass>(); Listing 11.12 An options class containing binding and nonbinding properties The binder can bind simple and complex object types, and read-only properties with a default. The binder will also bind collections, including interfaces; dictionaries must have string keys.
  • 353.
    325 Using strongly typedsettings with the options pattern internal string NotPublic { get; set; } public SubClass SetOnly { set => _setOnly = value; } public SubClass NullReadOnly { get; } = null; public SubClass NullPrivateSetter { get; private set; } = null; public SubClass this[int i] { get => _indexerList[i]; set => _indexerList[i] = value; } public List<SubClass> NullList { get; } public Dictionary<int, SubClass> IntegerKeys { get; set; } public IEnumerable<SubClass> ReadOnlyEnumerable { get; } = new List<SubClass>(); public SubClass _setOnly = null; private readonly List<SubClass> _indexerList = new List<SubClass>(); public class SubClass { public string Value { get; set; } } } As shown in the listing, the binder generally supports collections too—both imple- mentations and interfaces. If the collection property is already initialized, it will use that, but the binder can also create backing fields for them. If your property imple- ments any of the following classes, the binder will create a List<> of the appropriate type as the backing object:  IReadOnlyList<>  IReadOnlyCollection<>  ICollection<>  IEnumerable<> WARNING You can’t bind to an IEnumerable<> property that has already been initialized, as the underlying type doesn’t expose an Add function! You can bind to an IEnumerable<> if you leave its initial value as null. Similarly, the binder will create a Dictionary<,> as the backing field for properties with dictionary interfaces, as long as they use string keys:  IDictionary<,>  IReadOnlyDictionary<,> WARNING You can’t bind dictionaries with nonstring values, such as int. Clearly there are quite a few nuances here, but if you stick to the simple cases from the preceding example, you’ll be fine. Be sure to check for typos in your JSON files! That brings us to the end of this section on strongly typed settings. In the next sec- tion, we’ll look at how you can dynamically change your settings at runtime, based on the environment in which your app is running. The binder can’t bind nonpublic, set-only, null-read- only, or indexer properties. These collection properties can’t be bound. The backing fields for SetOnly and Indexer properties
  • 354.
    326 CHAPTER 11Configuring an ASP.NET Core application 11.5 Configuring an application for multiple environments Any application that makes it to production will likely have to run in multiple environ- ments. For example, if you’re building an application with database access, you’ll probably have a small database running on your machine that you use for develop- ment. In production, you’ll have a completely different database running on a server somewhere else. Another common requirement is to have different amounts of logging depending on where your app is running. In development, it’s great to generate lots of logs as it helps debugging, but once you get to production, too much logging can be over- whelming. You’ll want to log warnings and errors, maybe information-level logs, but definitely not debug-level logs! To handle these requirements, you need to make sure your app loads different configuration values depending on the environment it’s running in; load the produc- tion database connection string when in production and so on. You need to consider three aspects:  How does your app identify which environment it’s running in?  How do you load different configuration values based on the current environ- ment?  How can you change the environment for a particular machine? This section tackles each of these questions in turn, so you can easily tell your develop- ment machine apart from your production servers and act accordingly. 11.5.1 Identifying the hosting environment ASP.NET Core identifies the current environment by using, perhaps unsurprisingly, an environment variable! The WebHostBuilder created in Program.cs looks for a magic environment variable called ASPNETCORE_ENVIRONMENT and uses it to create an IHostingEnvironment object. The IHostingEnvironment interface exposes a number of useful properties about the running context of your app. Some of these you’ve already seen, such as Content- RootPath, which points to the folder containing your application’s content files; for example, the appsettings.json files. The property you’re interested in here is EnvironmentName. The IHostingEnvironment.EnvironmentName property is set to the value of the ASPNETCORE_ENVIRONMENT environment variable, so it can be anything, but you should stick to three commonly used values in most cases:  "Development"  "Staging"  "Production" ASP.NET Core includes a number of helper methods for working with these three val- ues, so you’ll have an easier time if you stick to them. In particular, whenever you’re
  • 355.
    327 Configuring an applicationfor multiple environments testing whether your app is running in a particular environment, you should use one of the following extension methods:  IHostingEnvironment.IsDevelopment()  IHostingEnvironment.IsStaging()  IHostingEnvironment.IsProduction()  IHostingEnvironment.IsEnvironment(string environmentName) These methods all make sure they do case-insensitive checks of the environment vari- able, so you don’t get any wonky errors at runtime if you don’t capitalize the environ- ment variable value. TIP Where possible, use the IHostingEnvironment extension methods over direct string comparison with EnvironmentValue, as they provide case- insensitive matching. IHostingEnvironment doesn’t do anything other than expose the details of your cur- rent environment, but you can use it in a number of different ways. In chapter 8, you saw the Environment Tag Helper, which you used to show and hide HTML based on the current environment. Now you know where it was getting its information— IHostingEnvironment. You can use a similar approach to customize which configuration values you load at runtime by loading different files when running in development versus production. This is common and is included out of the box in most ASP.NET Core templates, and in the ASP.NET Core 2.0 CreateDefaultBuilder helper method. 11.5.2 Loading environment-specific configuration files The EnvironmentName value is determined early in the process of bootstrapping your application, before your ConfigurationBuilder is created. This means you can dynamically change which configuration providers are added to the builder, and hence which configuration values are loaded when the IConfigurationRoot is built. A common pattern is to have an optional, environment-specific appsettings .ENVIRONMENT.json file that’s loaded after the default appsettings.json file. This list- ing shows how you could achieve this in the AddAppConfiguration method of Program. public class Program { /* Additional Program configuration*/ public static void AddAppConfiguration( WebHostBuilderContext hostingContext, IConfigurationBuilder config) { var env = hostingContext.HostingEnvironment; config Listing 11.13 Adding environment-specific appsettings.json files The current IHostingEnvironment is available on WebHostBuilderContext.
  • 356.
    328 CHAPTER 11Configuring an ASP.NET Core application .AddJsonFile( "appsettings.json", optional: false) .AddJsonFile $"appsettings.{env.EnvironmentName}.json", optional: true); } } With this pattern, a global appsettings.json file contains settings applicable to most environments. Additional, optional, JSON files called appsettings.Development.json, appsettings.Staging.json, and appsettings.Production.json are subsequently added to ConfigurationBuilder, depending on the current EnvironmentName. Any settings in these files will overwrite values from the global appsettings.json, if they have the same key, as you’ve seen previously. This lets you do things like set the logging to be verbose in the development environment only and switch to more selec- tive logs in production. Another common pattern is to completely add or remove configuration providers depending on the environment. For example, you might use the User Secrets pro- vider when developing locally, but Azure Key Vault in production. This listing shows how you could use IHostingEnvironment to conditionally include the User Secrets provider. public class Program { /* Additional Program configuration*/ public static void AddAppConfiguration( WebHostBuilderContext hostingContext, IConfigurationBuilder config) { var env = hostingContext.HostingEnvironment config .AddJsonFile( "appsettings.json", optional: false) .AddJsonFile $"appsettings.{env.EnvironmentName}.json", optional: true); if(env.IsDevelopment()) { builder.AddUserSecrets<Startup>(); } } } Another common place to customize your application based on the environment is when setting up your middleware pipeline. In chapter 3, you learned about Listing 11.14 Conditionally including the User Secrets configuration provider It’s common to make the base appsettings.json compulsory. Adds an optional environment- specific JSON file where the filename varies with the environment Extension methods make checking the environment simple and explicit. In Staging and Production, the User Secrets provider wouldn’t be used.
  • 357.
    329 Configuring an applicationfor multiple environments DeveloperExceptionPageMiddleware and how you should use it when developing locally. The following listing shows how you can use IHostingEnvironment to control your pipeline in this way, so that when you’re in Staging or Production, your app uses ExceptionHandlerMiddleware instead. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseStaticFiles(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); } You can inject IHostingEnvironment anywhere in your app, but I’d advise against using it in your own services, outside of Startup and Program. It’s far better to use the configuration providers to customize strongly typed settings based on the current hosting environment. As useful as it is, setting IHostingEnvironment can be a little cumbersome if you want to switch back and forth between different environments during testing. Person- ally, I’m always forgetting how to set environment variables on the various operating systems I use. The final skill I’d like to teach you is how to set the hosting environment when you’re developing. 11.5.3 Setting the hosting environment In this section, I’ll show you a couple of ways to set the hosting environment when you’re developing. These makes it easy to test a specific app’s behavior in different environments, without having to change the environment for all the apps on your machine. If your ASP.NET Core application can’t find an ASPNETCORE_ENVIRONMENT environment variable when it starts up, it defaults to a production environment, as shown in figure 11.6. This means that when you deploy to your configuration provid- ers, you’ll be using the correct environment by default. Listing 11.15 Using IHostingEnvironment to customize your middleware pipeline In Development, DeveloperExceptionPageMiddleware is added to the pipeline. In Production, the pipeline uses ExceptionHandlerMiddleware instead.
  • 358.
    330 CHAPTER 11Configuring an ASP.NET Core application TIP By default, the current hosting environment is logged to the console at startup, which can be useful for quickly checking that the environment vari- able has been picked up correctly. If you’re working with the .NET CLI and a text editor, you’re probably going to be comfortable setting environment variables from the command line for your OS, so you’ll want to set your ASPNETCORE_ENVIRONMENT development machine envi- ronment to Development. If you’re using Visual Studio, you can set temporary environment variables for when you debug your application. Double-click the Properties node and choose the Debug tab; you can see in figure 11.7 that the ASPNETCORE_ENVIRONMENT is set to Development, so there’s no need to dive back to the command line if you don’t want to. TIP These values are all stored in the properties/launchSettings.json file, so you can edit them by hand if you prefer.1 1 In ASP.NET Core 2.1 you can use the launchSettings.json file with both the CLI tools and Visual Studio. If the WebHostBuilder can’t find the ASPNETCORE_ENVIRONMENT variable at runtime, it will default to Production. Figure 11.6 By default, ASP.NET Core applications run in the Production hosting environment. You can override this by setting the ASPNETCORE_ENVIRONMENT variable. Figure 11.7 You can set temporary environment variables when you debug your application in Visual Studio.
  • 359.
    331 Configuring an applicationfor multiple environments Visual Studio Code uses a similar approach with its own launch.json file. Add the ASPNETCORE_ENVIRONMENT variable, as shown in figure 11.8. These temporary environment variables are a great way to test your application in different variables. In Visual Studio, you could even add a new debugging profile, IIS Express (Production) perhaps, so you can quickly switch between testing different environments. There’s one final trick I’ve seen used. If you want to completely hardcode the envi- ronment your app runs in to a single value, you can use the UseEnvironment exten- sion method on WebHostBuilder, as shown next. This forces the app to use the value you provide. I still suggest using one of the standard values though, preferably EnvironmentName.Production. public class Program { public static void Main(string[] args) { Listing 11.16 Hardcoding EnvironmentName with UseEnvironment You can define temporary variables to use when debugging your application in the launch.json file. Figure 11.8 Visual Studio code lets you define temporary environment variables to use when debugging. Your application will treat them the same as any other environment variable but they won’t affect other applications, only the ASP.NET Core app you’re debugging.
  • 360.
    332 CHAPTER 11Configuring an ASP.NET Core application BuildWebHost(args).Run(); } public static IWebHost BuildWebHost(string[] args) => new WebHostBuilder() .UseEnvironment(EnvironmentName.Production) .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .ConfigureAppConfiguration( /* Not shown*/) .UseIISIntegration() .UseStartup<Startup>() .Build(); } TIP This approach only makes sense if you always want to run your app in a single environment. That’s overly restrictive, so I recommend setting the host- ing environment using an environment variable or your IDE instead. That brings us to the end of this chapter on configuration. Configuration isn’t glam- orous, but it’s an essential part of all apps. The ASP.NET Core configuration provider model handles a wide range of scenarios, letting you store settings and secrets in a variety of locations. Simple settings can be stored in appsettings.json, where they’re easy to tweak and modify during development, and can be overwritten by using environment-specific JSON files. Meanwhile, your secrets and sensitive settings can be stored outside the project file, in the User Secrets manager, or as environment variables. This gives you both flexibility and safety—as long as you don’t go writing your secrets to appsettings.json! In the next chapter, we’ll take a brief look at the new object relational mapper that fits well with ASP.NET Core: Entity Framework Core. We’ll only be getting a taste of it in this book, but you’ll learn how to load and save data, build a database from your code, and migrate the database as your code evolves. Summary  Anything that could be considered a setting or a secret is normally stored as a configuration value.  ASP.NET Core uses configuration providers to load key-value pairs from a vari- ety of sources. Applications can use many different configuration providers.  ConfigurationBuilder describes how to construct the final configuration rep- resentation for your app and IConfigurationRoot holds the configuration val- ues themselves.  You create a configuration object by adding configuration providers to an instance of ConfigurationBuilder using extension methods such as AddJson- File(). WebHostBuilder creates the ConfigurationBuilder instance for you and calls Build() to create an instance of IConfigurationRoot.  ASP.NET Core includes built-in providers for JSON files, XML files, environ- ment files, command-line arguments, and Azure Key Vault, among others. This ignores ASPNETCORE_ENVIRONMENT and hardcodes the environment to Production.
  • 361.
    333 Summary  The orderin which you add providers to ConfigurationBuilder is important; subsequent providers replace the values of settings defined in earlier providers.  Configuration keys aren’t case-sensitive.  ASP.NET Core 1.x doesn’t register the IConfigurationRoot object with the DI container by default. In ASP.NET Core 2.0, it’s registered as an IConfiguration object.  You can retrieve settings from IConfiguration directly using the indexer syn- tax, for example Configuration["MySettings:Value"].  In ASP.NET Core 2.0, the CreateDefaultBuilder method configures JSON, environment variable, command-line argument, and User Secret providers for you. You can customize the configuration providers used in your app by manu- ally creating a WebHostBuilder and calling ConfigureAppConfiguration.  In production, store secrets in environment variables. These can be loaded after your file-based settings in the configuration builder.  On development machines, the User Secrets Manager is a more convenient tool than using environment variables. It stores secrets in your OS user’s profile, outside the project folder.  Be aware that neither environment variables nor the User Secrets Manager tool encrypt secrets, they merely store them in locations that are less likely to be made public, as they’re outside your project folder.  File-based providers, such as the JSON provider, can automatically reload con- figuration values when the file changes. This allows you to update configuration values in real time, without having to restart your app.  Use strongly typed POCO options classes to access configuration in your app.  Use the Configure<T>() extension method in ConfigureServices to bind your POCO options objects to ConfigurationSection.  You can inject the IOptions<T> interface into your services using DI. You can access the strongly typed options object on the Value property.  If you want to reload your POCO options objects when your configuration changes, use the IOptionsSnapshot<T> interface instead.  Applications running in different environments, Development versus Produc- tion for example, often require different configuration values.  ASP.NET Core determines the current hosting environment using the ASPNETCORE_ENVIRONMENT environment variable. If this variable isn’t set, the environment is assumed to be Production.  The current hosting environment is exposed as an IHostingEnvironment interface. You can check for specific environments using IsDevelopment(), IsStaging(), and IsProduction().  You can use the IHostingEnvironment object to load files specific to the cur- rent environment, such as appsettings.Production.json.
  • 362.
    334 Saving data withEntity Framework Core Most applications that you’ll build with ASP.NET Core will require storing and loading some kind of data. Even the examples so far in this book have assumed you have some sort of data store—storing exchange rates, user shopping carts, or the locations of physical main street stores. I’ve glossed over this for the most part but, typically, you’ll store this data in a database. This chapter includes  What Entity Framework Core is and why you should use it  Adding Entity Framework Core to an ASP.NET Core application  Building a data model and using it to create a database  Querying, creating, and updating data using Entity Framework Core
  • 363.
    335 Working with databasescan often be a rather cumbersome process. You have to manage connections to the database, translate data from your application to a format the database can understand, as well as hande a plethora of other subtle issues. You can manage this complexity in a variety of ways, but I’m going to focus on using a new library built for .NET Core: Entity Framework Core (EF Core). EF Core is a library that lets you quickly and easily build database access code for your ASP.NET Core applications. It’s modeled on the popular Entity Framework 6.x library, but has significant changes that mean it stands alone in its own right and is more than an upgrade. The aim of this chapter is to provide a quick overview of EF Core and how you can use it in your applications to quickly query and save to a database. You’ll have enough knowledge to connect your app to a database, and to manage schema changes to the database, but I won’t be going into great depth on any topics. NOTE For an in-depth look at EF Core I recommend, Entity Framework Core in Action by Jon P Smith (Manning, 2018). Alternatively, you can read about EF Core and its cousin, Entity Framework, on the Microsoft documentation web- site at https://docs.microsoft.com/en-us/ef/core/index. Section 12.1 introduces EF Core and explains why you might want to use it in your applications. You’ll learn how the design of EF Core helps you to quickly iterate on your database structure and reduce the friction of interacting with a database. In section 12.2, you’ll learn how to add EF Core to an ASP.NET Core app and con- figure it using the ASP.NET Core configuration system. You’ll see how to build a model for your app that represents the data you’ll store in the database and how to hook it into the ASP.NET Core DI container. NOTE For this chapter, and the rest of the book, I’m going to be using SQL Server Express’ LocalDB feature. This is installed as part of Visual Studio 2017 (when you choose the .NET Desktop Development workload) and provides a lightweight SQL Server engine.1 Very little of the code is specific to SQL Server, so you should be able to follow along with a different database if you prefer. No matter how carefully you design your original data model, the time will come when you need to change it. In section 12.3, I show how you can easily update your model and apply these changes to the database itself, using EF Core for all the heavy lifting. Once you have EF Core configured and a database created, section 12.4 shows how to use EF Core in your application code. You’ll see how to create, read, update, and delete (CRUD) records, as well as some of the patterns to use when designing your data access. 1 You can read more about LocalDB at http://mng.bz/24P8.
  • 364.
    336 CHAPTER 12Saving data with Entity Framework Core In section 12.5, I highlight a few of the issues you’ll want to take into consideration when using EF Core in a production app. A single chapter on EF Core can only offer a brief introduction to all of the related concepts though, so if you choose to use EF Core in your own applications—especially if this is your first time using such a data access library—I strongly recommend reading more once you have the basics from this chapter. Before we get into any code, let’s look at what EF Core is, what problems it solves, and when you might want to use it. 12.1 Introducing Entity Framework Core Database access code is ubiquitous across web applications. Whether you’re building an e-commerce app, a blog, or the Next Big Thing™, chances are you’ll need to inter- act with a database. Unfortunately, interacting with databases from app code is often a messy affair and there are many different approaches you can take. For example, something as simple as reading data from a database requires handling network connections, writing SQL statements, and handling variable result data. The .NET ecosystem has a whole array of libraries you can use for this, ranging from the low-level ADO.NET libraries, to higher-level abstractions like EF Core. In this section, I describe what EF Core is and the problem it’s designed to solve. I cover the motivation behind using an abstraction such as EF Core, and how it helps to bridge the gap between your app code and your database. As part of that, I present some of the trade-offs you’ll make by using it in your apps, which should help you decide if it’s right for your purposes. Finally, we’ll take a look at an example EF Core mapping, from app code to database, to get a feel for EF Core’s main concepts. 12.1.1 What is EF Core? EF Core is a library that provides an object-oriented way to access databases. It acts as an object-relational mapper (ORM), communicating with the database for you and map- ping database responses to .NET classes and objects, as shown in figure 12.1. DEFINITION With an object-relational mapper (ORM), you can manipulate a database using object-oriented concepts such as classes and objects by map- ping them to database concepts such as tables and columns. EF Core is based on, but is distinct from, the existing Entity Framework libraries (cur- rently, up to version 6.x). It was built as part of the .NET Core push to work cross- platform, but with additional goals in mind. In particular, the EF Core team wanted to make a highly performant library that could be used with a wide range of databases. There are many different types of databases, but probably the most commonly used family is relational databases, accessed using Structured Query Language (SQL). This is the bread and butter of EF Core; it can map Microsoft SQL Server, MySQL,
  • 365.
    337 Introducing Entity FrameworkCore Postgres, and many other relational databases. It even has a cool in-memory feature you can use when testing to create a temporary database. EF Core uses a provider model, so that support for other relational databases can be plugged in later, as they become available. NOTE EF Core was designed to work with nonrelational, NoSQL, or document databases too but, at the time of writing, that feature hasn’t yet been imple- mented. That covers what EF Core is, but it doesn’t dig into why you’d want to use it. Why not access the database directly using the traditional ADO.NET libraries? Most of the arguments for using EF Core can be applied to ORMs in general, so what are the advantages of an ORM? 12.1.2 Why use an object-relational mapper? One of the biggest advantages an ORM brings is the speed with which you can develop an application. You can stay in the familiar territory of object-oriented .NET, in many cases without ever needing to directly manipulate a database or write custom SQL. .NET classes map to tables and properties map to columns. References between types map to foreign key relationships between tables. Product Product Product Each object (an instance of a class) maps to a row in a table. .NET Application Database Products PK FK1 Id CategoryId Name Price PK Categories Id Name Product Class Properties: + Id: int + Name: string + Price: decimal + Category: Category Category Class Properties: + Id: int + Name: string Id 1 2 3 Products CategoryId 12 45 123 Name Mac mini iPad Xbox One Price 479.00 339.00 280.00 Figure 12.1 EF Core maps .NET classes and objects to database concepts such as tables and rows.
  • 366.
    338 CHAPTER 12Saving data with Entity Framework Core Imagine you have an e-commerce site and you want to load the details of a product from the database. Using low-level database access code, you’d have to open a connection to the database, write the necessary SQL using the correct table and column names, read the data over the connection, create a POCO to hold the data, and manually set the properties on the object, converting the data to the correct format as you go. Sounds painful, right? An ORM, such as EF Core, takes care of most of this for you. It handles the connec- tion to the database, generating the SQL, and mapping data back to your POCO objects. All you need to provide is a LINQ query describing the data you want to retrieve. ORMs serve as high-level abstractions over databases, so they can significantly reduce the amount of plumbing code you need to write to interact with a database. At the most basic level, they take care of mapping SQL statements to objects and vice versa, but most ORMs take this a step further and provide a number of additional features. ORMs like EF Core keep track of which properties have changed on any objects they rehydrate from the database. This lets you load an object from the database by mapping it from a database table, modify it in .NET code, and then ask the ORM to update the associated record in the database. The ORM will work out which proper- ties have changed and issue update statements for the appropriate columns, saving you a bunch of work. As is so often the case in software development, using an ORM has its drawbacks. One of the biggest advantages of ORMs is also their Achilles heel—they hide you from the database. Sometimes, this high level of abstraction can lead to problematic data- base query patterns in your apps. A classic example is the N+1 problem, where what should be a single database request turns into separate requests for every single row in a database table. Another commonly cited drawback is performance. ORMs are abstractions over a number of concepts, and so inherently do more work than if you were to hand craft every piece of data access in your app. Most ORMs, EF Core included, trade off some degree of performance for ease of development. That said, if you’re aware of the pitfalls of ORMs, you can often drastically simplify the code required to interact with a database. As with anything, if the abstraction works for you, use it, otherwise, don’t. If you only have minimal database access requirements, or you need the best performance you can get, then an ORM such as EF Core may not be the right fit. An alternative is to get the best of both worlds: use an ORM for the quick develop- ment of the bulk of your application, and then fall back to lower level APIs such as ADO.NET for those few areas that prove to be the bottlenecks in your application. That way, you can get good-enough performance with EF Core, trading off perfor- mance for development time, and only optimize those areas that need it.
  • 367.
    339 Introducing Entity FrameworkCore Even if you do decide to use an ORM in your app, there are many different ORMs available for .NET, of which EF Core is one. Whether EF Core is right for you will depend on the features you need and the trade-offs you’re willing to make to get them. The next section compares EF Core to Microsoft’s other offering, Entity Framework, but there many other alternatives you could consider, such as Dapper and NHibernate, each with their own set of trade-offs. 12.1.3 When should you choose EF Core? Microsoft designed EF Core as a reimagining of the mature Entity Framework 6.x (EF 6.x) ORM, which it released in 2008. With ten years of development behind it, EF 6.x is a stable and feature-rich ORM, though it runs on the full .NET Framework, not .NET Core. In contrast, EF Core, like .NET Core and ASP.NET Core, is a relatively new project. The APIs of EF Core are designed to be close to that of EF 6.x—though they aren’t iden- tical—but the core components have been completely rewritten. You should consider EF Core as distinct from EF 6.x; upgrading directly from EF 6.x to EF Core is nontrivial. Microsoft supports both EF Core and EF 6.x, and both will see ongoing improve- ments, so which should you choose? You need to consider a number of things:  Cross platform—EF Core targets .NET Standard, so it can be used in cross- platform apps that target .NET Core. EF 6.x targets .NET Framework, so you’re limited to Windows only.  Database providers—Both EF 6.x and EF Core let you connect to various database types by using pluggable providers. EF Core has a growing number of providers, but there aren’t as many for EF 6.x. If there isn’t a provider for the database you’re using, that’s a bit of a deal breaker!  Performance—The performance of EF 6.x has been a bit of a black mark on its record, so EF Core aims to rectify that. EF Core is designed to be fast and light- weight, significantly outperforming EF 6.x. But it’s unlikely to ever reach the performance of a more lightweight ORM, such as Dapper, or handcrafted SQL statements.  Features—Features are where you’ll find the biggest disparity between EF 6.x and EF Core. EF Core has some features that EF 6.x doesn’t have (batching statements, client-side key generation, in-memory database for testing), but EF 6.x is much more feature-rich than EF Core. At the time of writing, EF Core is missing some headline features, such as lazy- loading and full server-side Group By, but EF Core is under active development, so those features will no doubt appear soon.1 For example, limited Group By 1 For a detailed list of feature differences between EF 6.x and EF Core, see the documentation at https:// docs.microsoft.com/en-us/ef/efcore-and-ef6/features.
  • 368.
    340 CHAPTER 12Saving data with Entity Framework Core support should be available in EF Core 2.1. In contrast, EF 6.x will likely only see incremental improvements and bug fixes, rather than major feature additions. Whether these trade-offs and limitations are a problem for you will depend a lot on your specific app. It’s a lot easier to start a new application bearing these limitations in mind than trying to work around them later. WARNING EF Core isn’t recommended for everyone. Be sure you understand the trade-offs involved, and keep an eye on the guidance from the EF team here: https://docs.microsoft.com/en-us/ef/efcore-and-ef6/choosing. If you’re working on a new ASP.NET Core application, you want to use an ORM for rapid development, and you don’t require any of the unavailable features, then EF Core is a great contender. It’s also supported out of the box by various other subsys- tems of ASP.NET Core. For instance, in chapter 14 you’ll see how to use EF Core with the ASP.NET Core Identity authentication system for managing users in your apps. Before we get into the nitty-gritty of using EF Core in your app, I’ll describe the application we’re going to be using as the case study for this chapter. We’ll go over the application and database details and how to use EF Core to communicate between the two. 12.1.4 Mapping a database to your application code EF Core focuses on the communication between an application and a database, so to show it off, we need an application! This chapter uses the example of a simple cook- ing app that lists recipes, and lets you view a recipe’s ingredients, as shown in figure 12.2. Users can browse for recipes, add new ones, edit recipes, and delete old ones. This is obviously a simple application, but it contains all the database interactions you need with its two entities, Recipe and Ingredient. DEFINITION An entity is a .NET class that’s mapped by EF Core to the data- base. These are classes you define, typically as POCO classes that can be saved and loaded by mapping to database tables using EF Core. When you interact with EF Core, you’ll be primarily using POCO entities and a data- base context that inherits from the DbContext EF Core class. The entity classes are the object-oriented representations of the tables in your database; they represent the data you want to store in the database. You use the DbContext in your application to both configure EF Core and to access the database at runtime. NOTE You can potentially have multiple DbContexts in your application and can even configure them to integrate with different databases. When your application first uses EF Core, EF Core creates an internal representation of the database, based on the DbSet<T> properties on your application’s DbContext and the entity classes themselves, as shown in figure 12.3.
  • 369.
    341 Introducing Entity FrameworkCore For your recipe app, EF Core will build a model of the Recipe class because it’s exposed on the AppDbContext as a DbSet<Recipe>. Furthermore, EF Core will loop through all the properties on Recipe, looking for types it doesn’t know about, and add them to its internal model. In your app, the Ingredients collection on Recipe exposes the Ingredient entity as an ICollection<Ingredient>, so EF Core models the entity appropriately. Each entity is mapped to a table in the database, but EF Core also maps the rela- tionships between the entities. Each recipe can have many ingredients, but each ingredient (which has a name, quantity, and unit) belongs to one recipe, so this is a many-to-one relationship. EF Core uses that knowledge to correctly model the equiva- lent many-to-one database structure. The main page of the application shows a list of all current recipes. Click View to show the detail page for the recipe. This includes the ingredients associated with the recipe. Click Create to add a new recipe to the application. You can also edit or delete the recipe. Figure 12.2 The cookery app lists recipes. You can view, update, and delete recipes, or create new ones.
  • 370.
    342 CHAPTER 12Saving data with Entity Framework Core NOTE Two different recipes, say fish pie and lemon chicken, may use an ingredient that has both the same name and quantity, for example the juice of one lemon, but they’re fundamentally two different instances. If you update the lemon chicken recipe to use two lemons, you wouldn’t want this change to automatically update the fish pie to use two lemons too! EF Core uses the internal model it builds when interacting with the database. This ensures it builds the correct SQL to create, read, update, and delete entities. Right, it’s about time for some code! In the next section, you’ll start building the recipe app. You’ll see how to add EF Core to an ASP.NET Core application, configure a database provider, and design your application’s data model. 12.2 Adding EF Core to an application In this section, we’re going to focus on getting EF Core installed and configured in your ASP.NET Core recipe app. As we’re talking about EF Core in this chapter, I’m Recipe + RecipeId: int + Name: string + Ingredients: ICollection<Ingredient> Ingredient + IngredientId: int + RecipeId: int + Name: string + Quantity: decimal + Unit: string 1 0..* The application’s DbContext serves as the entry point for all interactions with EF Core. 2. It scans all the properties on known entities for linked types and adds them to its internal model. AppDbContext + Recipes: DbSet<Recipe> 1. EF Core looks for any DbSet properties on the DbContext (Recipes) and adds them to its internal model. 3. EF Core uses relationships between .NET classes to model the relationship between database tables. .NET application EF Core internal model Ingredients PK IngredientId RecipeId Name Quantity Units Recipes PK RecipeId Name Figure 12.3 EF Core creates an internal model of your application’s data model by exploring the types in your code. It adds all of the types referenced in the DbSet<> properties on your app’s DbContext, and any linked types.
  • 371.
    343 Adding EF Coreto an application not going to go into how to create the application in general—I created a simple MVC app using Razor to render the HTML, nothing fancy. Interaction with EF Core occurs in a service layer that encapsulates all of the data access outside of the MVC framework, as shown in figure 12.4. This keeps your con- cerns separated and makes both your services and MVC controllers testable. Adding EF Core to an application is a multistep process: 1 Choose a database provider; for example, MySQL, Postgres, or MS SQL Server. 2 Install the EF Core NuGet packages. 3 Design your app’s DbContext and entities that make up your data model. 4 Register your app’s DbContext with the ASP.NET Core DI container. 5 Use EF Core to generate a migration describing your data model. 6 Apply the migration to the database to update the database’s schema. This might seem a little daunting already, but we’ll walk through steps 1–4 in this sec- tion, and steps 5–6 in section 12.3, so it won’t take long. Given the space constraints of this chapter, I’m going to be sticking to the default conventions of EF Core in the code I show. EF Core is far more customizable than it may initially appear, but I 1. A request is received to the URL /recipe. Request Index action method View 2. The request is routed to the RecipeController.Index action. 5. The controller uses the List<RecipeSummary> returned by the RecipeService as the model for the ViewResult. List<RecipeSummary> Data model RecipeService Routing RecipeController HTML 3. The index action calls the RecipeService to fetch the list of RecipeSummary models. EF Core Db 4. The RecipeService calls into EF Core to load the Recipes from the database and uses them to create instances of RecipeSummary. Figure 12.4 Handling a request by loading data from a database using EF Core. Interaction with EF Core is restricted to RecipeService only—the MVC controller doesn’t access EF Core directly.
  • 372.
    344 CHAPTER 12Saving data with Entity Framework Core encourage you to stick to the defaults wherever possible. It will make your life easier in the long run! The first step in setting up EF Core is to decide which database you’d like to inter- act with. It’s likely that a client or your company’s policy will dictate this to you, but it’s still worth giving the choice some thought. 12.2.1 Choosing a database provider and installing EF Core EF Core supports a range of databases by using a provider model. The modular nature of EF Core means you can use the same high-level API to program against different underlying databases, and EF Core knows how to generate the necessary implementation-specific code and SQL statements. You probably already have a database in mind when you start your application, and you’ll be pleased to know that EF Core has got most of the popular ones covered. Add- ing support for a particular database involves adding the correct NuGet package to your csproj file. For example  PostgreSQL—Npgsql.EntityFrameworkCore.PostgreSQL  Microsoft SQL Server—Microsoft.EntityFrameworkCore.SqlServer  MySQL—MySql.Data.EntityFrameworkCore  SQLite—Microsoft.EntityFrameworkCore.SQLite Some of the database provider packages are maintained by Microsoft, some are main- tained by the open source community, and some may require a paid license (for example, the Oracle provider, so be sure to check your requirements. You can find a list of providers here at https://docs.microsoft.com/en-us/ ef/core/providers/. You install a database provider into your application in the same way as any other library: by adding a NuGet package to your project’s csproj file and running dotnet restore from the command line (or letting Visual Studio automatically restore for you). EF Core is inherently modular, so you’ll want to install four different packages. I’m using the SQL Server database provider with LocalDB for the recipe app, so I’ll be using the SQL Server packages.  Microsoft.EntityFrameworkCore.SqlServer—This is the main database provider pack- age for using EF Core at runtime. It also contains a reference to the main EF Core NuGet package.  Microsoft.EntityFrameworkCore.Design—This contains shared design-time compo- nents for EF Core.  Microsoft.EntityFrameworkCore.SqlServer.Design—More design-time components for EF Core, this time specific to Microsoft SQL Server.  Microsoft.EntityFrameworkCore.Tools.DotNet—This package is optional, but you’ll use it later on to create and update your database from the command line. TIP ASP.NET Core 2.0 includes the EF Core libraries as part of the framework- provided Microsoft.AspNetCore.All package. If you reference this in your csproj
  • 373.
    345 Adding EF Coreto an application file, you won’t need to explicitly include the default EF Core NuGet libraries. But there’s no harm if you do! You’ll still need to add the Microsoft.Entity- FrameworkCore.Tools.DotNet tooling package to use the EF Core .NET CLI commands, unless you're using ASP.NET Core 2.1. ASP.NET Core 2.1 auto- matically installs the tooling packages globally, so you don't need to install them in your project. Listing 12.1 shows the recipe app’s csproj after adding the EF Core libraries. I’m going to be building a .NET Core 2.0 app, so the only package I need to add is the tooling package. Remember, you add NuGet packages as PackageReference elements, and tool packages as DotNetCliToolReference elements. <Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>netcoreapp2.0</TargetFramework> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0 " /> </ItemGroup> <ItemGroup> <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0 " /> <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" /> </ItemGroup> </Project> With these packages installed and restored, you have everything you need to start building the data model for your application. In the next section, we’ll create the entity classes and the DbContext for your recipe app. 12.2.2 Building a data model In section 12.1.4, I showed an overview of how EF Core builds up its internal model of your database from the DbContext and entity models. Apart from this discovery mech- anism, EF Core is pretty flexible in letting you define your entities the way you want to, as POCO classes. Some ORMs require your entities to inherit from a specific base class or decorate your models with attributes to describe how to map them. EF Core heavily favors a convention over configuration approach, as you can see in this listing, which shows the Recipe and Ingredient entity classes for your app. Listing 12.1 Installing EF Core into an ASP.NET Core 1.1 application The app targets .NET Core 2.0. Default ASP.NET Core metapackage and tools for creating base MVC app Tools for creating migrations and managing the database using the .NET CLI
  • 374.
    346 CHAPTER 12Saving data with Entity Framework Core public class Recipe { public int RecipeId { get; set; } public string Name { get; set; } public TimeSpan TimeToCook { get; set; } public bool IsDeleted { get; set; } public string Method { get; set; } public ICollection<Ingredient> Ingredients { get; set; } } public class Ingredient { public int IngredientId { get; set; } public int RecipeId { get; set; } public string Name { get; set; } public decimal Quantity { get; set; } public string Unit { get; set; } } These classes conform to certain default conventions that EF Core uses to build up a picture of the database it’s mapping. For example, the Recipe class has a RecipeId property and the Ingredient class has an IngredientId property. EF Core identifies this pattern of TId as indicating the primary key of the table. DEFINITION The primary key of a table is a value that uniquely identifies the row among all the others in the table. It’s often an int or a Guid. Another convention visible here is the RecipeId property on the Ingredient class. EF Core interprets this to be a foreign key pointing to the Recipe class. When considered with ICollection<Ingredient> on the Recipe class, this represents a many-to-one relationship, where each recipe has many ingredients, but each ingredient only belongs to a single recipe, as shown in figure 12.5. DEFINITION A foreign key on a table points to the primary key of a different table, forming a link between the two rows. Many other conventions are at play here, such as the names EF Core will assume for the database tables and columns, or the database column types it will use for each property, but I’m not going to discuss them here. The EF Core documentation con- tains details about all of the conventions, as well as how to customize them for your application: https://docs.microsoft.com/en-us/ef/core/modeling/relational/. TIP You can also use DataAnnotations attributes to decorate your entity classes, controlling things like column naming or string length. EF Core will use these attributes to override the default conventions. Listing 12.2 Defining the EF Core entity classes A Recipe can have many Ingredients, represented by ICollection.
  • 375.
    347 Adding EF Coreto an application As well as the entities, you also define the DbContext for your application. This is the heart of EF Core in your application, used for all your database calls. Create a custom DbContext, in this case called AppDbContext, and derive from the DbContext base class, as shown next. This exposes the DbSet<Recipe> so EF Core can discover and map the Recipe entity. You can expose multiple instances of DbSet<> in this way. public class AppDbContext : DbContext { public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { } public DbSet<Recipe> Recipes { get; set; } } The AppDbContext for your app is simple, containing a list of your root entities, but you can do a lot more with it in a more complex application. If you wanted, you could completely customize how EF Core maps entities to the database, but for this app you’re going to use the defaults. NOTE You didn’t list Ingredient on AppDbContext, but it will be modeled by EF Core as it’s exposed on the Recipe. You can still access the Ingredient objects in the database, but you have to go via the Recipe entity’s Ingredients property to do so, as you’ll see in section 12.4. Listing 12.3 Defining the application DbContext Recipe + RecipeId = 123 + Ingredients = ICollection<> The Recipe object can have many Ingredients, indicated by an ICollection<Ingredient>. Each Ingredient belongs to a single Recipe, indicated by a RecipeId property on Ingredient. RecipeId 123 123 Ingredients IngredientId 1 2 Name Apple Juice Corn Sugar RecipeId 123 124 Recipes Name Apfelwein Pork Wellington Ingredient + IngredientId = 1 + RecipeId = 123 Ingredient + IngredientId = 2 + RecipeId = 123 The many-to-one relationship between the entities corresponds to a foreign key relationship between the database tables. Figure 12.5 Many-to-one relationships in code are translated to foreign key relationships between tables. The constructor options object, containing details such as the connection string You’ll use the Recipes property to query the database.
  • 376.
    348 CHAPTER 12Saving data with Entity Framework Core For this simple example, your data model consists of these three classes: AppDb- Context, Recipe, and Ingredient. The two entities will be mapped to tables and their columns to properties, and you’ll use the AppDbContext to access them. NOTE This code first approach is typical, but if you have an existing database, you can automatically generate the EF entities and DbContext instead. (More information can be found at http://mng.bz/Ymtw.) The data model is complete, but you’re not quite ready to use it yet. Your ASP.NET Core app doesn’t know how to create your AppDbContext, and your AppDbContext needs a connection string so that it can talk to the database. In the next section, we’ll tackle both of these issues, and will finish setting up EF Core in your ASP.NET Core app. 12.2.3 Registering a data context Like any other service in ASP.Net Core, you should register your AppDbContext with the DI container. When registering your context, you also configure a database pro- vider and set the connection string, so EF Core knows how to talk with the database. You register the AppDbContext in the ConfigureServices method of Startup.cs. EF Core provides a generic AddDbContext<T> extension method for this purpose, which takes a configuration function for a DbContextOptionsBuilder instance. This builder can be used to set a host of internal properties of EF Core and lets you com- pletely replace the internal services of EF Core if you want. The configuration for your app is, again, nice and simple, as you can see in the fol- lowing listing. You set the database provider with the UseSqlServer extension method, made available by the Microsoft.EntityFrameworkCore.SqlServer package, and pass it a connection string. public void ConfigureServices(IServiceCollection services) { var connString = Configuration .GetConnectionString("DefaultConnection"); services.AddDbContext<AppDbContext>( options => options.UseSqlServer(connString)); // Add other services. } NOTE If you’re using a different database provider, for example a provider for PostgreSQL, you will need to call the appropriate Use* method on the options object when registering your AppDbContext. The connection string is a typical secret as I discussed in the previous chapter, so load- ing it from configuration makes sense. At runtime, the correct configuration string Listing 12.4 Registering a DbContext with the DI container The connection string is taken from configuration, from the ConnectionStrings section. Register your app’s DbContext by using it as the generic parameter. Specify the database provider in the customization options for the DbContext.
  • 377.
    349 Managing changes withmigrations for your current environment will be used, so you can use different databases when developing locally and in production. TIP You can configure your AppDbContext in other ways and provide the connection string, such as with the OnConfiguring method, but I recommend the method shown here for ASP.NET Core websites. You now have a DbContext, AppDbContext, registered with the DI container, and a data model corresponding to your database. Code-wise, you’re ready to start using EF Core, but the one thing you don’t have is a database! In the next section, you’ll see how you can easily use the .NET CLI to ensure your database stays up to date with your EF Core data model. 12.3 Managing changes with migrations Managing schema changes for databases, such as when you need to add a new table or a new column, is notoriously difficult. Your application code is explicitly tied to a par- ticular version of a database, and you need to make sure the two are always in sync. DEFINITION Schema refers to how the data is organized in a database, includ- ing, among others, the tables, columns, and the relationships between them. When you deploy an app, you can normally delete the old code/executable and replace it with the new code—job done. If you need to roll back a change, delete that new code and deploy an old version of the app. The difficulty with databases is that they contain data! That means that blowing it away and creating a new database with every deployment isn’t possible. A common best practice is to explicitly version a database’s schema in addition to your application’s code. You can do this in a number of ways but, typically, you need to store a diff between the previous schema of the database and the new schema, often as a SQL script. You can then use libraries such as DbUp and FluentMigrator1 to keep track of which scripts have been applied and ensure your database schema is up to date. Alternatively, you can use external tools that manage this for you. EF Core provides its own version of schema management called migrations. Migra- tions provide a way to manage changes to a database schema when your EF Core data model changes. A migration is a C# code file in your application that defines how the data model changed—which columns were added, new entities, and so on. Migrations provide a record over time of how your database schema evolved as part of your appli- cation, so the schema is always in sync with your app’s data model. You can use command-line tools to create a new database from the migrations, or to update an existing database by applying new migrations to it. You can even rollback a migration, which will update a database to a previous schema. 1 DbUp and FluentMigrator are open source projects, available at https://github.com/fluentmigrator/ fluentmigrator and https://github.com/DbUp/DbUp, respectively.
  • 378.
    350 CHAPTER 12Saving data with Entity Framework Core WARNING Applying migrations modifies the database, so you always have to be aware of data loss. If you remove a table from the database using a migra- tion and then rollback the migration, the table will be recreated, but the data it previously contained will be gone forever! In this section, you’ll see how to create your first migration and use it to create a data- base. You’ll then update your data model, create a second migration, and use it to update the database schema. 12.3.1 Creating your first migration Before you can create migrations, you’ll need to install the necessary tooling. There are two primary ways to do this:  Package manager console—Provides a number of PowerShell cmdlets for use inside Visual Studio’s Package Manager Console (PMC). You can install them directly from the PMC or by adding the Microsoft.EntityFrameworkCore.Tools package to your project. They’re also included as part of the Microsoft.AspNet- Core.All metapackage.  .NET CLI—Cross-platform tooling that you can run from the command line. You can install these by adding the Microsoft.EntityFrameworkCore.Tools.Dot- Net package to your project, as you did in listing 12.4. In this book, I’ll be using the cross-platform .NET CLI tools, but if you’re familiar with EF 6.X or prefer to use the Visual Studio PMC, then there are equivalent com- mands for all of the steps you’re going to take.1 You’ve already installed the .NET CLI tools by adding the package to your csproj. You can check they installed correctly by running dotnet ef --help. This should produce a help screen like the one shown in figure 12.6. TIP If you get the No executable found matching command “dotnet-ef” message when running the preceding command, make sure you have restored pack- ages and check the folder you’re executing from. You need to run the dotnet ef tools from the project folder in which you have registered your AppDb- Context, not at the solution-folder level. With the tools installed, you can create your first migration by running the following command and providing a name for the migration—in this case, "InitialSchema": dotnet ef migrations add InitialSchema This command creates three files in your solution:  Migration file—A file with the Timestamp_MigrationName.cs format. This describes the actions to take on the database, such as Create table or Add column.  Migration designer.cs file—This file describes EF Core’s internal model of your data model at the point in time the migration was generated. 1 Documentation for PowerShell cmdlets can be found at http://mng.bz/lm6J.
  • 379.
    351 Managing changes withmigrations  AppDbContextModelSnapshot.cs—This describes EF Core’s current internal model. This will be updated when you add another migration, so it should always be the same as the current, latest migration. EF Core can use AppDbContextModelSnapshot.cs to determine a database’s previous state when creating a new migration, without interacting with the data- base directly. These three files encapsulate the migration process but adding a migration doesn’t update anything in the database itself. For that, you must run a different command to apply the migration to the database. TIP You can, and should, look inside the migration file EF Core generates to check what it will do to your database before running the following com- mands. Better safe than sorry! You can apply migrations in one of three ways:  Using the .NET CLI  Using the Visual Studio PowerShell cmdlets  In code, by obtaining an instance of your AppDbContext and calling context .Database.Migrate(). Which is best for you is a matter of how you’ve designed your application, how you’ll update your production database, and your personal preference. I’ll use the .NET CLI for now, but I’ll discuss some of these considerations in section 12.5. You can apply migrations to a database by running dotnet ef database update Figure 12.6 Running the dotnet ef --help command to check the .NET CLI EF Core tools are installed correctly.
  • 380.
    352 CHAPTER 12Saving data with Entity Framework Core from the project folder of your application. I won’t go into the details of how this works, but this command performs four steps: 1 Builds your application. 2 Loads the services configured in your app’s Startup class, including AppDb- Context. 3 Checks whether the database in the AppDbContext connection string exists. If not, creates it. 4 Updates the database by applying any unapplied migrations. If everything is configured correctly, as I showed in section 12.2, then running this command will set you up with a shiny new database, such as the one shown in figure 12.7! When you apply the migrations to the database, EF Core creates the necessary tables in the database and adds the appropriate columns and keys. You may have also noticed the __EFMigrationsHistory table. EF Core uses this to store the names of migrations that it’s applied to the database. Next time you run dotnet ef database update, EF Core can compare this table to the list of migrations in your app and will apply only the new ones to your database. In the next section, we’ll look at how this makes it easy to change your data model, and update the database schema, without having to recreate the database from scratch. The __EFMigrationsHistory contains a list of all the migrations that have been applied to the database. The entities in our data model, Recipe and Ingredient, correspond to tables in the database. The properties on the Recipe entity correspond to the columns in the Recipes table. Figure 12.7 Applying migrations to a database will create the database if it doesn’t exist and update the database to match EF Core’s internal data model. The list of applied migrations is stored in the __EFMigrationsHistory table.
  • 381.
    353 Managing changes withmigrations 12.3.2 Adding a second migration Most applications inevitably evolve, whether due to increased scope or simple mainte- nance. Adding properties to your entities, adding new entities entirely, and removing obsolete classes—all are likely. EF Core migrations make this simple. Change your entities to your desired state, generate a migration, and apply it to the database, as shown in figure 12.8. Imagine you decide that you’d like to highlight vegetarian and vegan dishes in your recipe app by exposing IsVegetarian and IsVegan properties on the Recipe entity. public class Recipe { public int RecipeId { get; set; } public string Name { get; set; } public TimeSpan TimeToCook { get; set; } public bool IsDeleted { get; set; } public string Method { get; set; } public bool IsVegetarian { get; set; } public bool IsVegan { get; set; } public ICollection<Ingredient> Ingredients { get; set; } } Listing 12.5 Adding properties to the Recipe entity dotnet ef migrations add ExtraRecipeFields 20170525215800_NewFields.cs 20170525220541_ExtraRecipeFields.cs Db dotnet ef database update 1. Update your entities by adding new properties and relationships. Properties: + RecipeId: int + Name: string + TimeToCook: Timespan + IsVegetarian: boolean Recipe Class 2. Create a new migration from the command line and provide a name for it. 3. Creating a migration generates a migration file and a migration designer file. It also updates the app’s DbContext snapshot, but it does not update the database. 4. You can apply the migration to the database using the command line. This will update the database schema to match your entities. >_ >_ + Is Vegan:boolean Figure 12.8 Creating a second migration and applying it to the database using the command- line tools.
  • 382.
    354 CHAPTER 12Saving data with Entity Framework Core After changing your entities, you need to update EF Core’s internal representation of your data model. You do this in the exact same way as for the first migration, by calling dotnet ef migrations add and providing a name for the migration: dotnet ef migrations add ExtraRecipeFields This creates a second migration in your project by adding the migration file and its .designer.cs snapshot file and updating AppDbContextModelSnapshot.cs, as shown in figure 12.9. As before, this creates the migration’s files, but it doesn’t modify the database. You can apply the migration, and update the database, by running dotnet ef database update This compares the migrations in your application to the __EFMigrationsHistory table on your database to see which migrations are outstanding and then runs them. EF Core will run the 20170525220541_ExtraRecipeFields migration, adding the IsVegetarian and IsVegan fields to the database, as shown in figure 12.10. Using migrations is a great way to ensure your database is versioned along with your app code in source control. You can easily check out your app’s source code for a historical point in time and recreate the database schema that your application used at that point. Migrations are easy to use when you’re working alone, or when you’re deploying to a single web server, but even in these cases, there are important things to consider Creating a migration adds a cs file to your solution with a timestamp and the name you gave the migration. It also adds a Designer.cs file that contains a snapshot of EF Core’s internal data model at that point in time. The AppDbContextModelSnapshot is updated to match the snapshot for the new migration. Figure 12.9 Adding a second migration adds a new migration file and a migration Designer.cs file. It also updates AppDbContextModelSnapshot to match the new migration’s Designer.cs file.
  • 383.
    355 Querying data fromand saving data to the database when deciding how to manage your databases. For apps with multiple web servers using a shared database, or for containerized applications, you have even more things to think about. This book is about ASP.NET Core, not EF Core, so I don’t want to dwell on data- base management too much, but section 12.5 points out some of the things you need to bear in mind when using migrations in production. In the next section, we’ll get back to the meaty stuff—defining our business logic and performing CRUD operations on the database. 12.4 Querying data from and saving data to the database Let’s review where you are in creating the recipe application:  You created a simple data model for the application, consisting of recipes and ingredients.  You generated migrations for the data model, to update EF Core’s internal model of your entities.  You applied the migrations to the database, so its schema matches EF Core’s model. In this section, you’ll build the business logic for your application by creating a RecipeService. This will handle querying the database for recipes, creating new reci- pes, and modifying existing ones. As this app only has a simple domain, I’ll be using RecipeService to handle all of the requirements, but in your own apps you may have multiple services that cooperate to provide the business logic. NOTE For simple apps, you may be tempted to move this logic into your MVC Controllers. I’d encourage you to resist this urge; extracting your business logic to other services decouples the HTTP-centric nature of MVC from the underlying business logic. This will often make your business logic easier to test and more reusable. Our database doesn’t have any data in it yet, so we’d better start by letting you create a recipe! Applying the second migration to the database adds the new fields to the Recipes table. Figure 12.10 Applying the ExtraRecipeFields migration to the database adds the IsVegetarian and IsVegan fields to the Recipes table.
  • 384.
    356 CHAPTER 12Saving data with Entity Framework Core 12.4.1 Creating a record In this section, you’re going to build the functionality to let users create a recipe in the app. This will primarily consist of a form that the user can use to enter all the details of the recipe using Razor Tag Helpers, which you learned about in chapters 7 and 8. This form is posted to the CreateRecipe action on RecipeController, which uses model binding and validation attributes to confirm the request is valid, as you saw in chapter 6. If the request is valid, the action method calls RecipeService to create the new Recipe object in the database. As EF Core is the topic of this chapter, I’m going to focus on this service alone, but you can always check out the source code for this book if you want to see how the MVC controller and Razor templates fit together. The business logic for creating a recipe in this application is simple—there is no logic! Map the command binding model provided by the controller to a Recipe entity and its Ingredients, add the Recipe object to AppDbContext, and save that in the database, as shown in figure 12.11. WARNING Many simple, equivalent, sample applications using EF or EF Core allow you to bind directly to the Recipe entity as the view model for your MVC actions. Unfortunately, this exposes a security vulnerability known as over- posting and is a bad practice. If you want to avoid the boilerplate mapping code in your applications, consider using a library such as AutoMapper (http://automapper.org/). Creating an entity in EF Core involves adding a new row to the mapped table. For your application, whenever you create a new Recipe, you also add the linked Ingredient entities. EF Core takes care of linking these all correctly by creating the correct RecipeId for each Ingredient in the database. The bulk of the code required for this example involves translating from Create- RecipeCommand to the Recipe entity—the interaction with the AppDbContext consists of only two methods: Add() and SaveChanges(). readonly AppDbContext _context; public int CreateRecipe(CreateRecipeCommand cmd) { var recipe = new Recipe { Name = cmd.Name, TimeToCook = new TimeSpan( cmd.TimeToCookHrs, cmd.TimeToCookMins, 0), Method = cmd.Method, IsVegetarian = cmd.IsVegetarian, IsVegan = cmd.IsVegan, Ingredients = cmd.Ingredients?.Select(i => Listing 12.6 Creating a Recipe entity in the database An instance of the AppDbContext is injected in the class constructor using DI. CreateRecipeCommand is passed in from the action method. Create a Recipe by mapping from the command object to the Recipe entity.
  • 385.
    357 Querying data fromand saving data to the database new Ingredient { Name = i.Name, Quantity = i.Quantity, Unit = i.Unit, }).ToList() }; _context.Add(recipe); _context.SaveChanges(); return recipe.RecipeId; } 1. A request is POSTed to the URL /recipe/Create. Request Create action method RedirectResult 2. The request is routed to the Create action, and the form body is bound to a CreateRecipeCommand. 7. The action method uses the RecipeId to create a RedirectResult to the new Recipe detail page. Recipe Map command to entites RecipeService.CreateRecipe() 3. The Create action calls the CreateRecipe method on the RecipeService, passing in the CreateRecipeCommand. Save using DbContext RecipeId CreateRecipeCommand SQL 4. A new Recipe object is created from the CreateRecipeCommand. 5. The Recipe is added to EF Core using the app’s DbContext. 6. EF Core generates the SQL necessary to insert a new row into the Recipes table and returns the new row’s RecipeId. Db Figure 12.11 Calling the Create action method and creating a new entity. A Recipe is created from the CreateRecipeCommand and is added to the DbContext. EF Core generates the SQL to add a new row to the Recipes table in the database. Map each CreateIngredientCommand onto an Ingredient entity. Tell EF Core to track the new entities. Tell EF Core to write the entities to the database. EF Core populates the RecipeId field on your new Recipe when it’s saved.
  • 386.
    358 CHAPTER 12Saving data with Entity Framework Core All interactions with EF Core and the database start with an instance of AppDbContext, which is typically DI injected via the constructor. Creating a new entity requires three steps: 1 Create the Recipe and Ingredient entities. 2 Add the entities to EF Core’s list of tracked entities using _context.Add (entity). 3 Execute the SQL INSERT statements against the database, adding the necessary rows to the Recipe and Ingredient tables, by calling _context.SaveChanges(). If there’s a problem when EF Core tries to interact with your database—you haven’t run the migrations to update the database schema, for example—it will throw an exception. I haven’t shown it here, but it’s important to handle these in your applica- tion so you’re not presenting users with an ugly error page when things go wrong. Assuming all goes well, EF Core updates all the auto-generated IDs of your entities (RecipeId on Recipe, and both RecipeId and IngredientId on Ingredient). Return this ID to the MVC controller so it can use it, for example, to redirect to the View Recipe page. And there you have it—you’ve created your first entity using EF Core. In the next section, we’ll look at loading these entities from the database so you can view them in a list. 12.4.2 Loading a list of records Now that you can create recipes, you need to write the code to view them. Luckily, loading data is simple in EF Core, relying heavily on LINQ methods to control which fields you need. For your app, you’ll create a method on RecipeService that returns a summary view of a recipe, consisting of the RecipeId, Name, and TimeToCook as a RecipeSummaryViewModel, as shown in figure 12.12. NOTE Creating a view model is technically a UI concern rather than a busi- ness logic concern. I’m returning them directly from RecipeService here mostly to hammer home that you shouldn’t be using EF Core entities directly in your action methods. The GetRecipes method in RecipeService is conceptually simple and follows a com- mon pattern for querying an EF Core database, as shown in figure 12.13. EF Core uses a fluent chain of LINQ commands to define the query to return on the database. The DbSet<Recipe> property on AppDataContext is an IQueryable, so you can use all the usual Select() and Where() clauses that you would with other IQueryable providers. EF Core will convert these into a SQL statement to query the database with when you call an execute function such as ToList(), ToArray(), or Single(). You can also use the Select() extension method to map to objects other than your entities as part of the SQL query. You can use this to efficiently query the database by only fetching the columns you need.
  • 387.
    359 Querying data fromand saving data to the database Listing 12.7 shows the code to fetch a list of RecipeSummaryViewModels, following the same basic pattern as in figure 12.12. It uses a Where LINQ expression to filter out rec- ipes marked as deleted, and a Select clause to map to the view models. The ToList() command instructs EF Core to generate the SQL query, execute it on the database, and build RecipeSummaryViewModel from the data returned. 1. A request is made to the URL /recipe. Request Index action method ViewResult 2. The request is routed to the Index action which calls the RecipeService to load the view models. 6. The action method passes the view models to the view. RecipeService.GetRecipes() 3. The GetRecipes method uses the app’s DbContext to query the database for the data needed for the view models. Query using DbContext List<RecipeSummaryViewModel> SQL 4. EF Core generates SQL and queries the database. 5. The database returns the data as rows, and EF Core maps them to view model objects. Db Figure 12.12 Calling the Index action and querying the database to retrieve a list of RecipeSummaryViewModels. EF Core generates the SQL to retrieve the necessary fields from the database and maps them to view model objects. _context.Recipes.Where(r => !r.IsDeleted).ToList() AppDbContext DbSet Property access LINQ commands to modify data returned Execute query command Figure 12.13 The three parts of an EF Core database query.
  • 388.
    360 CHAPTER 12Saving data with Entity Framework Core public ICollection<RecipeSummaryViewModel> GetRecipes() { return _context.Recipes .Where(r => !r.IsDeleted) .Select(r => new RecipeSummaryViewModel { Id = r.RecipeId, Name = r.Name, TimeToCook = "{r.TimeToCook.TotalMinutes}mins" }) .ToList(); } Notice that in the Select method, you convert the TimeToCook property from a Time- Span to a string using string interpolation: TimeToCook = $"{x.TimeToCook.TotalMinutes}mins" I said before that EF Core converts the series of LINQ expressions into SQL, but that’s only a half-truth; EF Core can’t or doesn’t know how to convert some expressions to SQL. For those cases, such as in this example, EF Core finds the fields from the DB that it needs in order to run the expression on the client side, selects those from the database, and then runs the expression in C# afterwards. This lets you combine the power and performance of database-side evaluation without compromising the func- tionality of C#. WARNING Client-side evaluation is both powerful and useful but has the potential to cause issues. For certain queries, you could end up pulling back all the data from the database and processing it in memory, instead of in the database. At this point, you have a list of records, displaying a summary of the recipe’s data, so the obvious next step is to load the detail for a single record. 12.4.3 Loading a single record For most intents and purposes, loading a single record is the same as loading a list of records. They share the same common structure you saw in figure 12.13, but when loading a single record, you’ll typically use a Where clause and execute a command that restricts the data to a single entity. Listing 12.8 shows the code to fetch a recipe by ID following the same basic pattern as before (figure 12.12). It uses a Where() LINQ expression to restrict the query to a single recipe, where RecipeId == id, and a Select clause to map to RecipeDetail- ViewModel. The SingleOrDefault() clause will cause EF Core to generate the SQL query, execute it on the database, and build the view model. Listing 12.7 Loading a list of items using EF Core A query starts from a DbSet property EF Core will only query the Recipe columns it needs to map the view model correctly This executes the SQL query and creates the final view models.
  • 389.
    361 Querying data fromand saving data to the database NOTE SingleOrDefault() will throw an exception if the previous Where clause returns more than one record. If you’re loading a single record by ID, you can use the EF Core-specific Find method instead, which has some per- formance benefits when used. public RecipeDetailViewModel GetRecipeDetail(int id) { return _context.Recipes .Where(x => x.RecipeId == id) .Select(x => new RecipeDetailViewModel { Id = x.RecipeId, Name = x.Name, Method = x.Method, Ingredients = x.Ingredients .Select(item => new RecipeDetailViewModel.Item { Name = item.Name, Quantity = $"{item.Quantity} {item.Unit}" }) }) .SingleOrDefault(); } Notice that, as well as mapping the Recipe to a RecipeDetailViewModel, you also map the related Ingredients for a Recipe, as though you’re working with the objects directly in memory. This is one of the advantages of using an ORM—you can easily map child objects and let EF Core decide how best to build the underlying queries to fetch the data. NOTE EF Core logs all the SQL statements it runs as LogLevel.Information events by default, so you can easily see what queries are being run against the database. Our app is definitely shaping up; you can create new recipes, view them all in a list, and drill down to view individual recipes with their ingredients and method. Pretty soon though, someone’s going to introduce a typo and want to change their model. To do this, you’ll have to implement the U in CRUD: update. 12.4.4 Updating a model with changes Updating entities when they have changed is generally the hardest part of CRUD operations, as there are so many variables. Figure 12.14 gives an overview of this pro- cess as it applies to your recipe app. Listing 12.8 Loading a single item using EF Core The id of the recipe to load is passed as a parameter. As before, a query starts from a DbSet property. Limit the query to the recipe with the provided id. Map the Recipe to a RecipeDetailViewModel. Load and map linked Ingredients as part of the same query. Execute the query and map the data to the view model.
  • 390.
    362 CHAPTER 12Saving data with Entity Framework Core I’m not going to handle the relationship aspect in this book because that’s generally a complex problem, and how you tackle it depends on the specifics of your data model. Instead, I’ll focus on updating properties on the Recipe entity itself.1 For web applications, when you update an entity, you’ll typically follow the steps outlined in Figure 12.14: 1 Read the entity from the database 2 Modify the entity’s properties 3 Save the changes to the database You’ll encapsulate these three steps in a method on RecipeService, UpdateRecipe, which takes UpdateRecipeCommand, containing the changes to make to the Recipe entity. NOTE As with the Create command, you don’t directly modify the entities in the MVC controllers, ensuring you keep the UI concern separate from the business logic. The following listing shows the RecipeService.UpdateRecipe method, which updates the Recipe entity. It’s the three steps you defined previously to read, modify, 1 For a detailed discussion on handling relationship updates in EF Core, see EF Core in Action by Jon P Smith (Manning, 2018) https://livebook.manning.com#!/book/smith3/Chapter-3/109. Command Db SQL 2. The DbContext generates the SQL necessary to load the entity from the database. Read entity using DbContext Update properties on entity Db SQL Save entity using DbContext Update entity relationships Recipe 1. The update method receives a command indicating which entity to update and the new property values. Recipe 3. The command is used to update the properties on the Recipe entity. 4. If the ingredients of the Recipe have changed, these are also updated using the Command. 5. Save is called on the DbContext, which generates the necessary SQL to update the entity in the database. Figure 12.14 Updating an entity involves three steps: read the entity using EF Core, update the properties of the entity, and call SaveChanges() on the DbContext to generate the SQL to update the correct rows in the database.
  • 391.
    363 Querying data fromand saving data to the database and save the entity. I’ve extracted the code to update the recipe with the new values to a helper method. public void UpdateRecipe(UpdateRecipeCommand cmd) { var recipe = _context.Recipes.Find(cmd.Id); if(recipe == null) { throw new Exception("Unable to find the recipe"); } UpdateRecipe(recipe, cmd); _context.SaveChanges(); } static void UpdateRecipe(Recipe recipe, UpdateRecipeCommand cmd) { recipe.Name = cmd.Name; recipe.TimeToCook = new TimeSpan(cmd.TimeToCookHrs, cmd.TimeToCookMins, 0); recipe.Method = cmd.Method; recipe.IsVegetarian = cmd.IsVegetarian; recipe.IsVegan = cmd.IsVegan; } In this example, I read the Recipe entity using the Find(id) method exposed by DbSet. This is a simple helper method for loading an entity by its ID, in this case RecipeId. I could’ve written an equivalent query directly using LINQ as _context.Recipes.Where(r=>r.RecipeId == cmd.Id).FirstOrDefault(); Using Find() is a little more declarative and concise. You may be wondering how EF Core knows which columns to update when you call SaveChanges(). The simplest approach would be to update every column—if the field hasn’t changed, then it doesn’t matter if you write the same value again. But EF Core is a bit more clever than that. EF Core internally tracks the state of any entities it loads from the database. It cre- ates a snapshot of all the entity’s property values, so it can track which ones have changed. When you call SaveChanges(), EF Core compares the state of any tracked entities (in this case, the Repair entity) with the tracking snapshot. Any properties that have been changed are included in the UPDATE statement sent to the database, unchanged properties are ignored. NOTE EF Core provides other mechanisms to track changes, as well as options to disable change-tracking altogether. See the documentation or Jon P Smith’s EF Core in Action (Manning, 2018) for details (https://livebook .manning.com#!/book/smith3/Chapter-3/59). Listing 12.9 Updating an existing entity with EF Core Find is exposed directly by Recipes and simplifies reading an entity by id. If an invalid id is provided, recipe will be null. A helper method for setting the new properties on the Recipe entity Execute the SQL to save the changes to the database. Set the new values on the Recipe entity.
  • 392.
    364 CHAPTER 12Saving data with Entity Framework Core With the ability to update recipes, you’re almost done with your recipe app. “But wait,” I hear you cry, “we haven’t handled the D in CRUD—delete!” And that’s true, but in reality, I’ve found few occasions when you want to delete data. Let’s consider the requirements for deleting a recipe from the application, as shown in figure 12.15. You need to add a (scary-looking) Delete button next to a rec- ipe. After the user clicks Delete, the recipe is no longer visible in the list and can’t be viewed. Now, you could achieve this by deleting the recipe from the database, but the problem with data is once it’s gone, it’s gone! What if a user accidentally deletes a record? Also, deleting a row from a relational database typically has implications on other entities. For example, you can’t delete a row from the Recipe table in your application without also deleting all of the Ingredient rows that reference it, thanks to the foreign key con- straint on Ingredient.RecipeId. EF Core can easily handle these true deletion scenarios for you with the DbContext .Remove(entity) command but, typically, what you mean when you find a need to delete data is to archive it or hide it from the UI. A common approach to handling The main page of the application shows a list of all current recipes. Clicking Delete returns you to the list view, but the deleted recipe is no longer visible. Clicking View opens the recipe detail page. Figure 12.15 The desired behavior when deleting a recipe from the app. Clicking Delete should return you to the application’s main list view, with the deleted recipe no longer visible.
  • 393.
    365 Querying data fromand saving data to the database this scenario is to include some sort of “Is this entity deleted” flag on your entity, such as the IsDeleted flag I included on the Recipe entity: public bool IsDeleted {get;set;} If you take this approach then, suddenly, deleting data becomes simpler, as it’s noth- ing more than an update to the entity. No more issues of lost data and no more refer- ential integrity problems. NOTE The main exception I’ve found to this pattern is when you’re storing your users’ personally identifying information. In these cases, you may be duty-bound (and, potentially, legally bound) to scrub their information from your database on request. With this approach, you can create a delete method on RecipeService, which updates the IsDeleted flag, as shown in the following listing. In addition, you should ensure you have Where() clauses in all the other methods in your RecipeService, to ensure you can’t display a deleted Recipe, as you saw in listing 12.9, for the GetRecipes() method. public void DeleteRecipe(int recipeId) { var recipe = _context.Recipes.Find(recipeId); if(recipe == null) { throw new Exception("Unable to find the recipe"); } recipe.IsDeleted = true; _context.SaveChanges(); } This approach satisfies the requirements—it removes the recipe from the UI of the application—but it simplifies a number of things. This soft delete approach won’t work for all scenarios, but I’ve found it to be a common pattern in projects I’ve worked on. TIP EF Core 2.0 gained a feature called Model-level query filters. These allow you to specify a Where clause at the model level, so you could, for example, ensure that EF Core never loads Recipes for which IsDeleted is true. See this announcement post for details: https://github.com/aspnet/ EntityFrameworkCore/ issues/8923. We’re almost at the end of this chapter on EF Core. We’ve covered the basics of add- ing EF Core to your project and using it to simplify data access, but you’ll likely need to read more into EF Core as your apps become more complex. In the final section of this chapter, I’d like to pinpoint a number of things you need to take into consider- ation before using EF Core in your own applications, so you’re familiar with some of the issues you’ll face as your apps grow. Listing 12.10 Marking entities as deleted in EF Core Fetch the Recipe entity by id. If an invalid id is provided, recipe will be null. Mark the Recipe as deleted. Execute the SQL to save the changes to the database.
  • 394.
    366 CHAPTER 12Saving data with Entity Framework Core 12.5 Using EF Core in production applications This book is about ASP.NET Core, not EF Core, so I didn’t want to spend too much time exploring EF Core. This chapter should’ve given you enough to get up and run- ning, but you’ll definitely need to learn more before you even think about putting EF Core into production. As I’ve said a number of time, I recommend EF Core in Action by Jon P Smith (Manning, 2018) for details (https://livebook.manning.com/#!/ book/smith3/Chapter-11/), or exploring the EF Core documentation site at https://docs.microsoft.com/en-us/ef/core/. The following topics aren’t essential to getting started with EF Core, but you’ll quickly come up against them if you build a production-ready app. This section isn’t a prescriptive guide to how to tackle each of these; it’s more a set of things to consider before you dive into production!  Scaffolding of columns—EF Core uses conservative values for things like string columns by allowing strings of unlimited length. In practice, you’ll often want to restrict these and other data types to sensible values.  Validation—You can decorate your entities with DataAnnotations validation attri- butes, but EF Core won’t automatically validate the values before saving to the database. This differs from EF 6.x behavior, in which validation was automatic.  Handling concurrency—EF Core provides a number of ways to handle concur- rency, where multiple users attempt to update an entity at the same time. One partial solution is through the use of Timestamp columns on your entities.  Synchronous vs. asynchronous—EF Core provides both synchronous and asyn- chronous commands for interacting with the database. Often, async is better for web apps, but there are nuances to this argument that make it impossible to rec- ommend one approach over the other in all situations. EF Core is a great tool for being productive when writing data-access code, but there are some aspects of working with a database that are unavoidably awkward. The issue of database management is one of the thorniest issues to tackle. This book is about ASP.NET Core, not EF Core, so I don’t want to dwell on database management too much. Having said that, most web applications use some sort of database, so the fol- lowing issues are likely to impact you at some point.  Automatic migrations—If you automatically deploy your app to production as part of some sort of DevOps pipeline, you’ll inevitably need some way of apply- ing migrations to a database automatically. You can tackle this in a number of ways, such as scripting the .NET CLI, applying migrations in your app’s startup code, or using a custom tool. Each approach has its pros and cons.  Multiple web hosts—One specific consideration is whether you have multiple web servers hosting your app, all pointing to the same database. If so, then applying migrations in your app’s startup code becomes harder, as you must ensure only one app can migrate the database at a time.
  • 395.
    367 Summary  Making backward-compatibleschema changes—A corollary of the multiple-web host approach is that you’ll often be in a situation where your app is accessing a data- base that has a newer schema than the app thinks. That means you should nor- mally endeavor to make schema changes backward-compatible wherever possible.  Storing migrations in a different assembly—In this chapter, I included all my logic in a single project, but in larger apps, data access is often in a different project to the web app. For apps with this structure, you must use slightly different com- mands when using the .NET CLI or PowerShell cmdlets.  Seeding data—When you first create a database, you often want it to have some initial seed data, such as a default user. EF 6.X had a mechanism for seeding data built in, whereas EF Core requires you to explicitly seed your database yourself. How you choose to handle each of these issues will depend on the infrastructure and deployment approach you take with your application. None of them are particularly fun to tackle but they’re an unfortunate necessity. Take heart though, they can all be solved one way or another! That brings us to the end of this chapter on EF Core. In the next chapter, we’ll look at one of the slightly more advanced topics of MVC, the filter pipeline, and how you can use it to reduce duplication in your code. Summary  EF Core is an object-relational mapper (ORM) that lets you interact with a data- base by manipulating standard POCO classes, called entities, in your application.  EF Core maps entity classes to tables, properties on the entity to columns in the tables, and instances of entity objects to rows in these tables.  EF Core uses a database-provider model that lets you change the underlying database without changing any of your object manipulation code. EF Core has database providers for Microsoft SQL Server, SQLite, PostgreSQL, MySQL, and many others.  EF Core is cross-platform and has good performance for an ORM, but has fewer features than some other ORMs, such as EF 6.x.  EF Core stores an internal representation of the entities in your application and how they map to the database, based on the DbSet<T> properties on your appli- cation’s DbContext. EF Core builds a model based on the entity classes them- selves and any other entities they reference.  You add EF Core to your app by adding a NuGet database provider package. You should also install the Design packages for both EF Core and your database provider (if available). These are used to generate and apply migrations to a database.
  • 396.
    368 CHAPTER 12Saving data with Entity Framework Core  EF Core includes a number of conventions for how entities are defined, such as primary keys and foreign keys. You can customize how entities are defined either declaratively, using DataAnotations, or using a fluent API.  Your application uses a DbContext to interact with EF Core and the database. You register it with a DI container using AddDbContext<T>, defining the data- base provider and providing a connection string.  EF Core uses migrations to track changes to your entity definitions. They’re used to ensure your entity definitions, EF Core’s internal model, and the data- base schema all match.  After changing an entity, you can create a migration either using the .NET CLI tool or using Visual Studio PowerShell cmdlets.  To create a new migration with the .NET CLI, run dotnet ef migrations add NAME in your project folder, where NAME is the name you want to give the migration.  You can apply the migration to the database using dotnet ef database update. This will create the database if it doesn’t already exist and apply any outstand- ing migrations.  EF Core doesn’t interact with the database when it creates migrations, only when you explicitly update the database.  You can add entities to an EF Core database by creating a new entity, e, calling _context.Add(e) on an instance of your application’s data context, _context, and calling _context.SaveChanges(). This generates the necessary SQL INSERT statements to add the new rows to the database.  You can load records from a database using the DbSet<T> properties on your app’s DbContext. These expose the IQueryable interface, so you can use LINQ statements to filter and transform the data in the database before it’s returned. If you use a C# expression that EF Core can’t translate to SQL, it will load the data necessary to perform the operation in your app, instead of in the database.  Updating an entity consists of three steps: read the entity from the database, modify the entity, and save the changes to the database. EF Core will keep track of which properties have changed so that it can optimize the SQL it generates.  You can delete entities in EF Core using the Remove method, but you should consider carefully whether you need this functionality. Often a soft delete tech- nique using an IsDeleted flag on entities is safer and easier to implement.  This chapter only covers a subset of the issues you must consider when using EF Core in your application. Before using it in a production app, you should con- sider, among other things: the data types generated for fields, validation, how to handle concurrency, the seeding of initial data, handling migrations on a run- ning application, and handling migrations in a web-farm scenario.
  • 397.
    369 The MVC filterpipeline In part 1, I covered the MVC framework of ASP.NET Core in some detail. You learned about MvcMiddleware and how routing is used to select an action method to execute. You also saw model binding, validation, and how to generate a response by returning an IActionResult from your actions. In this chapter, I’m going to head deeper into MvcMiddleware and look at the MVC filter pipeline, sometimes called the MVC action invocation pipeline. This chapter covers  The MVC filter pipeline and how it differs from middleware  Creating custom filters to refactor complex action methods  Using authorization filters to protect your action methods  Short-circuiting the filter pipeline to bypass action execution  Injecting dependencies into filters
  • 398.
    370 CHAPTER 13The MVC filter pipeline MVC uses a number of built-in filters to handle crosscutting concerns, such as authorization (controlling which users can access which action methods in your appli- cation). Any application that has the concept of users will use authorization filters as a minimum, but filters are much more powerful than this single use case. This chapter describes the MVC filter pipeline in the context of an MVC request. You’ll learn how to create custom filters that you can use in your own apps, and how you can use them to reduce duplicate code in your action methods. You’ll learn how to customize your application’s behavior for specific actions, as well as how to apply fil- ters globally to modify all of the actions in your app. Think of the filter pipeline as a mini middleware pipeline running inside Mvc- Middleware. Like the middleware pipeline in ASP.NET Core, the filter pipeline con- sists of a series of components connected as a pipe, so the output of one filter feeds into the input of the next. This chapter starts by looking at the similarities and differences between MVC fil- ters and middleware, and when you should choose one over the other. You’ll learn about all the different types of filters and how they combine to create the filter pipe- line for a request that reaches MvcMiddleware. In section 13.2, I’ll take you through each filter type in detail, how they fit into the MVC pipeline, and what to use them for. For each one, I’ll provide example imple- mentations that you might use in your own application. A key feature of filters is the ability to short-circuit a request by generating a response and halting progression through the MVC filter pipeline. This is similar to the way short-circuiting works in middleware, but there are subtle differences. On top of that, the exact behavior is slightly different for each filter, which I cover in section 13.3. You typically add MVC filters to the pipeline by implementing them as attributes added to your controller classes and action methods. Unfortunately, you can’t easily use DI with attributes due to the limitations of C#. In section 13.4, I’ll show you how to use the ServiceFilterAttribute and TypeFilterAttribute base classes to enable dependency injection in your filters. Before we can start writing code, we should get to grips with the basics of the MVC filter pipeline. The first section of this chapter explains what the pipeline is, why you might want to use it, and how it differs from the middleware pipeline. 13.1 Understanding filters and when to use them The MVC filter pipeline is a relatively simple concept, in that it provides hooks into the normal MVC request, as shown in figure 13.1. Say you wanted to ensure that users can create or edit products on an e-commerce app only if they’re logged in. The app would redirect anonymous users to a login page instead of executing the action. Without filters, you’d need to include the same code to check for a logged-in user at the start of each specific action method. With this approach, MvcMiddleware would still execute the model binding and validation, even if the user were not logged in.
  • 399.
    371 Understanding filters andwhen to use them With filters, you can use the hooks in the MVC request to run common code across all, or a subset of, requests. This way, you can do a wide range of things, such as  Ensuring a user is logged in before an action method, model binding, or valida- tion runs  Customizing the output format of particular action methods  Handling model validation failures before an action method is invoked  Catching exceptions from an action method and handling them in a special way In many ways, the MVC filter pipeline is like a middleware pipeline, but restricted to Mvc- Middleware only. Like middleware, filters are good for handling crosscutting concerns for your application and are a useful tool for reducing code duplication in many cases. 1. A request is received to the URL /recipe/ . 1 Request 2. The routing module matches the request to the RecipeController.View action and sets id= . 1 Routing Action ViewResult Execution MVC Controller HTML Model binding / validation 3. A variety of different MVC filters run as part of the MVC middleware. 4. Filters run before model binding, before the action method runs, and before and after the IActionResult is executed. MvcMiddleware Figure 13.1 Filters run at multiple points in MvcMiddleware in the normal handling of a request.
  • 400.
    372 CHAPTER 13The MVC filter pipeline In this section, I’ll describe the MVC filter pipeline and how it fits into the overall MVC request. You’ll learn about the types of MVC filters, how you can add them to your own apps, and how to control the order in which they execute when handling a request. 13.1.1 The MVC filter pipeline As you saw in figure 13.1, MVC filters run at a number of different points in the MVC request. This linear view of an MVC request and the filter pipeline that you’ve used so far doesn’t quite match up with how these filters execute. There are five types of filter, each of which runs at a different stage in MvcMiddleware, as shown in figure 13.2. Each stage lends itself to a particular use case, thanks to its specific location in Mvc- Middleware, with respect to model binding, action execution, and result execution.  Authorization filters—These run first in the pipeline, so are useful for protecting your APIs and action methods. If an authorization filter deems the request unauthorized, it will short-circuit the request, preventing the rest of the filter pipeline from running.  Resource filters—After authorization, resource filters are the next filters to run in the pipeline. They can also execute at the end of the pipeline, in much the same way that middleware components can handle both the incoming request and the outgoing response. Alternatively, they can completely short-circuit the request pipeline and return a response directly. Thanks to their early position in the pipeline, resource filters can have a vari- ety of uses. You could add metrics to an action method, prevent an action method from executing if an unsupported content type is requested, or, as they run before model binding, control the way model binding works for that request.  Action filters—Action filters run just before and after an action is executed. As model binding has already happened, action filters let you manipulate the argu- ments to the method—before it executes—or they can short-circuit the action completely and return a different IActionResult. Because they also run after the action executes, they can optionally customize IActionResult before it’s executed.  Exception filters—Exception filters can catch exceptions that occur in the filter pipeline and handle them appropriately. They let you write custom MVC- specific error-handling code, which can be useful in some situations. For exam- ple, you could catch exceptions in Web API actions and format them differently to exceptions in your MVC actions.  Result filters—Result filters run before and after an action method’s IAction- Result is executed. This lets you control the execution of the result, or even short-circuit the execution of the result.
  • 401.
    373 Understanding filters andwhen to use them Exactly which filter you pick to implement will depend on the functionality you’re try- ing to introduce. Want to short-circuit a request as early as possible? Resource filters are a good fit. Need access to the action method parameters? Use an action filter. Think of the filter pipeline as a small middleware pipeline that lives by itself in MvcMiddleware. Alternatively, you could think of filters as hooks into the MVC action invocation process, which let you run code at a particular point in a request’s lifecycle. One of the main questions I hear when people learn about filters in ASP.NET Core is “Why do we need them?” If the filter pipeline is like a mini middleware pipeline, why not use a middleware component directly, instead of introducing the filter con- cept? That’s an excellent point, which I’ll tackle in the next section. 13.1.2 Filters or middleware: which should you choose? The filter pipeline is similar to the middleware pipeline in many ways, but there are a number of subtle differences that you should consider when deciding which approach to use. When considering the similarities, they have three main parallels: Authorization filters Model binding / validation Resource filters Action filters Exception filters Result filters Action invocation IActionResult Execution Authorization filters run first for every MVC request. If the request isn’t authorized, it will short-circuit the pipeline. Resource filters run next before model binding runs. Action filters run before and after the action method executes. As they run after model binding, you can use them to customize the arguments passed to the action. If an exception occurs somewhere in the pipeline, the ExceptionFilter will execute. If the action method returns an IActionResult, the result filters will execute before and after the IActionResult is executed. Resource filters also run at the end of the pipeline after the result has been executed. Request Response Figure 13.2 The MVC filter pipeline, including the five different filter stages. Some filter stages (resource, action, and result) run twice, before and after the remainder of the pipeline.
  • 402.
    374 CHAPTER 13The MVC filter pipeline  Requests pass through a middleware component on the way “in” and responses pass through again on the way “out.” Resource, action, and result filters are also two- way, though authorization and exception filters run only once for a request.  Middleware can short-circuit a request by returning a response, instead of passing it on to later middleware. Filters can also short-circuit the MVC filter pipeline by return- ing a response.  Middleware is often used for crosscutting application concerns, such as logging, perfor- mance profiling, and exception handling. Filters also lend themselves to crosscut- ting concerns. In contrast, there are three main differences between middleware and filters:  Middleware can run for all requests; filters will only run for requests that reach MvcMiddleware.  Filters have access to MVC constructs such as ModelState and IActionResults. Middleware, in general, is independent from MVC, so can’t use these concepts.  Filters can be easily applied to a subset of requests; for example, all actions on a sin- gle controller. Middleware doesn’t have this concept as a first-class idea (though you could achieve something similar with custom middleware components). That’s all well and good, but how should we interpret these differences? When should we choose one over the other? I like to think of middleware versus filters as a question of specificity. Middleware is the more general concept, so has the wider reach. If the functionality you need has no MVC- specific requirements, then you should use a middleware component. Exception han- dling is a great example of this; exceptions could happen anywhere in your application, and you need to handle them, so using exception-handling middleware makes sense. On the other hand, if you do need access to MVC constructs, or you want to behave differently for some MVC actions, then you should consider using a filter. Ironically, this can also be applied to exception handling. You don’t want exceptions in your Web API controllers to automatically generate HTML error pages when the client is expecting JSON. Instead, you could use an exception filter on your Web API actions to render the exception to JSON, while letting the exception-handling middleware catch errors from elsewhere in your app. TIP Where possible, consider using middleware for crosscutting concerns. Use filters when you need different behavior for different action methods, or where the functionality relies on MVC concepts like ModelState validation. The middleware versus filters argument is a subtle one, and it doesn’t matter which you choose as long as it works for you. You can even use middleware components inside the filter pipeline as filters, but that’s outside the scope of this book.1 1 The “middleware as filters” feature was introduced in ASP.NET Core 1.1. If you’re interested, I wrote an intro- duction to the feature here: http://mng.bz/Mg97.
  • 403.
    375 Understanding filters andwhen to use them TIP The middleware as filters feature was introduced in ASP.NET Core 1.1 and is also available in 2.0. The canonical use case is for localizing requests to multiple languages. I have a blog series on how to use the feature here: http://mng.bz/a6Rb. Filters can be a little abstract in isolation, so in the next section, we’ll look at some code and learn how to write a custom filter in ASP.NET Core. 13.1.3 Creating a simple filter In this section, I show how to create your first filters; in section 13.1.4, you’ll see how to apply them to your MVC actions. We’ll start small, creating filters that just write to the console, but in section 13.2, we’ll look at some more practical examples and dis- cuss some of their nuances. You implement an MVC filter for a given stage by implementing one of a pair of interfaces—one synchronous (sync), one asynchronous (async):  Authorization filters—IAuthorizationFilter or IAsyncAuthorizationFilter  Resource filters—IResourceFilter or IAsyncResourceFilter  Action filters—IActionFilter or IAsyncActionFilter  Exception filters—IExceptionFilter or IAsyncExceptionFilter  Result filters—IResultFilter or IAsyncResultFilter You can use any POCO class to implement a filter, but you’ll typically implement them as C# attributes, which you can use to decorate your MVC controllers and actions, as you’ll see in section 13.1.4. You can achieve the same results with either the sync or async interface, so which you choose should depend on whether any services you call in the filter require async support. NOTE You should implement either the sync interface or the async interface, not both. If you implement both, then only the async interface will be used. Listing 13.1 shows a resource filter that implements IResourceFilter and writes to the console when it executes. The OnResourceExecuting method is called when a request first reaches the resource filter stage of the filter pipeline. In contrast, the OnResourceExecuted method is called after the rest of the pipeline has executed; after model binding, action execution, result execution, and all intermediate filters have run. public class LogResourceFilter : Attribute, IResourceFilter { public void OnResourceExecuting( ResourceExecutingContext context) { Console.WriteLine("Executing!"); } Listing 13.1 Example resource filter implementing IResourceFilter Executed at the start of the pipeline, after authorization filters. The context contains the HttpContext, routing details, and information about the current action.
  • 404.
    376 CHAPTER 13The MVC filter pipeline public void OnResourceExecuted( ResourceExecutedContext context) { Console.WriteLine("Executed”"); } } The interface methods are simple and are similar for each stage in the filter pipeline, passing a context object as a method parameter. Each of the two-method sync filters has an *Executing and an *Executed method. The type of the argument is different for each filter, but it contains all of the details for the filter pipeline. For example, the ResourceExecutingContext passed to the resource filter con- tains the HttpContext object itself, details about the route that selected this action, details about the action itself, and so on. Contexts for later filters will contain addi- tional details, such as the action method arguments for an action filter and the Model- State. The context object for the ResourceExecutedContext method is similar, but it also contains details about how the rest of the pipeline executed. You can check whether an unhandled exception occurred, you can see if another filter from the same stage short-circuited the pipeline, or you can see the IActionResult used to generate the response. These context objects are powerful and are the key to advanced filter behaviors like short-circuiting the pipeline and handling exceptions. We’ll make use of them in section 13.2 when creating more complex filter examples. The async version of the resource filter requires implementing a single method, as shown in listing 13.2. As for the sync version, you’re passed a ResourceExecuting- Context object as an argument, and you’re passed a delegate representing the remain- der of the filter pipeline. You must call this delegate (asynchronously) to execute the remainder of the pipeline, which will return an instance of ResourceExecutedContext. public class LogAsyncResourceFilter : Attribute, IAsyncResourceFilter { public async Task OnResourceExecutionAsync( ResourceExecutingContext context, ResourceExecutionDelegate next) { Console.WriteLine("Executing async!"); ResourceExecutedContext executedContext = await next(); Console.WriteLine("Executed async!"); } } Listing 13.2 Example resource filter implementing IAsyncResourceFilter Executed after model binding, action execution, and result execution. Contains additional context information, such as the IActionResult returned by the action Executed at the start of the pipeline, after authorization filters. You’re provided a delegate, which encapsulates the remainder of the MVC filter pipeline. Called before the rest of the pipeline executes. Called after the rest of the pipeline executes. Executes the rest of the pipeline and obtains a ResourceExecutedContext instance
  • 405.
    377 Understanding filters andwhen to use them The sync and async filter implementations have subtle differences, but for most pur- poses they’re identical. I recommend implementing the sync version if possible, and only falling back to the async version if you need to. You’ve created a couple of filters now, so we should look at how to use them in the application. In the next section, we’ll tackle two specific issues: how to control which requests execute your new filters and how to control the order in which they execute. 13.1.4 Adding filters to your actions, your controllers, and globally In section 13.1.2, I discussed the similarities and differences between middleware and filters. One of those differences is that filters can be scoped to specific actions or con- trollers, so that they only run for certain requests. Alternatively, you can apply a filter globally, so that it runs for every MVC action. By adding filters in different ways, you can achieve a number of different results. Imagine you have a filter that forces you to log in to view an action. How you add the filter to your app will significantly change your app’s behavior:  Apply the filter to a single action—Anonymous users could browse the app as normal, but if they tried to access the protected action, they would be forced to log in.  Apply the filter to a controller—Anonymous users could access actions from other controllers, but accessing any action on the protected controller would force them to log in.  Apply the filter globally—Users couldn’t use the app without logging in. Any attempt to access an action would redirect the user to the login page. NOTE ASP.NET Core comes with just such a filter out of the box, Authorize- Filter. I’ll discuss this filter in section 13.2.1, and you’ll be seeing a lot more of it in chapter 15. As I described in the previous section, you normally create filters as attributes, and for good reason—it makes applying them to MVC controllers and actions easy. In this sec- tion, you’ll see how to apply LogResourceFilter from listing 13.1 to an action, a con- troller, and globally. The level at which the filter applies is called its scope. DEFINITION The scope of a filter refers to how many different actions it applies to. A filter can be scoped to the action method, to the controller, or globally. You’ll start at the most specific scope—applying filters to a single action. The follow- ing listing shows an example of an MVC controller that has two action methods: one with LogResourceFilter and one without. public class HomeController : Controller { [LogResourceFilter] public IActionResult Index() { return View(); } Listing 13.3 Applying filters to an action method LogResourceFilter will run as part of the pipeline when executing this action.
  • 406.
    378 CHAPTER 13The MVC filter pipeline public IActionResult About() { return View(); } } Alternatively, if you want to apply the same filter to every action method, you could add the attribute at the controller scope, as in the next listing. Every action method in the controller will use LogResourceFilter, without having to specifically decorate each method: [LogResourceFilter] public class HomeController : Controller { public IActionResult Index () { return View(); } public IActionResult SendInvoice() { return View(); } } Filters you apply as attributes to controllers and actions are automatically discovered by MvcMiddleware when your application starts up. For common attributes, you can go one step further and apply filters globally, without having to decorate individual controllers. You add global filters in a different way to controller- or action-scoped filters—by adding a filter directly to the MVC services, when configuring MVC in Startup. This listing shows three equivalent ways to add a globally scoped filter. public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddMvc(options => { options.Filters.Add(new LogResourceFilter()); options.Filters.Add(typeof(LogAsyncResourceFilter)); options.Filters.Add<LogAsyncResourceFilter>(); }); } } Listing 13.4 Applying filters to a controller Listing 13.5 Applying filters globally to an application This action method has no filters at the action level. The LogResourceFilter Is added to every action on the controller. Every action in the controller is decorated with the filter. Adds filters using the MvcOptions object Alternatively, the framework can create a global filter using a generic type parameter. . . . or pass in the Type of the filter and let the framework create it. You can pass an instance of the filter directly. . .
  • 407.
    379 Understanding filters andwhen to use them With three different scopes in play, you’ll often find action methods that have multi- ple filters applied to them, some applied directly to the action method, and others inherited from the controller or globally. The question then becomes: Which filters run first? 13.1.5 Understanding the order of filter execution You’ve seen that the filter pipeline contains five different stages, one for each type of filter. These stages always run in the fixed order I described in section 13.1.1. But within each stage, you can also have multiple filters of the same type (for example, multiple resource filters) that are part of a single action method’s pipeline. These could all have multiple scopes, depending on how you added them, as you saw in the last section. In this section, we’re thinking about the order of filters within a given stage and how scope affects this. We’ll start by looking at the default order, then move on to ways to customize the order to your own requirements. THE DEFAULT SCOPE EXECUTION ORDER When thinking about filter ordering, it’s important to remember that resource, action, and result filters implement two methods: an *Executing before method and an *Executed after method. The order in which each method executes depends on the scope of the filter, as shown in figure 13.3 for the resource filter stage. By default, filters execute from the broadest scope (global) to the narrowest (action) when running the *Executing method for each stage. The *Executed meth- ods run in reverse order, from the narrowest scope (action) to the broadest (global). You’ll often find you need a bit more control over this order, especially if you have, for example, multiple action filters applied at the same scope. The MVC framework caters to this requirement by way of the IOrderedFilter interface. Global filters run first in each filter stage. Base controller filter Global scope filter Controller scope filter Action scope filter The scope of the filters determines the order in which they run within a single stage. Filters scoped to the controller level run after global filters and before action filters. Filters can also be applied to base Controller classes. Base class scoped filters run later than filters on the derived controllers. Filters scoped to the action level run last in a filter stage. Figure 13.3 The default filter ordering within a given stage, based on the scope of the filters. For the *Executing method, globally scoped filters run first, followed by controller-scoped, and finally, action-scoped filters. For the *Executed method, the filters run in reverse order.
  • 408.
    380 CHAPTER 13The MVC filter pipeline OVERRIDING THE DEFAULT ORDER OF FILTER EXECUTION WITH IORDEREDFILTER Filters are great for extracting crosscutting concerns from your controller actions, but if you have multiple filters applied to an action, then you’ll often need to control the precise order in which they execute. Scope can get you some of the way, but for those other cases, you can implement IOrderFilter. This interface consists of a single property, Order: public interface IOrderedFilter { int Order { get; } } You can implement this property in your filters to set the order in which they execute. MvcMiddleware will order the filters for a stage based on this value first, from lowest to highest, and use the default scope order to handle ties, as shown in figure 13.4. The filters for Order = -1 execute first, as they have the lowest Order value. The controller filter executes first because it has a broader scope than the action filter. The filters with Order=0 execute next, in the default scope order, as shown in figure 13.4. Finally, the filter with Order=1 executes. By default, if a filter doesn’t implement IOrderedFilter, it’s assumed to have Order = 0. All of the filters that ship as part of ASP.NET Core have Order = 0, so you can implement your own filters relative to these. This section has covered most of the technical details you need to use filters and create custom implementations for your own application. In the next section, you’ll Controller scope filter Order = -1 Controller scope filter Order = 0 The Order and scope of the filters determines the order in which they run within a single stage. Filters with the lowest Order number run first. Scope is used to decide tie breaks. Filters with the highest value of Order run last for the stage. Global scope filter Order = 0 Action scope filter Order = 0 Global scope filter Order = 1 By default, filters have an Order of 0. Action scope filter Order = -1 Figure 13.4 Controlling the filter order for a stage using the IOrderedFilter interface. Filters are ordered by the Order property first, and then by scope.
  • 409.
    381 Creating custom filtersfor your application see some of the built-in filters provided by ASP.NET Core, as well as some practical examples of filters you might want to use in your own applications. 13.2 Creating custom filters for your application ASP.NET Core includes a number of filters that you can use, but often, the most use- ful filters are the custom ones that are specific to your own apps. In this section, you’ll work through each of the five types of filters. I’ll explain in more detail what they’re for and when you should use them. I’ll point out examples of these filters that are part of ASP.NET Core itself and you’ll see how to create custom filters for an example application. To give you something realistic to work with, you’ll start with a Web API controller for accessing the recipe application from chapter 12. This controller contains two actions: one for fetching a RecipeDetailViewModel and another for updating a Recipe with new values. This listing shows your starting point for this chapter, includ- ing both of the action methods. [Route("api/recipe")] public class RecipeApiController : Controller { private const bool IsEnabled = true; public RecipeService _service; public RecipeApiController(RecipeService service) { _service = service; } [HttpGet("{id}")] public IActionResult Get(int id) { if (!IsEnabled) { return BadRequest(); } try { if (!_service.DoesRecipeExist(id)) { return NotFound(); } var detail = _service.GetRecipeDetail(id); Response.GetTypedHeaders().LastModified = detail.LastModified; return Ok(detail); } catch (Exception ex) { return GetErrorResponse(ex); } } Listing 13.6 Recipe Web API controller before refactoring to use filters These fields would be passed in as configuration values and are used to control access to actions. If the API isn’t enabled, block further execution. If the requested Recipe doesn’t exist, return a 404 response. Fetch RecipeDetailViewModel. Sets the Last-Modified response header to the value in the model Returns the view model with a 200 response If an exception occurs, catch it, and return the error in an expected format, as a 500 error.
  • 410.
    382 CHAPTER 13The MVC filter pipeline [HttpPost("{id}")] public IActionResult Edit( int id, [FromBody] UpdateRecipeCommand command) { if (!IsEnabled) { return BadRequest(); } try { if (!ModelState.IsValid) { return BadRequest(ModelState); } if (!_service.DoesRecipeExist(id)) { return NotFound(); } _service.UpdateRecipe(command); return Ok(); } catch (Exception ex) { return GetErrorResponse(ex); } } private static IActionResult GetErrorResponse(Exception ex) { var error = new { Success = false, Errors = new[] { ex.Message } }; return new ObjectResult(error) { StatusCode = 500 }; } } These action methods currently have a lot of code to them, which hides the intent of each action. There’s also quite a lot of duplication between the methods, such as checking that the Recipe entity exists, and formatting exceptions. In this section, you’re going to refactor this controller to use filters for all the code in the methods that’s unrelated to the intent of each action. By the end of the chapter, you’ll have a much simpler controller that’s far easier to understand, as shown here. If the API isn’t enabled, block further execution. Validates the binding model and returns a 400 response if there are errors If the requested Recipe doesn’t exist, return a 404 response. Updates the Recipe from the command and returns a 200 response If an exception occurs, catch it, and return the error in an expected format, as a 500 error.
  • 411.
    383 Creating custom filtersfor your application [Route("api/recipe")] [ValidateModel, HandleException, FeatureEnabled(IsEnabled = true)] public class RecipeApiController : Controller { public RecipeService _service; public RecipeApiController(RecipeService service) { _service = service; } [HttpGet("{id}"), EnsureRecipeExists, AddLastModifedHeader] public IActionResult Get(int id) { var detail = _service.GetRecipeDetail(id); return Ok(detail); } [HttpPost("{id}"), EnsureRecipeExists] public IActionResult Edit( int id, [FromBody] UpdateRecipeCommand command) { _service.UpdateRecipe(command); return Ok(); } } I think you'll have to agree, the controller in listing 13.7 is much easier to read! In this section, you’ll refactor the controller bit by bit, removing crosscutting code to get to something more manageable. All the filters I’ll create in this section will use the sync filter interfaces—I’ll leave it as an exercise for the reader to create their async coun- terparts. You’ll start by looking at authorization filters and how they relate to security in ASP.NET Core. 13.2.1 Authorization filters: protecting your APIs Authentication and authorization are related, fundamental concepts in security that we’ll be looking at in detail in chapters 14 and 15. DEFINITION Authentication is concerned with determining who made a request. Authorization is concerned with what a user is allowed to access. Authorization filters run first in the MVC filter pipeline, before any other filters. They control access to the action method by immediately short-circuiting the pipeline when a request doesn’t meet the necessary requirements. ASP.NET Core has a built-in authorization framework that you should use when you need to protect your MVC application or your Web APIs. You can configure this framework with custom policies that let you finely control access to your actions. Listing 13.7 Recipe Web API controller after refactoring to use filters The filters encapsulate the majority of logic common to multiple action methods. Placing filters at the action level limits them to a single action. The intent of the action, return a Recipe view model, is much clearer. Placing filters at the action level can be used to control the order in which they execute. The intent of the action, update a Recipe, is much clearer.
  • 412.
    384 CHAPTER 13The MVC filter pipeline TIP It’s possible to write your own authorization filters by implementing IAuthorizationFilter or IAsyncAuthorizationFilter, but I strongly advise against it. The ASP.NET Core authorization framework is highly configurable and should meet all your needs. At the heart of the ASP.NET Core authorization framework is an Authorization filter, AuthorizeFilter, which you can add to the filter pipeline by decorating your actions or controllers with the [Authorize] attribute. In its simplest form, adding the [Authorize] attribute to an action, as in the following listing, means the request must be made by an authenticated user to be allowed to continue. If you’re not logged in, it will short-circuit the pipeline, returning a 401 Unauthorized response to the browser. public class RecipeApiController : Controller { public IActionResult Get(int id) { // method body } [Authorize] public IActionResult Edit( int id, [FromBody] UpdateRecipeCommand command) { // method body } } As with all filters, you can apply the [Authorize] attribute at the controller level to protect all the actions on a controller, or even globally, to protect every method in your app. NOTE We’ll explore authorization in detail in chapter 15, including how to add more detailed requirements, so that only specific sets of users can exe- cute an action. The next filters in the pipeline are resource filters. In the next section, you’ll extract some of the common code from RecipeApiController and see how easy it is to create a short-circuiting filter. 13.2.2 Resource filters: short-circuiting your action methods Resource filters are the first general-purpose filters in the MVC filter pipeline. In sec- tion 13.1.3, you saw minimal examples of both sync and async resource filters, which logged to the console. In your own apps, you can use resource filters for a wide range of purposes, thanks to the fact they execute so early (and late) in the filter pipeline. Listing 13.8 Adding [Authorize] to an action method The Get method has no [Authorize] attribute, so can be executed by anyone. The Edit method can only be executed if you’re logged in. Adds the AuthorizeFilter to the MVC filter pipeline using [Authorize]
  • 413.
    385 Creating custom filtersfor your application The ASP.NET Core framework includes a few different implementations of resource filters you can use in your apps, for example:  ConsumesAttribute—Can be used to restrict the allowed formats an action method can accept. If your action is decorated with [Consumes("application/ json")] but the client sends the request as XML, then the resource filter will short-circuit the pipeline and return a 415 Unsupported Media Type response.  DisableFormValueModelBindingAttribute—This filter prevents model bind- ing from binding to form data in the request body. This can be useful if you know an action method will be handling large file uploads that you need to manage manually yourself. The resource filters run before model binding, so you can disable the model binding for a single action in this way.2 resource filters are useful when you want to ensure the filter runs early in the pipeline, before model binding. They provide an early hook into the pipeline for your logic, so you can quickly short-circuit the request if you need to. Look back at listing 13.6 and see if you can refactor any of the code into a Resource filter. One candidate line appears at the start of both the Get and Edit methods: if (!IsEnabled) { return BadRequest(); } This line of code is a feature toggle that you can use to disable the availability of the whole API, based on the IsEnabled field. In practice, you’d probably load the IsEnabled field from a database or configuration file so you could control the avail- ability dynamically at runtime but, for this example, I’m using a hardcoded value. This piece of code is self-contained, crosscutting logic, which is somewhat tangen- tial to the main action method intent—a perfect candidate for a filter. You want to execute the feature toggle early in the pipeline, before any other logic, so a resource filter makes sense. TIP Technically, you could also use an Authorization filter for this example, but I’m following my own advice of “Don’t write your own Authorization filters!” The next listing shows an implementation of FeatureEnabledAttribute, which extracts the logic from the action methods and moves it into the filter. I’ve also exposed the IsEnabled field as a property on the filter. public class FeatureEnabledAttribute : Attribute, IResourceFilter { public bool IsEnabled { get; set; } 2 For details on handling file uploads, see http://mng.bz/2rrk. Listing 13.9 The FeatureEnabledAttribute resource filter Defines whether the feature is enabled
  • 414.
    386 CHAPTER 13The MVC filter pipeline public void OnResourceExecuting( ResourceExecutingContext context) { if (!IsEnabled) { context.Result = new BadRequestResult(); } } public void OnResourceExecuted( ResourceExecutedContext context) { } } This simple resource filter demonstrates a number of important concepts, which are applicable to most filter types:  The filter is an attribute as well as a filter. This lets you decorate your controller and action methods with it using [FeatureEnabled(IsEnabled = true)].  The filter interface consists of two methods—*Executing that runs before model binding and *Executed that runs after the result has been executed. You must implement both, even if you only need one for your use case.  The filter execution methods provide a context object. This provides access to, among other things, the HttpContext for the request and metadata about the action method the middleware will execute.  To short-circuit the pipeline, set the context.Result property to IAction- Result. The MVC pipeline will execute this result to generate the response, bypassing any remaining filters in the pipeline and the action method itself. In this example, if the feature isn’t enabled, you bypass the pipeline by returning BadRequestResult, which will return a 400 error to the client. By moving this logic into the resource filter, you can remove it from your action meth- ods, and instead decorate the whole API controller with a simple attribute: [Route("api/recipe"), FeatureEnabled(IsEnabled = true)] public class RecipeApiController : Controller You’ve only extracted two lines of code from your action methods so far, but you’re on the right track. In the next section, we’ll move on to Action filters and extract two more filters from the action method code. 13.2.3 Action filters: customizing model binding and action results Action filters run just after model binding, before the action method executes. Thanks to this positioning, action filters can access all the arguments that will be used to execute the action method, which makes them a powerful way of extracting com- mon logic out of your actions. On top of this, they also run just after the action method has executed and can completely change or replace the IActionResult returned by the action if you want. They can even handle exceptions thrown in the action. Executes before model binding, early in the filter pipeline If the feature isn’t enabled, short-circuits the pipeline by setting the context.Result property Must be implemented to satisfy IResourceFilter, but not needed in this case.
  • 415.
    387 Creating custom filtersfor your application The ASP.NET Core framework includes a number of action filters out of the box. One of these commonly used filters is ResponseCacheFilter, which sets HTTP cach- ing headers on your action-method responses. TIP Caching is a broad topic that aims to improve the performance of an application over the naive approach. But caching can also make debugging issues difficult and may even be undesirable in some situations. Consequently, I often apply ResponseCacheFilter to my action methods to set HTTP cach- ing headers that disable caching! You can read about this and other approaches to caching in the docs at http://mng.bz/AMSQ. The real power of action filters comes when you build filters tailored to your own apps by extracting common code from your action methods. To demonstrate, I’m going to create two custom filters for RecipeApiController:  ValidateModelAttribute—This will return BadRequestResult if the model state indicates that the binding model is invalid and will short-circuit the action execution.  EnsureRecipeExistsAttribute—This will use each action method’s id argu- ment to validate that the requested Recipe entity exists before the action method runs. If the Recipe doesn’t exist, the filter will return NotFoundResult and will short-circuit the pipeline. As you saw in chapter 6, MvcMiddleware automatically validates your binding models for you before your actions execute, but it’s up to you to decide what to do about it. For Razor pages, this can be somewhat complicated by the need to build a view model, so you’ll often need some custom code in each action method to handle this. But for Web API controllers, it’s common to return a 400 Bad Request response containing a list of the errors, as shown in figure 13.5. The request is POSTed to the RecipeApiController. The request body is bound to the action method’s binding model. A 400 Bad Request response is sent, indicating that validation failed for the request. The response body is sent as a JSON object, providing the name of each field and the error. Figure 13.5 Posting data to a Web API using Postman. The data is bound to the action method’s binding model and validated. If validation fails, it’s common to return a 400 BadRequest response with a list of the validation errors.
  • 416.
    388 CHAPTER 13The MVC filter pipeline It’s likely that all of your Web API controllers will use this approach, so an action filter that automatically validates your binding models is a perfect fit. It’s normally the first filter I create for any new project. Listing 13.10 shows a simple implementation that you can use in your own apps.3 public class ValidateModelAttribute : ActionFilterAttribute { public override void OnActionExecuting( ActionExecutingContext context) { if (!context.ModelState.IsValid) { context.Result = new BadRequestObjectResult(context.ModelState); } } } This attribute is self-explanatory and follows a similar pattern to the resource filter in section 13.2.2, but with a few interesting points:  I have derived from the abstract ActionFilterAttribute. This class imple- ments IActionFilter and IResultFilter, as well as their async counterparts, so you can override the methods you need as appropriate. This avoids needing to add an unused OnActionExecuted() method, but is entirely optional and a matter of preference.  Action filters run after model binding has taken place, so context.ModelState contains the validation errors if validation failed.  Setting the Result property on context short-circuits the pipeline. But, due to the position of the action filter stage, only the action method execution and later action filters are bypassed; all the other stages of the pipeline run as though the action had executed as normal. If you apply this action filter to your RecipeApiController, you can remove if (!ModelState.IsValid) { return BadRequest(ModelState); } Listing 13.10 The action filter for validating ModelState 3 ASP.NET Core 2.1 added this behavior by default for controllers decorated with the [ApiController] attri- bute. For details, see https://blogs.msdn.microsoft.com/webdev/2018/02/27/asp-net-core-2-1-web-apis/. For convenience, you derive from the ActionFilterAttribute base class. Overrides the Executing method to run the filter before the Action executes Model binding and validation have already run at this point, so you can check the state. If the model isn’t valid, set the Result property; this short- circuits the action execution.
  • 417.
    389 Creating custom filtersfor your application from the start of both the action methods, as it will run automatically in the filter pipe- line. You’ll use a similar approach to remove the duplicate code checking whether the id provided as an argument to the action methods corresponds to an existing Recipe entity. This listing shows the EnsureRecipeExistsAttribute action filter. This uses an instance of RecipeService to check whether the Recipe exists and returns a 404 Not Found if it doesn’t. public class EnsureRecipeExistsAtribute : ActionFilterAttribute { public override void OnActionExecuting( ActionExecutingContext context) { var service = (RecipeService) context.HttpContext .RequestServices.GetService(typeof(RecipeService)); var recipeId = (int) context.ActionArguments["id"]; if (!service.DoesRecipeExist(recipeId)) { context.Result = new NotFoundResult(); } } } As before, you’ve derived from ActionFilterAttribute for simplicity and overridden the OnActionExecuting method. The main functionality of the filter relies on the DoesRecipeExist() method of RecipeService, so the first step is to obtain an instance of RecipeService. The context parameter provides access to the Http- Context for the request, which in turn lets you access the DI container and use RequestServices.GetService() to return an instance of RecipeService. WARNING This technique for obtaining dependencies is known as service loca- tion and is generally considered an antipattern.4 In section 13.4, I’ll show a much better way to use the DI container to inject dependencies into your filters. As well as RecipeService, the other piece of information you need is the id argument of the Get and Edit action methods. In action filters, model binding has already occurred, so the arguments that the MVC middleware will use to execute the action method are already known and are exposed on context.ActionArguments. The action arguments are exposed as Dictionary<string, object>, so you can obtain the id parameter using the "id" string key. Remember to cast the object to the correct type. Listing 13.11 An action filter to check whether a Recipe exists 4 For a detailed discussion on DI patterns and antipatterns, see Dependency Injection in .NET by Mark Seemann (Manning, 2012) https://livebook.manning.com/#!/book/dependency-injection-in-dot-net/chapter-5/. Fetches an instance of RecipeService from the DI container Retrieves the id parameter that will be passed to action method when it executes Checks whether a Recipe entity with the given RecipeId exists If it doesn’t exist, returns a 404 Not Found result and short-circuits the pipeline.
  • 418.
    390 CHAPTER 13The MVC filter pipeline TIP Whenever I see magic strings like this, I always like to try to replace them by using the nameof operator. Unfortunately, nameof won’t work for method arguments like this, so be careful when refactoring your code. I suggest explicitly applying the action filter to the action method (instead of globally, or to a controller) to remind you about that implicit coupling. With RecipeService and id in place, it’s a case of checking whether the identifier cor- responds to a Recipe entity and, if not, setting context.Result to NotFoundResult. This will short-circuit the pipeline and bypass the action method altogether. NOTE Remember, you can have multiple action filters running in a single stage. Short-circuiting the pipeline by setting context.Result will prevent later filters in the stage from running, as well as bypassing the action method execution. Before we move on, it’s worth mentioning a special case for action filters. The Controller base class implements IActionFilter and IAsyncActionFilter itself. If you find yourself creating an action filter for a single controller and you want to apply it to every action in that controller, then you can override the appropriate methods on your controller. public class HomeController : Controller { public override void OnActionExecuting( ActionExecutingContext context) { } public override void OnActionExecuted( ActionExecutedContext context) { } } If you override these methods on your controller, they’ll run in the action filter stage of the filter pipeline for every action on the controller. The OnActionExecuting Controller method runs before any other action filters, regardless of ordering or scope, and the OnActionExecuted method runs after all other filters. TIP The controller implementation can be useful in some cases, but you can’t control the ordering related to other filters. Personally, I generally pre- fer to break logic into explicit, declarative filter attributes but, as always, the choice is yours. With the resource and action filters complete, your controller is looking much tidier, but there’s one aspect in particular that would be nice to remove: the exception han- dling. In the next section, we’ll look at how to create a custom exception filter for your controller, and why you might want to do this instead of using exception- handling middleware. Listing 13.12 Overriding action filter methods directly on a Controller Derives from the Controller base class Runs before any other action filters for every action in the controller Runs after all other action filters for every action in the controller
  • 419.
    391 Creating custom filtersfor your application 13.2.4 Exception filters: custom exception handling for your action methods In chapter 3, I went into some depth about types of error-handling middleware you can add to your apps. These let you catch exceptions thrown from any later middle- ware and handle them appropriately. If you’re using exception-handling middleware, you may be wondering why we need exception filters at all! The answer to this is pretty much the same as I outlined in section 13.1.2: filters are great for crosscutting concerns, when you need behavior that’s either specific to MVC or should only apply to certain routes. Both of these can apply in exception handling. Exception filters run within Mvc- Middleware, so they have access to the context in which the error occurred, such as the action that was executing. This can be useful for logging additional details when errors occur, such as the action parameters that caused the error. WARNING If you use exception filters to record action method arguments, make sure you’re not storing sensitive data, such as passwords or credit card details, in your logs. You can also use exception filters to handle errors from different routes in different ways. Imagine you have both MVC and Web API controllers in your app, as we do in the recipe app. What happens when an exception is thrown by an MVC controller? As you saw in chapter 3, the exception travels back up the middleware pipeline, and is caught by exception-handler middleware. The exception-handler middleware will re-execute the pipeline and generate an MVC error page. That’s great for your MVC controllers, but what about exceptions in your Web API controllers? If your API throws an exception, and consequently returns HTML gener- ated by the exception-handler middleware, that’s going to break a client who has called the API expecting a JSON response! Instead, exception filters let you handle the exception inside MvcMiddleware and generate an appropriate response body. The exception handler middleware only intercepts errors without a body, so it will let the modified Web API response pass untouched. Exception filters can catch exceptions from more than your action methods. They’ll run if an exception occurs in MvcMiddleware  During model binding or validation  When the action method is executing  When an action filter is executing You should note that exception filters won’t catch exceptions thrown in any filters other than action filters, so it’s important your resource and result filters don’t throw exceptions. Similarly, they won’t catch exceptions thrown when executing IAction- Result itself.
  • 420.
    392 CHAPTER 13The MVC filter pipeline Now that you know why you might want an exception filter, go ahead and imple- ment one for RecipeApiController, as shown next. This lets you safely remove the try-catch block from your action methods, knowing that your filter will catch any errors. public class HandleExceptionAttribute : ExceptionFilterAttribute { public override void OnException(ExceptionContext context) { var error = new { Success = false, Errors = new [] { context.Exception.Message } }; context.Result = new ObjectResult(error) { StatusCode = 500 }; context.ExceptionHandled = true; } } It’s quite common to have one or two different exception filters in your application, one for your MVC controllers and one for your Web API controllers, but they’re not always necessary. If you can handle all the exceptions in your application with a single piece of middleware, then ditch the exception filters and go with that instead. You’re coming to the last type of filter now, result filters, and with it, you’re almost done refactoring your RecipeApiController. Custom result filters tend to be rela- tively rare in the apps I’ve written, but they have their uses, as you’ll see. 13.2.5 Result filters: customizing action results before they execute If everything runs successfully in the pipeline, and there’s no short-circuiting, then the next stage of the pipeline after action filters are result filters. These run just before and after the IActionResult returned by the Action method (or action filters) is executed. WARNING If the pipeline is short-circuited by setting context.Result, the result filter stage won’t be run, but IActionResult will still be executed to generate the response. The one exception to this rule is action filters—these only short-circuit the action execution, as you saw in figure 13.2, and so result filters run as normal, as though the action itself generated the response. Listing 13.13 The HandleExceptionAttribute exception filter ExceptionFilterAttibute is an abstract base class that implements IExceptionFilter. There’s only a single method to override for IExceptionFilter. Building a custom object to return in the response Creates an ObjectResult to serialize the error object and to set the response status code Marks the exception as handled to prevent it propagating out of MvcMiddleware
  • 421.
    393 Creating custom filtersfor your application Result filters run immediately after action filters, so many of their use cases are similar, but you typically use result filters to customize the way the IActionResult executes. For example, ASP.NET Core has several result filters built into its framework:  ProducesAttribute—This forces the Web API result to be serialized to a specific output format. For example, decorating your action method with [Produces("application/xml")] forces the formatters to try to format the response as XML, even if the client doesn’t list XML in its Accept header.  FormatFilterAttribute—Decorating an action method with this filter tells the formatter to look for a route value or query string parameter called format, and to use that to determine the output format. For example, you could call /api/recipe/11?format=json and FormatFilter will format the response as JSON, or call api/recipe/11?format=xml and get the response as XML.5 As well as controlling the output formatters, you can use result filters to make any last- minute adjustments before IActionResult is executed and the response is generated. As an example of the kind of flexibility available to you, in the following listing I demonstrate setting the Last-Modified header, based on the object returned from the action. This is a somewhat contrived example—it’s specific enough to a single action that it doesn’t warrant being moved to a result filter—but, hopefully, you get the idea! public class AddLastModifedHeaderAttribute : ResultFilterAttribute { public override void OnResultExecuting( ResultExecutingContext context) { if (context.Result is OkObjectResult result && result.Value is RecipeDetailViewModel detail) { var viewModelDate = detail.LastModified; context.HttpContext.Response .GetTypedHeaders().LastModified = viewModelDate; } } } I’ve used another helper base class here, ResultFilterAttribute, so you only need to override a single method to implement the filter. Fetch the current IActionResult, 5 Remember, you need to explicitly configure the XML formatters if you want to serialize to XML. For details, see http://mng.bz/0J2z. Listing 13.14 Setting a response header in a result filter ResultFilterAttribute provides a useful base class you can override You could also override the Executed method, but the response would already be sent by then. Checks whether the action result returned a 200 Ok result with a view model Checks whether the view model type is RecipeDetailView Model . . . . . . if it is, fetches the LastModified field and sets the Last-Modified header in the response
  • 422.
    394 CHAPTER 13The MVC filter pipeline exposed on context.Result, and check that it’s OkObjectResult with RecipeDetail- ViewModel. If it is, then fetch the LastModified field from the view model and add a Last-Modified header to the response. TIP GetTypedHeaders() is an extension method that provides strongly typed access to request and response headers. It takes care of parsing and format- ting the values for you. You can find it in the Microsoft.AspNetCore.Http namespace. As with resource and action filters, result filters can implement a method that runs after the result has been executed, OnResultExecuted. You can use this method, for example, to inspect exceptions that happened during the execution of IAction- Result. WARNING Generally, you can’t modify the response in the OnResult- Executed method, as MvcMiddleware may have already started streaming the response to the client. That brings us to the end of this detailed look at each of the filters in the MVC pipe- line. Looking back and comparing listings 13.6 and 13.7, you can see filters allowed us to refactor the controllers and make the intent of each action method much clearer. Writing your code in this way makes it easier to reason about, as each filter and action has a single responsibility. In the next section, I’ll take a slight detour into exactly what happens when you short-circuit a filter. I’ve described how to do this, by setting the context.Result prop- erty on a filter, but I haven’t yet described exactly what happens. For example, what if there are multiple filters in the stage when it’s short-circuited? Do those still run? 13.3 Understanding pipeline short-circuiting A brief warning: the topic of filter short-circuiting can be a little confusing. Unlike middleware short-circuiting, which is cut-and-dried, the MVC filter pipeline is a bit more nuanced. Luckily, you won’t often find you need to dig into it, but when you do, you’ll be glad of the detail. You short-circuit the authorization, resource, action, and result filters by setting context.Result to IActionResult. Setting an action result in this way causes some, or all, of the remaining pipeline to by bypassed. But the filter pipeline isn’t entirely linear, as you saw in figure 13.2, so short-circuiting doesn’t always do an about-face back down the pipeline. For example, short-circuited action filters only bypass action method execution—the result filters and result execution stages still run. The other difficultly is what happens if you have more than one type of filter. Let’s say you have three resource filters executing in a pipeline. What happens if the second filter causes a short circuit? Any remaining filters are bypassed, but the first resource fil- ter has already run its *Executing command, as shown in figure 13.6. This earlier filter gets to run its *Executed command too, with context.Cancelled = true, indicating that a filter in that stage (the resource filter stage) short-circuited the pipeline.
  • 423.
    395 Understanding pipeline short-circuiting Understandingwhich other filters will run when you short-circuit a filter can be some- what of a chore, but I’ve summarized each filter in table 13.1. You’ll also find it useful to refer to figure 13.2 to visualize the shape of the pipeline when thinking about short- circuits. The most interesting point here is that short-circuiting an action filter doesn’t short- circuit much of the pipeline at all. In fact, it only bypasses later action filters and the action method execution itself. By primarily building action filters, you can ensure that other filters, such as result filters that define the output format, run as usual, even when your action filters short-circuit. Table 13.1 The effect of short-circuiting filters on filter-pipeline execution Filter type How to short-circuit? What else runs? Authorization filters Set context.Result Nothing, the pipeline is immediately halted. Resource filters Set context.Result Resource-filter *Executed functions from earlier filters run with context.Cancelled = true. Action filters Set context.Result Only bypasses action method execution. Action fil- ters earlier in the pipeline run their *Executed methods with context.Cancelled = true, then result filters, result execution, and resource filters’ *Executed methods all run as normal. Exception filters Set context.Result and Exception.Handled = true All resource-filter *Executed functions run. Result filters Set context.Cancelled = true Result filters earlier in the pipeline run their *Executed functions with context.Cancelled = true. All resource- filter *Executed functions run as normal. Filter1 Filter 3 Filter2 OnResourceExecuting OnResourceExecuting OnResourceExecuted 1 1 . Resource filter runs its Executing function. ∗ 2. Resource filter 2 runs its Executing function and ∗ short-circuits the pipeline by setting context.Result. context.Result 3. Resource filter 3 (or the rest of the pipeline) never runs. 5. Resource filter runs its 1 Executed function. Cancelled ∗ is set to true, indicating the pipeline was cancelled. 4. Resource filter 2 doesn’t run its Executed function as ∗ it short-circuited the pipeline. context.cancelled=true Figure 13.6 The effect of short-circuiting a resource filter on other resource filters in that stage. Later filters in the stage won’t run at all, but earlier filters run their OnResourceExecuted function.
  • 424.
    396 CHAPTER 13The MVC filter pipeline The last thing I’d like to talk about in this chapter is how to use DI with your filters. You saw in chapter 10 that DI is integral to ASP.NET Core, and in the next section, you’ll see how to design your filters so that the framework can inject service depen- dencies into them for you. 13.4 Using dependency injection with filter attributes The previous version of ASP.NET used filters, but they suffered from one problem in particular: it was hard to use services from them. This was a fundamental issue with implementing them as attributes that you decorate your actions with. C# attributes don’t let you pass dependencies into their constructors (other than constant values), and they’re created as singletons, so there’s only a single instance for the lifetime of your app. In ASP.NET Core, this limitation is still there in general, in that filters are typically created as attributes that you add to your controller classes and action methods. What happens if you need to access a transient or scoped service from inside the singleton attribute? Listing 13.11 showed one way of doing this, using a pseudo service locator pattern to reach into the DI container and pluck out RecipeService at runtime. This works but is generally frowned upon as a pattern, in favor of proper DI. How can you add DI to your filters? The key is to split the filter into two. Instead of creating a class that’s both an attri- bute and a filter, create a filter class that contains the functionality and an attribute that tells MvcMiddleware when and where to use the filter. Let’s apply this to the action filter from listing 13.11. Previously, I derived from ActionFilterAttribute and obtained an instance of RecipeService from the context passed to the method. In the following listing, I show two classes, EnsureRecipeExists- Filter and EnsureRecipeExistsAttribute. The filter class is responsible for the func- tionality and takes in RecipeService as a constructor dependency. public class EnsureRecipeExistsFilter : IActionFilter { private readonly RecipeService _service; public EnsureRecipeExistsFilter(RecipeService service) { _service = service; } public void OnActionExecuting(ActionExecutingContext context) { var recipeId = (int) context.ActionArguments["id"]; if (!_service.DoesRecipeExist(recipeId)) { context.Result = new NotFoundResult(); } } Listing 13.15 Using DI in a filter by not deriving from Attribute Doesn’t derive from an Attribute class RecipeService is injected into the constructor. The rest of the method remains the same.
  • 425.
    397 Using dependency injectionwith filter attributes public void OnActionExecuted(ActionExecutedContext context) { } } public class EnsureRecipeExistsAttribute : TypeFilterAttribute { public EnsureRecipeExistsAttribute() : base(typeof(EnsureRecipeExistsFilter)) {} } EnsureRecipeExistsFilter is a valid filter; you could use it on its own by adding it as a global filter (as global filters don’t need to be attributes). But you can’t use it directly by decorating controller classes and action methods, as it’s not an attribute. That’s where EnsureRecipeExistsAttribute comes in. You can decorate your methods with EnsureRecipeExistsAttribute instead. This attribute inherits from TypeFilterAttribute and passes the Type of filter to create as an argument to the base constructor. This attribute acts as a factory for EnsureRecipe- ExistsFilter by implementing IFilterFactory. When MvcMiddleware initially loads your app, it scans your actions and controllers, looking for filters and filter factories. It uses these to form a filter pipeline for every action in your app, as shown in figure 13.7. When an action decorated with EnsureRecipeExistsAttribute is called, Mvc- Middleware calls CreateInstance() on the attribute. This creates a new instance of EnsureRecipeExistsFilter and uses the DI container to populate its dependencies (RecipeService). You must implement the Executed action to satisfy the interface. Passes the type EnsureRecipeExistsFilter as an argument to the base TypeFilter constructor Derives from TypeFilter, which is used to fill dependencies using the DI container. public class HomeController { [ValidateModel] [EnsureRecipeExistsFilter] public IActionResult Index() { return View(); } } ValidateModelAttribute EnsureRecipeExistsFilter IFilterFactory Attributes that implement filter interfaces are added directly to the pipeline. The MvcMiddleware calls CreateInstance () on each IFilterFactory when a request is received to create a filter instance, which is added to the pipeline. CreateInstance() The MvcMiddleware scans your app looking for filters or attributes that implement IFilterFactory. Figure 13.7 The MvcMiddleware scans your app on startup to find both filters and attributes that implement IFilterFactory. At runtime, the middleware calls CreateInstance() to get an instance of the filter.
  • 426.
    398 CHAPTER 13The MVC filter pipeline By using this IFilterFactory approach, you get the best of both worlds; you can dec- orate your controllers and actions with attributes, and you can use DI in your filters. Out of the box, two similar classes provide this functionality, which have slightly differ- ent behaviors:  TypeFilterAttribute—Loads all of the filter’s dependencies from the DI con- tainer and uses them to create a new instance of the filter.  ServiceFilterAttribute—Loads the filter itself from the DI container. The DI container takes care of the service lifetime and building the dependency graph. Unfortunately, you also have to explicitly register your filter with the DI con- tainer in ConfigureServices in Startup: services.AddTransient<EnsureRecipeExistsFilter>(); Whether you choose to use TypeFilterAttribute or ServiceFilterAttribute is somewhat a matter of preference, and you can always implement a custom IFilter- Factory if you need to. The key takeaway is that you can now use DI in your filters. If you don’t need to use DI for a filter, then implement it as an attribute directly for simplicity. TIP I like to create my filters as a nested class of the attribute class when using this pattern. This keeps all the code nicely contained in a single file and indicates the relationship between the classes. That brings us to the end of this chapter on the filter pipeline. Filters are a somewhat advanced topic, in that they aren’t strictly necessary for building basic apps, but I find them extremely useful for ensuring my controller and action methods are simple and easy to understand. In the next chapter, we’ll take our first look at securing your app. We’ll discuss the difference between authentication and authorization, the concept of identity in ASP.NET Core, and how you can use the ASP.NET Core Identity system to let users register and log in to your app. Summary  The filter pipeline executes as part of MvcMiddleware after routing has selected an action method.  The filter pipeline consists of authorization filters, resource filters, action fil- ters, exception filters, and Result filters. Each filter type is grouped into a stage.  Resource, action, and result filters run twice in the pipeline: an *Executing method on the way in and an *Executed method on the way out.  Authorization and exception filters only run once as part of the pipeline; they don’t run after a response has been generated.  Each type of filter has both a sync and an async version. For example, resource filters can implement either the IResourceFilter interface or the IAsync- ResourceFilter interface. You should use the synchronous interface unless your filter needs to use asynchronous method calls.
  • 427.
    399 Summary  You canadd filters globally, at the controller level, or at the action level. This is called the scope of the filter.  Within a given stage, global-scoped filters run first, then controller-scoped, and finally, action-scoped.  You can override the default order by implementing the IOrderedFilter inter- face. Filters will run from lowest to highest Order and use scope to break ties.  Authorization filters run first in the pipeline and control access to APIs. ASP.NET Core includes an [Authorization] attribute that you can apply to action methods so that only logged-in users can execute the action.  Resource filters run after authorization filters, and again after a result has been executed. They can be used to short-circuit the pipeline, so that an action method is never executed. They can also be used to customize the model bind- ing process for an action method.  Action filters run after model binding has occurred, just before an action method executes. They also run after the action method has executed. They can be used to extract common code out of an action method to prevent duplication.  The Controller base class also implements IActionFilter and IAsyncAction- Filter. They run at the start and end of the action filter pipeline, regardless of the ordering or scope of other action filters.  Exception filters execute after action filters, when an action method has thrown an exception. They can be used to provide custom error handling specific to the action executed.  Generally, you should handle exceptions at the middleware level, but exception filters let you customize how you handle exceptions for specific actions or controllers.  Result filters run just before and after an IActionResult is executed. You can use them to control how the action result is executed, or to completely change the action result that will be executed.  You can use ServiceFilterAttribute and TypeFilterAttribute to allow dependency injection in your custom filters. ServiceFilterAttribute requires that you register your filter and all its dependencies with the DI container, whereas TypeFilterAttribute only requires that the filter’s dependencies have been registered.
  • 428.
    400 Authentication: adding users toyour application with Identity One of the selling points of a web framework like ASP.NET Core is the ability to provide a dynamic app, customized to individual users. Many apps have the con- cept of an “account” with the service, which you can “sign in” to and get a different experience. Depending on the service, an account gives you varying things: on some apps you may have to sign in to get access to additional features, on others you might see suggested articles. On an e-commerce app, you’d be able to make and view your This chapter covers  How authentication works in web apps in ASP.NET Core  Creating a project using the ASP.NET Core Identity system  Adding user functionality to an existing web app
  • 429.
    401 past orders, onStack Overflow you can post questions and answers, whereas on a news site you might get a customized experience based on previous articles you’ve viewed. When you think about adding users to your application, you typically have two aspects to consider:  Authentication—The process of creating users and letting them log in to your app  Authorization—Customizing the experience and controlling what users can do, based on the current logged-in user In this chapter, I’m going to be discussing the first of these points, authentication and membership, and in the next chapter, I’ll tackle the second point, authorization. In section 14.1, I discuss the difference between authentication and authorization, how authentication works in a traditional ASP.NET Core web app, and ways you can archi- tect your system to provide sign-in functionality. I also touch on the typical differences in authentication between a traditional web app and Web APIs used with client-side or mobile web apps. This book focuses on tra- ditional web apps for authentication, but many of the principles are applicable to both. In section 14.2, I introduce a user management system called ASP.NET Core Iden- tity (or Identity for short). Identity integrates with EF Core and provides a number of services for creating and managing users, storing and validating passwords, and sign- ing users in and out of your app. In section 14.3, you’ll create an app using a default template that includes ASP.NET Core Identity out of the box. This will give you an app to explore, to see the features Identity provides, as well as everything it doesn’t. Creating an app is great for seeing an example of how the pieces fit together, but you’ll often need to add users to an existing app. In section 14.4, you’ll see how you can extract the Identity elements from the default template and apply it to an existing app: the recipe application from chapters 12 and 13. In section 14.5, you’ll customize the login process. It’s common to need to store additional information about a user (such as their name or date of birth) and to pro- vide permissions to them that you can later use to customize the app’s behavior (if the user is a VIP, for example). NOTE The authentication and authorization subsystem of ASP.NET Core underwent some breaking changes between ASP.NET Core 1.1 and 2.0 to solve some general design deficiencies. I only talk about the ASP.NET Core 2.0 system in this book. You can read about the changes at https://github .com/aspnet/Announcements/issues/232. Additionally, ASP.NET Core 2.1, in preview at the time of writing, hides much of the UI code from you by default. You can generate the UI code again, if required, using the scaffolder described in the following post http://mng.bz/tZZz.
  • 430.
    402 CHAPTER 14Authentication: adding users to your application with Identity Before we break out the braces, let’s take a look at authentication and authorization in ASP.NET Core, what’s happening when you sign in to a website, and some of the ways to design your apps to provide this functionality. 14.1 Introducing authentication and authorization When you add sign-in functionality to your app and control access to certain functions based on the currently-signed-in user, you’re using two distinct aspects of security:  Authentication—The process of determining who you are  Authorization—The process of determining what you’re allowed to do Generally, you need to know who the user is before you can determine what they’re allowed to do, so authentication typically comes first, followed by authorization. In this chapter, we’re only looking at authentication; we’ll cover authorization in chapter 15. I’ll start by discussing how ASP.NET Core thinks about users, and cover some of the terminology and concepts that are central to authentication. I always found this to be the hardest part to grasp when first learning about authentication, so I’ll take it slow! Next, we’ll look at what it means to sign in to a traditional web app. After all, you only provide your password and sign in to an app on a single page—how does the app know the request came from you for subsequent requests? Finally, we’ll look at how authentication works when you need to support client-side apps and mobile apps that call Web APIs, in addition to traditional web apps. Many of the concepts are similar, but the requirement to support multiple types of users, tradi- tional apps, client-side apps, and mobile apps has led to alternative solutions. In the next section, we’ll look at a practical implementation of a user management system called ASP.NET Core Identity, and how you can use this in your own projects. 14.1.1 Understanding users and claims in ASP.NET Core The concept of a user is baked-in to ASP.NET Core. In this section, we’ll look at the fundamental model used to describe a user, including its properties, such as an associ- ated email address, as well as any permission-related properties, such as whether the user is an admin user. In chapter 3, you learned that the HTTP server, Kestrel, creates an HttpContext object for every request it receives. This object is responsible for storing all of the details related to that request, such as the request URL, any headers sent, the body of the request, and so on. The HttpContext object also exposes the current principal for a request as the User property. This is ASP.NET Core’s view of which user made the request. Any time your app needs to know who the current user is, or what they’re allowed to do, it can look at the HttpContext.User principal. DEFINITION You can think of the principal as the user of your app.
  • 431.
    403 Introducing authentication andauthorization In ASP.NET Core, principals are implemented as ClaimsPrincipals, which has a col- lection of claims associated with it, as shown in figure 14.1. You can think about claims as properties of the current user. For example, you could have claims for things like email, name, or date of birth. DEFINITION A claim is a single piece of information about a principal, which consists of a claim type and an optional value. Claims can also be indirectly related to permissions and authorization, so you could have a claim called HasAdminAccess or IsVipCustomer. These would be stored in exactly the same way—as claims associated with the user principal. NOTE Earlier versions of ASP.NET used a role-based approach to security, rather than claims-based. The ClaimsPrincipal used in ASP.NET Core is compatible with this approach for legacy reasons, but you should use the claims-based approach for new apps. Kestrel assigns a user principal to every request that arrives at your app. Initially, that principal is a generic, anonymous, unauthenticated principal with no claims. How do you log in, and how does ASP.NET Core know that you’ve logged in on subsequent requests? In the next section, we’ll look at how authentication works in a traditional web app using ASP.NET Core, and the process of signing in to a user account. 14.1.2 Authentication in ASP.NET Core: services and middleware Adding authentication to any web app involves a number of moving parts. The same gen- eral process applies whether you’re building a traditional web app or a client-side app, though there are often differences in implementation, as I’ll discuss in section 14.1.3: Email=test@test.com ClaimsPrincipal FirstName=Andrew LastName=Lock HomePhone=555 123 HasAdminAccess The principal associated with the request is the current user. The principal is implemented by the ClaimsPrincipal class, which has a collection of Claims. Claims describe properties of the principal. These normally consist of a type and a value but can be a name only. Figure 14.1 The principal is the current user, implemented as ClaimsPrincipal. It contains a collection of Claims that describe the user.
  • 432.
    404 CHAPTER 14Authentication: adding users to your application with Identity  The client sends an identifier and a secret to the app, which identify the current user. For example, you could send an email (identifier) and a password (secret).  The app verifies that the identifier corresponds to a user known by the app and that the corresponding secret is correct.  If the identifier and secret are valid, the app can set the principal for the cur- rent request, but it also needs a way of storing these details for subsequent requests. For traditional web apps, this is typically achieved by storing an encrypted version of the user principal in a cookie. This is the typical flow for most web apps, but in this section, I’m going to look at how it works in ASP.NET Core. The overall process is the same, but it’s good to see how this pattern fits into the services, middleware, and MVC aspects of an ASP.NET Core appli- cation. We’ll step through the various pieces at play in a typical app when you sign in as a user, what that means, and how you can make subsequent requests as that user. SIGNING IN TO AN ASP.NET CORE APPLICATION When you first arrive on a site and sign in to a traditional web app, the app will send you to a sign-in page and ask you to enter your username and password. After you sub- mit the form to the server, the app redirects you to a new page, and you’re magically logged in! Figure 14.2 shows what’s happening behind the scenes in an ASP.NET Core app when you submit the form. 1. The user enters their login details and clicks submit to POST them to the server. Request AccountController. Login Redirect Result 5. Finally, the user principal is serialized and returned as an encrypted cookie to the browser. SignInManager 3. The action method calls the sign in manager. This loads the user from the database using EF Core and validates their password. 4. If the password is correct, the user is signed in. The User property is set to the authenticated user principal. HttpContext.User ? HttpContext.User 2. Initially, the User property is set to an anonymous user principal. Db Figure 14.2 Signing in to an ASP.NET Core application. SignInManager is responsible for setting HttpContext.User to the new principal and serializing the principal to the encrypted cookie.
  • 433.
    405 Introducing authentication andauthorization This shows the series of steps from the moment you submit the login form, to the point the redirect is returned to the browser. When the request first arrives, Kestrel creates an anonymous user principal and assigns it to the HttpContext.User property. The request is then routed to a normal MVC controller, AccountController, which reads the email and password from the request. The meaty work happens inside the SignInManager service. This is responsible for loading a user entity from the database with the provided username and validating that the password they provided is correct. WARNING Never store passwords in the database directly. They should be hashed using a strong one-way algorithm. The ASP.NET Core Identity system does this for you, but it’s always wise to reiterate this point! If the password is correct, SignInManager creates a new ClaimsPrincipal from the user entity it loaded from the database and adds the appropriate claims, such as the email. It then replaces the old, anonymous, HttpContext.User principal with the new, authenticated principal. Finally, SignInManager serializes the principal, encrypts it, and stores it as a cookie. A cookie is a small piece of text that’s sent back and forth between the browser and your app along with each request, consisting of a name and a value. This authentication process explains how you can set the user for a request when they first log in to your app, but what about subsequent requests? You only send your password when you first log in to the app, so how does the app know that it’s the same user making the request? AUTHENTICATING USERS FOR SUBSEQUENT REQUESTS The key to persisting your identity across multiple requests lies in the final step of fig- ure 14.2, where you serialized the principal in a cookie. Browsers will automatically send this cookie with all requests made to your app, so you don’t need to provide your password with every request. ASP.NET Core uses the authentication cookie sent with the requests to rehydrate ClaimsPrincipal and set the HttpContext.User principal for the request, as shown in figure 14.3. The important thing to note is when this process happens—in AuthenticationMiddleware. When a request containing the authentication cookie is received, Kestrel creates the default, unauthenticated, anonymous principal and assigns it to the HttpContext .User principal. Any middleware that runs at this point, before Authentication- Middleware, will see the request as unauthenticated, even if there’s a valid cookie. TIP If it looks like your authentication system isn’t working, double-check your middleware pipeline. Only middleware that runs after Authentication- Middleware will see the request as authenticated.
  • 434.
    406 CHAPTER 14Authentication: adding users to your application with Identity AuthenticationMiddleware is responsible for setting the current user for a request. The middleware calls authentication services, which will read the cookie from the request and deserialize it to obtain the ClaimsPrincipal created when the user logged in. AuthenticationMiddleware sets the HttpContext.User principal to the new, authenticated principal. All subsequent middleware will now know the user principal for the request and can adjust behavior accordingly (for example, displaying the user’s name on the homepage, or restricting access to some areas of the app). The process described so far, in which a single app authenticates the user when they log in and sets a cookie that’s read on subsequent requests, is common with tradi- tional web apps, but it isn’t the only possibility. In the next section, we’ll take a look at authentication for client-side and mobile apps, and how the authentication system changes for those scenarios. 14.1.3 Authentication for APIs and distributed applications The process I’ve outlined so far applies to traditional web apps, where you have a sin- gle endpoint that’s doing all the work. It’s responsible for authenticating and manag- ing users, as well as serving your app data, as shown in figure 14.4. 1. An authenticated user makes a request to /recipes. Request HttpContext.User ? 2. The browser sends the authentication cookie with the request. Authentication middleware Static file middleware 3. Any middleware before the authentication middleware treat the request as though it is unauthenticated. Authentication services 4. The authentication middleware calls the Authentication services which deserialize the user principal from the cookie and confirms it’s valid. MVC middleware HttpContext.User 6. All middleware after the authentication middleware see the request as from the authenticated user. 5. The HttpContext.User property is set to the deserialized principal, and the request is now authenticated. Figure 14.3 A subsequent request after signing in to an application. The cookie sent with the request contains the user principal, which is validated and used to authenticate the request.
  • 435.
    407 Introducing authentication andauthorization In addition to this traditional web app, it’s common to use ASP.NET Core as a Web API to serve data for mobile and client-side SPAs. Similarly, the trend towards micro- services on the backend means that even traditional web apps using Razor often need to call Web APIs behind the scenes, as shown in figure 14.5. In this situation, you have multiple apps and APIs, all of which need to understand that the same user is making a request across all of the apps and APIs. If you keep the same approach as before, where each app manages its own users, things can quickly become complicated! You’ll need to duplicate all of the sign-in logic between the apps and APIs, as well as needing to have some central database holding the user details. Users may need to sign in multiple times to access different parts of the service. On top of that, using cookies becomes problematic for some mobile clients in particular, or where you’re making requests to multiple domains (as cookies only belong to a single domain). Browsers call traditional web apps. Traditional web apps serve requests and handle authentication/authorization of users. Figure 14.4 Traditional apps typically handle all the functionality of an app: the business logic, generating the UI, authentication, and user management. Browsers call traditional web apps. Client-side and mobile apps call APIs. Both traditional web apps and APIs call other APIs. Each app or API needs to be authenticated/authorized. Figure 14.5 Modern applications typically need to expose Web APIs for mobile and client-side apps, as well as potentially calling APIs on the backend. When all of these services need to authenticate and manage users, this becomes complicated logistically.
  • 436.
    408 CHAPTER 14Authentication: adding users to your application with Identity How can you improve this? The typical approach is to extract the code that’s common to all of the apps and APIs, and move it to an identity provider, as shown in figure 14.6. Instead of signing in to an app directly, the app redirects to an identity provider app. The user signs in to this identity provider, which passes bearer tokens back to the client that indicate who the user is and what they’re allowed to access. The clients and apps can pass these tokens to the APIs, to provide information about the logged-in user, without needing to re-authenticate or manage users directly. This architecture is clearly more complicated on the face of it, as you’ve thrown a whole new service—the identity provider—into the mix, but in the long run this has a number of advantages:  Users can share their identity between multiple services. As you’re logged in to the cen- tral identity provider, you’re essentially logged in to all apps that use that ser- vice. This gives you the “single-sign-on” experience, where you don’t have to keep logging in to multiple services.  Reduced duplication. All of the sign-in logic is encapsulated in the identity pro- vider, so you don’t need to add sign-in screens to all your apps.  Can easily add new providers. Whether you use the identity provider approach or the traditional approach, it’s possible to use external services to handle the authentication of users. You’ll have seen this on apps that allow you to “log in using Facebook” or “log in using Google,” for example. If you use a centralized identity provider, adding support for additional providers can be handled in one place, instead of having to configure every app and API explicitly. Instead of authenticating directly with the app, browsers and APIs authenticate with an Identity Provider which issues tokens. The tokens are passed to the traditional web apps and APIs. Authentication is now centralized. Tokens can be passed between APIs and services as necessary. Figure 14.6 An alternative architecture involves using a central identity provider to handle all the authentication and user management for the system. Tokens are passed back and forth between the identity provider, apps, and APIs.
  • 437.
    409 Introducing authentication andauthorization Out of the box, ASP.NET Core supports architectures like this, and for consuming issued bearer tokens, but it doesn’t include support for issuing those tokens. That means you’ll need to use another library or service for the identity provider. One option for an identity provider is to delegate all the authentication responsi- bilities to a third-party identity provider, such as Okta, Auth0, or Azure Active Direc- tory B2C. These will manage users for you, so user information and passwords are stored in their database, rather than your own. The biggest advantage of this approach is that you don’t have to worry about making sure your customer data is safe; you can be pretty sure that a third party will protect it, as it’s their whole business! TIP Wherever possible, I recommend this approach, as it delegates security responsibilities to someone else. You can’t lose your user’s details if you never had them! Another common option is to build your own identity provider. This may sound like a lot of work, but thanks to excellent libraries like OpenIddict (https://github.com/ openiddict) and IdentityServer4 (http://docs.identityserver.io), it’s perfectly possible to write your own identity provider to serve bearer tokens that will be consumed by an application. An aspect often overlooked by people getting started with OpenIddict and Identity- Server is that they aren’t prefabricated solutions. You, as a developer, need to write the code that knows how to create a new user (normally in a database), how to load a user’s details, and how to validate their password. In that respect, the development process of creating an identity provider is similar to the traditional web app with cookie authentication that I discussed in section 14.1.2. In fact, you can almost think of an identity provider as a traditional web app that only has account management pages. It also has the ability to generate tokens for other services, but it contains no other app-specific logic. The need to manage users in a database, as well as providing an interface for users to log in, is common to both approaches and is the focus of this chapter. NOTE Hooking up your apps and APIs to use an identity provider requires a fair amount of tedious configuration, both of the app and the identity pro- vider. For simplicity, this book will focus on traditional web apps using the process outlined in section 14.1.2. For details on how to configure your apps and client-side SPAs to use IdentityServer, see the excellent documentation at http://docs.identityserver.io. ASP.NET Core Identity (hereafter shortened to Identity) is a system that makes build- ing the user management aspect of your app (or identity provider app) simpler. It handles all of the boilerplate for saving and loading users to a database, as well as a number of best practices for security, such as user lock out, password hashing, and two- factor authentication.
  • 438.
    410 CHAPTER 14Authentication: adding users to your application with Identity DEFINITION Two-factor authentication (2FA) is where you require an extra piece of information to sign in, in addition to a password. This could involve sending a code to a user’s phone by SMS, or using a mobile app to generate a code, for example. In the next section, I’m going to talk about the ASP.NET Core Identity system, the problems it solves, when you’d want to use it, and when you might not want to use it. In section 14.3, we’ll take a look at some code and see ASP.NET Core Identity in action. 14.2 What is ASP.NET Core Identity? Whether you’re writing a traditional web app using Razor templates or are setting up a new identity provider using a library like IdentityServer, you’ll need a way of persist- ing details about your users, such as their usernames and passwords. This might seem like a relatively simple requirement but, given this is related to security and people’s personal details, it’s important you get it right. As well as storing the claims for each user, it’s important to store passwords using a strong hashing algo- rithm, to allow users to use 2FA where possible, and to protect against brute force attacks, to name a few of the many requirements! Although it’s perfectly possible to write all the code to do this manually and to build your own authentication and membership system, I highly recommend you don’t. I’ve already mentioned third-party identity providers such as Auth0 or Azure Active Directory B2C. These are Software-as-a-Service (SaaS) solutions that take care of the user management and authentication aspects of your app for you. If you’re in the pro- cess of moving apps to the cloud generally, then solutions like these can make a lot of sense. If you can’t or don’t want to use these solutions, then I recommend you consider using the ASP.NET Core Identity system to store and manage user details in your data- base. ASP.NET Core Identity takes care of some of the boilerplate associated with authentication, but remains flexible and lets you control the login process for users. NOTE ASP.NET Core Identity is an evolution of ASP.NET Identity, with some design improvements and converted to work with ASP.NET Core. By default, ASP.NET Core Identity uses EF Core to store user details in the database. If you’re already using EF Core in your project, then this is a perfect fit. Alternatively, it’s possible to write your own stores for loading and saving user details in another way. Identity takes care of the low-level parts of user management, as shown in table 14.1. As you can see from this list, Identity gives you a lot, but not everything—by a long shot! The biggest missing piece is the fact that you need to provide all the UI for the application, as well as tying all the individual Identity services together to create a functioning sign-in process. That’s a pretty big missing piece, but it makes the Identity system extremely flexible.
  • 439.
    411 What is ASP.NETCore Identity? Luckily, the .NET CLI and Visual Studio come with templates that give you a huge amount of the UI boilerplate for free. And I do mean a huge amount—if you’re using all of the features of Identity, such as 2FA and external login providers, then there’s a lot of Razor to write! NOTE ASP.NET Core 2.1 (in preview at the time of writing) uses new features to deliver this UI code as a library package, drastically reducing the amount of code you need to write. For details, see http://mng.bz/tZZz. For that reason, I strongly recommend using one of the templates as a starting point, whether you’re creating an app or adding user management to an existing app. But the question still remains: when should you use Identity, and when should you con- sider rolling your own? I’m a big fan of Identity, so I tend to suggest it in most situations, as it handles a lot of security-related things for you that are easy to mess up. I’ve heard a number of arguments against it, some of which are valid, and others less so:  I already have user authentication in my app. Great! In that case, you’re probably right, Identity may not be necessary. But does your custom implementation use 2FA? Do you have account lockout? If not, and you need to add them, then con- sidering Identity may be worthwhile.  I don’t want to use EF Core. That’s a reasonable stance. You could be using Dap- per, some other ORM, or even a document database for your database access. Luckily, the database integration in Identity is pluggable, so you could swap out the EF Core integration and use your own database integration libraries instead. Table 14.1 Which services are and aren’t handled by ASP.NET Core Identity Managed by ASP.NET Core Identity Implemented by the developer Database schema for storing users and claims. UI for logging in, creating, and managing users (controller, actions, view models). This is included in the default templates. Creating a user in the database. Sending emails and SMS messages. Password validation and rules. Customizing claims for users (adding new claims). Handling user account lockout (to prevent brute- force attacks). Configuring third-party identity providers. Managing and generating 2FA codes. Generating password-reset tokens. Saving additional claims to the database. Managing third-party identity providers (for exam- ple Facebook, Google, Twitter).
  • 440.
    412 CHAPTER 14Authentication: adding users to your application with Identity  My use case is too complex for Identity. Identity provides lower-level services for authentication, so you can compose the pieces however you like. It’s also exten- sible, so if you need to, for example, transform claims before creating a princi- pal, you can.  I don’t want to build my own identity system. I’m glad to hear it. Using an external identity provider like Azure Active Directory B2C or Auth0 is a great way of shifting the responsibility and risk associated with storing users’ personal infor- mation onto a third party. Any time you’re considering adding user management to your ASP.NET Core applica- tion, I’d recommend looking at Identity as a great option for doing so. In the next section, I’ll demonstrate what Identity provides by creating a new MVC application using the default Identity templates. In section 14.4, we’ll take that template and apply it to an existing app instead. 14.3 Creating a project that uses ASP.NET Core Identity I’ve covered authentication and Identity in general terms a fair amount now, but the best way to get a feel for it is to see some working code. In this section, we’re going to look at the default code generated by the ASP.NET Core templates with Identity, how it works, and how Identity fits in. 14.3.1 Creating the project from a template You’ll start by using the Visual Studio templates to generate a simple MVC application that uses Identity for storing individual user accounts in a database. TIP You can create an equivalent project using the .NET CLI by running dotnet new mvc -au Individual -uld To create the template using Visual Studio, you must be using VS 2017 Update 3 or later and have the ASP.NET Core 2.0 SDK installed:1 1 Choose File > New > Project. 2 From the New Project dialog, choose .NET Core, and then select ASP.NET Core Web Application. 3 Enter a Name, Location, and optionally a solution name, and click OK. 4 Choose the Web Application (Model-View-Controller) template and click Choose Authentication to bring up the Authentication dialog, shown in figure 14.7. 5 Choose Individual User Accounts to create an application configured with EF Core and ASP.NET Core Identity. Click OK. 1 ASP.NET Core 2.1 (in preview at the time of writing) uses new features to deliver this UI code as a library package. If you have the 2.1 SDK installed, the templates won’t generate all the UI code as described, though the behavior will be similar. For details, see http://mng.bz/tZZz.
  • 441.
    413 Creating a projectthat uses ASP.NET Core Identity 6 Click OK to create the application. Visual Studio will automatically run dotnet restore to restore all the necessary NuGet packages for the project. 7 Run the application to see the default app, as shown in figure 14.8. Choose No Authentication to create a template without authentication. Choose Individual User Accounts to store local user accounts using ASP .NET Core Identity and EF Core. Work or School Accounts will configure the application to use an external Identity Provider, using Active Directory (or Office365 for example) to handle user management and authentication. Choose Windows Authentication for intranet sites where the Windows login of the user provides the authentication mechanism. Choose Store user accounts in-app. Figure 14.7 Choosing the authentication mode of the new ASP.NET Core application template in VS 2017 You can create new users and sign in using the log in widget. Figure 14.8 The default template with individual account authentication looks similar to the no authentication template, with the addition of a login widget in the top right of the page.
  • 442.
    414 CHAPTER 14Authentication: adding users to your application with Identity This template should look familiar, with one particular twist: you now have Register and Log-in buttons! Feel free to play with the template—creating a user, logging in and out—to get a feel for the app. Once you’re happy, look at the code generated by the template and the boilerplate it saved you from writing! 14.3.2 Exploring the template in Solution Explorer At first glance, the number of files in the solution for a new template can be a bit over- whelming, so I’ll talk through each aspect first. The template uses the standard folder layout for the templates, where files are split by role (controller, service, data, and so on), as shown in figure 14.9. Starting from the top, the first folder contains your MVC controllers. Of particular interest are AccountController and ManageController:  AccountController—Used for registering as a new user, logging in, and send- ing password reset emails.  ManageController—Used for managing your user account when logged in. Changing user details, password, and enabling two-factor authentication. The ManageController is used to manage your account, such as changing you password. The template includes an EF Core DbContext and migrations to configure the database schema for ASP .NET Core Identity. The Models folder contains view models for the MVC controllers. The ApplicationUser is the EF Core entity and is used as the ASP .NET Core Indentity user type. The services folder contains stub interfaces and services. These can be expanded to provide additional features such as password reset emails and two-factor authentication. The AccountController is used to create new users and sign in. Figure 14.9 The project layout of the default template. Depending on your version of Visual Studio, the exact files may vary slightly.
  • 443.
    415 Creating a projectthat uses ASP.NET Core Identity Most of the direct interaction between your app and the Identity system happens in here, in the default templates, so we’ll look at these files in more detail in section 14.3.4. The next folder, the Data folder, contains your application’s EF Core DbContext, called ApplicationDbContext, and the migrations for configuring the database schema to use Identity. I’ll discuss this schema in more detail in section 14.3.3. The Models folder contains two types of models:  The binding/view models for use with action methods and Razor view results.  ApplicationUser, an EF Core entity model. This is the entity that the Identity system stores in the database. Next up is the Services folder. This contains stub services for sending an email and an SMS, which are required in order to allow password resets and two-factor authentication. NOTE These services are stubs and don’t do anything. You’ll need to hook them up, often to a third-party service, to make them functional.2 In some ver- sions of Visual Studio, the stub SMS service may be missing. The last folder, Views, contains all the Razor view templates for the app. Of particular note are the Account and Manage folders, which contain the view templates for AccountController and ManageController. In addition to all the new files included thanks to ASP.NET Core Identity, it’s worth opening up Startup.cs and looking at the changes there. The most obvious change is the additional configuration in ConfigureServices, which adds all the services Iden- tity requires. public void ConfigureServices(IServiceCollection services) { services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer( Configuration.GetConnectionString("DefaultConnection"))); services.AddIdentity<ApplicationUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); services.AddTransient<IEmailSender, AuthMessageSender>(); services.AddTransient<ISmsSender, AuthMessageSender>(); services.AddMvc(); } 2 The documentation shows how to create a provider to send an email using SendGrid (https://docs.microsoft .com/en-us/aspnet/core/security/authentication/accconfirm) and SMS using Twilio or ASPSMS (2FA). Listing 14.1 Adding ASP.NET Core Identity services to ConfigureServices ASP.NET Core Identity uses EF Core, so it includes the standard EF Core configuration. Adds the Identity system, and configures the user and role types Configures Identity to store its data in EF Core Registers the stub service for sending email Uses the default Identity providers forgenerating2FA codes Registers the stub service for sending SMS; may be missing in some versions of Visual Studio
  • 444.
    416 CHAPTER 14Authentication: adding users to your application with Identity There’s one other change in Startup, in the Configure method: app.UseAuthentication(); This adds AuthenticationMiddleware to the pipeline, so that you can authenticate incoming requests, as you saw in figure 14.3. It’s placed early in the pipeline, before MvcMiddleware, so that the HttpContext.User principal will be set correctly when the MVC controllers execute. Now you’ve got an overview of the additions made by Identity, we’ll look in a bit more detail at the database schema and how Identity stores users in the database. 14.3.3 The ASP.NET Core Identity data model Out of the box, and in the default templates, Identity uses EF Core to store user accounts. It provides a base DbContext that you can inherit from, called Identity- Context<TUser>, where TUser is the user entity in your app. It also provides a base user entity, IdentityUser, to inherit from. In the template, the app’s DbContext is called ApplicationDbContext, and the user entity is called ApplicationUser. If you open up these files, you’ll see they’re sparse; they only inherit from the Identity classes I described earlier. What do those base classes give you? The easiest way to see is to update a database with the migrations and take a look! Applying the migrations is the same process as in chapter 12. Ensure the connec- tion string points to where you want to create the database, and run dotnet ef database update to update the database with the migrations. If the database doesn’t yet exist, the CLI will create it. Figure 14.10 shows what the database looks like for the default template. The claims associated with each user are stored in AspNetUserClaims. ASP .NET Core uses EF Core migrations. The history of applied migrations is stored in the __EFMigrationsHistory table. The AspNetUserLogins and AspNetUserTokens are used to manage details of third- party logins like Facebook and Google. The AspNetRoles, AspNetRoleClaims, and AspNetUserRoles provide role-based authorization for legacy reasons. The user entities are stored in the AspNetUsers table. Figure 14.10 The database schema used by ASP.NET Core Identity
  • 445.
    417 Creating a projectthat uses ASP.NET Core Identity That’s a lot of tables! You shouldn’t need to interact with these tables directly—Iden- tity handles that for you—but it doesn’t hurt to have a basic grasp of what they’re for:  __EFMigrationsHistory—The EF Core migrations table that records which migra- tions have been applied.  AspNetUsers—The user profile table itself. This is where ApplicationUser is serialized to. We’ll take a closer look at this table shortly.  AspNetUserClaims—The claims associated with a given user. A user can have many claims, so it’s modeled as a many-to-one relationship.  AspNetUserLogins and AspNetUserTokens—These are related to third-party logins. When configured, these let users sign in with a Google or Facebook account (for example), instead of creating a password on your app.  AspNetUserRoles, AspNetRoles, and AspNetRoleClaims—These tables are somewhat of a legacy left over from the old role-based permission model of the pre-.NET 4.5 days, instead of the claims-based permission model. These tables let you define roles that multiple users can belong to. Each role can be assigned a num- ber of claims. These claims are effectively inherited by a user principal when they are assigned that role. You can explore these tables yourself, but the most interesting of them is the AspNet- Users table, shown in figure 14.11. All of the columns in the AspNetUsers table are security related—their email and password hash, whether they have confirmed their email, whether they have 2FA enabled, and so on. There’s no room for additional information, like the user’s name, for example. By default, ASP .NET Core Identity uses GUIDs for the user Id stored as strings in the database. The table contains all the neccessary fields for authenticating a user, email and phone number confirmation, two factor authentication, and account lockout. Figure 14.11 The AspNetUsers table is used to store all the details required to authenticate a user.
  • 446.
    418 CHAPTER 14Authentication: adding users to your application with Identity NOTE You can see from figure 14.11 that the primary key Id is stored as a string column. By default, Identity uses Guid for the identifier. To customize the data type, see http://mng.bz/Z7Y2. Any extra properties of the user are stored as claims in the AspNetUserClaims table associated with that user. This lets you add arbitrary additional information, without having to change the database schema to accommodate it. Want to store the user’s date of birth? Add a claim to that user—no need to change the database. You can see this in action in section 14.5, when you add a Name claim to every new user. It’s useful to have a mental model of the underlying database schema Identity uses, but in day-to-day work, you won’t have to interact with it directly—that’s what Identity is for! In the next section, we’ll look at the other end of the scale—the UI of the app— and how the action methods and controllers interact with the Identity system. 14.3.4 Interacting with ASP.NET Core Identity: the MVC controllers You saw in table 14.1 that Identity provides low-level constructs and services for adding users to your application, but that you still need to put all the pieces together to create your app. In the templates, this all happens in the MVC Controllers. In this section, we’ll look at the main action methods they expose for creating users and logging in, to get a feel for the authentication process and how you need to interact with Identity. NOTE I’m only going to cover the main action methods in this chapter. It’s worth exploring the code in the templates to see how to include additional func- tionality like two-factor authentication, external authentication, and password reset emails. CREATING NEW USERS WITH THE ACCOUNTCONTROLLER.REGISTER ACTION AccountController is responsible for registering new users and logging them in. The first action method a user will hit is the Register action, which will display the login form, asking the user to provide an email and password, as shown in figure 14.12. When they click submit, this will POST the form to the Register method of AccountController, as shown in listing 14.2. This action is responsible for creating a new user entity and signing the user in. This involves several steps:  Create the user entity in the database  Create and store claims associated with the user (the email and username claims)  Set the principal for the current request  Serialize the principal to a cookie for subsequent requests These steps would be relatively involved if you had to do them manually but, luckily, Identity takes care of most of that for us.
  • 447.
    419 Creating a projectthat uses ASP.NET Core Identity public async Task<IActionResult> Register( RegisterViewModel model, string returnUrl = null) { ViewData["ReturnUrl"] = returnUrl; if (ModelState.IsValid) { var user = new ApplicationUser { UserName = model.Email, Email = model.Email }; var result = await _userManager.CreateAsync( user, model.Password); if (result.Succeeded) { await _signInManager.SignInAsync(user); return RedirectToLocal(returnUrl); } AddErrors(result); } return View(model); } Listing 14.2 AccountController.Register POST action for creating new users Figure 14.12 The user registration form in the default template The form is bound to RegisterViewModel. If the binding mode is valid, creates an instance of the user entity Checks that the provided password is valid and creates the new user in the database If the user was created successfully, sign them in. Updates the HttpContext.User principal and sets a cookie containing the serialized principal Redirects to the previous page using a helper method to protect against open redirect attacks If something went wrong, adds the errors to the model state and redisplays the form
  • 448.
    420 CHAPTER 14Authentication: adding users to your application with Identity As with all action methods for modifying data, the action first validates the binding model. If that’s successful, the action creates a new instance of ApplicationUser and attempts to create it in the database using the UserManager provided by the Identity system. This validates that the password passes some password-strength validation (it is of sufficient length for example) and creates the user in the database. TIP You can customize the password rules used by Identity when you config- ure your services. I’ll show how to do this in section 14.4.1. If UserManager created the user successfully, then you use SignInManager to sign the new user in. SignInManager is responsible for setting the HttpContext.User property to your user principal and serializing the principal to a cookie for use in subsequent requests, as you saw in figure 14.2. Once the user is signed in, you can redirect them to what they were doing before they signed in. Identity takes care of all the heavy lifting; you’re left to coordinate individual services and to generate the appropriate user interaction using IAction- Results. SIGNING OUT WITH THE ACCOUNTCONTROLLER.LOGOUT ACTION Great—so you can create a new user and sign in, but what if a user is finished with the app and wants to sign out? Funnily enough, there’s a Logout action! The Logout action method, shown in listing 14.3, is simple and asks SignInManager to sign the user out. This will clear the HttpContext.User property and removes the authentication cookie from the response so that, in subsequent requests, you’ll be anonymous again. Finally, it redirects you to the HomeController index page. public async Task<IActionResult> Logout() { await _signInManager.SignOutAsync(); return RedirectToAction(nameof(HomeController.Index), "Home"); } On subsequent requests, there won’t be an authentication cookie, so the request will be unauthenticated. SIGNING IN WITH THE ACCOUNTCONTROLLER.LOGIN ACTION The final method I want to look at is the Login action method. As with the Register action, this displays a form to the user to enter their email and password, as shown in figure 14.13, and POSTs it to the Login action on AccountController. The login screen is a standard setup, with fields for email and password, and a link to reset your password if you’ve forgotten it. On the right-hand side of the screen, you can see a section for logging in using an external identity provider, such as Facebook or Google. Listing 14.3 The AccountController.Logout POST action for signing out Replaces the HttpContext.User with an anonymous principal and deletes the authentication cookie Redirects to the app’s homepage
  • 449.
    421 Creating a projectthat uses ASP.NET Core Identity NOTE Adding external providers is a fully supported feature of Identity, which requires relatively minimal configuration to your app. Unfortunately, you also have to register your app with the external provider, Google or Face- book, for example, which can be a convoluted process!3 As with the Register method, the Identity system does all the hard work associated with loading a user from the database, validating the password, checking that the account isn’t locked, and whether they have enabled 2FA on the account. The follow- ing listing shows that the SignInManager handles all this; you only need to display a view or redirect to an action, as appropriate. public async Task<IActionResult> Login( LoginViewModel model, string returnUrl = null) { ViewData["ReturnUrl"] = returnUrl; if (ModelState.IsValid) { var result = await _signInManager.PasswordSignInAsync( model.Email, model.Password, model.RememberMe); if (result.Succeeded) { return RedirectToLocal(returnUrl); } 3 For documentation on adding external providers see http://mng.bz/MR2m. Listing 14.4 The AccountController.Login POST action for logging in Figure 14.13 The login form in the default templates. The right-hand side of the screen can be used to configure external identity providers, so users can log in to your app using their Google or Facebook accounts. Attempts to sign in with the email and password The sign-in succeeded, so the HttpContext.User and authentication cookie have been set.
  • 450.
    422 CHAPTER 14Authentication: adding users to your application with Identity if (result.RequiresTwoFactor) { return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl, model.RememberMe }); } if (result.IsLockedOut) { return View("Lockout"); } else { ModelState.AddModelError( string.Empty, "Invalid login attempt."); return View(model); } } return View(model); } This method also showcases some of the additional features included in the templates, such as 2FA and account lockout. These are disabled by default in the templates, but the templates include comments that describe how to enable them. You’ve covered the most important methods of AccountController now. If you browse the templates, you’ll see others that handle 2FA and external login providers. I strongly recommend turning on these features by following the comments in the code and the tutorials in the documentation. AccountController contains the most important Identity-related actions for log- ging in and out. ManageController allows users to manage their account by configur- ing 2FA or managing their third-party logins. As I said earlier, these features are optional for the most part, but you’ll probably need the ChangePassword action in your apps! CHANGING YOUR PASSWORD WITH THE MANAGECONTROLLER.CHANGEPASSWORD ACTION As you might expect by now, the Identity system exposes methods to handle the change password functionality for you—your job is to wire it all up! This shows the ChangePassword method from the default templates. public async Task<IActionResult> ChangePassword( ChangePasswordViewModel model) { if (!ModelState.IsValid) { return View(model); } var user = await _userManager.GetUserAsync(HttpContext.User); if (user != null) { Listing 14.5 The ManageController.ChangePassword action method If 2FA is enabled for the user, they’ll be redirected to an action to generate and validate a code. Too many failed login attempts have been made for the email and lockout is enabled. An error occurred, or the view model wasn’t valid. Redisplays the form. Loads the user entity for the currently- signed-in principal
  • 451.
    423 Adding ASP.NET CoreIdentity to an existing project var result = await _userManager.ChangePasswordAsync( user, model.OldPassword, model.NewPassword); if (result.Succeeded) { await _signInManager.SignInAsync(user); return RedirectToAction(nameof(Index))); } AddErrors(result); return View(model); } return RedirectToAction(nameof(Index)); } The action goes through several steps to change a user’s password: 1 If the binding model isn’t valid, redisplay the form (never forget to do this!). 2 Load the current user using UserManager. 3 Attempt to change the user’s password by providing both the old and new pass- word. UserManager will validate that the old password matches the existing hash and that the new password meets the configured password-strength require- ments. 4 If UserManager changed the password successfully, use SignInManager to sign in the user principal again. This ensures the principal on HttpContext.User, and stored in the cookie, matches the values on the database user entity. Even with the Identity system handling all of the low-level security aspects for you, there’s still quite a lot of code to configure correctly here. In fact, there’s so much code associated with the Identity templates in general, that I rarely add Identity to an existing app manually. Instead, I take a shortcut. In the next section, you’ll see how easy it is to take the code generated by the tem- plates and add it to an existing app, without having to figure it all out for yourself from scratch. 14.4 Adding ASP.NET Core Identity to an existing project In this section, we’re going to add users to the recipe application from chapters 12 and 13. This is a working app that you want to add user functionality to. In chapter 15, we’ll extend this work to restrict control regarding who’s allowed to edit recipes on the app. By the end of this section, you’ll have an application with a registration page, a login screen, and a manage account screen, like the default templates. You’ll also have a persistent widget in the top right of the screen, showing the login status of the cur- rent user, as shown in figure 14.14. As with the default templates, I’m not going to set up external login providers or 2FA; I’m only concerned with adding ASP.NET Core Identity to an existing app that’s already using EF Core. If something went wrong, add the errors to ModelState and redisplay the form. If the password was changed successfully, sign in again to update the principal. Validates the old password is correct and the new password is valid, and updates the password
  • 452.
    424 CHAPTER 14Authentication: adding users to your application with Identity TIP It’s worth making sure you’re comfortable with the new project tem- plates before you go about adding Identity to an existing project. Create a test app, set up an external login provider, configure the email and SMS, and enable 2FA. This will take a bit of time, but it’ll be invaluable for deciphering errors when you come to adding Identity to your own apps. To add Identity to your app, you’ll need to: 1 Add the ASP.NET Core Identity NuGet packages. 2 Configure Startup to use AuthenticationMiddleware and add Identity ser- vices to the DI container. 3 Update the EF Core data model with the Identity entities. 4 Add AccountController and ManageController, as well as the associated views and view models. This section will tackle each of these steps in turn. At the end of the section, you’ll have successfully added user accounts to your recipe app. 14.4.1 Configuring the ASP.NET Core Identity services and middleware You can add ASP.NET Core Identity to an existing app by referencing a single NuGet package in your csproj, Microsoft.AspNetCore.Identity.EntityFrameworkCore: <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="2.0.0" /> This one package will bring in all the additional required dependencies you need to add Identity. Be sure to run dotnet restore after adding it to your project. The Login widget shows the current signed in user’s email and a link for them to log out. Figure 14.14 The recipe app after adding authentication
  • 453.
    425 Adding ASP.NET CoreIdentity to an existing project NOTE If your app uses the Microsoft.AspNetCore.All or Microsoft.AspNet- Core.App package (used by the default templates), then the Identity package is already included and you don’t need an explicit reference. Once you’ve added the Identity package, you can update your Startup.cs file to include the Identity services, as shown next. This is similar to the default template setup you saw in listing 14.1, but make sure to reference your existing AppDbContext. public void ConfigureServices(IServiceCollection services) { services.AddDbContext<AppDbContext>(options => options.UseSqlServer( Configuration.GetConnectionString("DefaultConnection"))); services.AddIdentity<ApplicationUser, IdentityRole>(options => options.Password.RequiredLength = 10) .AddEntityFrameworkStores<AppDbContext>() .AddDefaultTokenProviders(); services.AddTransient<IEmailSender, AuthMessageSender>(); services.AddTransient<ISmsSender, AuthMessageSender>(); services.AddMvc(); services.AddScoped<RecipeService>(); } This adds all the necessary services and configures Identity to use EF Core. You can also see that I’ve customized the password strength requirements in the AddIdentity call, increasing the minimum password length to 10 characters. You’ve also configured the IEmailSender and ISmsSender interfaces used by the default templates. If your app already has email- or SMS-sending capabilities, you can configure them to use your existing classes. For now, I copied across the stub imple- mentations from the default templates. NOTE You’ll need to implement IEmailSender at a minimum if you want users to be able to reset their password when they forget it! See the documen- tation for details: http://mng.bz/pJp3. Configuring AuthenticationMiddleware is somewhat easier: add it to the pipeline in the Configure method. As you can see in listing 14.7, I’ve added the middleware after StaticFileMiddleware, but before MvcMiddleware. In this position, serving static files won’t come with the overhead of authentication, but your MVC controllers will be able to correctly read the user principal from HttpContext.User. Listing 14.6 Adding ASP.NET Core Identity services to the recipe app The existing service configuration is unchanged. Adds the Identity services to the DI container Adds the stub services to the DI container Updates the password rules to set the minimum password length to 10 Makes sure you use the name of your existing DbContext app
  • 454.
    426 CHAPTER 14Authentication: adding users to your application with Identity public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseStaticFiles(); app.UseAuthentication(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Recipe}/{action=Index}/{id?}"); }); } You’ve configured your app to use Identity, so the next step is to update EF Core’s data model. You’re already using EF Core in this app, so you need to update your data- base schema to include the tables Identity requires. 14.4.2 Updating the EF Core data model to support Identity You saw in section 14.3.3 that Identity provides a DbContext called IdentityDb- Context, which you can inherit from. The IdentityDbContext base class includes the necessary DbSet<T> to store your user entities using EF Core. Updating an existing DbContext for Identity is simple—update your app’s DbContext to inherit from IdentityDbContext, as shown in the following listing. You’ll also need to copy the ApplicationUser entity class to your app. public class AppDbContext : IdentityDbContext<ApplicationUser> { public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { } public DbSet<Recipe> Recipes { get; set; } } Effectively, by updating the base class of your context in this way, you’ve added a whole load of new entities to EF Core’s data model. As you saw in chapter 12, whenever EF Core’s data model changes, you need to create a new migration and apply those changes to the database. At this point, your app should compile, so you can add a new migration called AddIdentitySchema using dotnet ef migrations add AddIdentitySchema. Listing 14.7 Adding AuthenticationMiddleware to the recipe app Listing 14.8 Updating AppDbContext to use IdentityDbContext Middleware after AuthenticationMiddleware can read the user principal from HttpContext.User. StaticFileMiddleware will never see requests as authenticated, even after you sign in. Adds AuthenticationMiddleware to the pipeline The remainder of the class remains the same Updates to inherit from the Identity context, instead of directly from DbContext
  • 455.
    427 Adding ASP.NET CoreIdentity to an existing project The final—biggest—step is adding the controllers, view models, and view templates for the app. Normally, this would be a lot of work, but by using the default templates as a base, you can add the code much faster. 14.4.3 Adding the controllers, view models, and views One of the most tedious parts of adding Identity to an existing app is adding all the UI code to interact with it. You need the login page and the register page, obviously, but if you’re also implementing features like 2FA and external login providers, there’s a whole host of other pages you need. To get around this, I like to copy and paste the default code from the templates and tweak it later! I copied AccountController and ManageController, their view models, and the associated view templates into their corresponding parts of the exist- ing recipe app, and it pretty much works. TIP To avoid namespace issues, I create a fresh, temporary project with the same name as the project I’m moving to. This generates the code with all the correct namespaces. You need to make a couple more changes before you’re done. First off, you’ll need to make sure your view templates are importing the correct namespaces for view models and Identity. The best place for these is the _ViewImports.cshtml file, as you saw in chapter 7. @using Microsoft.AspNetCore.Identity @using RecipeApplication @using RecipeApplication.Models @using RecipeApplication.Models.AccountViewModels @using RecipeApplication.Models.ManageViewModels @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers The last tweak is to add the login widget from figure 14.8. This shows the current login status of the user and lets you register or sign in. This widget is implemented as a shared partial template, _LoginPartial.cshtml, which you can copy directly across to the views/shared folder. All that remains is to invoke the partial by calling @await Html.PartialAsync("_LoginPartial") in the layout file of your app, _Layout.cshtml. And there you have it, you’ve added Identity to an existing application. Admittedly, copying the templates like this is a bit of a cheat, but as long as you can read through and understand what they’re doing, there’s no harm in saving yourself some work. On top of that, it reduces the chance of introducing accidental security bugs at the same time!4 Listing 14.9 Updating _ViewImports.cshtml to use additional namespaces 4 In ASP.NET Core 2.1 (currently in preview), using the default UI will be easier, as described in https://blogs.msdn.microsoft.com/webdev/2018/03/02/aspnetcore-2-1-identity-ui/. The Identity namespace The namespace of the Identity view models for the recipe app
  • 456.
    428 CHAPTER 14Authentication: adding users to your application with Identity Getting started with Identity like this is pretty easy, and the documentation for enabling additional features like 2FA and external providers is easily accessible and highlighted in the code comments. One area where people quickly get confused is when they want to extend the default templates. In the next section, I show how you can tackle a common follow requirement: how to add additional claims to users when they register. 14.5 Managing users: adding new claims to users Very often, the next step after adding Identity to an application is to customize it. The default templates only require an email and password to register. What if you need more details, like a friendly name for the user? Also, I’ve mentioned that we use claims for security, so what if you want to add a claim called IsAdmin to certain users? You know that every user principal has a collection of claims so, conceptually, add- ing any claim just requires adding it to the user’s collection. There are two main times that you would want to grant a claim to a user:  For every user, when they first register on the app. For example, you might want to add a “Name” field to the Register form and add that as a claim to the user when they register.  Manually, after the user has already registered. This is common for claims used as “permissions,” where an existing user might want to add an IsAdmin claim to a specific user after they have registered on the app. In this section, I show you the first approach, automatically adding new claims to a user when they’re created. The latter approach is the more flexible and, ultimately, is the approach many apps will need, especially line-of-business apps. Luckily, there’s nothing conceptually difficult to it; it requires a simple UI that lets you view users and add a claim through the same mechanism I’ll show here. Let’s say you want to add a new Claim to a user called FullName. A typical approach would be: 1 Add a “Name” field to the RegisterViewModel model of the Register action. 2 Add a “Name” input field to the Register.cshtml Razor template. 3 Create the new ApplicationUser entity as before, by calling CreateAsync on UserManager<ApplicationUser>. 4 Add a new Claim to the user by calling _userManager.AddClaimAsync(). 5 Sign the user in as before, using _signInManager.SignInAsync(). Steps 1 and 2 are fairly self-explanatory and just require updating the existing tem- plates with the new field. Steps 3-5 all take place in the AccountController.Register method, which you can compare to listing 14.2, where you didn’t add the extra claim.
  • 457.
    429 Managing users: addingnew claims to users public async Task<IActionResult> Register( RegisterViewModel model, string returnUrl = null) { ViewData["ReturnUrl"] = returnUrl; if (ModelState.IsValid) { var user = new ApplicationUser { UserName = model.Email, Email = model.Email }; var result = await _userManager.CreateAsync( user, model.Password); if (result.Succeeded) { var claim = new Claim("FullName", model.Name); await _userManager.AddClaimAsync(user, nameClaim); await _signInManager.SignInAsync(user); return RedirectToLocal(returnUrl); } AddErrors(result); } return View(model); } This is all that’s required to add the new claim, but you’re not using it anywhere cur- rently. What if you want to display it? Well, you’ve added a claim to the user principal, which was assigned to the HttpContext.User property when you called SignInAsync. That means you can access the claims anywhere you have access to HttpContext—in your controllers or in view templates. For example, you could display the user’s Full- Name claim anywhere in a Razor template with the following statement: @User.Claims.FirstOrDefault(x=>x.Type == "FullName").Value This finds the first claim on the current users with a Type of "FullName" and prints the assigned value. The Identity system even includes a handy extension method that tidies up this LINQ expression (found in the System.Security.Claims namespace): @User.FindFirstValue("FullName") And with that last titbit, we’ve reached the end of this chapter on ASP.NET Core Iden- tity. I hope you’ve come to appreciate the amount of effort using Identity can save you, especially when you make use of the templates that come with Visual Studio and the .NET CLI. ASP.NET Core Identity provides so many additional features, such as 2FA and external login providers, that there isn’t space to cover them here. Although not essential for adding user functionality to your app, I strongly recommend looking into them. In particular, consider adding 2FA to give your users additional protection. Listing 14.10 Adding a custom claim to a new user in the Register action Creates an instance of the ApplicationUser entity, as usual Validates that the provided password is valid and creates the user in the database Adds the new claim to the ApplicationUser’s collection Creates a claim, with a string name of "FullName" and the provided value Signs the user in by setting the HttpContext.User, the principal will include the custom claim
  • 458.
    430 CHAPTER 14Authentication: adding users to your application with Identity Adding user accounts and authentication to an app is typically the first step to cus- tomizing your app further. Once you have authentication, you can have authorization, which lets you lock down certain actions in your app, based on the current user. In the next chapter, you’ll learn about the ASP.NET Core authorization system and how you can use it to customize your apps, in particular, the recipe application, which is com- ing along nicely! Summary  Authentication is the process of determining who you are.  Authorization is the process of determining what you’re allowed to do.  Every request in ASP.NET Core is associated with a user, also known as a princi- pal. By default, without authentication, this is an anonymous user.  The current principal for a request is exposed on HttpContext.User.  Every user has a collection of claims. These claims are single pieces of informa- tion about the user. Claims could be properties of the physical user, such as Name and Email, or they could be related to things the user has, such as Has- AdminAccess or IsVipCustomer.  Earlier versions of ASP.NET used roles instead of claims. You can still use roles if you need to, but you should use claims where possible.  Authentication in ASP.NET Core is provided by AuthenticationMiddleware and a number of authentication services. These services are responsible for set- ting the current principal when a user logs in, saving it to a cookie, and loading the principal from the cookie on subsequent requests.  ASP.NET Core 2.0 includes support for consuming bearer tokens for authenti- cating API calls, but it doesn’t include a way to create them. You can use a third- party provider such as Azure AD B2C, or a library such as IdentityServer if you need this ability.  ASP.NET Core Identity handles low-level services needed for storing users in a database, ensuring their passwords are stored safely, and for logging users in and out. You must provide the UI for the functionality yourself and wire it up to the Identity sub-system.  The default template for an MVC Application with Individual Account Authenti- cation uses ASP.NET Core Identity to store users in the database with EF Core. It includes all the boilerplate code required to wire the UI up to the Identity system.  You can use the UserManager<T> class to create new user accounts, load them from the database, and change their passwords.  SignInManager<T> is used to sign a user in and out, by assigning the principal for the request and by setting an authentication cookie.  You can update an EF Core DbContext to support Identity by deriving from IdentityDbContext<TUser> where TUser is a class that derives from Identity- User, called ApplicationUser in the default templates.
  • 459.
    431 Summary  You canadd additional claims to a user using the UserManager<TUser> .AddClaimAsync(TUser user, Claim claim) method.  Claims consist of a type and a value. Both values are strings. You can use stan- dard values for types exposed on the ClaimTypes class, such as ClaimTypes .GivenName and ClaimTypes.FirstName, or you can use a custom string, such as "FullName".
  • 460.
    432 Authorization: securing your application Inchapter 14, I showed how to add users to an ASP.NET Core application by add- ing authentication. With authentication, users can register and log in to your app using an email and password. Whenever you add authentication to an app, you inevitably find you want to be able to restrict what some users can do. The process of determining whether a user can perform a given action on your app is called authorization. This chapter covers  Using authorization to control who can use your app  Using claims-based authorization with policies  Creating custom policies to handle complex requirements  Authorizing a request depending upon the resource being accessed  Hiding elements from a Razor template that the user is unauthorized to access
  • 461.
    433 Introduction to authorization Onan e-commerce site, for example, you may have admin users who are allowed to add new products and change prices, sales users who are allowed to view completed orders, and customer users who are only allowed to place orders and buy products. In this chapter, I show how to use authorization in an app to control what your users can do. In section 15.1, I introduce authorization and put it in the context of a real-life scenario you’ve probably experienced: an airport. I describe the sequence of events, from checking in, passing through security, to entering an airport lounge, and how these relate to the authorization concepts you’ll see in this chapter. In section 15.2, I show how authorization fits into an ASP.NET Core web applica- tion and how it relates to the ClaimsPrincipal class that you saw in the previous chap- ter. You’ll see how to enforce the simplest level of authorization in an ASP.NET Core app, ensuring that only authenticated users can execute an MVC action. We’ll extend that approach in section 15.3 by adding in the concept of policies. These let you make specific requirements about a given authenticated user, requiring that they have a specific claim in order to execute an action. You’ll use policies extensively in the ASP.NET Core authorization system, so in sec- tion 15.4, we explore how to handle more complex scenarios. You’ll learn about authorization requirements and handlers, and how you can combine them to create specific policies that you can apply to your MVC actions. Sometimes, whether a user is authorized for an action depends on which resource or document they’re attempting to access. A resource is anything that you’re trying to protect, so it could be a document or a post in a social media app. For example, you may allow users to create documents and read documents from other users, but only to edit documents that they created themselves. This type of authorization, where you need the details of the document to determine authoriza- tion, is called resource-based authorization, and is the focus of section 15.5. In the final section of this chapter, I show how you can extend the resource-based authorization approach to your MVC view models and Razor templates. This lets you modify the UI to hide elements that users aren’t authorized to interact with. In particu- lar, you’ll see how to hide the Edit button when a user isn’t authorized to edit the entity. We’ll start by looking more closely at the concept of authorization, how it differs from authentication, and how it relates to real-life concepts you might see in an airport. 15.1 Introduction to authorization For people who are new to web apps and security, authentication and authorization can sometimes be a little daunting. It certainly doesn’t help that the words look so sim- ilar! The two concepts are often used together, but they’re definitely distinct:  Authentication—The process of determining who made a request  Authorization—The process of determining whether the requested action is allowed
  • 462.
    434 CHAPTER 15Authorization: securing your application Typically, authentication occurs first, so that you know who is making a request to your app. For traditional web apps, your app authenticates a request by checking the encrypted cookie that was set when the user logged in (as you saw in the previous chapter). Web APIs typically use a header instead of a cookie for authentication, but the process is the same. Once a request is authenticated and you know who is making the request, you can determine whether they’re allowed to execute an action on your server. This process is called authorization and is the focus of this chapter. Before we dive into code and start looking at authorization in ASP.NET Core, I’ll put these concepts into a real-life scenario you’re hopefully familiar with: checking in at an airport. To enter an airport and board a plane, you must pass through several steps. In simplified form, these might look like: 1 Show passport at check-in desk. Receive a boarding pass. 2 Show boarding pass to enter security. Pass through security. 3 Show frequent flyer card to enter the airline lounge. Enter lounge. 4 Show boarding pass to board flight. Enter airplane. Obviously, these steps, also shown in figure 15.1, will vary somewhat (I don’t have a frequent flyer card!), but we’ll go with them for now. Let’s explore each step a little further. When you arrive at the airport, the first thing you do is go to the check-in counter. Here, you can purchase a plane ticket, but in order to do so, you need to prove who you are by providing a passport; you authenticate yourself. If you’ve forgotten your pass- port, you can’t authenticate, and you can’t go any further. Once you’ve purchased your ticket, you’re issued a boarding pass, which says which flight you’re on. We’ll assume it also includes a BoardingPassNumber. You can think of this number as an additional claim associated with your identity. DEFINITION A claim is a piece of information about a user that consists of a type and an optional value. The next step is security. The security guards will ask you to present your boarding pass for inspection, which they’ll use to check that you have a flight and so are allowed deeper into the airport. This is an authorization process: you must have the required claim (a BoardingPassNumber) to proceed. If you don’t have a valid BoardingPassNumber, there are two possibilities for what happens next:  If you haven’t yet purchased a ticket—You’ll be directed back to the check-in desk, where you can authenticate and purchase a ticket. At that point, you can try to enter security again.  If you have an invalid ticket—You won’t be allowed through security and there’s nothing else you can do. If, for example, you show up with a boarding pass a week late for your flight, they probably won’t let you through. (Ask me how I know!)
  • 463.
    435 Introduction to authorization Onceyou’re through security, you need to wait for your flight to start boarding, but unfortunately there aren’t any seats free. Typical! Luckily, you’re a regular flier, and you’ve notched up enough miles to achieve a “Gold” frequent flyer status, so you can use the airline lounge. You head to the lounge, where you’re asked to present your Gold Frequent Flyer card to the attendant and they let you in. This is another example of authorization. You must have a FrequentFlyerClass claim with a value of Gold in order to proceed. Rejected Rejected Rejected Purchase ticket at check-in desk Pass through Security Present claims: boarding pass number Present claims: gold frequent flyer class Enter airline lounge Present claims: boarding pass number Board airplane At security, you must present your boarding pass number to proceed. If you don’t have a valid boarding pass, you’re not authorized to pass through security. To enter the airline lounge, you must present a Gold frequent flyer class card. If you’re not authorized, you can’t enter the airline lounge. To board the airplane, you must present a boarding pass. If you’re authorized, you can board the plane. Figure 15.1 When boarding a plane at an airport, you pass through a number of authorization steps. At each authorization step, you must present a claim in the form of a boarding pass or a frequent flyer card. If you’re not authorized, then access will be denied.
  • 464.
    436 CHAPTER 15Authorization: securing your application NOTE You’ve used authorization twice so far in this scenario. Each time, you presented a claim in order to proceed. In the first case, the presence of any BoardingPassNumber was sufficient, whereas for the FrequentFlyerClass claim, you needed the specific value of Gold. When you’re boarding the airplane, you have one final authorization step, in which you must present the BoardingPassNumber claim again. You presented this claim ear- lier, but boarding the aircraft is a distinct action from entering security, so you have to present it again. This whole scenario has lots of parallels with requests to a web app:  Both processes start with authentication.  You have to prove who you are in order to retrieve the claims you need for autho- rization.  You use authorization to protect sensitive actions like entering security and the airline lounge. I’ll reuse this airport scenario throughout the chapter to build a simple web applica- tion that simulates the steps you take in an airport. We’ve covered the concept of authorization in general, so in the next section, we’ll look at how authorization works in ASP.NET Core. You’ll start with the most basic level of authorization, ensuring only authenticated users can execute an action, and look at what happens when you try to execute such an action. 15.2 Authorization in ASP.NET Core The ASP.NET Core framework has authorization built in,1 so you can use it anywhere in your app, but it’s most common to apply authorization as part of MVC. For both tra- ditional web apps and web APIs, users execute actions on your controllers. Authoriza- tion occurs before these actions execute, as shown in figure 15.2. This lets you use different authorization requirements for different action methods. As you can see in figure 15.2, authorization occurs as part of MvcMiddleware, after AuthenticationMiddeware has authenticated the request. You saw in chapter 13 that authorization occurs as part of the MVC filter pipeline, in the (cunningly named) authorization filters. 15.2.1 Preventing anonymous users from accessing your application When you think about authorization, you typically think about checking whether a particular user has permission to execute an action. In ASP.NET Core, you’d achieve this by checking whether a user has a particular claim. 1 You can find the authentication and authorization packages for ASP.NET Core on GitHub at https://github .com/aspnet/security.
  • 465.
    437 Authorization in ASP.NETCore There’s an even more basic level of authorization that you haven’t considered yet— only allowing authenticated users to execute an action. This is even simpler than the claims scenario (which we’ll come to later) as there are only two possibilities:  The user is authenticated—The action executes as normal.  The user is unauthenticated—The user can’t execute the action. You can achieve this basic level of authorization by using the [Authorize] attribute, as you saw in chapter 13, when we discussed authorization filters. You can apply this attri- bute to your actions, as shown in the following listing, to restrict them to authenti- cated (logged-in) users only. If an unauthenticated user tries to execute an action protected with the [Authorize] attribute in this way, they’ll be redirected to the login page. public class HomeController : Controller { public IActionResult Index() { return View(); } [Authorize] Listing 15.1 Applying [Authorize] to an action Static file middleware Authentication middleware Index action method Authorize filter Index view A request is made to the URL /recipe/index. MvcMiddleware The authentication middleware deserializes the ClaimsPrincipal from the encrypted cookie. The authorize filter runs after routing but before model binding or validation. If authorization is successful, the action method executes and generates a response as normal. If authorization fails, the authorize filter returns an error to the user, and the action is not executed. Figure 15.2 Authorization occurs before an action method executes as part of the MVC filter pipeline in MvcMiddleware. This action can be executed by anyone, even when not logged in. Applies [Authorize] to individual actions or whole controllers
  • 466.
    438 CHAPTER 15Authorization: securing your application public IActionResult AuthedUsersOnly() { return View(); } } You can apply the [Authorize] attribute at the action scope, controller scope, or glob- ally, as you saw in chapter 13. Any action that has the [Authorize] attribute applied in this way can be executed only by an authenticated user. Unauthenticated users will be redirected to the login page. Sometimes, especially when you apply the [Authorize] attribute globally, you might need to “poke holes” in this authorization requirement. If you apply the [Authorize] attribute globally, then any unauthenticated request will be redirected to the login page for your app. But the [Authorize] attribute is global, so when the login page tries to load, you’ll be unauthenticated and redirected to the login page again. And now you’re stuck in an infinite redirect loop. To get around this, you can designate specific actions to ignore the [Authorize] attribute by applying the [AllowAnonymous] attribute to an action, as shown next. This allows unauthenticated users to execute the action, so you can avoid the redirect loop that would otherwise result. [Authorize] public class AccountController : Controller { public IActionResult ManageAccount() { return View(); } [AllowAnonymous] public IActionResult Login() { return View(); } } WARNING If you apply the [Authorize] attribute globally, be sure to add the [AllowAnonymous] attribute to your login actions, error actions, password reset actions, and any other actions that you need unauthenticated users to execute. If an unauthenticated user attempts to execute an action protected by the [Authorize] attribute, traditional web apps will redirect them to the login page. But what about web APIs? And what about more complex scenarios, where a user is logged in but doesn’t have the necessary claims to execute an action? In the next section, we’ll look at how the ASP.NET Core authentication services handle all of this for you. Listing 15.2 Applying [AllowAnonymous] to allow unauthenticated access This action can only be executed by authenticated users. Applied at the controller scope, so user must be authenticated for all actions on the controller. Only authenticated users may execute ManageAccount. [AllowAnonymous] overrides [Authorize] to allow unauthenticated users. Login can be executed by anonymous users.
  • 467.
    439 Authorization in ASP.NETCore 15.2.2 Handling unauthorized requests In the previous section, you saw how to apply the [Authorize] attribute to an action to ensure only authenticated users can execute it. In section 15.3, we’ll look at more complex examples that require you to also have a specific claim. In both cases, you must meet one or more authorization requirements (for example, you must be authenticated) to execute the action. If the user meets the authorization requirements, then they can execute the action as normal and the request continues down the MVC filter pipeline. If they don’t meet the requirements, the authorization filter will short-circuit the request. Depending on why they failed authorization, the authorization filter will return one of two different types of IActionResult:  ChallengeResult—This indicates that the user was not authorized to execute the action because they weren’t yet logged in.  ForbidResult—This indicates that the user was logged in but didn’t meet the requirements to execute the action. They didn’t have a required claim, for example. NOTE If you apply the [Authorize] attribute in basic form, as you did in sec- tion 15.2.1, then the filter will only create ChallengeResults. The filter will return ChallengeResult for unauthenticated users, but authenticated users will always be authorized. When authorization fails, the authorization filter short-circuits the MVC filter pipe- line, and the ChallengeResult or ForbidResult executes to generate a response. This response passes back through the middleware pipeline as usual. The exact response generated depends on the type of application you’re building, and so the type of authentication your application uses. For traditional web apps using cookie authentication, such as when you use ASP.NET Core Identity, as in chapter 14, ChallengeResult and ForbidResult redi- rect users to a different page in your application. ChallengeResult indicates the user isn’t yet authenticated, so they’re redirected to the login page for the app. After log- ging in, they can attempt to execute the protected resource again. ForbidResult means the request was from a user that already logged in, but they’re still not allowed to execute the action. Consequently, the user is redirected to a Forbidden or Access Denied web page, as shown in figure 15.3, which informs them they can’t execute the action. The preceding behavior is typical for traditional web apps, but web APIs typically use a different approach to authentication, as you saw in chapter 14. Instead of log- ging in using the web API directly, you’d typically log in to a third-party application that provides a token to the client-side SPA or mobile app. The client-side app sends this token when it makes a request to your web API. Authenticating a request for a web API using tokens is essentially identical to a tra- ditional web app that uses cookies; AuthenticationMiddleware deserializes the
  • 468.
    440 CHAPTER 15Authorization: securing your application cookie or token to create the ClaimsPrincipal. The difference is in how a web API handles ChallengeResult and ForbidResult. When a web API app executes a ChallengeResult, it returns a 401 Unauthorized error response to the caller. Similarly, when the app executes a ForbidResult, it returns a 403 Forbidden response. The traditional web app essentially handled these errors by automatically redirecting unauthorized users to the login or Access Denied page, but the web API doesn’t do this. It’s up to the client-side SPA or mobile app to detect these errors and handle them as appropriate. The important takeaway from all this is that the framework largely handles these intricacies for you. Whether you’re building a web API or a traditional MVC web app, the authorization code looks the same in both cases. Apply your [Authorize] attri- butes as normal and let the framework take care of the differences for you. NOTE In chapter 14, you saw how to configure ASP.NET Core Identity as a traditional web app. This chapter assumes you’re building a traditional web app, but it’s equally applicable if you’re building a web API. Remember that a web API will return 401 and 403 status codes directly, instead of redirecting unauthorized users. You’ve seen how to apply the most basic authorization requirement—restricting an action to authenticated users only—but most apps need something more subtle than this all-or-nothing approach. Consider the airport scenario from section 15.1. Being authenticated (having a passport) isn’t enough to get you through security. Instead, you also need a specific claim: BoardingPassNumber. In the next section, we’ll look at how you can implement a similar requirement in ASP.NET Core. 15.3 Using policies for claims-based authorization In the previous chapter, you saw that authentication in ASP.NET Core centers around a ClaimsPrincipal object, which represents the user. This object has a collection of claims that contain pieces of information about the user, such as their name, email, and date of birth. Figure 15.3 In traditional web apps using cookie authentication, if you don’t have permission to execute an action and you’re already logged in, you’ll be redirected to an Access Denied page.
  • 469.
    441 Using policies forclaims-based authorization You can use these to customize the app for each user, by displaying a welcome mes- sage addressing the user by name, but you can also use claims for authorization. For example, you might only authorize a user if they have a specific claim (such as Boarding- PassNumber) or if a claim has a specific value (FrequentFlyerClass claim with the value Gold). In ASP.NET Core, the rules that define whether a user is authorized are encapsu- lated in a policy. DEFINITION A policy defines the requirements you must meet in order for a request to be authorized. Policies can be applied to an action using the [Authorize] attribute, similar to the way you saw in section 15.2.1. This listing shows a controller and action that represent the first step in the airport scenario. The AirportSecurity action method is pro- tected by an [Authorize] attribute, but you’ve also provided a policy name: "Can- EnterSecurity". public class AirportController : Controller { public IActionResult Index() { return View(); } [Authorize("CanEnterSecurity")] public IActionResult AirportSecurity() { return View(); } } If a user attempts to execute the AirportSecurity action, the authorize filter will ver- ify whether the user satisfies the policy’s requirements (we’ll look at the policy itself shortly). This gives one of three possible outcomes:  The user satisfies the policy.—The action will execute as normal.  The user is unauthenticated.—The user will be redirected to the login page.  The user is authenticated but doesn’t satisfy the policy.—The user will be redirected to a “Forbidden” or “Access Denied” page. These three outcomes correlate with the real-life outcomes you might expect when trying to pass through security at the airport:  You have a boarding pass.—You can enter security as normal.  You’re unauthenticated.—You’re redirected to purchase a ticket.  Your boarding pass is invalid (you turned up a day late, for example).—You’re blocked from entering. Listing 15.3 Applying an authorization policy to an action The Index method can be executed by unauthenticated users. Applying the "CanEnterSecurity" policy using [Authorize] Only users that satisfy the "CanEnterSecurity" policy can execute the AirportSecurity action.
  • 470.
    442 CHAPTER 15Authorization: securing your application Listing 15.3 shows how you can apply a policy to an action using the [Authorize] attribute, but you still need to define the CanEnterSecurity policy. You add policies to an ASP.NET Core application in the ConfigureServices method of Startup.cs, as shown in listing 15.4. First, you add the authorization services using AddAuthorization(), and then you can add policies by calling AddPolicy() on the AuthorizationOptions object. You define the policy itself by calling methods on a provided AuthorizationPolicyBuilder (called policyBuilder here). public void ConfigureServices(IServiceCollection services) { services.AddAuthorization(options => { options.AddPolicy( "CanEnterSecurity", policyBuilder => policyBuilder .RequireClaim("BoardingPassNumber")); }); // Additional service configuration } When you call AddPolicy you provide a name for the policy, which should match the value you use in your [Authorize] attributes, and you define the requirements of the policy. In this example, you have a single simple requirement: the user must have a claim of type BoardingPassNumber. If a user has this claim, whatever its value, then the policy will be satisfied and they’ll be authorized. AuthorizationPolicyBuilder contains several methods for creating simple poli- cies like this, as shown in table 15.1. For example, an overload of the RequireClaim() method lets you specify a specific value that a claim must have. The following would let you create a policy that the "BoardingPassNumber" claim must have a value of "A1234": policyBuilder => policyBuilder.RequireClaim("BoardingPassNumber", "A1234"); Listing 15.4 Adding an authorization policy using AuthorizationPolicyBuilder Table 15.1 Simple policy builder methods on AuthorizationPolicyBuilder Method Policy behavior RequireAuthenticatedUser() The required user must be authenticated. Creates a policy similar to the default [Authorize] attribute, where you don’t set a policy. RequireClaim(claim, values) The user must have the specified claim. Optionally, with one of the specified values. RequireUsername(username) The user must have the specified username. RequireAssertion(function) Executes the provided lambda function, which returns a bool, indicating whether the policy was satisfied. Calls AddAuthorization to configure AuthorizationOptions Adds a new policy Provides a name for the policy Defines the policy requirements using AuthorizationPolicyBuilder
  • 471.
    443 Creating custom policiesfor authorization You can use these methods to build simple policies that can handle basic situations, but often you’ll need something more complicated. What if you wanted to create a policy that enforces only users over the age of 18 can execute the action method? The DateOfBirth claim provides the information you need, but there’s not a sin- gle correct value, so you couldn’t use the RequireClaim() method. You could use the RequireAssertion() method and provide a function that calculates the age from the DateOfBirth claim, but that could get messy pretty quickly. For more complex policies that can’t be easily defined using the RequireClaim() method, I recommend you take a different approach and create a custom policy, as you’ll see in the following section. 15.4 Creating custom policies for authorization You’ve already seen how to create a policy by requiring a specific claim, or requiring a specific claim with a specific value, but often the requirements will be more complex than that. Also, you’ll sometimes need to account for there being multiple ways to sat- isfy a policy, any of which are valid. Let’s return to the airport example. You’ve already configured the policy for pass- ing through security, and now you’re going to configure the policy that controls whether you’re authorized to enter the airline lounge. As you saw in figure 15.1, you’re allowed to enter the lounge if you have a Frequent- FlyerClass claim with a value of Gold. If this was the only requirement, you could use AuthorizationPolicyBuilder to create a policy using: options.AddPolicy("CanAccessLounge", policyBuilder => policyBuilder.RequireClaim("FrequentFlyerClass", "Gold"); But what if the requirements are more complicated than this? For example, you can enter the lounge if Role-based authorization vs. claims-based authorization If you look at all of the methods available on the AuthorizationPolicyBuilder type, you might notice that there’s a method I didn’t mention in table 15.1, Require- Role(). This is a remnant of the role-based approach to authorization used in previ- ous versions of ASP.NET, and I don’t recommend using it. Before Microsoft adopted the claims-based authorization used by ASP.NET Core and recent versions of ASP.NET, role-based authorization was the norm. Users were assigned to one or more roles, such as Administrator or Manager, and authoriza- tion involved checking whether the current user was in the required role. This role-based approach to authorization is possible in ASP.NET Core, but it’s pri- marily for legacy compatibility reasons. Claims-based authorization is the suggested approach. Unless you’re porting a legacy app that uses roles, I suggest you embrace claims-based authorization and leave those roles behind!
  • 472.
    444 CHAPTER 15Authorization: securing your application  You’re a gold-class frequent flyer (have a FrequentFlyerClass claim with value "Gold")  You’re an employee of the airline (have an EmployeeNumber claim)  You’re at least 18 years old (as calculated from the DateOfBirth claim) If you’ve ever been banned from the lounge (you have an IsBannedFromLounge claim), then you won’t be allowed in, even if you satisfy the other requirements. There’s no way of achieving this complex set of requirements with the basic usage of AuthorizationPolicyBuilder you’ve seen so far. Luckily, these methods are a wrapper around a set of building blocks that you can combine to achieve the desired policy. 15.4.1 Requirements and handlers: the building blocks of a policy Every policy in ASP.NET Core consists of one or more requirements, and every require- ment can have one or more handlers. For the airport lounge example, you have a sin- gle policy ("CanAccessLounge"), two requirements (MinimumAgeRequirement and AllowedInLoungeRequirement), and several handlers, as shown in figure 15.4. For a policy to be satisfied, a user must fulfill all the requirements. If the user fails any of the requirements, the authorize filter won’t allow the protected action to be exe- cuted. A user must be allowed to access the lounge and must be over 18 years old. Each requirement can have one or more handlers, which will confirm that the requirement has been satisfied. For example, as shown in figure 15.4, AllowedIn- LoungeRequirement has two handlers that can satisfy the requirement:  FrequentFlyerHandler  IsAirlineEmployeeHandler CanAccessLounge Policy A policy consists of one or more requirements. AllowedInLoungeRequirement Each requirement can have one or more handlers. MinimnumAgeRequirement FrequentFlyerHandler IsAirlineEmployeeHandler BannedFromLoungeHandler DateOfBirthHandler Figure 15.4 A policy can have many requirements, and every requirement can have many handlers.
  • 473.
    445 Creating custom policiesfor authorization If the user satisfies either of these handlers, then AllowedInLoungeRequirement is sat- isfied. You don’t need all handlers for a requirement to be satisfied, you just need one. NOTE Figure 15.4 showed a third handler, BannedFromLoungeHandler, which I’ll cover in the next section. It’s slightly different, in that it can only fail a requirement, not satisfy it. You can use requirements and handlers to achieve most any combination of behavior you need for a policy. By combining handlers for a requirement, you can validate con- ditions using a logical OR: if any of the handlers are satisfied, the requirement is satis- fied. By combining requirements, you create a logical AND: all of the requirements must be satisfied for the policy to be satisfied, as shown in figure 15.5. TIP You can also add multiple policies to an action method by applying the [Authorize] attribute multiple times, for example [Authorize("Policy1"), Authorize("Policy2")]. All policies must be satisfied for the request to be authorized. I’ve highlighted requirements and handlers that will make up your "CanAccess- Lounge" policy, so in the next section, you’ll build each of the components and apply them to the airport sample app. 15.4.2 Creating a policy with a custom requirement and handler You’ve seen all the pieces that make up a custom authorization policy, so in this sec- tion, we’ll explore the implementation of the "CanAccessLounge" policy. CREATING AN IAUTHORIZATIONREQUIREMENT TO REPRESENT A REQUIREMENT As you’ve seen, a custom policy can have multiple requirements, but what is a require- ment in code terms? Authorization requirements in ASP.NET Core are any class that implements the IAuthorizationRequirement interface. This is a blank, marker inter- face, which you can apply to any class to indicate that it represents a requirement. Requirement 1 Requirement 2 AND AND Requirement N AND ... Handler 2A Handler 2B Policy Satisfied? = OR Handler M ... For the policy to be satisfied, every requirement must be satisfied. If any of the handlers are satisfied, the requirement is satisfied. OR OR Figure 15.5 For a policy to be satisfied, every requirement must be satisfied. A requirement is satisfied if any of the handlers are satisfied.
  • 474.
    446 CHAPTER 15Authorization: securing your application If the interface doesn’t have any members, you might be wondering what the requirement class needs to look like. Typically, they’re simple, POCO classes. The fol- lowing listing shows AllowedInLoungeRequirement, which is about as simple as a requirement can get! It has no properties or methods; it implements the required IAuthorizationRequirement interface. public class AllowedInLoungeRequirement : IAuthorizationRequirement { } This is the simplest form of requirement, but it’s also common for them to have one or two properties that make the requirement more generalized. For example, instead of creating the highly specific MustBe18YearsOldRequirement, you instead create a parametrized MinimumAgeRequirement, as shown in the following listing. By providing the minimum age as a parameter to the requirement, you can reuse the requirement for other policies with different minimum age requirements. public class MinimumAgeRequirement : IAuthorizationRequirement { public MinimumAgeRequirement(int minimumAge) { MinimumAge = minimumAge; } public int MinimumAge { get; } } The requirements are the easy part. They represent each of the components of the policy that must be satisfied for the policy to be satisfied overall. CREATING A POLICY WITH MULTIPLE REQUIREMENTS You’ve created the two requirements, so now you can configure the "CanAccess- Lounge" policy to use them. You configure your policies as you did before, in the ConfigureServices method of Startup.cs. Listing 15.7 shows how you could do this by creating an instance of each requirement and passing them to Authorization- PolicyBuilder. The authorization handlers will use these requirement objects when attempting to authorize the policy. public void ConfigureServices(IServiceCollection services) { services.AddAuthorization(options => Listing 15.5 AllowedInLoungeRequirement Listing 15.6 The parameterized MinimumAgeRequirement Listing 15.7 Creating an authorization policy with multiple requirements The interface identifies the class as an authorization requirement. The interface identifies the class as an authorization requirement. The minimum age is provided when the requirement is created. Handlers can use the exposed minimum age to determine whether the requirement is satisfied.
  • 475.
    447 Creating custom policiesfor authorization { options.AddPolicy( "CanEnterSecurity", policyBuilder => policyBuilder .RequireClaim(Claims.BoardingPassNumber)); options.AddPolicy( "CanAccessLounge", policyBuilder => policyBuilder.AddRequirements( new MinimumAgeRequirement(18), new AllowedInLoungeRequirement() )); }); // Additional service configuration } You now have a policy called "CanAccessLounge" with two requirements, so you can apply it to an action method using the [Authorize] attribute, in exactly the same way you did for the "CanEnterSecurity" policy: [Authorize("CanAccessLounge")] public IActionResult AirportLounge() { return View(); } When a user attempts to execute the AirportLounge action, the authorize filter policy will be executed and each of the requirements will be inspected. But you saw earlier that the requirements are purely data; they indicate what needs to be fulfilled, they don’t describe how that has to happen. For that, you need to write some handlers. CREATING AUTHORIZATION HANDLERS TO SATISFY YOUR REQUIREMENTS Authorization handlers contain the logic of how a specific IAuthorizationRequirement can be satisfied. When executed, a handler can do one of three things:  Mark the requirement handling as a success  Not do anything  Explicitly fail the requirement Handlers should implement AuthorizationHandler<T>, where T is the type of requirement they handle. For example, the following listing shows a handler for AllowedInLounge- Requirement that checks whether the user has a claim called FrequentFlyerClass with a value of Gold. public class FrequentFlyerHandler : AuthorizationHandler<AllowedInLoungeRequirement> { protected override Task HandleRequirementAsync( Listing 15.8 FrequentFlyerHandler for AllowedInLoungeRequirement Adds the previous simple policy for passing through security Adds a new policy for the airport lounge, called CanAccessLounge Adds an instance of each IAuthorizationRequirement object The handler implements AuthorizationHandler<T>. You must override the abstract HandleRequirementAsync method.
  • 476.
    448 CHAPTER 15Authorization: securing your application AuthorizationHandlerContext context, AllowedInLoungeRequirement requirement) { if(context.User.HasClaim("FrequentFlyerClass", "Gold")) { context.Succeed(requirement); } return Task.CompletedTask; } } This handler is functionally equivalent to the simple RequireClaim()handler you saw at the start of section 15.4, but using the requirement and handler approach instead. When a user attempts to execute the AirportLounge action method, the authoriza- tion filter added by the [Authorize] attribute validates the "CanAccessLounge" pol- icy. It loops through all of the requirements in the policy, and all of the handlers for each requirement, calling the HandleRequirementAsync method for each. The filter passes in the current AuthorizationHandlerContext and the require- ment to be checked. The current ClaimsPrincipal being authorized is exposed on the context as the User property. In listing 15.8, FrequentFlyerHandler uses the context to check for a claim called FrequentFlyerClass with the Gold value, and if it exists, con- cludes that the user is allowed to enter the airline lounge, by calling Succeed(). NOTE Handlers mark a requirement as being successfully satisfied by calling context.Succeed() and passing in the requirement. It’s important to note the behavior when the user doesn’t have the claim. Frequent- FlyerHandler doesn’t do anything if this is the case (it returns a completed Task to satisfy the method signature). NOTE Remember, if any of the handlers associated with a requirement pass, then the requirement is a success. Only one of the handlers has to succeed for the requirement to be satisfied. This behavior, whereby you either call context.Succeed() or do nothing, is typical for authorization handlers. This listing shows the implementation of IsAirline- EmployeeHandler, which uses a similar claim check to determine whether the require- ment is satisfied. public class IsAirlineEmployeeHandler : AuthorizationHandler<AllowedInLoungeRequirement> { Listing 15.9 IsAirlineEmployeeHandler handler The context contains details such as the ClaimsPrincipal user object. The requirement to handle Checks whether the user has the FrequentFlyerClass claim with the Gold value If the user had the necessary claim, then mark the requirement as satisfied by calling Succeed. If the requirement wasn’t satisfied, do nothing. The handler implements AuthorizationHandler<T>.
  • 477.
    449 Creating custom policiesfor authorization protected override Task HandleRequirementAsync( AuthorizationHandlerContext context, AllowedInLoungeRequirement requirement) { if(context.User.HasClaim(c => c.Type == "EmployeeNumber")) { context.Succeed(requirement); } return Task.CompletedTask; } } This pattern of authorization handler is common,2 but in some cases, instead of checking for a success condition, you might want to check for a failure condition. In the airport example, you don’t want to authorize someone who was previously banned from the lounge, even if they would otherwise be allowed to enter. You can handle this scenario by using the context.Fail() method exposed on the context, as shown in the following listing. Calling Fail() in a handler will always cause the requirement, and hence the whole policy, to fail. You should only use it when you want to guarantee failure, even if other handlers indicate success. public class BannedFromLoungeHandler : AuthorizationHandler<AllowedInLoungeRequirement> { protected override Task HandleRequirementAsync( AuthorizationHandlerContext context, AllowedInLoungeRequirement requirement) { if(context.User.HasClaim(c => c.Type == "IsBanned")) { context.Fail(); } return Task.CompletedTask; } } In most cases, your handlers will either call Succeed() or will do nothing, but the Fail() method is useful when you need this kill-switch to guarantee that a require- ment won’t be satisfied. NOTE Whether a handler calls Succeed(), Fail(), or neither, the authoriza- tion system will always execute all of the handlers for a requirement, so you can be sure your handlers will always be called. 2 I’ll leave the implementation of MinimumAgeHandler for MinimumAgeRequirement as an exercise for the reader. You can find an example in the code samples for the chapter. Listing 15.10 Calling context.Fail() in a handler to fail the requirement You must override the abstract HandleRequirementAsync method. Checks whether the user has the EmployeeNumber claim If the user has the necessary claim, then mark the requirement as satisfied by calling Succeed. If the requirement wasn’t satisfied, do nothing. You must override the abstract HandleRequirementAsync method. The handler implements AuthorizationHandler<T>. Checks whether the user has the IsBanned claim If the user has the claim, then fail the requirement by calling Fail. The whole policy will fail. If the claim wasn’t found, do nothing.
  • 478.
    450 CHAPTER 15Authorization: securing your application The final step to complete your authorization implementation for the app is to regis- ter the authorization handlers with the DI container, as shown in listing 15.11. public void ConfigureServices(IServiceCollection services) { services.AddAuthorization(options => { options.AddPolicy( "CanEnterSecurity", policyBuilder => policyBuilder .RequireClaim(Claims.BoardingPassNumber)); options.AddPolicy( "CanAccessLounge", policyBuilder => policyBuilder.AddRequirements( new MinimumAgeRequirement(18), new AllowedInLoungeRequirement() )); }); services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>(); services.AddSingleton<IAuthorizationHandler, FrequentFlyerHandler>(); services .AddSingleton<IAuthorizationHandler, BannedFromLoungeHandler>(); Services .AddSingleton<IAuthorizationHandler, IsAirlineEmployeeHandler>(); // Additional service configuration } For this app, the handlers don’t have any constructor dependencies, so I’ve registered them as singletons with the container. If your handlers have scoped or transient dependencies (the EF Core DbContext, for example), then you might want to register them as scoped instead, as appropriate. You can combine the concepts of policies, requirements, and handlers in many different ways to achieve your goals for authorization in your application. The exam- ple in this section, although contrived, demonstrates each of the components you need to apply authorization at the action method level, by creating policies and apply- ing the [Authorize] attribute as appropriate. There’s one area, however, where the [Authorize] attribute falls short: resource- based authorization. The [Authorize] attribute uses an authorization filter to autho- rize the user before the action method is executed, but what if you need to authorize the action during the action method? This is common when you’re applying authorization at the document or resource level. If users are only allowed to edit documents they created, then you need to load the document before you can tell whether you’re allowed to edit it! This isn’t possible with the declarative [Authorize] attribute approach, so you must use an alternative, Listing 15.11 Registering the authorization handlers with the DI container
  • 479.
    451 Controlling access withresource-based authorization imperative approach. In the next section, you’ll see how to apply this resource-based authorization to an action method. 15.5 Controlling access with resource-based authorization Resource-based authorization is a common problem for applications, especially when you have users that can create or edit some sort of document. Consider the recipe application you built in the previous 3 chapters. This app lets users create, view, and edit recipes. Up to this point, everyone is able to create new recipes, and anyone can edit a rec- ipe, even if they haven’t logged in. Now you want to add some additional behavior:  Only authenticated users should be able to create new recipes  You can only edit recipes you created You’ve already seen how to achieve the first of these requirements; decorate the Create action method with an [Authorize] attribute and don’t specify a policy, as shown in this listing. This will force the user to authenticate before they can create a new recipe. public class RecipeController : Controller { [Authorize] public IActionResult Create() { return View(new CreateRecipeCommand()); } [HttpPost, Authorize] public async Task<IActionResult> Create(CreateRecipeCommand command) { // Method body not shown for brevity } } TIP When applying the [Authorize] attribute to single actions, it’s import- ant to protect both the GET method and the POST action method. Adding the [Authorize] attribute fulfills your first requirement, but unfortunately, with the techniques you’ve seen so far, you have no way to fulfill the second. You could apply a policy that either permits or denies a user the ability to edit all recipes, but there’s currently no way to restrict this so that a user can only edit their own recipes. In order to find out who created the Recipe, you must first load it from the data- base. Only then can you attempt to authorize the user, taking the specific recipe (resource) into account. The following listing shows a partially implemented method for how this might look, where authorization occurs part way through the method, after the Recipe object has been loaded. Listing 15.12 Adding AuthorizeAttribute to RecipeController.Create Users must be authenticated to execute the Create action. It’s important to protect the HttpPost actions, too.
  • 480.
    452 CHAPTER 15Authorization: securing your application public IActionResult Edit(int id) { var recipe = _service.GetRecipe(id); var createdById = recipe.CreatedById; // Authorize user based on createdById if(isAuthorized) { return View(recipe); } } The [Authorize] attribute can’t help you here; authorization filters run before model binding and before the action method executes. In the next section, you’ll see the approach you need to take to handle these situations and to apply authorization inside the action. WARNING Be careful when exposing the integer ID of your entities in the URL, as in listing 15.13. Users will be able to edit every entity by modifying the ID in the URL to access a different entity. Be sure to apply authorization checks, otherwise you could expose a security vulnerability called Insecure Direct Object Reference.3 15.5.1 Manually authorizing requests with IAuthorizationService All of the approaches to authorization so far have been declarative. You apply the [Authorize] attribute, with or without a policy name, and you let the framework take care of performing the authorization itself. For this recipe-editing example, you need to use imperative authorization, so you can authorize the user after you’ve loaded the Recipe from the database. Instead of applying a marker saying “Authorize this method,” you need to write some of the authorization code yourself. DEFINITION Declarative and imperative are two different styles of programming. Declarative programming describes what you’re trying to achieve, and lets the framework figure out how to achieve it. Imperative programming describes how to achieve something by providing each of the steps needed. ASP.NET Core exposes IAuthorizationService, which you can inject into your con- trollers for imperative authorization. This listing shows how you can update the Edit action shown in listing 15.13 to use the IAuthorizationService and verify whether the action is allowed to continue execution. Listing 15.13 The Edit action must load the Recipe before authorizing the request 3 You can read about this vulnerability and ways to counteract it on the Open Web Application Security Project (OWASP): www.owasp.org/index.php/Top_10_2007-Insecure_Direct_Object_Reference. The id of the recipe to edit is provided by model binding. You must load the Recipe from the database before you know who created it. You must authorize the current user, to verify they’re allowed to edit this specific Recipe. The action method can only continue if the user was authorized.
  • 481.
    453 Controlling access withresource-based authorization public class RecipeController : Controller { private IAuthorizationService _authService public RecipeController(IAuthorizationService authService) { _authService = authService; } [Authorize] public async Task<IActionResult> Edit(int id) { var recipe = _service.GetRecipe(id); var authResult = await _authService .AuthorizeAsync(User, recipe, "CanManageRecipe"); if (!authResult.Succeeded) { return new ForbidResult(); } return View(recipe); } } IAuthorizationService exposes an AuthorizeAsync method, which requires three things to authorize the request:  The ClaimsPrincipal user object, exposed on the Controller as User  The resource being authorized: recipe  The policy to evaluate: "CanManageRecipe" The authorization attempt returns an AuthorizationResult object, which indicates whether the attempt was successful via the Succeeded property. If the attempt wasn’t successful, then you should return a new ForbidResult, which will return a 403 For- bidden response to the user. WARNING In ASP.NET Core 1.x resource-based authorization, you’d return a ChallengeResult instead of a ForbidResult and the framework would gener- ate the appropriate 401 or 403 response, depending on whether you were logged in or not. In the changes that came with ASP.NET Core 2.0, this behavior was lost, so you have to work out whether to return a 401 or 403 yourself. The easiest approach is: mark your action methods with the [Authorize] attribute and always return a ForbidResult. You’ve configured the imperative authorization in the Edit method itself, but you still need to define the "CanManageRecipe" policy that you use to authorize the user. This is the same process as for declarative authorization, so you have to:  Create a policy in ConfigureServices by calling AddAuthorization()  Define one or more requirements for the policy Listing 15.14 Using IAuthorizationService for resource-based authorization IAuthorizationService is injected into the class constructor using DI. Only authenticated users should be allowed to edit recipes. Load the recipe from the database. If authorization failed, returns a 403 Forbidden result Calls IAuthorizationService, providing ClaimsPrinicipal, resource, and the policy name If authorization was successful, continues the action method
  • 482.
    454 CHAPTER 15Authorization: securing your application  Define one or more handlers for each requirement  Register the handlers in the DI container With the exception of the handler, these steps are all identical to the declarative authorization approach with the [Authorize] attribute, so I’ll only run through them briefly here. First, you can create a simple IAuthorizationRequirement. As with all require- ments, this contains no functionality and simply implements the marker interface. public class IsRecipeOwnerRequirement : IAuthorizationRequirement { } Defining the policy in ConfigureServices is similarly simple, as you have only this sin- gle requirement. Note that there’s nothing resource-specific in any of this code so far: public void ConfigureServices(IServiceCollection services) { services.AddAuthorization(options => { options.AddPolicy("CanManageRecipe", policyBuilder => policyBuilder.AddRequirements(new IsRecipeOwnerRequirement())); }); } You’re halfway there; all you need to do now is create an authorization handler for IsRecipeOwnerRequirement and register it with the DI container. 15.5.2 Creating a resource-based AuthorizationHandler Resource-based authorization handlers are essentially the same as the authorization handler implementations you saw in section 15.4.2. The only difference is that the handler also has access to the resource being authorized. To create a resource-based handler, you should derive from the Authorization- Handler<TRequirement, TResource> base class, where TRequirement is the type of requirement to handle, and TResource is the type of resource that you provide when calling IAuthorizationService. Compare this to the AuthorizationHandler<T> class you implemented previously, where you only specified the requirement. This listing shows the handler implementation for your recipe application. You can see that you’ve specified the requirement as IsRecipeOwnerRequirement, the resource as Recipe, and have implemented the HandleRequirementAsync method. public class IsRecipeOwnerHandler : AuthorizationHandler<IsRecipeOwnerRequirement, Recipe> { private readonly UserManager<ApplicationUser> _userManager; public IsRecipeOwnerHandler( UserManager<ApplicationUser> userManager) { _userManager = userManager; } Listing 15.15 IsRecipeOwnerHandler for resource-based authorization Injects an instance of the UserManager<T> class using DI Implements the necessary base class, specifying the requirement and resource type
  • 483.
    455 Controlling access withresource-based authorization protected override async Task HandleRequirementAsync( AuthorizationHandlerContext context, IsRecipeOwnerRequirement requirement, Recipe resource) { var appUser = await _userManager.GetUserAsync(context.User); if(appUser == null) { return; } if(resource.CreatedById == appUser.Id) { context.Succeed(requirement); } } } This handler is slightly more complicated than the examples you’ve seen previously, primarily because you’re using an additional service, UserManager<>, to load the ApplicationUser entity based on ClaimsPrincipal from the request. The other significant difference is that the HandleRequirementAsync method is called with a Recipe resource. This is the same object that you provided when calling AuthorizeAsync on IAuthorizationService. You can use this resource to verify whether the current user created it. If so, you Succeed() the requirement, otherwise you do nothing. Our final task is to add IsRecipeOwnerHandler to the DI container. Your handler uses an additional dependency, UserManager<>, which you know uses EF Core, so you should register the handler as a scoped service: services.AddScoped<IAuthorizationHandler, IsRecipeOwnerHandler>(); TIP If you’re wondering how to know whether you register a handler as scoped or a singleton, think back to chapter 10. Essentially, if you have scoped dependencies, then you must register the handler as scoped; other- wise, singleton is fine. With everything hooked up, you can take the application for a spin. If you try to edit a recipe you didn’t create by clicking the Edit button on the recipe, you’ll either be redirected to the login page (if you hadn’t yet authenticated) or you’ll be presented with an Access Denied page, as shown in figure 15.6. By using resource-based authorization, you’re able to enact more fine-grained authorization requirements that you can apply at the level of an individual document or resource. Instead of only being able to authorize that a user can edit any recipe, you can authorize whether a user can edit this recipe. All of the authorization techniques you’ve seen so far have focused on server-side checks. Both the [Authorize] attribute and resource-based authorization approaches focus on stopping users from executing a protected action on the server. This is As well as the context and requirement, you’re also provided the resource instance. If you aren’t authenticated, then appUser will be null. Checks whether the current user created the Recipe by checking the CreatedById property If the user created the document, Succeed the requirement; otherwise, do nothing.
  • 484.
    456 CHAPTER 15Authorization: securing your application important from a security point of view, but there’s another aspect you should con- sider too: the user experience when they don’t have permission. Now you’ve protected the code executing on the server, but arguably, the Edit but- ton should never have been visible to the user if they weren’t going to be allowed to edit the recipe! In the next section, we’ll look at how you can conditionally hide the Edit button by using resource-based authorization in your view models. 15.6 Hiding elements in Razor templates from unauthorized users All of the authorization code you’ve seen so far has revolved around protecting action methods on the server side, rather than modifying the UI for users. This is important and should be the starting point whenever you add authorization to an app. Yes Is the current user authenticated? Did the current user create the recipe? No No Yes Click Edit button. Access denied. Edit recipe. Login page View recipe 401 Unauthorized 403 Fobidden Figure 15.6 If you’re not authorized to edit a recipe, you’ll be redirected to an Access Denied page. If you’re not logged in, you’ll be redirected to the login page.
  • 485.
    457 Hiding elements inRazor templates from unauthorized users WARNING Malicious users can easily circumvent your UI, so it’s important to always authorize your actions on the server, never on the client alone. From a user-experience point of view, however, it’s not friendly to have buttons or links that look like they’re available but present you with an Access Denied page when they’re clicked. A better experience would be for the links to be disabled, or not visi- ble at all. You can achieve this in several ways in your own Razor templates. In this section, I’m going to show you how to add an additional property to the page view model, CanEditRecipe, which the view template will use to change the rendered HTML. TIP An alternative approach would be to inject IAuthorizationService directly into the view template using the @inject directive, as you saw in chap- ter 10. When you’re finished, the rendered HTML will look unchanged for recipes you cre- ated, but the Edit button will be hidden when viewing a recipe someone else created, as shown in figure 15.7. The following listing shows RecipeDetailViewModel, which is used to render the rec- ipe page shown in figure 15.7. I’ve added an additional property, called CanEdit- Recipe, which you’ll set in the RecipeDetail action method, based on whether the current user can edit the recipe. If the user created the recipe, they can see the edit button for the recipe. For recipes created by other users, the edit button is hidden. Figure 15.7 Although the HTML will appear unchanged, the Edit button is hidden when someone else has created the recipe.
  • 486.
    458 CHAPTER 15Authorization: securing your application public class RecipeDetailViewModel { public int Id { get; set; } public string Name { get; set; } public string Method { get; set; } public DateTimeOffset LastModified { get; set; } public IEnumerable<Item> Ingredients { get; set; } public bool CanEditRecipe {get; set;} public class Item { public string Name { get; set; } public string Quantity { get; set; } } } The View.cshtml Razor template only requires a simple change: adding an if clause around the rendering of the edit link: @if(Model.CanEditRecipe) { <a asp-action="Edit" asp-route-id="@Model.Id" class="btn btn-primary">Edit</a> } The final step is to set the CanEditRecipe value as appropriate in the View action method. But how do you set that value? Hopefully, the previous section has given you a clue—you can use IAuthorization- Service to determine whether the current user has permission to edit the Recipe, by calling AuthorizeAsync, as shown here. public async Task<IActionResult> View(int id) { var recipe = _service.GetRecipe(id); var authResult = await _authService.AuthorizeAsync( User, recipe, "CanManageRecipe"); model.CanEditRecipe = authResult.Succeeded; return View(model); } Instead of blocking execution of the method (as you did in the Edit action), use the result of the call to AuthorizeAsync to set the CanEditRecipe value on the view model. This ensures that only users who will be able to execute the Edit action see the link to that page. Listing 15.16 RecipeDetailViewModel used by the View.cshtml template Listing 15.17 Setting a view model property using IAuthorizationService The CanEditRecipe property will be used to control whether the Edit button is rendered. Loads the Recipe resource for use with IAuthorizationService Verifies whether the user is authorized to edit the Recipe Sets the CanEditRecipe property on the view model as appropriate
  • 487.
    459 Summary WARNING Note thatyou didn’t remove the server-side authorization check from the Edit action. This is important for preventing malicious users from circumventing your UI. With that final change, you’ve finished adding authorization to the recipe application. Anonymous users can browse the recipes created by others, but they must log in to create new recipes. Additionally, users can only edit the recipes that they created, and won’t see an edit link for other people’s recipes. Authorization is a key aspect of most apps, so it’s important to bear it in mind from an early point. Although it’s possible to add authorization later, as you did with the recipe app, it’s normally preferable to consider authorization sooner rather than later in the app’s development. In the next chapter, we’re going to be looking at your ASP.NET Core application from a different point of view. Instead of focusing on the code and logic behind your app, we’re going to look at how you prepare an app for production. You’ll see how to specify the URLs your application uses, and how to publish an app so that it can be hosted in IIS. Finally, you’ll learn about the bundling and minification of client-side assets, why you should care, and how to use BundlerMinifier in ASP.NET Core. Summary  Authorization is the process of determining what a user can do. It’s distinct from authentication, the process of determining who a user is. Authentication typically occurs before authorization.  You can use authorization in any part of your application, but it’s typically applied to MVC controllers and actions, using the [Authorize] attribute to control which actions a user can execute.  The simplest form of authorization requires that a user is authenticated before executing an action. You can achieve this by applying the [Authorize] attribute to an action, to a controller, or globally.  Claims-based authorization uses the current user’s claims to determine whether they’re authorized to execute an action. You define the claims needed to exe- cute an action in a policy.  Policies have a name and are configured in Startup.cs as part of the call to AddAuthorization() in ConfigureServices. You define the policy using Add- Policy(), passing in a name and a lambda that defines the claims needed.  You can apply a policy to an action or controller by specifying the policy in the authorize attribute, for example [Authorize("CanAccessLounge")].  If an unauthenticated user attempts to execute a protected action, they’ll typi- cally be redirected to the login page for your app. If they’re already authenti- cated, but don’t have the required claims, they’ll be shown an Access Denied page instead.
  • 488.
    460 CHAPTER 15Authorization: securing your application  For complex authorization policies, you can build a custom policy. A custom policy consists of one or more requirements, and a requirement can have one or more handlers.  For a policy to be authorized, every requirement must be satisfied. For a requirement to be satisfied, one or more of the associated handlers must indi- cate success, and none must indicate explicit failure.  AuthorizationHandler<T> contains the logic that determines whether a requirement is satisfied. If a requirement requires that users are over 18, the handler could look for a DateOfBirth claim and calculate the user’s age.  Handlers can mark a requirement as satisfied by calling context.Succeed (requirement). If a handler can’t satisfy the requirement, then it shouldn’t call anything on the context, as a different handler could call Succeed() and satisfy the requirement.  If a handler calls context.Fail(), then the requirement will fail, even if a dif- ferent handler marked it as a success using Succeed().  Resource-based authorization uses details of the resource being protected to determine whether the current user is authorized. For example, if a user is only allowed to edit their own documents, then you need to know the author of the document before you can determine whether they’re authorized.  Resource-based authorization uses the same policy, requirements, and handlers system as before. Instead of applying authorization with the [Authorize] attri- bute, you must manually call IAuthorizationService and provide the resource you’re protecting.  You can modify the user interface to account for user authorization by adding additional properties to your view models. If a user isn’t authorized to execute an action, you can remove or disable the link to that action method in the UI. You should always authorize on the server, even if you’ve removed links from the UI.
  • 489.
    461 Publishing and deploying yourapplication We’ve covered a vast amount of ground so far in this book. We’ve gone over the basic mechanics of building an ASP.NET Core application, such as configuring dependency injection, loading app settings, and building a middleware pipeline. We’ve looked at the UI side, using Razor templates and layouts to build an HTML response. And we’ve looked at higher-level abstractions, such as EF Core and ASP.NET Core Identity, that let you interact with a database and add users to your application. This chapter covers  Publishing an ASP.NET Core application  Hosting an ASP.NET Core application in IIS  Customizing the URLs for an ASP.NET Core app  Optimizing client-side assets with bundling and minification
  • 490.
    462 CHAPTER 16Publishing and deploying your application In this chapter, we’re taking a slightly different route. Instead of looking at ways to build bigger and better applications, we focus on what it means to deploy your appli- cation so that users can access it. You’ll learn about  The process of publishing an ASP.NET Core application so that it can be deployed to a server.  How to prepare a reverse proxy (IIS) to host your application.  How to optimize your app so that it’s performant once deployed. We start by looking again at the ASP.NET Core hosting model in section 16.1 and examining why it’s a good idea to host your application behind a reverse proxy instead of exposing your app directly to the internet. I show you the difference between run- ning an ASP.NET Core app in development using dotnet run and publishing the app for use on a remote server. Finally, I describe some of the options available to you when deciding how and where to deploy your app. In section 16.2, I show you how to deploy your app to one such option, a Windows server running IIS (Internet Information Services). This will be a typical deployment scenario for many developers already familiar with ASP.NET, so it acts as a useful case study, but it’s certainly not the only possibility. I won’t go into all the technical details of configuring the venerable IIS system, but I’ll show the bare minimum required to get it up and running. If your focus is cross-platform development, then don’t worry, I don’t dwell on IIS for too long! In section 16.3, I provide an introduction to hosting on Linux. You’ll see how it dif- fers from hosting applications on Windows, learn the changes you need to make to your apps, and find out about some gotchas to look out for. I describe how reverse proxies on Linux differ from IIS and point you to some resources you can use to con- figure your environments, rather than giving exhaustive instructions in this book. If you’re not hosting your application using IIS, then you’ll likely need to set the URL that your ASP.NET Core app is using when you deploy your application. In sec- tion 16.4, I show two approaches to this: using the special ASPNETCORE_URLS environ- ment variable and loading the URLs using ConfigurationBuilder. Although generally not an issue during