dotnet / AspNetCore.Docs

Documentation for ASP.NET Core
https://docs.microsoft.com/aspnet/core
Creative Commons Attribution 4.0 International
12.55k stars 25.3k forks source link

Clearer Instructions for Migrating from .NET Core 2.2 to 3.0 (Contained Within) #14817

Closed robinwilson16 closed 4 years ago

robinwilson16 commented 4 years ago

Please can the instructions for migrating .NET Core 2.2 to 3.0 be made clearer as this article contains a mixture of instructions, release notes and demos. Compared to the migration guide for .NET Core 2.1 > 2.2 it says it takes 4 times as long to read. All this document should contain are the steps required to migrate with the other information contained within other/further information pages.

By comparing a blank .NET Core 2.2 and 3.0 project and hitting issues/doing research I was able to work out the migration steps which are below in case anyone else finds them useful:

1. Copy Existing Project Before starting it is worth taking a copy of your existing .NET Core 2.2 project as if anything goes wrong with the migration then you can always easily start again.

2. Delete Existing Published Files After copying the project files, navigate to the project directory where the solution file is located and then into the sub-folder containing the project files. Delete the following folders (renaming does not work as folders are still picked up and can cause issues due to conflicts with .NET Core 2.2):

3. Update the csproj file Whilst still in the folder containing the project files, edit the .csproj file with any text editor.

Find the reference to the target framework:

<TargetFramework>netcoreapp2.2</TargetFramework>

Replace with:

<TargetFramework>netcoreapp3.0</TargetFramework>

Find the reference to the hosting model. If this is set to InProcess this section can be removed as this is now the default:

<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>

If it was the only entry in a <PropertyGroup></PropertyGroup> section then remove these two otherwise leave in place.

Find and remove this line as this package is now split up into seperate packages to avoid duplication and ensure projects remain lean and efficient:

<PackageReference Include="Microsoft.AspNetCore.App" />

Also remove references to AspNetCore.Razor.Design (if it exists):

<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" />

Remove any item groups relating to bundleconfig.json as this is no longer used (left over from .NET Core 2.1 I believe so this won't exist in newer projects).

Add the following packages so the section should now look like this (aside from any additional packages where it says <!-- Additional existing packages here -->:

<ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="3.0.0" />
    <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.0.0" />
    <PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="3.0.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.0.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.0.0" />
    <!-- Additional existing packages here -->
</ItemGroup>

Now save and close the csproj file and open your project with Visual Studio (.sln file in the top-level project folder)

Once you open the project again it is good practice to ensure any other packages you are using are up to date to ensure there are not compatibility issues with any other outdated packages. Open the project and go to Tools > NuGet Package Manager > Manage NuGet Packages for Solution... On the updates tab, update any packages offered here.

4. Update Startup.cs Open your project with Visual Studio (if not already open) and go to Startup.cs and find the following in the ConfigureServices section:

services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

Replace with (compatibility version is no longer required):

services.AddRazorPages();

If you are using controllers/views then you will also need to add this line below the one above: services.AddControllersWithViews();

Now move to the Configure section: Find the function definition:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)

Replace with:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

Find:

app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc();

Replace with:

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

If your UseMvc() command included routes - e.g.:

app.UseMvc(routes =>
{
routes.MapRoute(
name: "FileUpload",
template: "{controller=FileUpload}/{action=Index}/{id?}");
});

Then update to this format:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute("FileUpload", "{controller=FileUpload}/{action=Index}/{id?}");
});

Also ensure the using command has been added at the top for Microsoft.Extensions.Hosting:

using Microsoft.Extensions.Hosting;

If you have this line in your project (added more recently in new .NET Core 2.2 projects) then remove the part in brackets as Bootstrap 4 is now the default so this is no longer declared here so find this:

.AddDefaultUI(UIFramework.Bootstrap4)

And replace with:

.AddDefaultUI()

If you have brought in the hosting environment into your startup function at the top this section will also require updating (this is not there by default in new projects but is often added as required at a later point). If not there then skip down to the next step.

Find the Startup function definition at the top:

public Startup(IConfiguration configuration, IHostingEnvironment env)

Replace it with

public Startup(IConfiguration configuration, IWebHostEnvironment env)

Below find the following:

public IHostingEnvironment HostingEnvironment { get; }

Replace with:

public IWebHostEnvironment HostingEnvironment { get; }

5. Update Program.cs Open Program.cs from within Visual Studio.

Find the following in the Main function at the top:

CreateWebHostBuilder(args).Build().Run();

Replace with:

CreateHostBuilder(args).Build().Run();

Find the IWebHostBuilder fuction:

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>();

Replace with the new CreateHostBuilder function:

public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder
                        .UseStartup<Startup>();
                });

If you set the web application to run on a specific port (if you are running multiple Kestrel web apps on the same Apache 2 Linux server) then ensure you also add back the line to specify the port number too. In this case the function would look like this (where xxxx is the port number as specified in your service file):

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder
.UseStartup<Startup>()
.UseUrls("http://localhost:xxxx");
});

Also ensure the using command has been added at the top for Microsoft.Extensions.Hosting:

using Microsoft.Extensions.Hosting;

At this stage you may find your project will build

6. Build, Publish and Deploy You should now be able to build, publish and deploy your project. The destination folder does now contain around 26 files whereas with .NET Core 2.2 it was around 9. A new runtimes folder is also now created. If this fails try opening a CMD window and navigate to the project directory and run:

dotnet build

If you get an error about AspNetCore, Linq, Webpages (and various other commands) not being recognised then close all instances of Visual Studio and re-open your project again.

If you get the following error when attempting to publish:

Assets file '...\obj\project.assets.json' doesn't have a target for '.NETCoreApp,Version=v2.2'. 
Ensure that restore has run and that you have included 'netcoreapp2.2' in the TargetFrameworks for your project.

Then go to Project Folder\Properties\PublishProfiles.

Edit FolderProfile.pubxml and look for

<TargetFramework>netcoreapp2.2</TargetFramework>

Replace with:

<TargetFramework>netcoreapp3.0</TargetFramework>

Sometimes this either fails to update itself or reverts back to 2.2 so even though on screen in the Publish section in VS it says target framework is netcoreapp3.0 it is actually trying to publish it as 2.2. This can happen when the project successfully published just minutes earlier.

The project should now work or if not you may need to follow the additional steps below:

6. Additional Migration Steps Depending on Code

JsonIgnore using Newtonsoft.Json If you have used [JsonIgnore] on any of your class attributes to avoid circular references when generating Json this no longer works and is now has no effect.

The fix is to find instances of this:

using Newtonsoft.Json;

And replace with:

using System.Text.Json;
using System.Text.Json.Serialization;

Then it works again

Also if the Newtonsoft serializer is being referenced such as to specify the Json attributes then this will also need updating. e.g. find this:

[JsonProperty(NamingStrategyType = typeof(DefaultNamingStrategy))]

Replace with:

[JsonPropertyName("Id")]

Stored Procedures If you are calling stored procedures or executing SQL statements directly from code then you need to update the function that calls these as the name has changed:

In .NET Core 2.2 this works:

var surnameParam = new SqlParameter("@Surname", SqlDbType.NVarChar);
surnameParam.Value = (object)surname ?? DBNull.Value;

ObjectFromDB = await _context.ObjectFromDB 
    .FromSql("EXEC SPR_StoredProcedure @Surname", surnameParam)
    .ToListAsync();

In .NET Core 3.0 you must do this instead:

ObjectFromDB = await _context.ObjectFromDB
    .FromSqlInterpolated( $"EXEC SPR_StoredProcedure @Surname={surname}")
    .ToListAsync();

SQL procedures that are executed but do not return a list also need to be updated from:

await _context.Database
     .ExecuteSqlCommandAsync("EXEC SPR_StoredProcedure @Surname", surnameParam);

To:

await _context.Database
     .ExecuteSqlInterpolatedAsync($"EXEC SPR_StoredProcedure @Surname={surname}");

Also FirstOrDefaultAsync() now also generates an error in .NET Core 3.0 with stored procedures as evaluates queries incorrectly.

e.g. this command:

StaffMember = await _context.StaffMember
                .FromSqlInterpolated($"EXEC SPR_IEL_GetStaffMember @UserName={username}")
                .FirstOrDefaultAsync();

Is now incorrectly sent to SQL Server as:

exec sp_executesql N'SELECT TOP(1) ...
FROM (
    EXEC SPR_IEL_GetStaffMember @UserName=@p0
) AS [s]',N'@p0 nvarchar(4000)',@p0=N'r.wilson'

So the command must be wrapped in a ToListAsync() then it is correctly handled:

StaffMember = (await _context.StaffMember
                .FromSqlInterpolated($"EXEC SPR_IEL_GetStaffMember @UserName={username}")
                .ToListAsync())
                .FirstOrDefault();

EF Core Queries EF Core queries are now forced to execute on the server and not the client until after the entries have been converted to objects using ToListAsync(). This means anything that would have historically forced client-side execution now generates an error so this is now much more restrictive (in an attempt to always generate efficient SQL). Sometimes this may be a case of removing a .ToString() command or other times (for more complex statements) it may be easier to re-write as a stored procedure and execute that instead.


Document Details

Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.

StanislavPrusac commented 4 years ago

Thank you! <3