AspNetCore.Docs/aspnetcore/migration/22-to-30.md

22 KiB

title author description ms.author ms.custom ms.date uid
Migrate from ASP.NET Core 2.2 to 3.0 Preview rick-anderson Learn how to migrate an ASP.NET Core 2.2 project to ASP.NET Core 3.0. riande mvc 09/03/2019 migration/22-to-30

Migrate from ASP.NET Core 2.2 to 3.0

By Scott Addie and Rick Anderson

This article explains how to update an existing ASP.NET Core 2.2 project to ASP.NET Core 3.0.

Prerequisites

Visual Studio

[!INCLUDE]

Visual Studio Code

[!INCLUDE]

Visual Studio for Mac

[!INCLUDE]


Update the project file

<PropertyGroup>
 <IncludeOpenAPIAnalyzers>true</IncludeOpenAPIAnalyzers>
</PropertyGroup>
  • Update the Version attribute on remaining <PackageReference> elements for Microsoft.AspNetCore.* packages to the current preview (for example, 3.0.0-preview5-19227-01).

    If there's no 3.0 version of a package, the package might have been deprecated in 3.0. Many of these packages are part of Microsoft.AspNetCore.App and shouldn't be referenced individually. For a preliminary list of packages no longer produced in 3.0, see Stop producing packages for shared framework assemblies in 3.0 (aspnet/AspNetCore #3756). The shared framework is the set of assemblies (.dll files) that are installed on the machine and referenced by Microsoft.AspNetCore.App. For more information, see The shared framework.

  • The assemblies for several notable components were removed from Microsoft.AspNetCore.App in 3.0. Add <PackageReference> elements if you're using APIs from packages listed in Assemblies being removed from Microsoft.AspNetCore.App 3.0 (aspnet/AspNetCore #3755).

    Examples of removed components include:

    • Microsoft.AspNet.WebApi.Client
    • Microsoft.EntityFrameworkCore
    • System.Data.SqlClient

    The list of assemblies shipping in Microsoft.AspNetCore.App hasn't been finalized and will change before 3.0 RTM.

    Consider the following code:

    var branches = await response.Content.ReadAsAsync<IEnumerable<GitHubBranch>>();
    

    The ReadAsAsync method called in the preceding code is included in Microsoft.AspNet.WebApi.Client. Install the Microsoft.AspNet.WebApi.Client NuGet package to resolve the compilation issue in 3.0.

  • Add Json.NET support.

  • Projects default to the in-process hosting model in ASP.NET Core 3.0 or later. You may optionally remove the <AspNetCoreHostingModel> property in the project file if its value is InProcess.

Json.NET support

As part of the work to improve the ASP.NET Core shared framework, Json.NET has been removed from the ASP.NET Core shared framework.

To use Json.NET in an ASP.NET Core 3.0 project:

  • Add a package reference to Microsoft.AspNetCore.Mvc.NewtonsoftJson.

  • Update Startup.ConfigureServices to call AddNewtonsoftJson.

    services.AddMvc()
        .AddNewtonsoftJson();
    

    AddNewtonsoftJson is compatible with the new MVC service registration methods:

    • AddRazorPages
    • AddControllersWithViews
    • AddControllers
    services.AddControllers()
        .AddNewtonsoftJson();
    

    Json.NET settings can be set in the call to AddNewtonsoftJson:

    services.AddMvc()
        .AddNewtonsoftJson(options =>
               options.SerializerSettings.ContractResolver =
                  new CamelCasePropertyNamesContractResolver());
    

MVC service registration

ASP.NET Core 3.0 adds new options for registering MVC scenarios inside Startup.ConfigureServices.

Three new top-level extension methods related to MVC scenarios on IServiceCollection are available. Templates use these new methods instead of UseMvc. However, AddMvc continues to behave as it has in previous releases.

The following example adds support for controllers and API-related features, but not views or pages. The API template uses this code:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
}

The following example adds support for controllers, API-related features, and views, but not pages. The Web Application (MVC) template uses this code:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
}

The following example adds support for Razor Pages and minimal controller support. The Web Application template uses this code:

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
}

The new methods can also be combined. The following example is equivalent to calling AddMvc in ASP.NET Core 2.2:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.AddRazorPages();
}

Update routing startup code

If an app calls UseMvc or UseSignalR, migrate the app to Endpoint Routing if possible. To improve Endpoint Routing compatibility with previous versions of MVC, we've reverted some of the changes in URL generation introduced in ASP.NET Core 2.2. If you experienced problems using Endpoint Routing in 2.2, expect improvements in ASP.NET Core 3.0 with the following exceptions:

Endpoint Routing supports the same route pattern syntax and route pattern authoring features as IRouter. Endpoint Routing supports IRouteContraint. Endpoint routing supports [Route], [HttpGet], and the other MVC routing attributes.

For most applications, only Startup requires changes.

Migrate Startup.Configure

General advice:

  • Add UseRouting.
  • If the app calls UseStaticFiles, place UseStaticFiles before UseRouting.
  • If the app uses authentication/authorization features such as AuthorizePage or [Authorize], place the call to UseAuthentication and UseAuthorization after UseRouting (and after UseCors if CORS Middleware is used).
  • Replace UseMvc or UseSignalR with UseEndpoints.
  • If the app uses CORS scenarios, such as [EnableCors], place the call to UseCors before any other middlewares that use CORS (for example, place UseCors before UseAuthentication, UseAuthorization, and UseMvc).
  • Replace IHostingEnvironment with IWebHostEnvironment and add a using statement for the Microsoft.Extensions.Hosting namespace.

The following is an example of Startup.Configure in a typical ASP.NET Core 2.2 app:

public void Configure(IApplicationBuilder app)
{
    ...

    app.UseStaticFiles();
    
    app.UseAuthentication();

    app.UseSignalR(hubs =>
    {
        hubs.MapHub<ChatHub>("/chat");
    });

    app.UseMvc(routes =>
    {
        routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
    });
}

After updating the previous Startup.Configure code:

public void Configure(IApplicationBuilder app)
{
    ...

    app.UseStaticFiles();

    app.UseRouting();

    app.UseCors();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapHub<ChatHub>("/chat");
        endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
    });
}

Security middleware guidance

Support for authorization and CORS is unified around the middleware approach. This allows use of the same middleware and functionality across these scenarios. An updated authorization middleware is provided in this release, and CORS Middleware is enhanced so that it can understand the attributes used by MVC controllers.

CORS

Previously, CORS could be difficult to configure. Middleware was provided for use in some use cases, but MVC filters were intended to be used without the middleware in other use cases. With ASP.NET Core 3.0, we recommend that all apps that require CORS use the CORS Middleware in tandem with Endpoint Routing. UseCors can be provided with a default policy, and [EnableCors] and [DisableCors] attributes can be used to override the default policy where required.

In the following example:

  • CORS is enabled for all endpoints with the default named policy.
  • The MyController class disables CORS with the [DisableCors] attribute.
public void Configure(IApplicationBuilder app)
{
    ...

    app.UseRouting();

    app.UseCors("default"); 

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapDefaultControllerRoute();
    });
}

[DisableCors]
public class MyController : ControllerBase
{
    ...
}

Authorization

In earlier versions of ASP.NET Core, authorization support was provided via the [Authorize] attribute. Authorization middleware wasn't available. In ASP.NET Core 3.0, authorization middleware is required. We recommend placing the ASP.NET Core Authorization Middleware (UseAuthorization) immediately after UseAuthentication. The Authorization Middleware can also be configured with a default policy, which can be overridden.

In ASP.NET Core 3.0 or later, UseAuthorization is called in Startup.Configure, and the following HomeController requires a signed in user:

public void Configure(IApplicationBuilder app)
{
    ...

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapDefaultControllerRoute();
    });
}

public class HomeController : ControllerBase
{
    [Authorize]
    public IActionResult BuyWidgets()
    {
        ...
    }
}

If the app uses an AuthorizeFilter as a global filter in MVC, we recommend refactoring the code to provide a policy in the call to AddAuthorization.

The DefaultPolicy is initially configured to require authentication, so no additional configuration is required. In the following example, MVC endpoints are marked as RequireAuthorization so that all requests must be authorized based on the DefaultPolicy. However, the HomeController allows access without the user signing into the app due to [AllowAnonymous]:

public void Configure(IApplicationBuilder app)
{
    ...

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapDefaultControllerRoute().RequireAuthorization();
    });
}

[AllowAnonymous]
public class HomeController : ControllerBase
{
    ...
}

Policies can also be customized. Building upon the previous example, the DefaultPolicy is configured to require authentication and a specific scope:

public void ConfigureServices(IServiceCollection services)
{
    ...
    
    services.AddAuthorization(options =>
    {
        options.DefaultPolicy = new AuthorizationPolicyBuilder()
          .RequireAuthenticatedUser()
          .RequireScope("MyScope")
          .Build();
    });
}

public void Configure(IApplicationBuilder app)
{
    ...

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapDefaultControllerRoute().RequireAuthorization();
    });
}

[AllowAnonymous]
public class HomeController : ControllerBase
{
    ...
}

Alternatively, all endpoints can be configured to require authorization without [Authorize] or RequireAuthorization by configuring a FallbackPolicy. The FallbackPolicy is different from the DefaultPolicy. The DefaultPolicy is triggered by [Authorize] or RequireAuthorization, while the FallbackPolicy is triggered when no other policy is set. FallbackPolicy is initially configured to allow requests without authorization.

The following example is the same as the preceding DefaultPolicy example but uses the FallbackPolicy to always require authentication on all endpoints except when [AllowAnonymous] is specified:

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddAuthorization(options =>
    {
        options.FallbackPolicy = new AuthorizationPolicyBuilder()
          .RequireAuthenticatedUser()
          .RequireScope("MyScope")
          .Build();
    });
}

public void Configure(IApplicationBuilder app)
{
    ...

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapDefaultControllerRoute();
    });
}

[AllowAnonymous]
public class HomeController : ControllerBase
{
    ...
}

Authorization by middleware works without the framework having any specific knowledge of authorization. For instance, health checks has no specific knowledge of authorization, but health checks can have a configurable authorization policy applied by the middleware.

Additionally, each endpoint can customize its authorization requirements. In the following example, UseAuthorization processes authorization with the DefaultPolicy, but the /healthz health check endpoint requires an admin user:

public void Configure(IApplicationBuilder app)
{
    ...

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints
            .MapHealthChecks("/healthz")
            .RequireAuthorization(new AuthorizeAttribute(){ Roles = "admin", });
    });
}

Protection is implemented for some scenarios. UseEndpoint middleware throws an exception if an authorization or CORS policy is skipped due to missing middleware. Analyzer support to provide additional feedback about misconfiguration is in progress.

Migrate SignalR

Mapping of SignalR hubs now takes place inside UseEndpoints.

Map each hub with MapHub. As in previous versions, each hub is explicitly listed.

In the following example, support for the ChatHub SignalR hub is added:

public void Configure(IApplicationBuilder app)
{
    ...

    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapHub<ChatHub>();
    });
}

There is a new option for controlling message size limits from clients. For example, in Startup.ConfigureServices:

services.AddSignalR(hubOptions =>
{
    hubOptions.MaximumReceiveMessageSize = 32768;
});

In ASP.NET Core 2.2, you could set the TransportMaxBufferSize and that would effectively control the maximum message size. In ASP.NET Core 3.0, that option now only controls the maximum size before backpressure is observed.

Migrate MVC controllers

Mapping of controllers now takes place inside UseEndpoints.

Add MapControllers if the app uses attribute routing. Since routing includes support for many frameworks in ASP.NET Core 3.0 or later, adding attribute-routed controllers is opt-in.

Replace the following:

  • MapRoute with MapControllerRoute
  • MapAreaRoute with MapAreaControllerRoute

Since routing now includes support for more than just MVC, the terminology has changed to make these methods clearly state what they do. Conventional routes such as MapControllerRoute/MapAreaControllerRoute/MapDefaultControllerRoute are applied in the order that they're added. Place more specific routes (such as routes for an area) first.

In the following example:

  • MapControllers adds support for attribute-routed controllers.
  • MapAreaControllerRoute adds a conventional route for controllers in an area.
  • MapControllerRoute adds a conventional route for controllers.
public void Configure(IApplicationBuilder app)
{
    ...

    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
        endpoints.MapAreaControllerRoute(
            "admin", 
            "admin", 
            "Admin/{controller=Home}/{action=Index}/{id?}");
        endpoints.MapControllerRoute(
            "default", "{controller=Home}/{action=Index}/{id?}");
    });
}

Migrate Razor Pages

Mapping Razor Pages now takes place inside UseEndpoints.

Add MapRazorPages if the app uses Razor Pages. Since Endpoint Routing includes support for many frameworks, adding Razor Pages is now opt-in.

In the following example, MapRazorPages adds support for Razor Pages:

public void Configure(IApplicationBuilder app)
{
    ...

    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

Use MVC without Endpoint Routing

Using MVC via UseMvc or UseMvcWithDefaultRoute in ASP.NET Core 3.0 requires an explicit opt-in inside Startup.ConfigureServices. This is required because MVC must know whether it can rely on the authorization and CORS Middleware during initialization. An analyzer is provided that warns if the app attempts to use an unsupported configuration.

If the app requires legacy IRouter support, disable EnableEndpointRouting using any of the following approaches in Startup.ConfigureServices:

services.AddMvc(options => options.EnableEndpointRouting = false);
services.AddControllers(options => options.EnableEndpointRouting = false);
services.AddControllersWithViews(options => options.EnableEndpointRouting = false);

services.AddRazorPages().AddMvcOptions(options => options.EnableEndpointRouting = false);

Migrate health checks

Health checks can be used as a router-ware with Endpoint Routing.

Add MapHealthChecks to use health checks with Endpoint Routing. The MapHealthChecks method accepts arguments similar to UseHealthChecks. The advantage of using MapHealthChecks over UseHealthChecks is the ability to apply authorization and to have greater fine-grained control over the matching policy.

In the following example, MapHealthChecks is called for a health check endpoint at /healthz:

public void Configure(IApplicationBuilder app)
{
    ...

    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapHealthChecks("/healthz", new HealthCheckOptions() { });
    });
}

HostBuilder replaces WebHostBuilder

The ASP.NET Core 3.0 templates use Generic Host. Previous versions used Web Host. The following code shows the ASP.NET Core 3.0 template generated Program class:

[!code-csharp]

The following code shows the ASP.NET Core 2.2 template-generated Program class:

[!code-csharp]

xref:Microsoft.AspNetCore.Hosting.IWebHostBuilder remains in 3.0 and is the type of the webBuilder seen in the preceding code sample. xref:Microsoft.AspNetCore.Hosting.WebHostBuilder will be deprecated in a future release and replaced by HostBuilder.

The most significant change from WebHostBuilder to HostBuilder is in dependency injection (DI). When using HostBuilder, you can only inject xref:Microsoft.Extensions.Configuration.IConfiguration and xref:Microsoft.AspNetCore.Hosting.IHostingEnvironment into Startup's constructor. The HostBuilder DI constraints:

  • Enable the DI container to be built only one time.
  • Avoids the resulting object lifetime issues like resolving multiple instances of singletons.

Update SignalR code

System.Text.Json is now the default Hub Protocol used by both the client and server.

In Startup.ConfigureServices, call AddJsonProtocol to set serializer options.

Server:

services.AddSignalR(...)
        .AddJsonProtocol(options =>
        {
            options.WriteIndented = false;
        })

Client:

new HubConnectionBuilder()
    .WithUrl("/chatHub")
    .AddJsonProtocol(options =>
    {
        options.WriteIndented = false;
    })
    .Build();

Switch to Newtonsoft.Json

If you're using features of Newtonsoft.Json that aren't supported in System.Text.Json, you can switch back to Newtonsoft.Json:

  1. Install the Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson NuGet package.

  2. On the client, chain an AddNewtonsoftJsonProtocol method call to the HubConnectionBuilder instance:

    new HubConnectionBuilder()
        .WithUrl("/chatHub")
        .AddNewtonsoftJsonProtocol(...)
        .Build();
    
  3. On the server, chain an AddNewtonsoftJsonProtocol method call to the AddSignalR method call in Startup.ConfigureServices:

    services.AddSignalR()
        .AddNewtonsoftJsonProtocol(...);
    

Opt in to runtime compilation

In 3.0, runtime compilation is an opt-in scenario. To enable runtime compilation, see https://docs.microsoft.com/aspnet/core/mvc/views/view-compilation?view=aspnetcore-3.0#runtime-compilation.