0% found this document useful (0 votes)
18 views110 pages

Porting ASP.NET Apps to .NET 6 Guide

The document is a guide for developers on porting existing ASP.NET applications to .NET 6, detailing migration considerations, architectural differences, and strategies for incremental migration. It emphasizes the advantages of .NET Core, such as cross-platform support, performance improvements, and maintainability, while also addressing scenarios where remaining on .NET Framework may be appropriate. The guide includes practical examples, deployment strategies, and additional resources to assist in the migration process.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
18 views110 pages

Porting ASP.NET Apps to .NET 6 Guide

The document is a guide for developers on porting existing ASP.NET applications to .NET 6, detailing migration considerations, architectural differences, and strategies for incremental migration. It emphasizes the advantages of .NET Core, such as cross-platform support, performance improvements, and maintainability, while also addressing scenarios where remaining on .NET Framework may be appropriate. The guide includes practical examples, deployment strategies, and additional resources to assist in the migration process.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Contents

Port existing [Link] Apps to .NET 6


Introduction to porting apps to .NET 6
Migration considerations
Migrate to [Link] Core 2.1
Choose the right .NET Core version
Strategies for migrating incrementally
Strategies for migrating [Link] Web Forms apps
Deployment strategies
Additional migration resources
Architectural differences between [Link] MVC and [Link] Core
App startup differences
Hosting differences
Serve static files
Dependency injection differences
Middleware, modules, and handlers
Configuration differences
Routing differences
Logging differences
Compare Razor Pages to [Link] MVC
Web API differences
Authentication differences
Identity differences
Controller differences
Razor differences
SignalR differences
Testing differences
Migrate large solutions
Identify sequence of projects to migrate
Understand and update dependencies
Strategies for migrating while running in production
Example migration of eShop project
More migration scenarios
Deployment scenarios
Summary - Port existing [Link] Apps to .NET 6
Porting Existing [Link] Apps to .NET 6
6/16/2022 • 3 minutes to read • Edit Online

PUBLISHED BY
Microsoft Developer Division, .NET, and Visual Studio product teams
A division of Microsoft Corporation
One Microsoft Way
Redmond, Washington 98052-6399
Copyright © 2022 by Microsoft Corporation
All rights reserved. No part of this book's contents may be reproduced or transmitted in any form or by any
means without the written permission of the publisher.
This book is provided "as-is" and expresses the author's views and opinions. The views, opinions, and
information expressed in this book, including URL and other Internet website references, may change without
notice.
Some examples depicted herein are provided for illustration only and are fictitious. No real association or
connection is intended or should be inferred.
Microsoft and the trademarks listed at [Link] on the "Trademarks" webpage are trademarks
of the Microsoft group of companies.
Mac and macOS are trademarks of Apple Inc.
The Docker whale logo is a registered trademark of Docker, Inc. Used by permission.
All other marks and logos are property of their respective owners.
Authors:

Steve "ardalis" Smith , Software Architect and Trainer - [Link]

Participants and Reviewers:

Nish Anil , Senior Program Manager, .NET team, Microsoft

Mike Rousos , Principal Software Engineer, .NET team, Microsoft

Scott Addie , Senior Content Developer, .NET team, Microsoft

David Pine , Senior Content Developer, .NET team, Microsoft

Version
This guide covers *.NET 6 and updates related to the same technology "wave" (that is, Azure and other third-
party technologies) coinciding in time with the .NET 6 release. This book covers migration of apps that are
currently running on .NET Framework 4.x. Migrating from .NET Framework 4.x to .NET 6 is similar to migrating
to .NET Core 3.1, which is discussed as a potential intermediate step later in the book. For more information, see
choosing the right .NET Core version.

Who should use this guide


This guide's audience is developers, development leads, and architects who are interested in migrating their
existing apps written for [Link] MVC and Web API (.NET Framework 4.x) to .NET Core. [Link] Web Forms
developers will benefit from this guide but should also read the Blazor for [Link] Web Forms Developers e-
book.
A secondary audience is technical decision-makers planning when to move their apps to .NET Core.
The target audience for this book is .NET developers with large, existing apps that run on [Link] MVC and Web
API. Apps built on [Link] Web Forms are outside of the focus of this book, though much of the information
comparing .NET Framework and .NET Core may still be relevant.

How you can use this guide


You can read this book straight through, as we expect many readers to do. This book will provide you first with
considerations for whether you should port your app at all. That content is followed by architectural differences
between .NET Framework and .NET Core. From there, you'll learn strategies for migrating a large solution over
time and how to port a real app. Next, the book includes deployment scenarios that address the need to run
different apps while appearing as a single app to users. The book concludes with two case studies describing
real apps that have migrated from [Link] MVC to [Link] Core.
Whether or not you choose to start from the first chapter, you can reference any of these chapters to learn about
specific concepts:
Architectural differences
Migrate large solutions
Sample migration
Deployment scenarios
This guide is available both in PDF form and online. Feel free to forward this document or links to its online
version to your team to ensure a common understanding of these concepts.

Send your feedback


This book and related samples are constantly evolving, so your feedback is welcomed! If you have comments
about how this book can be improved, use the feedback section at the bottom of any page built on GitHub
issues.

NE XT
Introduction to porting apps to .NET 6
6/16/2022 • 2 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

.NET Core and its latest version, .NET 6, represent a revolutionary step forward from .NET Framework. It offers a
host of advantages over .NET Framework across the board from productivity to performance, from cross-
platform support to developer satisfaction. [Link] Core was even voted the most-loved web framework in the
2020 Stack Overflow developer survey. Clearly there are strong reasons to consider migrating.
Even before .NET 6 shipped, Microsoft was clear: .NET Core is the Future of .NET. To quote this article:

New apps should be built on .NET Core. .NET Core is where future investments in .NET will happen. Existing
apps are safe to remain on .NET Framework which will be supported. Existing apps that want to take
advantage of the new features in .NET should consider moving to .NET Core. As we plan into the future, we
will be bringing in even more capabilities to the platform.

Today, .NET 6 is what new apps should target, and if you're migrating an existing app from .NET Framework,
.NET 6 is your ideal target framework.
However, upgrading your app to [Link] Core will require some effort. That effort should be balanced against
business value and goals. .NET Framework apps have a long life ahead of them, with support built into Windows
for the foreseeable future. What are some of the questions you should consider before deciding migration to
.NET 6 is appropriate? What are the expected advantages? What are the tradeoffs? How much effort is involved?
These obvious questions are just the beginning, but make for a great starting point as teams consider how to
support their customers' needs with apps today and tomorrow.
Is migration to .NET 6 appropriate?
When does it make sense to remain on .NET Framework?
Should apps target [Link] Core 2.1 as a stepping stone?
How should teams choose the right .NET version to target?
What strategies are recommended for incremental migration of large apps?
What deployment strategies should be considered when porting to .NET 6?
Where can we find additional resources?
This introductory chapter addresses all of these questions and more before moving on to more specific and
technical considerations in future chapters.
References
2020 Stack Overflow developer survey most loved web frameworks
.NET Core is the Future of .NET

PR EVIO U S NE XT
Migration considerations
6/16/2022 • 5 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

The most fundamental question teams must answer when it comes to porting their apps to .NET Core is, should
they port at all? In some cases, the best path forward is to remain on .NET Framework using [Link] MVC
and/or Web API. This chapter considers reasons why moving to .NET Core makes sense. The chapter also
considers scenarios and counterpoints for staying on .NET Framework.

Is migration to .NET Core appropriate?


Let's start with some of the reasons why you might want to move to .NET Core/.NET 6. There are quite a few, so
don't consider this list exhaustive.
Cross-platform support
Apps built on .NET Core are truly cross-platform and can run on Windows, Linux, and macOS. Not only can your
developers use whatever hardware they want, but you can also host your app anywhere. Examples range from
local IIS to Azure in the cloud or from Linux Docker containers to IoT devices.
Performance and scalability
Apps built with .NET Core are running on one of the fastest tech stacks available anywhere. [Link] MVC apps
often see performance improvements on [Link] Core, especially if they're updated to take advantage of some
new features available in .NET Core.
Cloud-native
For the above reasons and others, .NET Core apps are well-suited to running in cloud hosting environments.
Lightweight and fast, .NET Core apps can be deployed to Azure App Services or containers and scaled
horizontally as needed to meet immediate system demand.
Maintainable
For many apps, while they've continued to meet customer and business needs, technical debt has accumulated
and maintaining the app has grown expensive. [Link] Core apps are more easily tested than [Link] MVC
apps, making them easier to refactor and extend with confidence.
Modular
[Link] Core is modular, using NuGet packages as a first-class part of the framework. Apps built for .NET Core
all support dependency injection, making it easy to compose solutions from whatever implementations are
needed for a given environment. Building microservices with .NET Core is easier than with [Link] MVC with its
dependency on IIS, which opens up additional options to break up large apps into smaller modules.
Modern
Staying on a modern, actively developed technology stack has a host of advantages. New features and C#
language features will only be added to .NET Core. The .NET Framework has had its last release with version 4.8,
and versions of C# beyond 8 won't target .NET Framework. While [Link] MVC will remain supported by
Microsoft for many years, the best and brightest .NET software developers are likely looking to use the more
modern .NET Core framework, with all of the advantages it offers (only some of which are summarized above).
Finding developers with the skills to maintain an [Link] MVC app will start to become a challenge at some
point, as will finding online training and troubleshooting assistance. There probably aren't that many new blog
posts being written about [Link] MVC 5, while there are plenty being written for .NET 6, for example.
There are many compelling reasons to consider migrating to .NET Core, which presumably is why you're reading
this book! But let's consider some disadvantages and reasons why it may make more sense to remain on the
.NET Framework.

When is .NET Framework appropriate?


The biggest reason to stay on .NET Framework is when an app isn't under active development and wouldn't
benefit substantially from the advantages listed above. In that case, there probably isn't a good business case to
incur the cost of porting the app. If your app might benefit from the advantages .NET Core offers, you may still
need to stay on .NET Framework if you need certain technologies that are unavailable on .NET Core. There are
some .NET technologies that are unavailable on .NET Core, including AppDomains, Remoting, Code Access
Security (CAS), Security Transparency, and [Link] . A brief summary of these technologies
and their alternatives is included here. For more detailed guidance, see the documentation.

NOTE
Code Access Security (CAS) has been deprecated across all versions of .NET Framework and .NET. Recent versions of .NET
do not honor CAS annotations and produce errors if CAS-related APIs are used. Developers should seek alternative
means of accomplishing security tasks.

Application domains
Application domains (AppDomains) isolate apps from one another. AppDomains require runtime support and
can be expensive. Creating additional app domains isn't supported, and there are no plans to add this capability
to .NET Core in the future. For code isolation, use separate processes or containers as an alternative. Some
customers use AppDomains as a way of unloading assemblies. In .NET Core AssemblyLoadContext provides an
alternative way to unload assemblies.
WCF
.NET Core and .NET 5+ support WCF clients. Server-side WCF is possible through CoreWCF, which is officially
supported by Microsoft as of April 2022. Apps that require server-side WCF functionality can also consider a
different communication technology (such as gRPC or REST) as part of a migration.
There is a WCF client port available from the .NET Foundation. It's entirely open source, cross platform, and
supported by Microsoft.
To learn more about migrating from WCF to gRPC, consult the gRPC for WCF Developers ebook.
Remoting
.NET Remoting was identified as a problematic architecture. It's used for cross-AppDomain communication,
which is no longer supported. Also, Remoting requires runtime support, which is expensive to maintain. For
these reasons, .NET Remoting isn't supported on .NET Core, and the product team doesn't plan on adding
support for it in the future. There are several alternative messaging strategies and implementations you can use
to replace remoting in your .NET Core apps.
Code Access Security (CAS ) and Security Transparency
Neither of these technologies are supported by .NET Core. Instead, the recommendation is to use security
boundaries provided by the operating system. For example, virtualization, containers, or user accounts. Run
processes with the minimal set of privileges necessary.

References
.NET Framework Technologies Unavailable on .NET Core

PR EVIO U S NE XT
Migrate to [Link] Core 2.1
6/16/2022 • 2 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

[Link] Core 2.1 is an interesting release because it's the most recently supported [Link] Core release that
supported both .NET Core and .NET Framework runtimes. As such, it may offer an easier upgrade path for some
apps when compared to upgrading all parts of the app to .NET Core/.NET 6 at once. Although support for .NET
Core 2.1 ended in August 2021, it may make sense as an interim step for some apps. Also, support for [Link]
Core 2.1 running on .NET Framework will continue for as long as its underlying .NET Framework is supported. A
complete list of currently supported [Link] Core 2.1 packages is available for reference.

Should apps run on .NET Framework with [Link] Core 2.1


[Link] Core 2.2 and earlier supported both .NET Core and .NET Framework runtimes. Does it make sense to
migrate some or all of an app to [Link] Core 2.1 as a stepping stone, before porting over completely to .NET
Core? Apps, or subsets of apps, could see their front-end [Link] logic ported to use [Link] Core, while still
consuming .NET Framework libraries for business logic and infrastructure consumption. This approach may
make sense when there's a relatively thin UI layer without much business logic, and a much larger set of
functionality in class libraries.
The main benefit of porting just the front-end web layer to [Link] Core 2.1 is that the existing .NET class
libraries can remain as is during the initial migration. They may be in continued use by other .NET apps or
simply don't need to be in scope for the first iteration of a planned full migration to .NET Core. Reducing the
scope of the initial migration for large apps helps provide incremental goals that act as stepping stones toward
the desired end state, which is often a complete port to .NET Core.
If you have an existing app that may use this strategy, some things you can do today to help prepare for the
process are to move as much business logic, data access, and other non-UI logic out of the [Link] projects and
into separate class libraries as possible. It will also help if you have automated test coverage of your system, so
that you can verify behavior remains consistent before and after the migration.
If your app is so large that you can't migrate the entire web app at once, and you need to be able to deploy the
new [Link] Core app side-by-side with the existing [Link] app, there are deployment strategies that can be
used to achieve this. These are covered in Chapter 5: Deployment Scenarios.
Keep in mind that [Link] Core 2.1 was the last LTS release of .NET Core that supported running on .NET
Framework and consuming .NET Framework libraries. Although the release is now unsupported on .NET Core, it
continues to be supported for use with .NET Framework. It will remain supported for as long as the specific .NET
Framework version is supported. For more information, see [Link] Core 2.1 on .NET Framework.

References
Migrating from [Link] to [Link] Core 2.1 [Link] Core 2.1 on .NET Framework [Link] Core 2.1 Supported
Packages

PR EVIO U S NE XT
Choose the right .NET Core version
6/16/2022 • 2 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

The largest consideration for most organizations when choosing which version of .NET to target is the support
lifecycle. Long Term Support (LTS) releases ship less frequently but have a longer support window than Current
(non-LTS) releases. Currently, LTS releases are scheduled to ship every other year. Customers can choose which
releases to target, and can install different releases of .NET Core side by side on the same machine. LTS releases
will receive only critical and compatible fixes throughout their lifecycle. Current releases will receive these same
fixes and will also be updated with compatible innovations and features. LTS releases are supported for three
years after their initial release. Current releases are supported for three months after a subsequent Current or
LTS release.
Most customers looking to migrate a large .NET Framework app to .NET Core/.NET 6 today are probably looking
for a stable destination, given that they haven't already made the move to an earlier version of .NET Core. In this
case, the best .NET version to target for the migration is .NET 6, which is the most recent LTS version. While
support for .NET Core 3.1 ends in December 2022, support for .NET 6 will continue until November 2024.
Updating from .NET Core 3.1 to .NET 6+ requires much less effort than porting from .NET Framework to .NET
Core. For this reason, many customers may choose to upgrade to .NET Core 3.1 first as an incremental step, and
then further upgrade to .NET 6 once the upgrade to .NET Core 3.1 has succeeded.
This book assumes .NET Framework apps will be upgraded to .NET 6.

References
.NET and .NET Core Support Policy

PR EVIO U S NE XT
Strategies for migrating incrementally
6/16/2022 • 4 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

The biggest challenge with migrating any large app is determining how to break the process into smaller tasks.
There are several strategies that can be applied for this purpose, including breaking the app into horizontal
layers such as UI, data access, business logic, or breaking up the app into separate, smaller apps. Another
strategy is to upgrade some or all of the app to different framework versions on the way to a recent .NET Core
release. One approach you could use is to migrate vertical slices of the app, rather than attempting to migrate
one horizontal layer at a time. Let's consider each of these different approaches.

Migrating slice by slice


One successful approach to migrating is to identify vertical slices of functionality and migrate them to the target
platform one by one. The first step is to create a new [Link] Core 6 app. Next, identify the individual page or
API endpoint that will be migrated first. Build out just the necessary functionality to support this one route in the
new [Link] Core app. Then use HTTP rewriting and/or a reverse proxy to start sending requests for these
pages or endpoints to the new app rather than the [Link] app. This approach is well-suited to API projects, but
can also work for many MVC apps.
When migrating slice by slice, the entire stack of the individual API endpoint or requested route is recreated in
the new project or solution. The very first such slice typically requires the most effort, since it will typically need
several projects to be created and decisions to be made about data access and solution organization. Once the
first slice's functionality mirrors the existing app's, it can be deployed and the existing app can redirect to it or
simply be removed. This approach is then repeated until the entire app has been ported to the new structure.
Some specific guidance on how to follow this strategy using IIS is covered in Chapter 5, Deployment Scenarios.

Migrating layer by layer


Consider the challenge of migrating a large [Link] 4.5 app. One approach is to migrate the entire app directly
from .NET Framework 4.5 to .NET 6. However, this approach needs to account for every breaking change
between the two frameworks and versions, which are substantial. Performing this work on one project at a time
provides a set of stepping stones so that the entire solution doesn't need to be moved at once.
One piece of the .NET ecosystem that helps with interoperability between different .NET frameworks is .NET
Standard. .NET Standard allows libraries to build against an agreed upon set of common APIs, ensuring they can
be used in any .NET app. .NET Standard 2.0 is notable because it covers most base class library functionality
used by most .NET Framework and .NET Core apps. Unfortunately, the earliest version of .NET with support for
.NET Standard 2.0 is .NET Framework 4.6.1, and there are a number of updates in .NET Framework 4.8 that make
it a compelling choice for initial upgrades.
One approach to incrementally upgrade a .NET Framework 4.5 system layer-by-layer is to first update its class
library dependencies to .NET Framework 4.8. Then, modify these libraries to be .NET Standard class libraries. Use
multi-targeting and conditional compilation, if necessary. This step can be helpful in scenarios where app
dependencies require .NET Framework and cannot easily be ported directly to use .NET Standard and .NET Core.
Since .NET Framework libraries can be consumed by [Link] Core 2.1 apps, the next step is to migrate some or
all of the web functionality of the app to [Link] Core 2.1 (as described in the previous chapter). This is a
"bottom up" approach, starting with low level class library dependencies and working up to the web app entry
point.
Once the app is running on [Link] Core 2.1, migrating it to [Link] Core 3.1 in isolation is relatively
straightforward. The most likely challenge during this step is updating incompatible dependencies to support
.NET Core and possibly higher versions of .NET Standard. For apps that don't have problematic dependencies on
.NET Framework-only libraries, there's little reason to upgrade to [Link] Core 2.1 or even 3.1. Porting directly
to [Link] Core 6 makes more sense and requires less effort.
By the time the app is running on .NET Core 3.1, migrating to the latest .NET release is relatively painless. The
process primarily involves updating the target framework of your project files and their associated NuGet
package dependencies. While there are several breaking changes to consider when moving to .NET 6, most apps
don't require significant modifications to move from .NET Core 3.1 to .NET 6.
Now that .NET 6 is available and will be supported through November 2024, most teams will choose to upgrade
to .NET 6 to complete their app's migration.
Instead of a "bottom up" approach, another alternative is to start with the web app (or even the entire solution)
and use an automated tool to assist with the upgrade. The .NET Upgrade Assistant tool can be used to help
upgrade .NET Framework apps to .NET Core / .NET 6. It automates many of the common tasks related to
upgrading apps, such as modifying project file format, setting appropriate target frameworks, updating NuGet
dependencies, and more.

References
What is .NET Standard?
Announcing .NET 6 - The Fastest .NET Yet
Introducing .NET 5
Migrate from [Link] Core 3.1 to 6.0 LTS
.NET Upgrade Assistant tool

PR EVIO U S NE XT
Strategies for migrating [Link] Web Forms apps
6/16/2022 • 2 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

This book offers guidance for migrating large [Link] MVC and Web API apps to .NET Core. Some of these
[Link] apps may also include Web Forms (.aspx) pages that must be addressed. [Link] Web Forms isn't
supported in [Link] Core (nor are [Link] Web Pages). Typically, the functionality of these pages must be
rewritten when porting to [Link] Core. There are, however, some strategies you can apply before or during
such migration to help reduce the overall effort required.
Web Forms will continue to be supported for quite some time. One option may be to keep this functionality in
an [Link] 4.x app.

Separate business logic and other concerns


The less code in your [Link] Web Forms pages, the better. When possible, keep business logic and other
concerns like data access in separate classes, ideally in separate class libraries. These class libraries can be
ported to .NET Standard and consumed by any [Link] Core app.

Implement client behavior and web APIs


Consider the choice between implementing logic in Web Forms or in the browser with the help of API calls.
Favor the latter. Migrating APIs to [Link] Core is supported. Client-side behavior should be independent of the
server-side stack your app is using. Using this approach has the added benefit of providing a more responsive
user experience.

Consider Blazor
Blazor lets you build interactive web UIs with C# instead of JavaScript. It can run on the server or in the browser
using WebAssembly. [Link] Web Forms apps may be ported page-by-page to Blazor apps. For more
information on porting Web Forms apps to Blazor, see Blazor for [Link] Web Forms Developers. In addition,
many Web Forms controls have been ported to Blazor as part of an open-source community project, Blazor
Web Forms Components. With these components, you can more easily port Web Forms pages to Blazor even if
they use the built-in Web Forms controls.

Summary
Migrating directly from [Link] Web Forms to [Link] Core isn't supported. However, there are strategies to
make moving to [Link] Core easier. You can migrate your Web Forms functionality to [Link] Core
successfully by:
Keeping non-web functionality out of your projects.
Using web APIs wherever possible.
Considering Blazor as an option for a more modern UI.

References
Free e-book: Blazor for [Link] Web Forms Developers
Blazor Web Forms Components (Community Project)

PR EVIO U S NE XT
Deployment strategies
6/16/2022 • 2 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

One consideration as you plan the migration of your large [Link] app to [Link] Core is how you'll deploy the
new app. With [Link], deployment options were limited to IIS on Windows. With [Link] Core, a much wider
array of deployment options is available.

Cross-platform options
Because .NET Core runs on Linux, you'll find some hosting options available that weren't a consideration
previously. Linux-based hosting may be preferable for the following reasons:
Your organization has infrastructure or expertise.
Hosting providers offer attractive features or pricing for Linux-based hosting.
.NET Core opens the door to these options.

Cloud native development


Most organizations today are using cloud platforms for at least some of their software capabilities. With an app
migration to .NET Core, it's a good time to consider whether the app should be purposefully written with cloud
hosting in mind. Such cloud native apps are better able to apply cloud capabilities like serverless, microservices,
and can be less concerned with the low-level details of how and where they may be deployed.
Learn more about cloud native app development in this free e-book, Architecting Cloud Native .NET
Applications for Azure.

Leverage containers
Modern apps often leverage containers as a means of reducing variation between hosting environments. By
deploying an app to a particular container, the container-hosted app will run the same whether it's running on a
developer's laptop or in production. As part of a migration to .NET Core, it may make sense to consider whether
the app would benefit from deployment via container, either as a full monolith or broken up into multiple
smaller containerized apps.

Side-by-side deployment options


Migrating large .NET Framework apps to .NET Core often requires a substantial effort. Most organizations will
want to be able to break this effort up in some fashion, so that pieces of the app can be migrated and deployed
in production before the entire migration is complete. Running both the original [Link] app and its newly-
migrated [Link] Core sub-app(s) side by side is a frequently cited goal. This can be achieved through a
number of mechanisms including leveraging IIS routing, which is covered in chapter 5. Other options include
leveraging app gateways or cloud design patterns like backends for frontends to manage sets of [Link] Web
APIs and [Link] Core API endpoints.

IIS on Windows
You can continue hosting your apps on IIS running on Windows. This is a fine option for customers who want to
take advantage of [Link] Core features without changing their current deployment model. While moving to
[Link] Core provides more options in terms of how and where to deploy your apps, it doesn't require that you
change from the status quo of using the proven combination of IIS on Windows.

Other options on Windows


You can host apps side-by-side apps on Windows using any combination of Kestrel, [Link], and IIS hosts, in
addition to Docker containers, if needed. If your app requires a combination of Windows and Linux services,
hosting on a Windows server with WSL and/or Linux Docker containers provides a single server solution to
hosting all parts of the app.

References
Host and deploy [Link] Core
Host [Link] Core on Windows with IIS
Troubleshooting [Link] Core on Azure App Service and IIS

PR EVIO U S NE XT
Additional migration resources
6/16/2022 • 2 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

As you're planning and executing your migration from [Link] MVC and/or Web API to [Link] Core, there are
a number of resources available to help beyond this book. Make a note of these and leverage them where
appropriate to help you overcome obstacles you encounter on your migration journey.

Official documentation
The official documentation website, [Link], has the most up-to-date information available about
versions, frameworks, breaking changes, and support options. You'll find many links in this book to docs articles,
but for any problem you're facing it's often worth at least doing a quick search of the docs to see if there is
already information covering the issue and offering a solution or workaround.

GitHub
Because .NET Core is an open-source project, many issues are discovered, reported, discussed, and fixed on
GitHub. Microsoft has several GitHub organizations in which you'll find repositories that may be helpful. A
partial list of these organizations and some of their public repositories are listed below:
Microsoft
[Link] API Versioning
dotnet
[Link] Core
.NET Runtime
Entity Framework Core
C# Language
Docs
Docs Samples
Try Convert
.NET Upgrade Assistant tool
.NET Architecture Reference Apps
eShopModernizing
eShopOnWeb
eShopOnContainers
If you run into problems with your migration, these GitHub repositories are a good place to report them. The
product teams watch the issues and typically respond quickly to bug reports (though "how to" questions may be
more appropriately directed to Stack Overflow).

Stack Overflow
Stack Overflow has a wealth of information in the form of previous questions asked and answers given, with the
most helpful answers listed first and marked if they solved the problem. In addition to searching for an existing
solution to a problem you may encounter, you can of course also ask a question yourself and hope for some
response from the .NET community. Don't forget you can narrow down a search by using tags, and remember to
use appropriate tags when you ask questions to maximize the chances of someone with the experience needed
noticing your question.

YouTube channels
YouTube has a huge amount of .NET and .NET Core video content, which may include useful tutorials or
walkthroughs covering any scenario you may encounter. Consider searching it separately if your other efforts to
find help online come up short. Here are a few good places to get started:
dotnet
Visual Studio

Twitter, Gitter, Slack, and other community channels


You'll find many other ways to connect with .NET developers on the .NET Community page. You can also join the
DotNetEvolution Discord server. Additionally, many product teams and team members are on Twitter as well as
in various other communities. You can follow and communicate with the author of this book on Twitter as well.

References
Overview of porting from .NET Framework to .NET Core
.NET Upgrade Assistant tool
Migrate from [Link] to [Link] Core
.NET Community Resources

PR EVIO U S NE XT
Architectural differences between [Link] MVC
and [Link] Core
6/16/2022 • 2 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

There are many architectural differences between [Link] MVC on .NET Framework and [Link] Core. It's
important to have a broad understanding of these differences as teams evaluate the work involved in porting
their [Link] MVC apps to [Link] Core. This chapter looks at each of the ways in which [Link] Core differs
substantially from [Link] MVC.

Breaking changes
.NET Core is a cross-platform rewrite of .NET Framework. There are many breaking changes between the two
frameworks. The following sections identify specific differences between how [Link] MVC and [Link] Core
apps are designed and developed. Take care to also examine the documentation to determine which framework
libraries you're using that may need to change. In many cases, a replacement NuGet package exists to fill in any
gaps left between .NET Framework and .NET Core. In rare cases, you may need to find a third-party solution or
implement new custom code to address incompatibilities.

PR EVIO U S NE XT
App startup differences between [Link] MVC and
[Link] Core
6/16/2022 • 3 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

[Link] MVC apps lived entirely within Internet Information Server (IIS), the primary web server available on
Windows operating systems. Unlike [Link] MVC, [Link] Core apps are executable apps. You can run them
from the command line, using dotnet run . They have an entry point method like all C# programs, typically
public static void Main() or a similar variation (perhaps with arguments or async support). This is perhaps
the biggest architectural difference between [Link] Core and [Link] MVC, and is one of several differences
that allows [Link] Core to run on non-Windows systems.

[Link] MVC Startup


Hosted within IIS, [Link] apps rely on IIS to instantiate certain objects and call certain methods when a request
arrives. [Link] creates an instance of the [Link] file's class, which derives from HttpApplication . When
the first request is received, before handling the request itself, [Link] calls the Application_Start method in
the [Link] file's class. Any logic that needs to run when the [Link] MVC app begins can be added to this
method.
Many NuGet packages for [Link] MVC and Web API use the WebActivator package to let them run some code
during app startup. By convention, this code would typically be added to an App_Start folder and would be
configured via attribute to run either immediately before or just after Application_Start .
It's also possible to use the Open Web Interface for .NET (OWIN) and Project Katana with [Link] MVC. When
doing so, the app will include a [Link] file that is responsible for setting up request middleware in a way
that's very similar to how [Link] Core behaves.
If you need to run code when your [Link] MVC app starts up, it will typically use one of these approaches.

[Link] Core Startup


As noted previously, [Link] Core apps are standalone programs. As such, they typically include a [Link]
file containing the entry point for the app. A typical example of this file is shown in Figure 2-1. Notice that in
.NET 6, this file is streamlined by the use of implicit using statements and top-level statements, eliminating the
need for a lot of "boiler plate" code.
var builder = [Link](args);

// Add services to the container.


[Link]();

var app = [Link]();

// Configure the HTTP request pipeline.


if (![Link]())
{
[Link]("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see
[Link]
[Link]();
}

[Link]();
[Link]();

[Link]();

[Link]();

[Link]();

[Link]();

Figure 2-1 . A typical [Link] Core [Link] file.


The code shown in Figure 2-1 uses a builder to configure the host and its services. Then, it creates the request
pipeline for the app, which controls how every request to the app is handled.
Previous versions of .NET would use a separate [Link] file, referenced by [Link]. This approach is still
supported in .NET 6, but is no longer the default approach.
In addition to code related to configuring the app's services and request pipeline, apps may have other code that
must run when the app begins. Such code is typically placed in [Link] or registered as an IHostedService ,
which will be started by the generic host when the app starts.
The IHostedService interface just exposes two methods, StartAsync and StopAsync . You register the interface
when configuring the app's services and the host does the rest, calling the StartAsync method before the app
starts up.

Porting considerations
Teams looking to migrate their apps from [Link] MVC to [Link] Core need to identify what code is being run
when their app starts up and determine the appropriate location for such code in their [Link] Core app. For
custom code needed to run when the app starts up, especially async code, the recommended approach is
typically to put such code into IHostedService implementations.

References
[Link] Application Life Cycle Overview for IIS 7
[Link] Application Life Cycle Overview for IIS 5 and 6
Getting Started with OWIN and Katana
WebActivator
App Startup in [Link] Core
.NET Generic Host in [Link] Core
IHostedService

PR EVIO U S NE XT
Hosting differences between [Link] MVC and
[Link] Core
6/16/2022 • 2 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

[Link] MVC apps are hosted in IIS, and may rely on IIS configuration for their behavior. This often includes IIS
modules. As part of reviewing an app to prepare to port it from [Link] MVC to [Link] Core, teams should
identify which modules, if any, they're using from the list of IIS Modules installed on their server.
[Link] Core apps can run on a number of different servers. The default cross platform server, Kestrel, is a good
default choice. Apps running in Kestrel can be hosted by IIS, running in a separate process. On Windows, apps
can also run in process on IIS or using [Link]. Kestrel is generally recommended for best performance.
[Link] can be used in scenarios where the app is exposed to the Internet and required capabilities are
supported by [Link] but not Kestrel.
Kestrel does not have an equivalent to IIS modules (though apps hosted in IIS can still take advantage of IIS
modules). To achieve equivalent behavior, middleware configured in the [Link] Core app itself is typically used.

References
IIS Modules
[Link] Core Middleware
[Link] Core Servers

PR EVIO U S NE XT
Serve static files in [Link] MVC and [Link] Core
6/16/2022 • 2 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

Most web apps involve a combination of server-side logic and static files that must be sent to the client as-is.
How should your migration from [Link] MVC to [Link] Core handle serving static files?

Host static files in [Link] MVC


[Link] MVC apps, hosted by IIS, typically host static files directly from the app. [Link] MVC supports placing
static files side by side with files that should be kept private on the server. IIS and [Link] require explicitly
restricting certain files or file extensions from being served from the folder in which an [Link] app is hosted.
For many static files, using a content delivery network (CDN) is a good practice. Static content hosting allows
better performance while reducing load and bandwidth from app servers.

Host static files in [Link] Core


It may be surprising, but [Link] Core doesn't have built-in support for static files. This feature that has always
existed as just a part of [Link], enabled by IIS, isn't intrinsic to [Link] Core or its Kestrel web server. To serve
static files from an [Link] Core app, you must configure static files middleware.
With static files middleware configured, an [Link] Core app will serve all files located in a certain folder
(typically /wwwroot). No other files in the app or project folder are at risk of being accidentally exposed by the
server. No special restrictions based on file names or extensions need to be configured, as is the case with IIS.
Instead, developers explicitly choose to expose files publicly when they place them in the wwwroot folder. By
default, files outside of this folder aren't shared.
Because support for static files uses middleware, any other middleware can be applied as part of the same
request pipeline. Examples of middleware include authentication, caching, and compression.
Of course, CDNs remain a good choice for [Link] Core apps for all the same reasons they're used in [Link]
MVC apps. As part of preparing to migrate to .NET Core, if there are benefits your app could realize from using a
CDN, it would be good to move static files to a CDN before migrating to .NET Core. Doing so reduces the
migration effort's overall scope for static assets.

References
Static content hosting
Static files in [Link] Core

PR EVIO U S NE XT
Dependency injection differences between [Link]
MVC and [Link] Core
6/16/2022 • 2 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

Although dependency injection (DI) isn't built into [Link] MVC or Web API, many apps enable it by adding a
NuGet package with an inversion of control (IOC) container. These are sometimes referred to as DI containers,
for dependency injection (or inversion). Some of the most popular containers used in [Link] MVC apps
include:
Autofac
Unity
Ninject
StructureMap (deprecated)
Castle Windsor
If your [Link] MVC app isn't using DI, you will probably want to investigate the built-in support for DI in
[Link] Core. DI promotes loose coupling of modules in your app and enables better testability and adherence
to principles like SOLID.
If your app does use DI, then probably your best course of action is to see if the container you're using supports
[Link] Core. If so, you may be able to continue using it and your custom configuration rules describing your
type mappings and lifetimes.
Either way, you should consider using the built-in support for DI that ships with [Link] Core, as it may meet
your app's needs.

Dependency injection in [Link] Core


[Link] Core assumes apps will use DI. It's not just built into the framework, but is required in order to bring
support for framework features into your [Link] Core apps. In app startup, a call is made to
ConfigureServices which is responsible for registering all of the types that the DI container (service
collection/service provider) can create and inject in the app. Built-in [Link] Core features like Entity Framework
Core, Identity, and even MVC are brought into the app by configuring them as services in the ConfigureServices
method.
In addition to using the default implementation, apps can still use custom containers. The documentation covers
how to replace the default service container.
DI is fundamental to [Link] Core. If your team isn't already well-versed in this practice, you'll want to
understand it before porting your app.

References
Dependency Injection in .NET
Dependency Injection in [Link] Core

PR EVIO U S NE XT
Compare middleware to modules and handlers
6/16/2022 • 2 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

If your existing [Link] MVC or Web API app uses OWIN/Katana, you're most likely already familiar with the
concept of middleware and porting it to [Link] Core should be fairly straightforward. However, most [Link]
apps rely on HTTP modules and HTTP handlers instead of middleware. Migrating these to [Link] Core requires
extra effort.

[Link] modules and handlers


HTTP modules and HTTP handlers are an integral part of the [Link] architecture. While a request is being
processed, each request is processed by multiple HTTP modules (for example, the authentication module and
the session module) and is then processed by a single HTTP handler. After the handler has processed the
request, the request flows back through the HTTP modules.
If your app is using custom HTTP modules or HTTP handlers, you'll need a plan to migrate them to [Link]
Core. The most likely replacement in [Link] Core is middleware.

[Link] Core middleware


[Link] Core defines a request pipeline in each app's Configure method. This request pipeline defines how an
incoming request is handled by the app, with each method in the pipeline calling the next method until
eventually a method terminates, and the chain of middleware terminates and returns back up the stack.
Middleware can target all requests, or can be configured to only map to certain requests based on the requested
path or other factors. It can be configured wholly in the Configure method of an app, or implemented in a
separate class.
Behavior in an [Link] MVC app that uses HTTP modules is probably best suited to custom middleware.
Custom HTTP handlers can be replaced with custom routes or endpoints that respond to the same path.

Accessing HttpContext
Many .NET apps reference the current request's context through [Link] . This static access can be a
common source of problems with testing and other code usage outside of individual requests. When building
[Link] Core apps, access to the current HttpContext should be provided as a method parameter on
middleware, as this sample demonstrates:
public class Middleware
{
private readonly RequestDelegate _next;

public Middleware(RequestDelegate next)


{
_next = next;
}

public Task Invoke(HttpContext httpContext)


{
return _next(httpContext);
}
}

Similarly, [Link] Core filters pass a context argument to their methods, from which the current HttpContext
can be accessed:

public class MyActionFilterAttribute : ActionFilterAttribute


{
public override void OnResultExecuting(ResultExecutingContext context)
{
var headers = [Link];
// do something based on a header

[Link](context);
}
}

If you have components or services that require access to HttpContext, rather than using a static call like
[Link] you should instead use constructor dependency injection and the IHttpContextAccessor
interface:

public class MyService


{
private readonly IHttpContextAccessor _httpContextAccessor;

public MyService(IHttpContextAccessor httpContextAccessor)


{
_httpContextAccessor = httpContextAccessor;
}

public void DoSomething()


{
var currentContext = _httpContextAccessor.HttpContext;
}
}

This approach eliminates the static coupling of the method to the current context while providing access in a
testable fashion.

References
[Link] HTTP modules and HTTP handlers
[Link] Core middleware

PR EVIO U S NE XT
Configuration differences between [Link] MVC
and [Link] Core
6/16/2022 • 3 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

How configuration values are stored and read changed dramatically between [Link] and [Link] Core.

[Link] MVC configuration


In [Link] apps, configuration uses the built-in .NET configuration files, [Link] in the app folder and
[Link] on the server. Most [Link] MVC and Web API apps store their settings in the configuration
file's appSettings or connectionStrings elements. Some also use custom configuration sections that can be
mapped to a settings class.
Configuration in a .NET Framework app is accessed using the [Link] class.
Unfortunately, this class provides static access to the configuration elements. As a result, very few [Link] MVC
apps tend to abstract access to their configuration settings or inject them where needed. Instead, most .NET
Framework apps tend to directly access the configuration system anywhere the app needs to use a setting.
Typical [Link] MVC configuration access:

string connectionString =
[Link]["DefaultConnection"]
.ConnectionString;

[Link] Core configuration


In [Link] Core apps, configuration is, itself, configurable. However, most apps use a set of defaults provided as
part of the standard project templates and the ConfigureWebHostDefaults method used in them. The default
settings use JSON formatted files, with the ability to override settings in the base [Link] file with
environment-specific files like [Link]. Additionally, the default configuration system
further overrides all file-based settings with any environment variable that exists for the same named setting.
This is useful in many scenarios and is especially useful when deploying to a hosting environment, since it
eliminates the need to worry about whether deploying configuration files will accidentally overwrite important
production configuration settings. Configuration values can also be provided as command line arguments.
Accessing configuration values can be done in many ways in .NET Core. Because dependency injection is built
into .NET Core, configuration values are generally accessed through an interface that is injected into classes that
need them. This can involve passing a interface like IConfiguration, but usually it's better to pass just the settings
required by the class using the options pattern.
Figure 2-2 shows how to pass IConfiguration into a Razor Page and access configuration settings from it:

using [Link];

public class TestModel : PageModel


{
private readonly IConfiguration _configuration;

public TestModel(IConfiguration configuration)


{
_configuration= configuration;
}

public ContentResult OnGet()


{
var myKeyValue = _configuration["MyKey"];

// ...
}
}

Figure 2-2. Accessing configuration values with IConfiguration .


Using the options pattern, settings access is similar but is strongly typed and more specific to the setting(s)
needed by the consuming class, as Figure 2-3 demonstrates.

public class PositionOptions


{
public const string Position = nameof(Position);

public string Title { get; set; }


public string Name { get; set; }
}

public class Test2Model : PageModel


{
private readonly PositionOptions _options;

public Test2Model(IOptions<PositionOptions> options)


{
_options = [Link];
}

public ContentResult OnGet()


{
return Content($"Title: {_options.Title}\nName: {_options.Name}");
}
}

Figure 2-3. Using the options pattern in [Link] Core.


For the options pattern to work, the options type must be configured in ConfigureServices when the app starts
up:

// required in ConfigureServices
[Link]<PositionOptions>([Link]([Link]));
Migrate configuration
When considering how to port an app's configuration settings from .NET Framework to .NET Core, the first step
is to identify all of the configuration settings that are being used. Most of these will be in the [Link] file in
the app's root folder, but some apps expect settings to be found in the shared [Link] file as well. These
settings will include elements of the appSettings element, the connectionStrings element, and any custom
configuration elements as well. In .NET Core, all of these settings are typically stored in the [Link] file.
Once all settings in the config files have been cataloged, the next step should be to identify where and how the
settings are used in the app itself. If some settings aren't being used, these can probably be omitted from the
migration. For each setting, note all of the places it's being used so you can be sure you don't miss any when
you migrate the code.
If you're still maintaining the [Link] app, it may be helpful to avoid static references to ConfigurationManager
and replace them with access through interfaces. This will ease the transition to [Link] Core's configuration
system. In general, static access to external resources makes code harder to test and maintain, so be on the
lookout for anywhere else the app may be following this pattern.

References
Configuration in [Link] Core
Options pattern in [Link] Core
Migrate configuration to [Link] Core
Refactoring Static Config Access

PR EVIO U S NE XT
Routing differences between [Link] MVC and
[Link] Core
6/16/2022 • 5 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

Routing is responsible for mapping incoming browser requests to particular controller actions (or Razor Pages
handlers). This section covers how routing differs between [Link] MVC (and Web API) and [Link] Core
(MVC, Razor Pages, and otherwise).

Routing in [Link] MVC and Web API


[Link] MVC offers two approaches to routing:
1. The route table, which is a collection of routes that can be used to match incoming requests to controller
actions.
2. Attribute routing, which performs the same function but is achieved by decorating the actions themselves,
rather than editing a global route table.

Route table
The route table is configured when the app starts up. Typically, a static method call is used to configure the
global route collection, like so:
public class MvcApplication : [Link]
{
public static void RegisterRoutes(RouteCollection routes)
{
[Link]("{resource}.axd/{*pathInfo}");
[Link](
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = "" },
constraints: new { id = "\\d+" }
);
}

protected void Application_Start()


{
RegisterRoutes([Link]);
}
}

In the preceding code, the route table is managed by the RouteCollection type, which is used to add new routes
with MapRoute . Routes are named and include a route string, which can include parameters for controllers,
actions, areas, and other placeholders. If an app follows a standard convention, most of its actions can be
handled by this single default route, with any exceptions to this convention configured using additional routes.
Attribute routing in [Link] MVC
Routes that are defined with their actions may be easier to discover and reason about than routes defined in an
external location. Using attribute routing, an individual action method can have its route defined with a [Route]
attribute:

public class ProductsController


{
[Route("products")]
public ActionResult Index()
{
return View();
}

[Route("products/{id}")]
public ActionResult Details(int id)
{
return View();
}
}

Attribute routing in [Link] MVC 5 also supports defaults and prefixes, which can be added at the controller
level (and which are applied to all actions within that controller). Refer to the documentation for details.
Setting up attribute routing requires adding one line to the default route table configuration:

[Link]();

Attribute routing can take advantage of route constraints, both built-in and custom, and supports named routes
and areas using the [RouteArea] attribute. If your app uses areas, you'll need to configure support for them in
your route registration code by adding one more line:
[Link]();

[Link]();

Attribute routing in [Link] Web API 2


Attribute routing in [Link] Web API 2 is similar to routing in [Link] MVC 5, with minor differences.
Configuring Web API 2 is typically done in its own class, which is called during app startup. Attribute routing
configuration is handled in this class:

public static class WebApiConfig


{
public static void Register(HttpConfiguration config)
{
// Attribute routing.
[Link]();

// Convention-based routing.
[Link](
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = [Link] }
);
}
}

As shown in the preceding code, attribute routing may be combined with convention-based routing in Web API
apps.
In addition to attribute routing, [Link] Web API chooses which action to call based on the HTTP method (for
example, GET or POST), the {action} placeholder in a route (if any), and parameters of the action. In many
cases, the name of the action will help determine whether it's matched, since prefixing the action name with
"Get" or "Post" is used to match the appropriate HTTP method to it. Alternatively, actions can be decorated with
an appropriate HTTP method attribute, like [HttpGet] , allowing the use of action names that aren't prefixed with
an HTTP method.

public class ProductsController : ApiController


{
// matched by name and (lack of) parameters
public IEnumerable<Product> GetAll() { }

// matched by GET and string parameter


[HttpGet]
public IEnumerable<Product> FindProductsByName(string name) { }
}

Given the above controller, an HTTP GET request to localhost:123/products/ matches the GetAll action. An
HTTP GET request to localhost:123/products?name=ardalis matches the FindProductsByName action.

Routing in [Link] Core 3.1


In [Link] Core, routing is handled by routing middleware, which matches the URLs of incoming requests to
actions or other endpoints. Controller actions are either conventionally routed or attribute-routed. Conventional
routing is similar to the route table approach used in [Link] MVC and Web API. Whether you're using
conventional, attribute, or both, you need to configure your app to use the routing middleware. To use the
middleware, add the following code to your [Link] method:
[Link]();

Conventional routing
With conventional routing, you set up one or more conventions that will be used to match incoming URLs to
endpoints in the app. In [Link] Core, these endpoints may be controller actions, like in [Link] MVC or Web
API. The endpoints could also be Razor Pages, Health Checks, or SignalR hubs. All of these routable features are
configured in a similar fashion using endpoints:

[Link](endpoints =>
{
[Link]("/healthz").RequireAuthorization();
[Link](
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
[Link]();
});

The preceding code is used (in addition to UseRouting ) to configure various endpoints, including Health Checks,
controllers, and Razor Pages. For controllers, the above configuration specifies a default routing convention,
which is the fairly standard {controller}/{action}/{id?} pattern that's been recommended since the first
versions of [Link] MVC.
Attribute routing
Attribute routing in [Link] Core is the preferred approach for configuring routing in controllers. If you're
building APIs, the [ApiController] attribute should be applied to your controllers. Among other things, this
attribute requires the use of attribute routing for actions in such controller classes.
Attribute routing in [Link] Core behaves similarly in [Link] MVC and Web API. In addition to supporting the
[Route] attribute, however, route information can also be specified as part of the HTTP method attribute:

[HttpGet("api/products/{id}")]
public async ActionResult<Product> Details(int id)
{
// ...
}

As with previous versions, you can specify a default route with placeholders, and add this at the controller class
level or even on a base class. You use the same [Route] attribute for all of these cases. For example, a base API
controller class might look like this:

[Route("api/{controller}/{action}/{id?:int}")]
public abstract class BaseApiController : ControllerBase, IApiController
{
// ...
}

Using this attribute, classes inheriting from this type would route URLs to actions based on the controller name,
action name, and an optional integer id parameter.

References
[Link] MVC Routing Overview
Attribute Routing in [Link] MVC 5
Attribute routing in [Link] Web API 2
Routing and Action Selection in [Link] Web API
Routing in [Link] Core
Routing to controller actions in [Link] Core MVC

PR EVIO U S NE XT
Logging differences between [Link] MVC and
[Link] Core
6/16/2022 • 2 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

Application logging provides important diagnostic information, especially for apps running in production.
[Link] Core introduces a new system for adding standardized logging to any app. Existing [Link] MVC and
Web API apps most likely use third-party logging solutions, which likely continue to be supported on [Link]
Core.

[Link] MVC logging


There's no built-in logging solution in [Link] MVC and Web API apps. Instead, most apps use third-party
logging solutions like log4net, NLog, or Serilog. Many teams also choose to roll their own logging solution.
Logging frameworks typically support multiple "sinks" (or targets or appenders) for log output, such as text files,
databases, or even emails. They use configuration to determine which levels of log messages from which parts
of the system are routed to different sinks. When considering how to migrate an app's logging to .NET Core,
review how logging is performed and configured in the app.

[Link] Core logging


In [Link] Core, logging is a built-in feature that can be configured and extended when the app starts up. Third-
party loggers, including those mentioned above, can be integrated with [Link] Core to enhance its
functionality.
[Link] Core logging uses categories and levels to control what is logged and how. Classes typically use
instances of the ILogger<T> interface, with the class's type used as the generic T type. In this scenario, the
class's fully qualified name is used as the category. Loggers can also be created with a custom category using an
ILoggerFactory . Log levels range from the most detailed, Trace , to the most important, Critical . Using
configuration, apps can specify what minimum level of logging should be included on a per-category (with
wildcards) basis.
A typical logging configuration could log Information and above information by default, but only Warning or
above from Microsoft -prefixed categories:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning"
}
}
}

Support for logging in [Link] Core is extensive and flexible. For more detailed information, refer to the docs.

Migrate logging
How you migrate your .NET Framework app's logging to .NET Core depends on how the app is logging now. If
it's using a third-party NuGet package, refer to the upgrade documentation for that package. Most likely the
upgrade path will be fairly straightforward. If you're using your own logging solution, take one of the following
actions:
1. Migrate the logging solution yourself
2. Migrate to a third-party logging solution
3. Use the built-in logging support in [Link] Core
You can reference the [Link] package from .NET Framework apps as long as they're
using NuGet 4.3 or later and are on .NET Framework 4.6.1 or later. Once your app has referenced this package,
you can convert your logging statements to use the new extensions before migrating the app to .NET Core. This
can provide a stepping stone toward full migration, since the app can continue running on .NET Framework
while logging using the newer extensions package.

References
Logging in .NET Core and [Link] Core
[Link] NuGet Package

PR EVIO U S NE XT
Compare Razor Pages to [Link] MVC
6/16/2022 • 2 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

Razor Pages is the preferred way to create page- or form-based apps in [Link] Core. From the docs, "Razor
Pages can make coding page-focused scenarios easier and more productive than using controllers and views." If
your [Link] MVC app makes heavy use of views, you may want to consider migrating from actions and views
to Razor Pages.
A typical strongly typed view-based MVC app will use a controller to contain one or more actions. The controller
will interact with the domain or data model, and create an instance of a viewmodel class. Then this viewmodel
class is passed to the view associated with that action. Using this approach, coupled with the default folder
structure of MVC apps, to add a new page to an app requires modifying a controller in one folder, a view in a
nested subfolder in another folder, and a viewmodel in yet another folder.
Razor Pages group together the action (now a handler) and the viewmodel (called a PageModel) in one class,
and link this class to the view (called a Razor Page). All Razor Pages go into a Pages folder in the root of the
[Link] Core project. Razor Pages use a routing convention based on their name and location within this folder.
Handlers behave exactly like action methods but have the HTTP verb they handle in their name (for example,
OnGet ). They also don't necessarily need to return, since by default they're assumed to return the page they're
associated with. This tends to keep Razor Pages and their handlers smaller and more focused while at the same
time making it easier to find and work with all of the files needed to add or modify a particular part of an app.
As part of a move from [Link] MVC to [Link] Core, teams should consider whether they want to migrate
controllers and views to [Link] Core controllers and views, or to Razor Pages. The former will most likely
require slightly less overall effort, but won't allow the team to take advantage of the benefits of Razor Pages
over traditional view-based file organization.

References
Introduction to Razor Pages in [Link] Core
Simpler [Link] Core Apps with Razor Pages

PR EVIO U S NE XT
Compare [Link] Web API 2 and [Link] Core
6/16/2022 • 2 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

[Link] Core offers iterative improvements to [Link] Web API 2, but should feel familiar to developers who
have used Web API 2. [Link] Web API 2 was developed and shipped alongside [Link] MVC. This meant the
two approaches had similar-but-different approaches to things like attribute routing and dependency injection.
In [Link] Core, there's no longer any distinction between MVC and Web APIs. There's only [Link] MVC, which
includes support for view-based scenarios, API endpoints, and Razor Pages (and other variations like health
checks and SignalR).
In addition to being consistent and unified within [Link] Core, APIs built in .NET Core are much easier to test
than those built on [Link] Web API 2. We'll cover testing differences in more detail in a moment. The built-in
support for hosting [Link] Core apps, in a test host that can create an HttpClient that makes in-memory
requests to the app, is a huge benefit when it comes to automated testing.
When migrating from [Link] Web API 2 to [Link] Core, the transition is straightforward. If you have large,
bloated controllers, one approach you may consider to break them up is the use of the [Link]
NuGet packages. This package breaks up each endpoint into its own specific class, with associated request and
response types as appropriate. This approach yields many of the same benefits as Razor Pages offer over view-
based code organization.

References
Migrate from [Link] Web API to [Link] Core
[Link] NuGet package

PR EVIO U S NE XT
Compare authentication and authorization between
[Link] MVC and [Link] Core
6/16/2022 • 2 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

In [Link] MVC 5, authentication is configured in [Link] in the App_Start folder. In [Link] Core MVC,
this configuration occurs in [Link] or [Link], as part of configuring the app's services and middleware.
Authentication and authorization are performed using middleware added to the request pipeline:

[Link]();

[Link]();
[Link]();

[Link](endpoints =>
{
[Link](
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
[Link]();
});

It's important to add the auth middleware in the appropriate order in the middleware pipeline. Only requests
that make it to the middleware will be impacted by it. For instance, if a call to UseStaticFiles() were placed
above the code shown here, it wouldn't be protected by authentication and authorization.
In [Link] MVC and Web API, apps often refer to the current user using the [Link] property.
This property isn't set in [Link] Core, and any behavior in your app that depends on it will need to migrate
from [Link] by using the User property on ControllerBase or getting access to the current
HttpContext and referencing its User property. If neither of these solutions is an option, services can request
the User as an argument, in which case it must be supplied from elsewhere in the app, or the
IHttpContextAccessor can be requested and used to get the HttpContext .

Authorization
Authorization defines what a given user can do within the app. It's separate from authentication, which is
concerned merely with identifying who the user is. [Link] Core provides a simple, declarative role and a rich,
policy-based model for authorization. Specifying that a resource requires authorization is often as simple as
adding the [Authorize] attribute to the action or controller. If you're migrating to Razor Pages from MVC views,
you should specify conventions for authorization when you configure Razor Pages.
Authorization in [Link] Core may be as simple as prohibiting anonymous users while allowing authenticated
users. Or it can scale up to support role-based, claims-based, or policy-based authorization approaches. For
more information on these approaches, see the documentation on authorization in [Link] Core. You'll likely
find that one of them is closely aligned with your current authorization approach.

References
Security, Authentication, and Authorization with [Link] MVC
Migrate Authentication and Identity to [Link] Core
Migrate from [Link]
Introduction to Authorization in [Link] Core

PR EVIO U S NE XT
Compare [Link] Identity and [Link] Core
Identity
6/16/2022 • 2 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

In [Link] MVC, identity features are typically configured in [Link] in the App_Start folder. Review
how this is configured in the existing app, and compare it to the configuration required for [Link] Core Identity
in [Link].
[Link] Identity is an API that supports user interface login functionality and manages users, passwords, profile
data, roles, claims, tokens, email confirmations, and more. It supports external login providers like Facebook,
Google, Microsoft, and Twitter.
If your [Link] MVC app is using [Link] Membership, you'll find a guide to migrate from [Link]
Membership authentication to [Link] Core 2.0 Identity. This is mainly a data migration exercise, at the
completion of which you should be able to use [Link] Core Identity with your migrated user records.
If you migrate your [Link] Identity users to [Link] Core Identity, you may need to update their password
hashes, or track which passwords are hashed with the new [Link] Core Identity approach or the older [Link]
Identity approach. This approach described on Stack Overflow provides some options for migrating user
password hashes over time, rather than all at once.
One of the biggest differences when it comes to [Link] Core Identity compared to [Link] Identity is how little
code you need to include in your project. [Link] Core Identity now ships as a Razor Class Library, meaning its
UI and logic are all available from a NuGet package. You can override the existing UI and logic by scaffolding the
[Link] Core Identity files but even in this case you need only scaffold the pages you want to modify, not every
one that exists.

Migrate from OWIN / Katana


The following resources offer some guidance for migrating from OWIN / Katana:
Migration
Katana to ASPNET 5

References
Migrate Authentication and Identity to [Link] Core
Introduction to Identity on [Link] Core
Configure [Link] Core Identity
Scaffold Identity in [Link] Core projects

PR EVIO U S NE XT
Compare controllers in [Link] MVC and Web API
with controllers in [Link] Core
6/16/2022 • 2 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

In [Link] MVC 5 and Web API 2, there were two different Controller base types. MVC controllers inherited
from Controller ; Web API controllers inherited from ApiController . In [Link] Core, this object hierarchy has
been merged. It's recommended that API controllers in [Link] Core inherit from ControllerBase and add the
[ApiController] attribute. Standard view-based MVC controllers should inherit from Controller .

In both frameworks, controllers are used to organize sets of action methods. Filters and routes can be applied on
a controller level in addition to at the action level. These conventions can be extended further by using custom
base Controller types with default behavior and attributes applied to them.
In [Link] MVC, content negotiation isn't supported. [Link] Web API 2 does support content negotiation, as
does [Link] Core. Using content negotiation, the format of the content returned to a request can be
determined by headers the client provides indicating its preferred manner of receiving the content.
When migrating [Link] Web API controllers to [Link] Core, a few components need to be changed if they
exist. These include references to the ApiController base class, the [Link] namespace, and the
IHttpActionResult interface. Refer to the documentation for recommendations on how to migrate these specific
differences. Note that the preferred return type for API actions in [Link] Core is ActionResult<T> .
In addition, the [ChildActionOnly] attribute isn't supported in [Link] Core. In [Link] Core, similar
functionality is achieved using View Components.
[Link] Core includes two new attributes: ConsumesAttribute and ProducesAttribute. These are used to specify
the type an action consumes or produces, which can be helpful for routing and documenting the API using tools
like Swagger/OpenAPI.

References
Format response data in [Link] Core Web API
Migrate from [Link] Web API to [Link] Core
PR EVIO U S NE XT
Compare Razor usage in [Link] MVC and
[Link] Core
6/16/2022 • 2 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

The basic syntax of Razor hasn't changed substantially between [Link] MVC and [Link] Core. However, there
are certain differences, such as the introduction of Tag Helpers and Razor Pages, that should be considered when
migrating. If your app makes heavy use of custom Razor functionality, refer to the Razor syntax reference for
[Link] Core to see what changes may be required when you migrate to [Link] Core.

Tag Helpers
Tag Helpers enable server-side code to participate in creating and rendering HTML elements in Razor files. They
offer many advantages over HTML Helpers and should be used in place of HTML Helpers wherever possible.
They provide an HTML-friendly development experience, since they look like standard HTML and are ignored by
most tooling designed to edit HTML. Within Visual Studio, there's rich IntelliSense support for creating HTML
and Razor markup with Tag Helpers. Tag Helpers can provide simple or complex behavior from declarative
markup in Razor files.

Razor Pages
Razor Pages offer an alternative to controllers, actions, and views for page- and form-based apps. Razor Pages
were compared to [Link] MVC earlier in this chapter.

References
Migrate from [Link] MVC to [Link] Core MVC: Controllers and Views
Tag Helpers in [Link] Core
Introduction to Razor Pages in [Link] Core
Razor syntax reference for [Link] Core

PR EVIO U S NE XT
Compare [Link] SignalR and [Link] Core
SignalR
6/16/2022 • 2 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

[Link] Core SignalR is incompatible with clients or servers using [Link] SignalR. You'll need to update both
clients and server to use [Link] Core SignalR. Some differences are described in this section, while the full list
is available in the docs. [Link] Core SignalR requires .NET Core 2.1 or greater.

Feature differences
[Link] SignalR automatically attempts to reconnect dropped connections; this behavior is opt-in for
[Link] Core SignalR clients
Both frameworks support JSON; [Link] Core SignalR also supports a binary protocol based on
MessagePack, and custom protocols can be created.
The Forever Frame transport, supported by [Link] SignalR, isn't supported in [Link] Core SignalR.
[Link] Core SignalR must be configured by adding [Link]() and [Link] in
[Link] and [Link] , respectively.
[Link] Core SignalR requires sticky sessions; [Link] SignalR doesn't.
[Link] Core simplifies the connection model; connections are only made to a single hub.
[Link] Core SignalR supports streaming data from the hub to the client.
[Link] Core SignalR doesn't support passing state between clients and the hub (but method calls still allow
passing information between hubs and clients).
The PersistentConnection class doesn't exist in [Link] Core SignalR.
[Link] SignalR supports SQL Server and Redis. [Link] Core SignalR supports Azure SignalR and Redis.

References
Differences between [Link] SignalR and [Link] Core SignalR
Azure SignalR Service

PR EVIO U S NE XT
Compare testing options between [Link] MVC
and [Link] Core
6/16/2022 • 2 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

[Link] MVC apps support unit testing of controllers, but this approach often omits many MVC features like
routing, authorization, model binding, model validation, filters, and more. To test these MVC features in addition
to the logic within the controller action itself, frequently the app would need to be deployed and then tested with
a tool like Selenium. These tests are substantially more expensive, more brittle, and slower than typical unit tests
that can be run without the need for hosting and running the entire app.
[Link] Core controllers can be unit tested just like [Link] MVC controllers, but with the same limitations.
However, [Link] Core supports fast, easy-to-author integration tests as well. Integration tests are hosted by a
TestHost class and are typically configured in a custom WebApplicationFactory that can override or replace app
dependencies. For instance, frequently during integration tests the app will target a different data source and
may replace services that send emails with fake or mock implementations.
[Link] MVC and Web API did not support anything like the integration testing scenarios available in [Link]
Core. In any migration effort, you should allocate time to write some integration tests for your newly migrated
system to ensure it's working as expected and continues to do so. Even if you weren't writing tests of your web
app logic before the migration, you should strongly consider doing so as you move to [Link] Core.

References
Creating Unit Tests for [Link] MVC Applications
Unit test controller logic in [Link] Core
Integration tests in [Link] Core

PR EVIO U S NE XT
Migrate large solutions to [Link] Core
6/16/2022 • 2 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

Migrating large solutions from .NET Framework to .NET Core requires some planning. Dependencies must be
migrated or updated before the projects that depend on them. There are tools that can identify dependencies
and offer help with migrating to .NET Core. Depending on the app, its scope, and current usage patterns,
different strategies may be employed when migrating. Do you migrate it all at once, or over time, side-by-side
with the current system? Do you wrap the current system in the new one, and incrementally replace its
functionality?
In this chapter, you'll learn how create a migration plan for a large solution, how to use tools to help with the
migration, and some strategies to consider for the migration itself.

References
What topics are important to migrating large MVC and Web API apps to .NET Core?
Porting from .NET Framework to .NET Core

PR EVIO U S NE XT
Identify sequence of projects to migrate
6/16/2022 • 5 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

For solutions that involve multiple front-end apps, it's best to migrate the apps one by one. For example, create a
solution that only includes one front-end app and its dependencies so you can easily identify the scope of work
involved. Solutions are lightweight, and you can include projects in multiple solutions if needed. Take advantage
of solutions as an organizational tool when migrating.
Once you've identified the [Link] app to migrate and have its dependent projects located with it (ideally in a
solution), the next step is to identify framework and NuGet dependencies. Having identified all dependencies,
the simplest migration approach is a "bottom up" approach. With this approach, the lowest level of
dependencies is migrated first. Then the next level of dependencies is migrated, until eventually the only thing
left is the front-end app. Figure 3-1 shows an example set of projects composing an app. The low-level class
libraries are at the bottom, and the [Link] MVC project is at the top.
Figure 3-1. Project dependencies graph.
Choose a particular front-end app, an [Link] MVC 5 / Web API 2 project. Identify its dependencies in the
solution, and map out their dependencies until you have a complete list. A diagram like the one shown in Figure
3-1 may be useful when mapping out project dependencies. Visual Studio can produce a dependency diagram
for your solution, depending on which edition you're using. The .NET Portability Analyzer can also produce
dependency diagrams.
Figure 3-2 shows the installer for the .NET Portability Analyzer Visual Studio extension:
Figure 3-2. .NET Portability Analyzer installer.
The extension currently supports Visual Studio 2017 and 2019. Visual Studio 2022 support is planned.
Once installed, you configure it from the Analyze > Por tability Analyzer Settings menu, as shown in Figure
3-3.
Figure 3-3. Configure the .NET Portability Analyzer.
The analyzer produces a detailed report for each assembly. The report:
Describes how compatible each project is with a given target framework, such as .NET Core 3.1 or .NET
Standard 2.0.
Helps teams assess the effort required to port a particular project to a particular target framework.
The details of this analysis are covered in the next section.
Once you've mapped out the projects and their relationships with one another, you're ready to determine the
order in which you'll migrate the projects. Begin with projects that have no dependencies. Then, work your way
up the tree to the projects that depend on these projects.
In the example shown in Figure 3-1, you would start with the [Link] project, since it doesn't depend on
any other projects. Next, [Link] since it only depends on "Utils". Then migrate the "BusinessLogic" library,
and finally the front-end [Link] "Web" project. Following this "bottom up" approach works well for relatively
small and well-factored apps that can be migrated as a unit once all of their projects have migrated. Larger apps
with more complexity, or more code that will take longer to migrate, may need to consider more incremental
strategies.

Unit tests
Missing from the previous diagrams are unit test projects. Hopefully there are tests covering at least some of the
existing behavior of the libraries being ported.
If you have unit tests, it's best to convert those projects first. You'll want to continue testing changes in the
project you're working on. Remember that porting to .NET Core is a significant change to your codebase.
MSTest, xUnit, and NUnit all work on .NET Core. If you don't currently have tests for your app, consider building
some characterization tests that verify the system's current behavior. The benefit is that once the migration is
complete, you can confirm the app's behavior remains unchanged.

Considerations for migrating many apps


Some organizations will have many different apps to migrate, and migrating each one by hand may require too
many resources to be tenable. In these situations, some degree of automation is recommended. The steps
followed in this chapter can be automated. Structural changes, like project file differences and updates to
common packages, can be applied by scripts. These scripts can be refined as they're run iteratively on more
projects. On each run, examine whatever manual steps are required for each project. Automate those manual
steps, if possible. Using this approach, the organization should grow faster and better at porting their apps over
time, with improved automation support each step of the way.
Watch an overview of how to employ this approach in this dotNetConf presentation by Lizzy Gallagher of
Mastercard. The five phases employed in this presentation included:
Migrate third-party NuGet dependencies
Migrate apps to use new .csproj file format
Update internal NuGet dependencies to .NET Standard
Migrate apps to [Link] Core (targeting .NET Framework)
Update all apps to target .NET Core 3.1
When automating a large suite of apps, it helps significantly if they follow consistent coding guidelines and
project organization. Automation efforts rely on this consistency to be effective. In addition to parsing and
migrating project files, common code patterns can be migrated automatically. Some code pattern examples
include differences in how controller actions are declared or how they return results.
For example, a migration script could search files matching [Link] for lines of code matching one of these
patterns:

return new HttpStatusCodeResult(200);


// or
return new HttpStatusCodeResult([Link]);

In [Link] Core, either of the preceding lines of code can be replaced with:

return Ok();

Summary
The best approach to porting a large .NET Framework app to .NET Core is to:
1. Identify project dependencies.
2. Analyze what's required to port each project.
3. Start from the bottom up.
You can use the .NET Portability Analyzer to determine how compatible existing libraries may be with target
platforms. Automated tests will help ensure no breaking changes are introduced as the app is ported. These test
projects should be among the first projects ported.

References
Porting from .NET Framework to .NET Core
The .NET Portability Analyzer
2 Years, 200 Apps: A .NET Core Migration at Scale (Video)

PR EVIO U S NE XT
Understand and update dependencies
6/16/2022 • 4 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

After identifying the sequence in which the app's individual projects must be migrated, the next step is to
understand each project's dependencies and update them if necessary. For platform dependencies, the best way
to start is to run the .NET Portability Analyzer on the assembly in question, and then look at the detailed results
that are generated. You configure the tool to specify one or more target platforms, such as .NET Core 3.1 or .NET
Standard 2.0. Results are provided with details for each platform targeted. Figure 3-4 shows an example of the
tool's output.

Figure 3-4. .NET Portability Analyzer report details.

Update class library dependencies


Large apps typically involve multiple projects, and most projects other than the [Link] MVC web project are
likely to be class libraries. Class libraries tend to be the easiest to port between .NET platforms, especially
compared to [Link] projects, which are among the most difficult (and typically need to be re-created).
Teams can consider the try-convert tool or the .NET Upgrade Assistant tool for migrating class libraries to .NET
Core. These tools analyze a .NET Framework project file and attempt to migrate it to the .NET Core project file
format, making any modifications it can safely perform in the process. The tools may require some manual
assistance to work with [Link] projects, but can usually help speed up the process of migrating class libraries.
The try-convert and Upgrade Assistant tools are deployed as .NET Core command line tools. They only run on
Windows, since they're designed to work with .NET Framework apps. You can install try-convert by running the
following command from a command prompt:

dotnet tool install -g try-convert

Once you've successfully installed the tool, you can run try-convert in the folder where the class library's
project file is located.
Install the .NET Upgrade Assistant with the following command (after installing try-convert):

dotnet tool install -g upgrade-assistant

Run the tool with the command upgrade-assistant upgrade <project> in the folder where the project file is
located.

Update NuGet package dependencies


Analyze your use of third-party NuGet packages and determine if any of them don't yet support .NET Standard
(or do support it but only with a new version). It can be helpful to update NuGet packages to use
<PackageReference> syntax using Visual Studio's converter tool, so that top-level dependencies are visible. Next,
check whether the current or later versions of these packages support .NET Core or .NET Standard. This
information can be found on [[Link]] or within Visual Studio for each package.
If support exists using the version of the package the app currently uses, great! If not, see if a more recent
version of the package has the support and research what would be involved in upgrading. There may be
breaking changes in the package, especially if the major version of the package changes between your currently
used version and the one to which you're upgrading.
In some cases, no version of a given package works with .NET Core. In that case, teams have a couple options.
They can continue depending on the .NET Framework version, but this has limitations. The app may only run on
Windows, and the team may want to run Portability Analyzer on the package's binaries to see if there are any
issues likely to be encountered. Certainly the team will want to test thoroughly, since if .NET Framework
packages are used that reference APIs not available in .NET Core, a runtime exception will occur. The other
option is to find a different package or, if the required package is open source, upgrade it to .NET Standard or
.NET Core themselves.

Migrate [Link] MVC projects


The [Link] namespace and types don't exist in .NET Core. When you're analyzing dependencies and using
tools like try-convert , you'll find they don't offer many suggestions for automatic migration of [Link] MVC
projects and any code in them that references [Link] . For these projects, you'll need to start with a new
[Link] Core web project and manually migrate files to this project.
In general, it's a good practice to minimize how much of an app's business logic lives in its user interface layer.
It's also best to keep controllers and views small. Apps that have followed this guidance will be easier to port
than those that have a significant amount of their logic in the [Link] web project. If you have an app you're
considering porting, but haven't begun the process yet, keep this in mind as you maintain it. Any effort you put
toward minimizing how much code is in the [Link] MVC or Web API project will likely result in less work when
the time comes to port the app.
The next chapter digs into details of how to migrate from [Link] MVC and Web API projects to [Link] Core
projects. The previous chapter called out the biggest differences between the apps. Once the basic project
structure is in place, migrating individual controllers and views is usually straightforward, especially if they're
mainly focused on web responsibilities.
References
.NET Upgrade Assistant tool
try-convert tool
apiport tool

PR EVIO U S NE XT
Strategies for migrating while running in production
6/16/2022 • 5 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

Many teams have .NET Framework apps they plan to migrate to .NET Core/.NET 5+, but the app is so large that
the migration requires a significant amount of time to complete. The original app needs to live on while the
migration is done piece by piece. There needs to be a way for the old and new versions of the app to work
together side-by-side, or for the old version to be migrated in-place, at least some of the way, without breaking
it. Teams can employ many different strategies to support these goals.

Refactor the .NET Framework solution


A good place to start if you plan to port a .NET Framework app to .NET Core is to refactor it to work better with
.NET Core. This means updating individual class libraries to target .NET Standard and moving as much logic out
of your [Link] MVC projects and into these class libraries. Any code you have in .NET Standard libraries is
immediately usable from both .NET Framework to .NET Core apps, which is why this step is so valuable as part
of a migration.
When refactoring, make sure you're following good refactoring fundamentals. For example, create tests that
verify what the system does before you start refactoring. Run these tests when you're done to confirm you
didn't change the system's behavior. You may need to add characterization tests to the system if you don't
already have a good suite of automated tests you can rely on.

Extract front-end assets to a CDN


If your .NET Framework apps include a lot of static assets, like scripts, CSS files, or images, you may be able to
migrate these to a separate CDN. Then, update the existing app to reference the CDN links for these assets.
When you port the app to .NET Core, these static files won't be part of the migration, and you'll just continue
referencing them from the CDN in the [Link] Core app.

Extract and migrate individual microservices


Large .NET Framework apps may already be comprised of separate front-end systems that can be migrated
individually. Or they may be candidates for migration to a microservices architecture, with some pieces of
existing [Link] MVC apps being pulled out into new [Link] Core microservice implementations. You can
learn more about microservices in the associated ebook, .NET Microservices: Architecture for Containerized .NET
Applications.
For example, the existing app might have a set of features it uses related to user sign-in and registration. These
could be migrated to a separate microservice, which could be built and deployed using [Link] Core and then
integrated into the legacy .NET Framework app. Next, the app might have a few pages dedicated to tracking the
individual user's shopping cart. These pages could also be pulled out into their own separate microservice and
again integrated into the existing app. In this way, the original .NET Framework app continues running in
production, but with more and more of its features coming from modernized .NET Core microservices.

Deploy multiple versions of the app side-by-side in IIS


Using a combination of host headers and redirects, an existing [Link] MVC app can be configured to run side
by side with an [Link] Core app on the same IIS server. As pieces of functionality, such as individual
controllers, are ported to [Link] Core, their routes and URLs are mapped within IIS to target the [Link] Core
web site or sub-application (IIS virtual directories aren't supported with [Link] Core apps). An [Link] Core
app can be hosted as an IIS sub-application (sub-app). The sub-app's path becomes part of the root app's URL.

Apply the Strangler pattern


Large [Link] MVC apps can be gradually replaced with a new [Link] Core app by incrementally migrating
pieces of functionality. One approach to this is called the strangler pattern, named for strangler vines that
strangle and eventually tear down trees. This approach relies on first implementing a facade layer over top of
the existing solution. This facade should be built using the new approach to the problem, or an off-the-shelf
solution such as an API gateway.
Once the facade is in place, you can route part of it to a new [Link] Core app. As you port more of the original
.NET Framework app to .NET Core, you continue to update the facade layer accordingly, sending more of the
facade's total functionality to the new system. Figure 3-5 shows the strangler pattern progression over time.

Figure 3-5. The Strangler pattern over time.


Eventually, the entire facade layer corresponds to the new, modern implementation. At this point, both the
legacy system and the face layer can be retired.

Multi-targeting approaches
Multi-targeting is recommended for large apps that will be migrated over time and for teams applying the
Strangler pattern approach. This approach can address BindingRedirect and package restoration challenges
that surface from mixing PackageReference and [Link] restore styles. There are two options available
for code that must run in both .NET Framework and .NET Core environments.
Preprocessor directives (#if in C# or #If in Visual Basic) allow you to implement different functionality or
use different dependencies when run in .NET Framework versus .NET Core.
Project files can use conditional globbing patterns, such as *.[Link] , to include different sets of files
based on which framework is being targeted.
Typically you only follow these recommendations for class libraries. These techniques allow a single common
codebase to be maintained while new functionality is added and features of the app are incrementally ported to
use .NET Core.

Summary
Frequently, large [Link] MVC and Web API apps won't be ported to [Link] Core all at once, but will migrate
incrementally over time. This section offers several strategies for performing this incremental migration. Choose
the one(s) that will work best for your organization and app.

References
.NET Microservices: Architecture for Containerized .NET Applications
eShopOnContainers Reference Microservices Application
Host [Link] Core on Windows with IIS
Strangler pattern

PR EVIO U S NE XT
Example migration of eShop to [Link] Core
6/16/2022 • 29 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

In this chapter, you'll see how to migrate a .NET Framework app to .NET Core. The chapter examines a sample
online store app written for [Link] MVC 5. The app will use many of the concepts and tools described earlier in
this book. You'll find the starting point app in the eShopModernizing GitHub repository. There are several
different starting point apps. This chapter focuses on the eShopLegacyMVCSolution.
The initial version of the project is shown in Figure 4-1. It's a fairly standard [Link] MVC 5 app.

Figure 4-1. The eShopModernizing MVC sample project structure.


This chapter demonstrates how to perform many of the upgrade steps by hand. Alternatively, you can use the
.NET Upgrade Assistant tool to perform many of the initial steps, like converting the project file, changing the
target framework, and updating NuGet packages.

Run ApiPort to identify problematic APIs


The first step in preparing to migrate is to run the ApiPort tool. The tool identifies how many .NET Framework
APIs the app calls and how many of these have .NET Standard or .NET Core equivalents. Focus primarily on your
own app's logic, not third-party dependencies, and pay attention to [Link] dependencies that will need to
be ported. The ApiPort tool was introduced in the last chapter on understanding and updating dependencies.
Note that currently it requires Visual Studio 2019; Visual Studio 2022 support is planned.
After installing and configuring the ApiPort tool, run the analysis from within Visual Studio, as shown in Figure
4-2.

Figure 4-2. Analyze assembly portability in Visual Studio.


Choose the web project's assembly from the project's bin folder, as shown in Figure 4-3.
Figure 4-3. Choose the project's web assembly.
If your solution includes several projects, you can choose all of them. The eShop sample includes just a single
MVC project.
Once the report is generated, open the file and review the results. The summary provides a high-level view of
what percentage of .NET Framework calls your app is making have compatible versions. Figure 4-4 shows the
summary for the eShop MVC project.

Figure 4-4. ApiPort summary.


For this app, about 80 percent of the .NET Framework calls are compatible. 20 percent of the calls need to be
addressed during the porting process. Viewing the details reveals that all of the incompatible calls are part of
[Link] , which is an expected incompatibility. The dependencies on [Link] calls will be addressed
when the app's controllers and related classes are migrated in a later step. Figure 4-5 lists some of the specific
types found by the tool:
Figure 4-5. ApiPort incompatible type details.
Most of the incompatible types refer to Controller and various related attributes that have equivalents in
[Link] Core.

Update project files and NuGet reference syntax


Next, migrate from the older .csproj file structure to the newer, simpler structure introduced with .NET Core. In
doing so, you'll also migrate from using a [Link] file for NuGet references to using
<PackageReference> elements in the project file. Old-style project files may also use <PackageReference>
elements, so it usually makes sense to migrate all NuGet package references to this format first, before
upgrading to the new project file format.
The original project's [Link] file is 418 lines long. A sample of the project file is shown in
Figure 4-6. To offer a sense of its overall size and complexity, the right side of the image contains a miniature
view of the entire file.
Figure 4-6. The [Link] file structure.
A common way to create a new project file for an existing [Link] project is to create a new [Link] Core app
using dotnet new or File > New > Project in Visual Studio. Then files can be copied from the old project to the
new one to complete the migration.
In addition to the C# project file, NuGet dependencies are stored in a separate 45-line [Link] file, as
shown in Figure 4-7.

Figure 4-7. The [Link] file.


You can migrate [Link] in class library projects using Visual Studio. This functionality doesn't work
with [Link] projects, however. Learn more about migrating [Link] to <PackageReference> in Visual
Studio. If you have a large number of projects to migrate, this community tool may help. If you're using a tool to
migrate the project file to the new format, you should do that after you've finished migrating all NuGet
references to use <PackageReverence> .

Create new [Link] Core project


Add a new [Link] Core project to the existing app's solution to make moving files easier, as most of the work
can be done from within Visual Studio's Solution Explorer . In Visual Studio, right-click on your app's solution
and choose Add New Project . Choose [Link] Core web application , and give the new project a name as
shown in Figure 4-8.

Figure 4-8. Add new [Link] Core web application.


The next dialog will ask you to choose which template to use. Select the Empty template. Be sure to also change
the dropdown from .NET Core to .NET Framework . Select [Link] Core 2.2 , as shown in Figure 4-9.
Figure 4-9. Choose an Empty project template targeting .NET Framework with [Link] Core 2.2.
Migrating NuGet Packages
Since the built-in migration tool for migrating [Link] to <PackageReference> doesn't work on [Link]
projects, you can use a community tool instead, or migrate by hand. A community tool I've used uses an XSL file
to transform from one format to the other. To use it, first copy the [Link] file to the newly created
[Link] Core project folder. Make a backup of your files, as this script removes the [Link] file from all
folders under where you run the script. Then run these commands from the project folder (or for the entire
solution if you prefer):

iwr
[Link]
dccfb1ba3e80a0/Convert-ToPackageReference.ps1 -OutFile Convert-ToPackageReference.ps1
iwr
[Link]
dccfb1ba3e80a0/[Link] -OutFile [Link]
./Convert-ToPackageReference.ps1 | Out-Null

The first two commands download files so that they exist locally. The last line runs the script. After running it, try
to build the new project. You'll most likely get some errors. To resolve them, you'll want to eliminate some
references (like most of the [Link] and System packages), and you may need to remove some
xmlns attributes.

In most [Link] MVC apps, many client-side dependencies like Bootstrap and jQuery were deployed using
NuGet packages. In [Link] Core, NuGet packages are only used for server-side functionality. Client files should
be managed through other means. Review the list of <PackageReference> elements added and remove and
make note of any that are for client libraries, including:
Bootstrap
jQuery
[Link]
Modernizr
[Link]
Respond
The static client files installed by NuGet for these packages will be copied over to the new project's wwwroot
folder and hosted from there. It's worth considering whether these files are still needed by the app, and whether
it makes sense to continue hosting them or to use a content delivery network (CDN) instead. These library
versions can be managed at build time using tools like LibMan or npm. Figure 4-10 shows the full
[Link] file after migrating package references using the conversion tool shown and removing
unnecessary packages.

Figure 4-10. Package references in the [Link] file.


The package references can be further compacted by making the <Version>[Link]</Version> element a
Version=[Link] attribute on <PackageReference> .

Migrate static files


Any static files the app uses, including third-party scripts and frameworks but also custom images and
stylesheets, must be copied from the old project to the new one. In [Link] MVC apps, files were typically
accessed based on their location within the project folder. In [Link] Core apps, these static files will be accessed
based on their location within the wwwroot folder. For the eShop project, there are static files in the following
folders:
Content
fonts
Images
Pics
Scripts
The Empty project template used in the previous step doesn't include this folder by default, or the middleware
needed for it to work. You'll need to add them.
Add a wwwroot folder to the root of the project.
Add version 2.2.0 of the [Link] NuGet package.
In [Link], add a call to [Link]() in the Configure method:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)


{
if ([Link]())
{
[Link]();
}

[Link]();

// ...
}

Copy the Content folder from the [Link] MVC app to the new project's wwwroot folder.
Run the app and navigate to its /Content/[Link] folder to verify that the static file is served correctly from its
expected path. Continue copying the rest of the folders containing static files to the new project. You'll also want
to copy the [Link] file from the project's root to the wwwroot folder. Figure 4-11 shows the results after
these files and their folders have all been copied.

Figure 4-11. Static folders copied over to wwwroot folder.


Migrate C# files
Next, copy over the C# files used by the app, including standard MVC folders and their contents like Controllers,
Models, ViewModel, and Services. There will most likely be some changes needed in these files. It's best to copy
one folder (or subfolder) at a time and compile to see what errors need to be addressed as you go.
For the eShop sample, the first folder I choose to migrate is the Models folder, which includes C# entities and
Entity Framework classes. This folder's classes are used by most of the others, so they won't work until these
classes have been copied. After copying the folder and building, the compiler revealed errors related to missing
namespace [Link] , related access to HostingEnvironment , and a reference to
[Link] . The solution to these issues will be to pass in the necessary path data; for
now the breaking lines are commented out and a TODO: comment is added to each one to track it. After
changing five lines, the Task List shows five items and the project builds.
Next, the ViewModel folder, with its one class, is copied over. It's an easy one, and builds immediately.
The Services folder is copied over. This folder's classes depend on Entity Framework classes from the Models
folder, which is why it needed to be copied after that folder. Fortunately, it too builds without errors.
That leaves the Controllers folder and its two Controller classes. After copying the folder to the new project
and building, there are seven build errors. Four of them are related to ViewBag access and report an error of:

Missing compiler required member '[Link]'

To resolve this error, add a NuGet package reference to C#:

<PackageReference Include="[Link]" Version="4.7.0" />

The remaining three errors specify types that are defined in an assembly that isn't referenced. Specifically these
types:
HttpServerUtilityBase
RouteValueDictionary
HttpRequestBase

Let's look at each error one by one. The first error occurs while trying to reference the Server property of
Controller , which no longer exists. The goal of the operation is to get the path to an image file in the app:

if (item != null)
{
var webRoot = [Link]("~/Pics"); // compiler error on this line
var path = [Link](webRoot, [Link]);

string imageFileExtension = [Link]([Link]);


string mimetype = GetImageMimeTypeFromImageFileExtension(imageFileExtension);

var buffer = [Link](path);

return File(buffer, mimetype);


}

There are two possible solutions to this problem. The first is to keep the functionality as it is. In this case, rather
than using [Link] , a fixed path referencing the image files' location in wwwroot should be used.
Alternately, since the only purpose of this action method is to return a static image file, the references to this
action in view files can be updated to reference the static files directly, which improves runtime performance.
Since no processing is being done as part of this action, there's no reason not to just serve the files directly. If it's
not tenable to update all references to this action, the action could be rewritten to produce a redirect to the static
file's location.
The next two errors both occur in the same private method in the same line of code:

private void AddUriPlaceHolder(CatalogItem item)


{
[Link] = [Link]([Link], new { catalogItemId = [Link] },
[Link]);
}

Both [Link] and [Link] cause compiler errors. Looking at how this code is used, its purpose is to
build a link to the PicController action that renders image files. The same one we just discovered could
probably be replaced with direct links to the static files located in wwwroot. For now, it's worth commenting out
this code and adding a TODO: comment to reference the pics another way.
It's worth noting that the base Controller class, used by the CatalogController class in which this code
appears, is still referring to [Link] . There will undoubtedly be more errors to fix once we
update this to use [Link] Core. First, remove the using [Link]; line from the list of using statements
in CatalogController . Next, add the NuGet package [Link] . Finally, add a
using [Link]; statement, and build the app again.

This time, there are 16 errors:


Include is not a valid named attribute argument (2)
HttpStatusCodeResult not found (3)
HttpNotFound does not exist (3)
SelectList not found (8)

Once more, let's review these errors one by one. First, SelectList can be fixed by adding
using [Link]; , which eliminates half of the errors.

All references to return HttpNotFound(); should be replaced with return NotFound(); .


All references to return new HttpStatusCodeResult([Link]); should be replaced with
return BadRequest(); .
That just leaves the use of Include with a [Bind] attribute on a couple of action methods that look like this:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include =
"Id,Name,Description,Price,PictureFileName,CatalogTypeId,CatalogBrandId,AvailableStock,RestockThreshold,MaxS
tockThreshold,OnReorder")] CatalogItem catalogItem)
{

The preceding code restricts model binding to the properties listed in the Include string. In [Link] Core MVC,
the [Bind] attribute still exists, but no longer needs the Include = argument. Pass the list of properties directly
to the [Bind] attribute:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult
Create([Bind("Id,Name,Description,Price,PictureFileName,CatalogTypeId,CatalogBrandId,AvailableStock,RestockT
hreshold,MaxStockThreshold,OnReorder")] CatalogItem catalogItem)
{

With these changes, the project compiles once more. It's generally a better practice to use separate model types
for controller inputs, rather than using model binding directly to your domain model or data model types.

Migrate views
The two biggest [Link] Core MVC features related to views are Razor Pages and Tag Helpers. For the initial
migration, we won't use either feature. You should, however, keep the features in mind if you continue
supporting the app once it's been migrated. The next step is to copy the Views folder from the original project
into the new one. After building, there are nine errors:
HttpContext does not exist (2)
Scripts does not exist (5)
Styles does not exist (1)
HtmlString could not be found(1)
Investigating these errors finds that most of them are in the main _Layout.cshtml, with several related to
rendering script and style tags, or displaying when the server hosting the app was last restarted. The following
code listing shows problem areas in the _Layout.cshtml file:

// other lines omitted; only errors shown


@[Link]("~/Content/css")
@[Link]("~/bundles/modernizr")

@{ var sessionInfo = new HtmlString($"{[Link]["MachineName"]},


{[Link]["SessionStartTime"]}");}

@[Link]("~/bundles/jquery")
@[Link]("~/bundles/bootstrap")

The reference to Modernizr can be removed. The references to Bootstrap and jQuery can be replaced with CDN
links to the appropriate version.
Replace @[Link] line with:

<link rel="stylesheet" href="[Link]


integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">

Replace the last two [Link] lines with:

<script src="[Link] integrity="sha384-


q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="[Link] integrity="sha384-
UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="[Link] integrity="sha384-
JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>

Finally, after the Bootstrap <link> , add additional <link> elements for local styles your app uses. For eShop,
the result is shown here:

<link rel="stylesheet" href="~/Content/[Link]" />


<link rel="stylesheet" href="~/Content/[Link]" />
<link rel="stylesheet" href="~/Content/[Link]" />

To determine the order in which the <link> elements should appear, look at your original app's rendered HTML.
Alternatively, review [Link], which for the eShop sample includes this code indicating the appropriate
sequence:

[Link](new StyleBundle("~/Content/css").Include(
"~/Content/[Link]",
"~/Content/[Link]",
"~/Content/[Link]",
"~/Content/[Link]"));

Building again reveals one more error loading jQuery Validation on the Create and Edit views. Replace it with
this script:

<script src="[Link]
integrity="sha512-O/nUTF5mdFkhEoQHFn9N5wmgYyW323JO6v8kr6ltSRKriZyTr/8417taVWeabVS4iONGk2V444QD0P2cwhuTkg=="
crossorigin="anonymous"></script>
The last thing to fix in the views is the reference to Session to display how long the app has been running, and
on which machine. We can display this data directly in the site's _Layout.cshtml by using
[Link] and [Link]().StartTime :

<section class="col-sm-6">
<img class="esh-app-footer-text hidden-xs" src="~/images/main_footer_text.png" width="335" height="26"
alt="footer text image" />
<br />
<small>@[Link] - @[Link]().[Link]()
UTC</small>
</section>

At this point, the app once more builds successfully. However, trying to run it just yields Hello World! because
the Empty [Link] Core template is only configured to display that in response to any request. In the next
section, I complete the migration by configuring the app to use [Link] Core MVC, including dependency
injection and configuration. Once that's in place, the app should run. Then it will be time to fix the TODO: tasks
that were created earlier.

Migrate app startup components


The last migration step is to take the app startup tasks from [Link], and the classes it calls, and migrate
these to their [Link] Core equivalents. These tasks include configuration of MVC itself, setting up dependency
injection, and working with the new configuration system. In [Link] Core, these tasks are handled in the
[Link] file.
Configure MVC
The original [Link] MVC app has the following code in its Application_Start in [Link], which runs when
the app starts up:

protected void Application_Start()


{
container = RegisterContainer();
[Link]();
[Link]([Link]);
[Link]([Link]);
[Link]([Link]);
ConfigDataBase();
}

Looking at these lines one by one, the RegisterContainer method sets up dependency injection, which will be
ported below. The next three lines configure different parts of MVC: areas, filters, and routes. Bundles are
replaced by static files in the ported app. The last line sets up data access for the app, which will be shown in a
later section.
Since this app isn't actually using areas, there's nothing that needs to be done to migrate the area registration
call. If your app does need to migrate areas, the docs specify how to configure areas in [Link] Core.
The call to register global filters invokes a helper on the FilterConfig class in the app's App_Start folder:

public static void RegisterGlobalFilters(GlobalFilterCollection filters)


{
[Link](new HandleErrorAttribute());
}

The only attribute added to the app is the [Link] MVC filter, HandleErrorAttribute . This filter ensures that
when an exception occurs as part of a request, a default action and view are displayed, rather than the exception
details. In [Link] Core, this same functionality is performed by the UseExceptionHandler middleware. The
detailed error messages aren't enabled by default. They must be configured using the
UseDeveloperExceptionPage middleware. To configure this behavior to match the original app, the following code
must be added to the start of the Configure method in [Link]:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)


{
if ([Link]())
{
[Link]();
}
else
{
[Link]("/Error");
}
// ...
}

This takes care of the only filter used by the eShop app, and in this case it was done by using built-in
middleware. If you have global filters that must be configured in your app, this is done when MVC is added in
the ConfigureServices method, which is shown later in this chapter.
The last piece of MVC-related logic that needs to be migrated are the app's default routes. The call to
[Link]([Link]) passes the MVC route table to the RegisterRoutes helper
method, where the following code is executed when the app starts up:

public static void RegisterRoutes(RouteCollection routes)


{
[Link]();
[Link]("{resource}.axd/{*pathInfo}");

[Link](
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Catalog", action = "Index", id = [Link] }
);
}

Taking this code line-by-line, the first line sets up support for attribute routes. This is built into [Link] Core, so
it's unnecessary to configure it separately. Likewise, {resource}.axd files aren't used with [Link] Core, so there's
no need to ignore such routes. The MapRoute method configures the default for MVC, which uses the typical
{controller}/{action}/{id} route template. It also specifies the defaults for this template, such that the
CatalogController is the default controller used and the Index method is the default action. Larger apps will
frequently include more calls to MapRoute to set up additional routes.
[Link] Core MVC supports conventional routing and attribute routing. Conventional routing is analogous to
how the route table is configured in the RegisterRoutes method listed previously. To set up conventional
routing with a default route like the one used in the eShop app, add the following code to the bottom of the
Configure method in [Link]:

[Link](routes =>
{
[Link]("default", "{controller=Catalog}/{action=Index}/{id?}");
});
NOTE
With [Link] Core 3.0 and later, this is changed to use endpoints. For the initial port to [Link] Core 2.2, this is the
proper syntax for mapping conventional routes.

With these changes in place, the Configure method is almost done. The original template's [Link] method
that prints Hello World! should be deleted. At this point, the method is as shown here:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)


{
if ([Link]())
{
[Link]();
}
else
{
[Link]("/Home/Error");
}

[Link]();

[Link](routes =>
{
[Link]("default", "{controller=Catalog}/{action=Index}/{id?}");
});
}

Now it's time to configure MVC services, followed by the rest of the app's support for dependency injection (DI).
So far, the eShopPorted project's ConfigureServices method has remained empty. Now it's time to start
populating it.
First, to get [Link] Core MVC to work properly, it needs to be added:

public void ConfigureServices(IServiceCollection services)


{
[Link]();
}

The preceding code is the minimal configuration required to get MVC features working. There are many
additional features that can be configured from this call (some of which are detailed later in this chapter), but for
now this will suffice to build the app. Running it now routes the default request properly, but since we've not yet
configured DI, an error occurs while activating CatalogController , because no implementation of type
ICatalogService has been provided yet. We'll return to configure MVC further in a moment. For now, let's
migrate the app's dependency injection.
Migrate dependency injection configuration
The original app's [Link] file defines the following method, called when the app starts up:
protected IContainer RegisterContainer()
{
var builder = new ContainerBuilder();

[Link](typeof(MvcApplication).Assembly);

var mockData = [Link]([Link]["UseMockData"]);


[Link](new ApplicationModule(mockData));

var container = [Link]();


[Link](new AutofacDependencyResolver(container));

return container;
}

This code configures an Autofac container, reads a config setting to determine whether real or mock data should
be used, and passes this setting into an Autofac module (found in the app's /Modules directory). Fortunately,
Autofac supports .NET Core, so the module can be migrated directly. Copy the folder into the new project and
updates the class's namespace and it should compile.
[Link] Core has built-in support for dependency injection, but you can wire up a third-party container such as
Autofac easily if necessary. In this case, since the app is already configured to use Autofac, the simplest solution
is to maintain its usage. To do so, the ConfigureServices method signature must be modified to return an
IServiceProvider , and the Autofac container instance must be configured and returned from the method.

Note: In .NET Core 3.0 and later, the process for integrating a third-party DI container has changed.
Part of configuring Autofac requires a call to [Link](services) . This extension is found in the
[Link] NuGet package, which must be installed before the code will compile.

After modifying ConfigureServices to configure an Autofac container, the new method is as shown here:

public IServiceProvider ConfigureServices(IServiceCollection services)


{
[Link]();

// Create Autofac container builder


var builder = new ContainerBuilder();
[Link](services);
bool useMockData = true; // TODO: read from config
[Link](new ApplicationModule(useMockData));

ILifetimeScope container = [Link]();

return new AutofacServiceProvider(container);


}

For now, the setting for useMockData is set to true . This setting will be read from configuration in a moment. At
this point, the app compiles and should load successfully when run, as shown in Figure 4-12.
Figure 4-12. Ported eShop app running locally with mock data.
Migrate app settings
[Link] Core uses a new configuration system, which by default uses an [Link] file. By using
CreateDefaultBuilder in [Link], the default configuration is already set up in the app. To access
configuration, classes just need to request it in their constructor. The Startup class is no exception. To start
accessing configuration in Startup and the rest of the app, request an instance of IConfiguration from its
constructor:

public Startup(IConfiguration configuration)


{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

The original app referenced its settings using [Link] . A quick search for all
references of this term yields the set of settings the new app needs. There are only two:
UseMockData
UseCustomizationData

If your app has more complex configuration, especially if it's using custom configuration sections, you'll
probably want to create and bind objects to different parts of your app's configuration. These types can then be
accessed using the options pattern. However, as noted in the referenced doc, this pattern shouldn't be used in
ConfigureServices . Instead the ported app will reference the UseMockData configuration value directly.

First, modify the ported app's [Link] file and add the two settings in the root:
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*",
"UseMockData": "true",
"UseCustomizationData" : "true"
}

Now, modify ConfigureServices to access the UseMockData setting from the Configuration property (where
previously we set the value to true ):

bool useMockData = [Link]<bool>("UseMockData");

At this point, the setting is pulled from configuration. The other setting, UseCustomizationData , is used by the
CatalogDBInitializer class. When you first ported this class, you commented out the access to
[Link]["UseCustomizationData"] . Now it's time to modify it to use [Link] Core
configuration. Modify the constructor of CatalogDBInitializer as follows:

// add using [Link]


public CatalogDBInitializer(CatalogItemHiLoGenerator indexGenerator,
IConfiguration configuration)
{
[Link] = indexGenerator;
useCustomizationData = [Link]<bool>("UseCustomizationData");
}

All access to configuration within the web app should be modified in this manner to use the new
IConfiguration type. Dependencies that require access to .NET Framework configuration can include such
settings in an [Link] file added to the web project. The dependent projects can work with
ConfigurationManager to access settings, and shouldn't require any changes if they already use this approach.
However, since [Link] Core apps run as their own executable, they don't reference [Link] but rather
[Link]. By migrating settings from the legacy app's [Link] file to a new [Link] file in the [Link]
Core app, components that use ConfigurationManager to access their settings will continue to function properly.
The app's migration is nearly complete. The only remaining task is data access configuration.

Data access considerations


[Link] Core apps running on .NET Framework can continue to use Entity Framework (EF). If performing an
incremental migration, getting the app working with EF 6 before trying to port its data access to use EF Core
may be worthwhile. In this way, any problems with the app's migration can be identified and addressed before
another block of migration effort is begun.
As it happens, configuring EF 6 in the eShop sample migration doesn't require any special work, since this work
was performed in the Autofac ApplicationModule . The only problem is that currently the CatalogDBContext class
tries to read its connection string from [Link]. To address this, the connection details need to be added to
[Link]. Then the connection string must be passed into CatalogDBContext when it's created.
Update the [Link] to include the connection string. The full file is listed here:
{
"ConnectionStrings": {
"DefaultConnection": "Server=
(localdb)\\mssqllocaldb;Database=eShopPorted;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*",
"UseMockData": "false",
"UseCustomizationData": "true"
}

The connection string must be passed into the constructor when the DbContext is created. Since the instances
are created by Autofac, the change needs to be made in ApplicationModule . Modify the module to take in a
connectionString in its constructor and assign it to a field. Then modify the registration for CatalogDBContext to
add connection string as a parameter:

[Link]<CatalogDBContext>()
.WithParameter("connectionString", _connectionString)
.InstancePerLifetimeScope();

The parameter must also be added to a new constructor overload in CatalogDBContext itself:

public CatalogDBContext(string connectionString) : base(connectionString)


{
}

Finally, ConfigureServices must read the connection string from Config and pass it into the ApplicationModule
when it instantiates it:

bool useMockData = [Link]<bool>("UseMockData");


string connectionString = [Link]("DefaultConnection");
[Link](new ApplicationModule(useMockData, connectionString));

With this code in place, the app runs as it did before, connecting to a SQL Server database when UseMockData is
false .

The app can be deployed and run in production at this point, converted to [Link] Core but still running on
.NET Framework and EF 6. If desired, the app can be migrated to run on .NET Core and Entity Framework Core,
which will bring additional advantages described in earlier chapters. Specific to Entity Framework, this
documentation compares EF Core and EF 6 and includes a grid showing which library supports each of dozens
of individual features.
Migrate to Entity Framework Core
Assuming a decision is made to migrate to EF Core, the steps can be fairly straightforward, especially if the
original app used a code-based model approach. When preparing to port from EF 6 to EF Core, review the
availability of features in the destination version of EF Core you'll be using. Review the documentation on
porting from and EDMX-based model versus porting from a code-based model.
To upgrade to EF Core 2.2, the basic steps involved are to add the appropriate NuGet package(s) and update
namespaces. Then adjust how the connection string is passed to the DbContext type and how they're wired up
for dependency injection.
EF Core is added as a package reference to the project:

<PackageReference Include="[Link]" Version="2.2.6" />

The reference to EF 6 is removed:

<PackageReference Include="EntityFramework" Version="6.2.0" />

The compiler will report errors in CatalogDBContext and CatalogDBInitializer . CatalogDbContext needs to have
the old namespaces removed and replaced with [Link] . Its constructors can be
removed. DbModelBuilder should be replaced with ModelBuilder . The helper methods for configuring types are
moved to separate classes implementing IEntityTypeConfiguration<T> . Then the CatalogDBContext class's
OnModelCreating method simply becomes:

protected override void OnModelCreating(ModelBuilder builder)


{
[Link]([Link]());

[Link](builder);
}

Other changes involved include:


HasDatabaseGeneratedOption([Link]) replaced with ValueGeneratedNever()
HasRequired<T> replaced with HasOne<T>
Installed [Link] package
Add a constructor to CatalogDBContext taking DbContextOptions and passing it to the base constructor
An example configuration class for CatalogType is shown here:

using [Link];
using [Link];

namespace [Link]
{
public class CatalogTypeConfig : IEntityTypeConfiguration<CatalogType>
{
public void Configure(EntityTypeBuilder<CatalogType> builder)
{
[Link](nameof(CatalogType));

[Link](ci => [Link]);

[Link](ci => [Link])


.IsRequired();

[Link](cb => [Link])


.IsRequired()
.HasMaxLength(100);
}
}
}

The CatalogDBInitializer and its base class, CreateDatabaseIfNotExists<T> , are incompatible with EF Core. The
purpose of this class is to create and seed the database. Using EF Core will create and drop the associated
database for a DbContext using these methods:
[Link]();
[Link]();

Seeding data in EF Core can be done with manual scripts, or as part of the type configuration. Along with other
entity properties, seed data can be configured in IEntityTypeConfiguration classes by using [Link]() .
The original app loaded seed data from CSV files in the Setup directory. Given that there are only a handful of
items, these data records can instead be added as part of the entity configuration. This approach works well for
lookup data in tables that change infrequently. Adding the following to CatalogTypeConfig 's Configure method
ensures the associated rows are present when the database is created:

[Link](
new CatalogType { Id = 1, Type = "Mug" },
new CatalogType { Id = 2, Type = "T-Shirt" },
new CatalogType { Id = 3, Type = "Sheet" },
new CatalogType { Id = 4, Type = "USB Memory Stick" }
);

The initial app includes a PreconfiguredData class, which includes data for CatalogBrand and CatalogType , so
using this method the HasData call reduces to:

[Link](
[Link]()
);

The CatalogItem data can also be pulled from PreconfiguredData , and assuming the associated images are kept
in source control, that is the last table needed for the app to function. The CatalogDBInitializer class can be
removed, along with any references to it. The CatalogItemHiLoGenerator class and the SQL files in the
Infrastructure directory are also removed, along with any references to them (in CatalogService ,
ApplicationModule ).

With the elimination of the special key generator classes for CatalogItem , this code now is removed from
CatalogItemConfig :

[Link](ci => [Link])


.ValueGeneratedNever()
.IsRequired();

With these modifications, the [Link] Core app builds, but it doesn't yet work with EF Core, which must still be
configured for dependency injection. With EF Core, the simplest way to configure it is in ConfigureServices :
public IServiceProvider ConfigureServices(IServiceCollection services)
{
[Link]();
bool useMockData = [Link]<bool>("UseMockData");
if (!useMockData)
{
string connectionString = [Link]("DefaultConnection");

[Link]<CatalogDBContext>(options =>
[Link](connectionString)
);
}

// Create Autofac container builder


var builder = new ContainerBuilder();
[Link](services);
[Link](new ApplicationModule(useMockData));

ILifetimeScope container = [Link]();

return new AutofacServiceProvider(container);


}

The final version of Autofac's ApplicationModule only configures one type, depending on whether the app is
configured to use mock data:

public class ApplicationModule : Module


{
private bool _useMockData;

public ApplicationModule(bool useMockData)


{
_useMockData = useMockData;
}

protected override void Load(ContainerBuilder builder)


{
if (_useMockData)
{
[Link]<CatalogServiceMock>()
.As<ICatalogService>()
.SingleInstance();
}
else
{
[Link]<CatalogService>()
.As<ICatalogService>()
.InstancePerLifetimeScope();
}
}
}

The ported app runs, but doesn't display any data if configured to use non-mock data. The seed data added
through HasData is only inserted when migrations are applied. The source app didn't use migrations, and if it
had, they wouldn't migrate as-is. The best approach is to start with a new migration script. To do this, add a
package reference for [Link] and open a terminal window in the project root.
Then run:

dotnet ef migrations add Initial

Drop the existing eShopPorted database if it exists, then run:


dotnet ef database update

This creates and seeds the database. It's now ready to run, with a few small updates left to address.

Fix all TODO tasks


Running the ported app at this point reveals that no pictures are shown on the page. This is because the
PictureUri property of CatalogItem is never set. Looking at the list of TODO items we created using Visual
Studio's Task List , the only one that remains is in CatalogController , with a note to "Reference pic from
wwwroot." The code in question is:

private void AddUriPlaceHolder(CatalogItem item)


{
//TODO: Reference pic from wwwroot
//[Link] = [Link]([Link], new { catalogItemId = [Link] },
[Link]);
}

The simplest fix is to reference the public image files in the site's public wwwroot/Pics directory. This task can be
accomplished by replacing the method with the following code:

private void AddUriPlaceHolder(CatalogItem item)


{
[Link] = $"/Pics/{[Link]}.png";
}

With this change, running the app reveals the images work as before.

Additional MVC customizations


The eShopLegacyMVC app is fairly simple, so there isn't much to configure in terms of default MVC behavior.
However, if you do need to configure additional MVC components, such as CORS, filters, and route constraints,
you generally provide this information in [Link] , where UseMvc is called. For example, the
following code listing configures CORS and sets up a global action filter:

public void ConfigureServices(IServiceCollection services)


{
[Link](options =>
{
[Link](MyAllowSpecificOrigins,
builder =>
[Link]("[Link] "[Link]
.AllowAnyHeader()
.AllowAnyMethod());
});

[Link](options =>
{
[Link](new SampleGlobalActionFilter());
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

NOTE
To finish configuring CORS, you must also call [Link]() in Configure .
Other advanced scenarios, like adding custom model binders, formatters, and more are covered in the detailed
[Link] Core docs. Generally these can be applied on an individual controller or action basis, or globally using
the same options approach shown in the previous code listing.

Other dependencies
Dependencies that use .NET Framework features that had a dependency on the legacy configuration model, such
as the WCF client type and tracing code, must be modified when ported. Rather than having these types pull in
their configuration information directly, they should be configured in code. For example, a connection to a WCF
service that was configured in an [Link] app's [Link] to use basicHttpBinding could instead be
configured programmatically with the following code:

var binding = new BasicHttpBinding();


[Link] = 2_000_000;

var endpointAddress = new EndpointAddress("[Link]

var myClient = new MyServiceClient(binding, endpointAddress);

Rather than relying on config files for its settings, WCF clients and other .NET Framework types should have
their settings specified in code. Configured in this manner, these types can continue to work in [Link] Core 2.2
apps.

References
eShopModernizing GitHub repository
.NET Upgrade Assistant tool
Your API and ViewModels Should Not Reference Domain Models
Developer Exception Page Middleware
Deep Dive into EF Core HasData

PR EVIO U S NE XT
More migration scenarios
6/16/2022 • 18 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

This section describes several different [Link] app scenarios, and offers specific techniques for solving each of
them. You can use this section to identify scenarios applicable to your app, and evaluate which techniques will
work for your app and its hosting environment.

Migrate [Link] MVC 5 and WebApi 2 to [Link] Core MVC


A common scenario in [Link] MVC 5 and Web API 2 apps was for both products to be installed in the same
application. This is a supported and relatively common approach used by many teams, but because the two
products use different abstractions, there is some redundant effort needed. For example, setting up routes for
[Link] MVC is done using methods on RouteCollection , such as MapMvcAttributeRoutes() and MapRoute() .
But [Link] Web API 2 routing is managed with HttpConfiguration and methods like MapHttpAttributeRoutes()
and MapHttpRoute() .
The eShopLegacyMVC app includes both [Link] MVC and Web API, and includes methods in its App_Start
folder for setting up routes for both. It also supports dependency injection using Autofac, which also requires
two sets of similar work to configure:
protected IContainer RegisterContainer()
{
var builder = new ContainerBuilder();

var thisAssembly = [Link]();


[Link](thisAssembly); // MVC controllers
[Link](thisAssembly); // Web API controllers

var mockData = [Link]([Link]["UseMockData"]);


[Link](new ApplicationModule(mockData));

var container = [Link]();

// set mvc resolver


[Link](new AutofacDependencyResolver(container));

// set webapi resolver


var resolver = new AutofacWebApiDependencyResolver(container);
[Link] = resolver;

return container;
}

When upgrading these apps to use [Link] Core, this duplicate effort and the confusion that sometimes
accompanies it is eliminated. [Link] Core MVC is a unified framework with one set of rules for routing, filters,
and more. Dependency injection is built into .NET Core itself. All of this can can be configured in [Link] , as
is shown in the eShopPorted app in the sample.

Migrate HttpResponseMessage to [Link] Core


Some [Link] Web API apps may have action methods that return HttpResponseMessage . This type does not
exist in [Link] Core. Below is an example of its usage in a Delete action method, using the ResponseMessage
helper method on the base ApiController :

// DELETE api/<controller>/5
[HttpDelete]
public IHttpActionResult Delete(int id)
{
var brandToDelete = _service.GetCatalogBrands().FirstOrDefault(x => [Link] == id);
if (brandToDelete == null)
{
return ResponseMessage(new HttpResponseMessage([Link]));
}

// demo only - don't actually delete


return ResponseMessage(new HttpResponseMessage([Link]));
}

In [Link] Core MVC, there are helper methods available for all of the common HTTP response status codes, so
the above method would be ported to the following code:
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
var brandToDelete = _service.GetCatalogBrands().FirstOrDefault(x => [Link] == id);
if (brandToDelete == null)
{
return NotFound();
}

// demo only - don't actually delete


return Ok();
}

If you do find that you need to return a custom status code for which no helper exists, you can always use
return StatusCode(int statusCode) to return any numeric code you like.

Migrate content negotiation from [Link] Web API to [Link] Core


[Link] Web API 2 supports content negotiation natively. The sample app includes a BrandsController that
demonstrates this support by listing its results in either XML or JSON. This is based on the request's Accept
header, and changes when it includes application/xml or application/json .
[Link] MVC 5 apps do not have content negotiation support built in.
Content negotiation is preferable to returning a specific encoding type, as it is more flexible and makes the API
available to a larger number of clients. If you currently have action methods that return a specific format, you
should consider modifying them to return a result type that supports content negotiation when you port the
code to [Link] Core.
The following code returns data in JSON format regardless of client Accept header content:

[HttpGet]
public ActionResult Index()
{
return Json(new { Message = "Hello World!" });
}

[Link] Core MVC supports content negotiation natively, provided an appropriate return type is used. Content
negotiation is implemented by [ObjectResult] which is returned by the status code-specific action results
returned by the controller helper methods. The previous action method, implemented in [Link] Core MVC and
using content negotiation, would be:

public IActionResult Index()


{
return Ok(new { Message = "Hello World!"} );
}

This will default to returning the data in JSON format. XML and other formats will be used if the app has been
configured with the appropriate formatter.

Custom model binding


Most [Link] MVC and Web API apps make use of model binding. The default model binding syntax migrates
fairly seamlessly between these apps and [Link] Core MVC. However, in some cases customers have written
custom model binders to support specific model types or usage scenarios. Custom model binders in [Link]
MVC and Web API projects use separate IModelBinder interfaces defined in [Link] and
[Link] namespaces, respectively. In both cases, the custom binder exposes a Bind method that
accepts a controller or action context and a model binding context as arguments.
Once the custom binder is created, it must be registered with the app. This step requires creating another type, a
ModelBinderProvider , which acts as a factory and creates the model binder during a request. Binders can be
added during ApplicationStart in MVC apps as shown:

[Link](0, new MyCustomBinderProvider()); // MVC

In Web API apps, custom binders can be referenced using attributes. The ModelBinder attribute can be added to
action method parameters or to the parameter's type definition, as shown:

// attribute on action method parameter


public HttpResponseMessage([ModelBinder(typeof(MyCustomBinder))] CustomDTO custom)
{
}

// attribute on type
[ModelBinder(typeof(MyCustomBinder))]
public class CustomDTO
{
}

To register a model binder globally in [Link] Web API, its provider must be added during app startup:

public static class WebApiConfig


{
public static void Register(HttpConfiguration config)
{
var provider = new CustomModelBinderProvider(
typeof(CustomDTO), new CustomModelBinder());
[Link](typeof(ModelBinderProvider), 0, provider);

// ...
}
}

When migrating custom model providers to [Link] Core, the Web API pattern is closer to the [Link] Core
approach than the [Link] MVC 5. The main differences between [Link] Core's IModelBinder interface and
Web API's is that the [Link] Core method is async ( BindModelAsync ) and it only requires a single
BindingModelContext parameter instead of two parameters like Web API's version required. In [Link] Core, you
can use a [ModelBinder] attribute on individual action method parameters or their associated types. You can
also create a ModelBinderProvider that will be used globally within the app where appropriate. To configure such
a provider, you would add code to Startup in ConfigureServices :

public void ConfigureServices(IServiceCollection services)


{
[Link](options =>
{
[Link](0, new CustomModelBinderProvider());
});
}

Media formatters
[Link] Web API supports multiple media formats and can be extended by using custom media formatters. The
docs describe an example CSV Media Formatter that can be used to send data in a comma-separated value
format. If your Web API app uses custom media formatters, you'll need to convert them to [Link] Core custom
formatters.
To create a custom formatter in Web API 2, you inherited from an appropriate base class and then added the
formatter to the Web API pipeline using the HttpConfiguration object:

public static void ConfigureApis(HttpConfiguration config)


{
[Link](new ProductCsvFormatter());
}

In [Link] Core, the process is similar. [Link] Core supports both input formatters (used by model binding)
and output formatters (used to format responses). Adding a custom formatter to output responses in a specific
way involves inheriting from an appropriate base class and adding the formatter to MVC in Startup :

public void ConfigureServices(IServiceCollection services)


{
[Link](options =>
{
[Link](0, new CustomInputFormatter());
[Link](0, new CustomOutputFormatter());
});
}

You'll find a complete list of base classes in the [Link] namespace.


The steps to migrate from a Web API formatter to an [Link] Core MVC formatter are:
1. Identify an appropriate base class for the new formatter.
2. Create a new instance of the base class and implement its required methods.
3. Copy over the functionality from the Web API formatter to the new implementation.
4. Configure MVC in the [Link] Core App's ConfigureServices method to use the new formatter.

Custom filters
Filters are used in [Link] Core apps to execute code before and/or after certain stages in the request
processing pipeline. [Link] MVC and Web API also use filters in much the same way, but the details vary. For
instance, [Link] MVC supports four kinds of filters. [Link] Web API 2 supports similar filters, and both MVC
and Web API included attributes to override filters.
The most common filter used in [Link] MVC and Web API apps is the action filter, which is defined by an
IActionFilter interface. This interface provides methods for before ( OnActionExecuting ) and after (
OnActionExecuted ) which can be used to execute code before and/or after an action executes, as noted for each
method.
[Link] Core continues to support filters, and its unification of MVC and Web API means there is only one
approach to their implementation. The docs include detailed coverage of the five (5) kinds of filters built into
[Link] Core MVC. All of the filter variants supported in [Link] MVC and [Link] Web API have associated
versions in [Link] Core, so migration is generally just a matter of identifying the appropriate interface and/or
base class and migrating the code over.
In addition to the synchronous interfaces, [Link] Core also provides async interfaces like IAsyncActionFilter
which provide a single async method that can be used to incorporate code to run both before and after the
action, as shown:
public class SampleAsyncActionFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next)
{
// Do something before the action executes.

// next() calls the action method.


var resultContext = await next();
// [Link] is set.
// Do something after the action executes.
}
}

When migrating async code (or code that should be async), teams should consider leveraging the built in async
types that are provided for this purpose.
Most [Link] MVC and Web API apps do not use a large number of custom filters. Since the approach to filters
in [Link] Core MVC is closely aligned with filters in [Link] MVC and Web API, the migration of custom filters
is generally fairly straightforward. Be sure to read the detailed documentation on filters in [Link] Core's docs,
and once you're sure you have a good understanding of them, port the logic from the old system to the new
system's filters.

Route constraints
[Link] Core uses route constraints to help ensure requests are routed properly to route a request. [Link]
Core supports a large number of different route constraints for this purpose. Route constraints can be applied in
the route table, but most apps built with [Link] MVC 5 and/or [Link] Web API 2 use inline route constraints
applied to attribute routes. Inline route constraints use a format like this one:

[Route("/customer/{id:int}")]

The :int after the id route parameter constrains the value to match the the int type. One benefit of using
route constraints is that they allow for two otherwise-identical routes to exist where the parameters differ only
by their type. This allows for the equivalent of method overloading of routes based solely on parameter type.
The set of route constraints, their syntax, and usage is very similar between all three approaches. Custom route
constraints are fairly rare in customer applications. If your app uses a custom route constraint and needs to port
to [Link] Core, the docs include examples showing how to create custom route constraints in [Link] Core.
Essentially all that's required is to implement IRouteConstraint and its Match method, and then add the custom
constraint when configuring routing for the app:

public void ConfigureServices(IServiceCollection services)


{
[Link]();

[Link](options =>
{
[Link]("customName", typeof(MyCustomConstraint));
});
}

This is very similar to how custom constraints are used in [Link] Web API, which uses IHttpRouteConstraint
and configures it using a resolver and a call to [Link] :
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
var constraintResolver = new DefaultInlineConstraintResolver();
[Link]("nonzero", typeof(CustomConstraint));

[Link](constraintResolver);
}
}

[Link] MVC 5 follows a very similar approach, using IRouteConstraint for its interface name and configuring
the constraint as part of route configuration:

public class RouteConfig


{
public static void RegisterRoutes(RouteCollection routes)
{
[Link]("{resource}.axd/{*pathInfo}");

var constraintsResolver = new DefaultInlineConstraintResolver();


[Link]("values", typeof(ValuesConstraint));
[Link](constraintsResolver);
}
}

Migrating route constraint usage as well as custom route constraints to [Link] Core is typically very
straightforward.

Custom route handlers


Another fairly advanced feature of [Link] MVC 5 is route handlers. Custom route handlers implement
IRouteHandler , which includes a single method that returns an IHttpHandler for a give request. The
IHttpHandler , in turn, exposes an IsReusable property and a single ProcessRequest method. In [Link] MVC
5, you can configure a particular route in the route table to use your custom handler:

public static void RegisterRoutes(RouteCollection routes)


{
[Link]("{resource}.axd/{*pathInfo}");

[Link](new Route("custom", new CustomRouteHandler()));


}

To migrate custom route handlers from [Link] MVC 5 to [Link] Core, you can either use a filter (such as an
action filter) or a custom IRouter . The filter approach is relatively straightforward, and can be added as a global
filter when MVC is added to the app's services during startup:

[Link](options =>
{
[Link](typeof(CustomActionFilter));
});

The IRouter option requires implementing the interface's RouteAsync and GetVirtualPath methods. The
custom router is added to the request pipeline during app startup.
// ...
[Link](routes =>
{
[Link](new CustomRouter([Link]));
});

In [Link] Web API, these handlers are referred to as custom message handlers, rather than route handlers.
Message handlers must derive from DelegatingHandler and override its SendAsync method. Message handlers
can be chained together to form a pipeline in a fashion that is very similar to [Link] Core middleware and its
request pipeline.
[Link] Core has no DelegatingHandler type or separate message handler pipeline. Instead, such handlers
should be migrated using global filters, custom IRouter instances (see above), or custom middleware. [Link]
Core MVC filters and IRouter types have the advantage of having built-in access to MVC constructs like
controllers and actions, while middleware is a lower level approach that has no ties to MVC. This makes it more
flexible but also requires more effort if you need to access MVC components.

CORS support
CORS, or Cross-Origin Resource Sharing, is a W3C standard that allows servers to accept requests that don't
originate from responses they've served. [Link] MVC 5 and [Link] Web API 2 support CORS in different
ways. The simplest way to enable CORS support in [Link] MVC 5 is with an action filter like this one:

public class AllowCrossSiteAttribute : ActionFilterAttribute


{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
[Link](
"Access-Control-Allow-Origin", "[Link]");
[Link](filterContext);
}
}

[Link] Web API can also use such a filter, but it has built-in support for enabling CORS as well:

public static class WebApiConfig


{
public static void Register(HttpConfiguration config)
{
[Link]();
// ...
}
}

Once this is added, you can configure allowed origins, headers, and methods using the EnableCors attribute,
like so:

[EnableCors(origins: "[Link] headers: "*", methods: "*")]


public class TestController : ApiController
{
// Controller methods not shown...
}

Before migrating your CORS implementation from [Link] MVC 5 or [Link] Web API 2, be sure to review
how CORS works and create some automated tests that demonstrate CORS is working as expected in your
current system.
In [Link] Core, there are three built-in ways to enable CORS:
Configured via policy in ConfigureServices
Enabled with endpoint routing
Enabled with the EnableCors attribute

Each of these approaches is covered in detail in the docs, which are linked from the above options. Which one
you choose will largely depend on how your existing app supports CORS. If the app uses attributes, you can
probably migrate to use the EnableCors attribute most easily. If your app uses filters, you could continue using
that approach (though it's not the typical approach used in [Link] Core), or migrate to use attributes or
policies. Endpoint routing is a relatively new feature introduced with [Link] Core 3 and as such it doesn't have
a close analog in [Link] MVC 5 or [Link] Web API 2 apps.

Custom areas
Many [Link] MVC apps use Areas to organize the project. Areas typically reside in the root of the project in an
Areas folder, and must be registered when the application starts, typically in Application_Start() :

[Link]();

An alternative to registering all areas in startup is to use the RouteArea attribute on individual controllers:

[RouteArea("Admin")]
public class SomeController : Controller

When using Areas, additional arguments are passed into HTML helper methods to generate links to actions in
different areas:

@[Link]("News", "Index", "News", new { area = "News" }, null)

[Link] Web API apps don't typically use areas explicitly, since their controllers can be placed in any folder in
the project. Teams can use any folder structure they like to organize their API controllers.
Areas are supported in [Link] Core MVC. The approach used is nearly identical to the use of areas in [Link]
MVC 5. Developers migrating code using areas should keep in mind the following differences:
[Link] is not used in [Link] Core MVC
Areas are applied using the [Area("name")] attribute (not RouteArea as in [Link] MVC 5)
Areas can be added to the route table templates, if desired (or they can use attribute routing)
To add area support to the route table in [Link] Core MVC, you would add the following during app startup:

[Link](endpoints =>
{
[Link](
name: "MyArea",
pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");

[Link](
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});

Areas can also be used with attribute routing, using the {area} keyword in the route definition (it's one of
several reserved routing names that can be used with route templates).
Tag helpers support areas with the asp-area attribute, which can be used to generate links in Razor views and
pages:

<ul>
<li>
<a asp-area="Products" asp-controller="Home" asp-action="About">
Products/Home/About
</a>
</li>
<li>
<a asp-area="Services" asp-controller="Home" asp-action="About">
Services About
</a>
</li>
<li>
<a asp-area="" asp-controller="Home" asp-action="About">
/Home/About
</a>
</li>
</ul>

If you're migrating to Razor Pages you will need to use an Areas folder in your Pages folder. For more
information, see Areas with Razor Pages.
In addition to the above guidance, teams should review how routing in [Link] Core works with areas as part of
their migration planning process.

Integration tests for [Link] MVC and [Link] Web API


Integration tests are automated tests that verify several different parts of an app work together correctly.
Writing integration tests for [Link] MVC and [Link] Web API usually involved deploying the app to a real
web server, such as a local instance of IIS or IIS Express, and then making requests to this hosted application
using an HTTP client. Some of these tests may interact with the client-side user interface using browser
automation tools like Selenium, though often these are referred to as UI tests rather than integration tests.
If your migrated app shares the same behavior as its original version, whatever existing technology the team is
using to perform integration tests (and UI tests) should continue to work just as it did before. These tests are
usually indifferent to the underlying technology used to host the app they're testing, and interact with it only
through HTTP requests. Where things may get more challenging is with how the tests interact with the app to
get it into a known good state prior to each test. This may require some migration effort, since configuration
and startup are significantly different in [Link] Core compared to [Link] MVC or [Link] Web API.
Teams should strongly consider migrating their integration tests to use [Link] Core's built-in integration
testing support. In [Link] Core, apps can be tested by deploying them to a TestHost , which is configured
using a WebApplicationFactory . There's a little bit of setup required to host the app for testing, but once this is in
place, creating individual integration tests is very straightforward.
One of the best features of [Link] Core's integration testing support is that the app is hosted in memory.
There's no need to configure a real webserver to host the app. There's no need to use a browser automation tool
(if you're only testing [Link] Core and not client-side behavior). Many of the problems that can be encountered
when trying to use a real web server for automated integration tests, such as firewall issues or process
start/stop issues, are eliminated with this approach. Since the requests are all made in memory with no network
requirement, the tests also tend to run much faster than tests that must set up a separate webserver and
communicate with it over the network (even if it's running on the same machine).
Below you can see an example [Link] Core integration test (sometimes referred to as functional tests to
distinguish them from lower-level integration tests) from the eShopOnWeb reference application:

public class GetByIdEndpoint : IClassFixture<ApiTestFixture>


{
JsonSerializerOptions _jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };

public GetByIdEndpoint(ApiTestFixture factory)


{
Client = [Link]();
}

public HttpClient Client { get; }

[Fact]
public async Task ReturnsItemGivenValidId()
{
var response = await [Link]("api/catalog-items/5");
[Link]();
var stringResponse = await [Link]();
var model = [Link]<GetByIdCatalogItemResponse>();

[Link](5, [Link]);
[Link]("Roslyn Red Sheet", [Link]);
}
}

If the app being migrated has no integration tests, the migration process can be a great opportunity to add
some. These tests can verify that the migrated app behaves as the team expects. When such tests are in place
early in a migration, they can ensure that later migration efforts do not break previously migrated portions of
the app. Given how easy it is to set up and run integration tests in [Link] Core, the return on the investment
spent setting up such tests is usually pretty high.

WCF client configuration


If your app currently relies on WCF services as a client, this scenario is supported. However, you will need to
migrate your configuration from [Link] to use the new [Link] file. Another option is to add any
necessary configuration to your clients programmatically when you create them. For example:

var wcfClient = new OrderServiceClient(


new BasicHttpBinding([Link]),
new EndpointAddress("[Link]

If your organization has extensive services built using WCF that your app relies on, consider migrating them to
use gRPC instead. For more details on gRPC, why you may wish to migrate, and a detailed migration guide,
consult the gRPC for WCF Developers eBook.

References
[Link] Web API Content Negotiation
Format response data in [Link] Core Web API
Custom Model Binders in [Link] Web API
Custom Model Binders in [Link] Core
Media Formatters in [Link] Web API 2\
Custom formatters in [Link] Core Web API
Filters in [Link] Core
Route constraints in [Link] Web API 2
Route constraints in [Link] MVC 5
[Link] Core Route Constraint Reference
Custom message handlers in [Link] Web API 2
Simple CORS control in MVC 5 and Web API 2
Enabling Cross-Origin Requests in Web API
Enable Cross-Origin Requests (CORS) in [Link] Core
Areas in [Link] Core
Integration tests in [Link] Core

PR EVIO U S NE XT
Deployment scenarios when migrating to [Link]
Core
6/16/2022 • 6 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

Existing [Link] MVC and Web API apps run on IIS and Windows. Large apps may require a phased or side-by-
side approach when porting to [Link] Core. In previous chapters, you learned a number of strategies for
migrating large .NET Framework apps to [Link] Core in phases. In this chapter, you will see how different
deployment scenarios can be achieved when there is a need to maintain the original app in production while
migrating portions of it.

Split a large web app


Consider the common scenario of a large web app that currently is hosted on IIS in a single web site. Within the
large app, functionality is segmented into different routes and/or directories. The app is a mix of MVC views and
API endpoints. The MVC routes include many different paths based on functionality and all start from the root of
the app using the standard /{controller}/{action}/{id?} route template. The API endpoints follow a similar
pattern, but are all under an /api root.
Assuming the task of porting the app is split such that either the MVC functionality or the API functionality is
migrated to [Link] Core first, how would the original site continue to function seamlessly with the new
[Link] Core app running somewhere else? Users of the system should continue to see the same URLs they did
prior to the migration, unless it's absolutely necessary to change them.
Fortunately, IIS is a feature-rich web server, and two features it has are URL Rewrite module and Application
Request Routing. Using these features, IIS can act as a reverse proxy, routing client requests to the appropriate
back-end web app. To configure IIS as a reverse proxy, check the Enable proxy checkbox in the Application
Request Routing feature, then add a URL Rewrite rule like this one:

<rule name="NetCoreProxy">
<match url="(.*)> />
<action type="Rewrite" url="[Link] />
</rule>

As a reverse proxy, IIS can route traffic matching certain patterns to entirely separate apps, potentially on
different servers.
Using just the URL Rewrite module (perhaps combined with host headers), IIS can easily support multiple web
sites, each potentially running different versions of .NET. A large web app might be deployed as a collection of
individual sites, each responding to different IP addresses and/or host headers, or as a single web site with one
or more sub-applications in it responding to certain URL paths (which doesn't even require URL Rewrite).

IMPORTANT
Subdomains typically refer to the portion of a domain preceding the top two levels. For example, in the domain
[Link] , api is a subdomain of the [Link] domain (which itself is composed of the contoso
domain name and the .com top-level domain or TLD). URL paths refer to portion of the URL that follows the domain
name, starting with a / . The URL [Link] has a path of /api .

There are pros and cons to using the same or different subdomains (and domains) to host a single app. Features
like cookies and intra-app communication using mechanisms like CORS may require more configuration to
work properly in distributed apps. However, apps that use different subdomains can more easily use DNS to
route requests to entirely different network destinations, and so can more easily be deployed to many different
servers (virtual or otherwise) without the need for IIS to act as a reverse proxy.
In the example described above, assume the API endpoints are designated as the first part of the app to be
ported to [Link] Core. In this case, a new [Link] Core app is created and hosted in IIS as a separate web
application within the existing [Link] MVC web site. Since it will be added as a child of the original web site
and will be named api, its default route should no longer begin with api/ . Keeping this would result in it
matching URLs of the form /api/api/endpoint .
Figure 5-1 shows how the [Link] Core 2.1 api app appears in IIS Manager as a part of the existing
DotNetMvcApp site.

Figure 5-1 . .NET Framework Site with .NET Core app in IIS.
The DotNetMvcApp site is hosted as an MVC 5 app running on .NET Framework 4.7.2. It has its own IIS app pool
configured in integrated mode and running .NET CLR version 4.0.30319. The api app is an [Link] Core app
running on .NET Framework 4.6.1 ( net461 ). It was added to the DotNetMvcApp as a new IIS app and configured
to use its own Application Pool. Its Application Pool is also running in integrated mode but is configured with a
.NET CLR version of No Managed Code since it will be executed using the [Link] Core Module. The version
of the [Link] Core app is just an example. It could also be configured to run on .NET Core 3.1 or .NET 5.
Though at that point, it would no longer be able to target .NET Framework libraries (see Choose the Right .NET
Core Version)
Configured in this manner, the only change that must be made in order for the [Link] Core app's APIs to be
routed properly is to change its default route template from [Route("[api/controller]")] to
[Route("[controller]")] .

Alternately the [Link] Core app can be another top-level web site in IIS. In this case, you can configure the
original site to use a rewrite rule (with URL Rewrite) that will redirect to the other app if the path starts with
/api . The [Link] Core app can use a different host header for its route so that it doesn't conflict with the main
app but can still respond to requests using root-based routes.
As an example, the same [Link] Core app used in Figure 5-1 can be deployed to another folder configured as
an IIS web site. The site should use an app pool configured just as before, with No Managed Code . Configure
its bindings to respond to a unique host name on the server, such as [Link] . To configure URL Rewrite
to rewrite requests matching /api just add a new inbound rule at the IIS server (or individual site) level. Match
the pattern ^/api(.*) and specify an Action type of Rewrite and a Rewrite URL of [Link]/{R:1} . The
combination of using (.*) in the matching pattern and {R:1} in the rewrite URL will ensure the rest of the
path gets used with the new URL. With this in place, separate sites on the same IIS server can coexist running
separate versions of .NET, but they can be made to appear to the Internet as one web app. Figure 5-2 shows the
rewrite rule as configured in IIS with the separate web site.

Figure 5-2 . Rewrite rule to rewrite subfolder requests to another web site.
If your app requires single sign-on between different sites or apps within IIS, refer to the documentation on how
to share authentication cookies among [Link] apps for detailed instructions on supporting this scenario.
Summary
A common approach to porting large apps from .NET Framework to [Link] Core is to choose individual
portions of the app to migrate one by one. As each piece of the app is ported, the entire app remains running
and usable, with some parts of it running in its original configuration and other parts running on some version
of .NET Core. By following this approach, a large app migration can be performed incrementally. This approach
results in limiting risk by providing more rapid feedback and reducing total surface area involved in testing. It
also allows for more rapid realization of benefits of .NET Core, such as performance increases. Although [Link]
Core apps are no longer required to be hosted on IIS, IIS remains a very flexible and powerful web server that
can be configured to support a variety of hosting scenarios involving both .NET Framework and [Link] Core
apps on the same IIS instance or even hosted on different servers.

References
Host [Link] Core on Windows with IIS
URL Rewrite module and Application Request Routing
URL Rewrite
[Link] Core Module
Share authentication cookies among [Link] apps
Samples used in this section

PR EVIO U S NE XT
Summary: Port existing [Link] Apps to .NET Core
6/16/2022 • 2 minutes to read • Edit Online

TIP
This content is an excerpt from the eBook, Porting existing [Link] Apps to .NET 6, available on .NET Docs or as a free
downloadable PDF that can be read offline.
Download PDF

In this book, you've been given the resources needed to decide whether it makes sense to port your
organization's existing [Link] apps running on .NET Framework to [Link] Core. You've learned about
important considerations for choosing when it makes sense to migrate to .NET Core, and when it may be
appropriate to keep (parts of) your app on .NET Framework. There are differences between .NET Core versions
and their capabilities and compatibilities with .NET Framework, and you learned how to choose the right version
of .NET Core for your app.
Porting a large app often entails a fair amount of risk and effort. You learned how to mitigate this risk by
employing one or more incremental migration strategies along with several deployment strategies for keeping
partially migrated apps running in production.
There are many architectural differences between [Link] and [Link] Core. In chapter 2, you learned about
many of these differences and how they relate to your app's migration. This chapter covered everything from
app startup and low-level middleware to high-level controller and Web API differences and new features
enabling much better testing scenarios.
Large apps are often comprised of many projects and packages, and dependencies can play a major role in
determining how easy or difficult migration may be. Chapter 3 helped you identify the sequence in which to
migrate projects and how to understand and update your app's dependencies. It also detailed additional
strategies for migrating apps while keeping them running in production.
In chapter 4, you saw how a real [Link] MVC reference app was migrated to [Link] Core. This chapter
included a detailed breakdown of all the changes that were needed to take the existing app and port it over to
run on [Link] Core. Refer back to it if you have specific questions about the porting process and some of its
more specific details.
Finally, chapter 5 detailed specific deployment scenarios focused on IIS. You saw how you can use your existing
IIS web server to host parts of your app that have been ported to [Link] Core while keeping the app's public
URLs consistent. IIS includes great support for URL rewriting and request routing that enables it to host multiple
versions of your site side by side or even on different servers, with no change to the public-facing URLs the app
exposes.

PR EVIO U S

You might also like