Updating a real-life ASP.NET Core 1.1 project to ASP.NET Core 2.0

General

Updating a real-life ASP.NET Core 1.1 project to ASP.NET…

With the rapid releases of .NET Core, it’s hard to keep up. I’ve just had the same problem hitting me again, where I have JUST released a new Pluralsight course (read more about it here). This course is based on ASP.NET Core 1.1 MVC and yes, creating such a course takes time. A lot of time, considering it’s a course of almost 7 hours of deep-dive content. After it was handed over to production late July 2017, Microsoft actually has already released another update to ASP.NET Core, namely ASP.NET Core 2.0. This update isn’t as breaking as the update from 1.0 to 1.1 was earlier this year, however, some things changed a little bit and some things got a little simpler.

For the purpose of this Pluralsight course, I have outlined the changes below that you can follow to convert the sample application (Bethany’s Pie Shop) to ASP.NET Core MVC 2.0. Note that there’s no real need to upgrade! ASP.NET Core 1.1 has an LTS (Long-term service) release that you can keep using without a problem. However, if you want to use the latest version of the .NET Core framework, make the following small changes and you’re on your way. All other code and all other content in the course stays exactly the same!

The updated code can be downloaded from GitHub.

The *.csproj file

The *.csproj file was already simplified with ASP.NET Core 1.1, now it’s even simpler. Microsoft added a new metapackage called Microsoft.AspNetCore.All which references *A LOT* of other packages by default. Therefore, when following along with the Pluralsight course, everywhere where I say: “Add this package reference in the csproj file”, you can basically omit this. Adding the reference (if you’re using the 2.0 version that is) won’t do any harm but it’s really not necessary.

Here’s the updated csproj  file. Note that I made some other changes here as well, the most important one is of course that you now are using netcoreapp2.0.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<Project Sdk="Microsoft.NET.Sdk.Web">

 
  <PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <None Update="wwwroot\**\*">
      <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
    </None>
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />

    <PackageReference Include="Serilog" Version="2.5.0" />
    <PackageReference Include="Serilog.Extensions.Logging" Version="2.0.2" />
    <PackageReference Include="Serilog.Sinks.RollingFile" Version="3.3.0" />
  </ItemGroup>

  <ItemGroup>
    <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" />
  </ItemGroup>

  <ItemGroup>
    <Folder Include="Areas\Promo\Models" />
    <Folder Include="Views\Shared\Components\SystemStatusPage" />
  </ItemGroup>

  <ItemGroup>
    <WCFMetadata Include="Connected Services" />
  </ItemGroup>

</Project>

Note that you’ll need to update both the csproj file of the ASP.NET Core MVC project as well as the unit test project. Below you can find the csproj for the unit test project.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>
    <IsPackable>false</IsPackable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0-preview-20170628-02" />
    <PackageReference Include="Moq" Version="4.7.99" />
    <PackageReference Include="xunit" Version="2.2.0" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\src\BethanysPieShop\BethanysPieShop.csproj" />
  </ItemGroup>

  <ItemGroup>
    <Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
  </ItemGroup>

</Project>

After you’ve made the changes to the csproj files, save them and perform a clean and rebuild. You may also need to remove the bin and obj directories manually as they may contain some lingering references to .NET Core 1.1 stuff.

API Changes

Next to the csproj files, ASP.NET Core 2.0 MVC does have some API changes on-board. You can read about them here.

When compiling at this point, you’ll get some errors, all of them are easy to fix </phew>.

IdentityUser has changed slightly. There’s now a generic and non-generic version. In the application, we’re using the non-generic one (the “old” one), so this requires that we make a change basically everywhere IdentityUser is being used. For example, the ApplicationUser class need a new using statement.

1
2
3
4
5
6
7
8
9
10
11
12
13
using System;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;

namespace BethanysPieShop.Auth
{
public class ApplicationUser : IdentityUser
{
public DateTime Birthdate { get; set; }
public string City { get; set; }
public string Country { get; set; }
}
}

IdentityRole has also changed (same as IdentityUser), so also wherever we are using IdentityRole, you’ll need to include the using Microsoft.AspNetCore.Identity; statement.

Third-party authentication with Google (and other providers) has also changed. Previously, we were using the following code in the Startup > Configure method.

1
2
3
4
5
app.UseGoogleAuthentication(new GoogleOptions
{
ClientId = "KEY-apps.googleusercontent.com",
ClientSecret = "SECRET"
});

Remove this block entirely. In the Configure method, also replace app.UseIdentity(); with app.UseAuthentication();.

Next, in the ConfigureServices, we now need to add the third-party authentication. Add the following code in the ConfigureServices method.

1
2
3
4
5
6
 services.AddAuthentication()
.AddGoogle(options =>
{
options.ClientId = "KEY.apps.googleusercontent.com";
options.ClientSecret = "SECRET";
});

Next, you’ll also see an error on the ModelBindingMessageProvider, which I use to translate error messages. The used property is now get only, so we now need to use a method to change the value, as follows.

1
2
3
4
 .AddMvcOptions(options =>;
{
options.ModelBindingMessageProvider.SetValueIsInvalidAccessor(s => $"You can't leave this {s} empty");
}

Claims have changed a little bit as well. Previously, the IdentityUser class had a Claims property, which I used to store the user claims. That’s gone, so according to Microsoft, we now need to add it ourselves. OK, pretty easy. Go to the ApplicationUser again and add the Claims with the type parameter you’ll want to use.

1
2
3
4
5
6
7
8
9
10
11
12
13
 public class ApplicationUser : IdentityUser
{
public DateTime Birthdate { get; set; }
public string City { get; set; }
public string Country { get; set; }

///

/// Navigation property for the claims this user possesses.
///

public virtual ICollection<IdentityUserClaim>; Claims { get; } = new List<IdentityUserClaim>();
}

We’ll want to persist these to the database as well, right? To do this, go to your AppDbContext and add the following configuration.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);
            builder.Entity<Pie>()
                .HasOne(p => p.RecipeInformation)
                .WithOne(i => i.Pie)
                .HasForeignKey<RecipeInformation>(b => b.PieId);

            builder.Entity<ApplicationUser>()
                .HasMany(e => e.Claims)
                .WithOne()
                .HasForeignKey(e => e.UserId)
                .IsRequired()
                .OnDelete(DeleteBehavior.Cascade);
        }

Note that if you have already built the database using a migration, things will keep running fine.

Finally, we also need to make a small change in the login.cshtml. GetExternalAuthenticationSchemes doesn’t exist anymore and has been replaced with GetExternalAuthenticationSchemesAsync. Update the code as follows.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<h4>Or use another service to log in!</h4>
@{
var loginProviders = (await SignInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (loginProviders.Count == 0)
{
<div>

There are no external authentication services configured. See <a href="http://go.microsoft.com/fwlink/?LinkID=532715">this article</a>
for details on setting up this ASP.NET application to support logging in via external services.

</div>
}
else
{

<a class="btn btn-info">
Google
</a>

@*

<form class="form-horizontal" method="post">
<div>

@foreach (var provider in loginProviders)
{
<button class="btn btn-default" title="Log in using your @provider.DisplayName account" name="provider" type="submit" value="@provider.AuthenticationScheme">@provider.AuthenticationScheme</button>
}

</div>
</form>*@
}
}

Voila! You should now have an ASP.NET Core 2.0 version of Bethany’s Pie Shop!

gill

3 COMMENTS
  • F. Morrison

    In the version 2.0 of the code, I need to know how to seed the database with a default administrator, WIthout that, I’m in a Catch-22 situation in which there is no administrator to login to in order to start registering users.

  • Tom Simmons

    The update to the ApplicationUser class here has an incorrect semicolon after the ICollection data type declaration (half way through the line).

    I also found it necessary to create a new migration and update the database, maybe I misunderstood the line above about not needing to do this?

  • Rodrigo HMS

    Hello

    I’m kind lost to run code 2.0. If you can help me. I got the error message Unable to create an object of type ‘AppDbContext’. Add an implementation of ‘IDesignTimeDbContextFactory’ to the project, or see https://go.microsoft.com/fwlink/?linkid=851728 for additional patterns supported at design time.

    PS: My works goal is to be like you. Which way should I take first ?

Comments are closed.