ASP.NET Core 1.1 and Identity adventures

Hello and Welcome ☺

Recently a friend and I were trying to implement the Identity pattern from ASP.Net Core 1.1 on a different database schema and with custom tables from a legacy application and though some trial and error we managed to do it.

Today I want to share the steps we took and how we managed to solve it. I hope you find it useful :D.

The Setup

First off, we started with the old database we had and added a new schema to separate the business entities from the security ones. Here’s a script of how the tables were setup in SQL Server.

CREATE SCHEMA [Security]
GO

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [Security].[Client](
    [ID] [nvarchar](450) NOT NULL,
    [AccessFailedCount] [int] NOT NULL,
    [CNP] [nvarchar](30) NULL,
    [ConcurrencyStamp] [nvarchar](max) NULL,
    [Email] [nvarchar](256) NULL,
    [EmailConfirmed] [bit] NOT NULL,
    [FirstName] [nvarchar](50) NOT NULL,
    [LastName] [nvarchar](50) NOT NULL,
    [LockoutEnabled] [bit] NOT NULL,
    [LockoutEnd] [datetimeoffset](7) NULL,
    [NormalizedEmail] [nvarchar](256) NULL,
    [NormalizedUserName] [nvarchar](256) NULL,
    [NumarCI] [nvarchar](20) NULL,
    [PasswordHash] [nvarchar](max) NULL,
    [PhoneNumber] [nvarchar](max) NULL,
    [PhoneNumberConfirmed] [bit] NOT NULL,
    [RegDate] [datetime2](7) NOT NULL,
    [SecurityStamp] [nvarchar](max) NULL,
    [SerieCI] [nvarchar](2) NULL,
    [TwoFactorEnabled] [bit] NOT NULL,
    [UserName] [nvarchar](256) NULL,
 CONSTRAINT [PK_Client] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [Security].[ClientClaim](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [ClaimType] [nvarchar](max) NULL,
    [ClaimValue] [nvarchar](max) NULL,
    [ClientID] [nvarchar](450) NOT NULL,
 CONSTRAINT [PK_ClientClaim] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [Security].[ClientRole](
    [ClientID] [nvarchar](450) NOT NULL,
    [RoleID] [nvarchar](450) NOT NULL,
 CONSTRAINT [PK_ClientRole] PRIMARY KEY CLUSTERED 
(
    [ClientID] ASC,
    [RoleID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [Security].[ClientToken](
    [ClientID] [nvarchar](450) NOT NULL,
    [LoginProvider] [nvarchar](450) NOT NULL,
    [Name] [nvarchar](450) NOT NULL,
    [Value] [nvarchar](max) NULL,
 CONSTRAINT [PK_ClientToken] PRIMARY KEY CLUSTERED 
(
    [ClientID] ASC,
    [LoginProvider] ASC,
    [Name] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [Security].[ExternalLogin](
    [LoginProvider] [nvarchar](450) NOT NULL,
    [ProviderKey] [nvarchar](450) NOT NULL,
    [ProviderDisplayName] [nvarchar](max) NULL,
    [ClientID] [nvarchar](450) NOT NULL,
 CONSTRAINT [PK_ExternalLogin] PRIMARY KEY CLUSTERED 
(
    [LoginProvider] ASC,
    [ProviderKey] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [Security].[Role](
    [ID] [nvarchar](450) NOT NULL,
    [ConcurrencyStamp] [nvarchar](max) NULL,
    [Name] [nvarchar](256) NULL,
    [NormalizedName] [nvarchar](256) NULL,
 CONSTRAINT [PK_Role] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [Security].[RoleClaim](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [ClaimType] [nvarchar](max) NULL,
    [ClaimValue] [nvarchar](max) NULL,
    [RoleID] [nvarchar](450) NOT NULL,
 CONSTRAINT [PK_RoleClaim] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO
SET ANSI_PADDING ON

GO

CREATE NONCLUSTERED INDEX [EmailIndex] ON [Security].[Client]
(
    [NormalizedEmail] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
SET ANSI_PADDING ON

GO

CREATE UNIQUE NONCLUSTERED INDEX [UserNameIndex] ON [Security].[Client]
(
    [NormalizedUserName] ASC
)
WHERE ([NormalizedUserName] IS NOT NULL)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
SET ANSI_PADDING ON

GO

CREATE NONCLUSTERED INDEX [IX_ClientClaim_ClientID] ON [Security].[ClientClaim]
(
    [ClientID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
SET ANSI_PADDING ON

GO

CREATE NONCLUSTERED INDEX [IX_ClientRole_RoleID] ON [Security].[ClientRole]
(
    [RoleID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
SET ANSI_PADDING ON

GO

CREATE NONCLUSTERED INDEX [IX_ExternalLogin_ClientID] ON [Security].[ExternalLogin]
(
    [ClientID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
SET ANSI_PADDING ON

GO

CREATE UNIQUE NONCLUSTERED INDEX [RoleNameIndex] ON [Security].[Role]
(
    [NormalizedName] ASC
)
WHERE ([NormalizedName] IS NOT NULL)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
SET ANSI_PADDING ON

GO

CREATE NONCLUSTERED INDEX [IX_RoleClaim_RoleID] ON [Security].[RoleClaim]
(
    [RoleID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
ALTER TABLE [Security].[Client] ADD  DEFAULT (getdate()) FOR [RegDate]
GO
ALTER TABLE [Security].[ClientClaim]  WITH CHECK ADD  CONSTRAINT [FK_ClientClaim_Client_ClientID] FOREIGN KEY([ClientID])
REFERENCES [Security].[Client] ([ID])
ON DELETE CASCADE
GO
ALTER TABLE [Security].[ClientClaim] CHECK CONSTRAINT [FK_ClientClaim_Client_ClientID]
GO
ALTER TABLE [Security].[ClientRole]  WITH CHECK ADD  CONSTRAINT [FK_ClientRole_Client_ClientID] FOREIGN KEY([ClientID])
REFERENCES [Security].[Client] ([ID])
ON DELETE CASCADE
GO
ALTER TABLE [Security].[ClientRole] CHECK CONSTRAINT [FK_ClientRole_Client_ClientID]
GO
ALTER TABLE [Security].[ClientRole]  WITH CHECK ADD  CONSTRAINT [FK_ClientRole_Role_RoleID] FOREIGN KEY([RoleID])
REFERENCES [Security].[Role] ([ID])
ON DELETE CASCADE
GO
ALTER TABLE [Security].[ClientRole] CHECK CONSTRAINT [FK_ClientRole_Role_RoleID]
GO
ALTER TABLE [Security].[ExternalLogin]  WITH CHECK ADD  CONSTRAINT [FK_ExternalLogin_Client_ClientID] FOREIGN KEY([ClientID])
REFERENCES [Security].[Client] ([ID])
ON DELETE CASCADE
GO
ALTER TABLE [Security].[ExternalLogin] CHECK CONSTRAINT [FK_ExternalLogin_Client_ClientID]
GO
ALTER TABLE [Security].[RoleClaim]  WITH CHECK ADD  CONSTRAINT [FK_RoleClaim_Role_RoleID] FOREIGN KEY([RoleID])
REFERENCES [Security].[Role] ([ID])
ON DELETE CASCADE
GO
ALTER TABLE [Security].[RoleClaim] CHECK CONSTRAINT [FK_RoleClaim_Role_RoleID]
GO

Now, if we look at this schema we can see it’s identical to the normal identity table except they have different table names, they belong to a different database schema (as they should) and the *Client *table (which represents the former AspNetUsers table) has 3 new columns needed in our application.

So we made the changes to the database first, since that was the easiest to modify in this application migration, next we create a new ASP.NET Core 1.1 solution and set the authentication to Individual User Accounts.

The Changes

Next, we deleted the migrations the template project come with that are found under Data-> Migrations to get those from interfering with our work.

So now, we have an old database that was modified for our new requests and a brand new created ASP.NET Core 1.1 application, in which we want to do Code-First from an existing database. So then we did the following steps:

  1. Opened up the Package Manager console

  2. Rand the following command by switching the “” part with the connection string for our old database Scaffold-DbContext -Connection -Provider "Microsoft.EntityFrameworkCore.SqlServer" -o "Models\" -Schemas "dbo","Security”

  3. Removed the class ApplicationUser since we will be using our own class for this purpose.

Now that we have the models created into our Models folder we need to do the following updates, we’re going to go through each class one by one from how it looked when it was imported to how it will be when we’re done.

Client.cs

When we imported our models, the Client class ended up looking like this:

using System;
using System.Collections.Generic;

namespace WebApplication1.Models
{
    public partial class Client
    {
        public Client()
        {
            ClientClaim = new HashSet<ClientClaim>();
            ClientRole = new HashSet<ClientRole>();
            ExternalLogin = new HashSet<ExternalLogin>();
        }

        public string Id { get; set; }
        public int AccessFailedCount { get; set; }
        public string Cnp { get; set; }
        public string ConcurrencyStamp { get; set; }
        public string Email { get; set; }
        public bool EmailConfirmed { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public bool LockoutEnabled { get; set; }
        public DateTimeOffset? LockoutEnd { get; set; }
        public string NormalizedEmail { get; set; }
        public string NormalizedUserName { get; set; }
        public string NumarCi { get; set; }
        public string PasswordHash { get; set; }
        public string PhoneNumber { get; set; }
        public bool PhoneNumberConfirmed { get; set; }
        public DateTime RegDate { get; set; }
        public string SecurityStamp { get; set; }
        public string SerieCi { get; set; }
        public bool TwoFactorEnabled { get; set; }
        public string UserName { get; set; }

        public virtual ICollection<ClientClaim> ClientClaim { get; set; }
        public virtual ICollection<ClientRole> ClientRole { get; set; }
        public virtual ICollection<ExternalLogin> ExternalLogin { get; set; }
    }
}

But like we said before, this will be the model for our users, as such the class needs to implement the IdentityUser interface, but since we’re creating all of the tables, we will also need to specify the types of the other dependent classes and the type of the Id property. At the end the class looked like this:

using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;

namespace WebApplication1.Models
{
    public class Client : IdentityUser<string, ClientClaim, ClientRole, ExternalLogin>
    {
        public Client()
        {
            ClientClaim = new HashSet<ClientClaim>();
            ClientRole = new HashSet<ClientRole>();
            ExternalLogin = new HashSet<ExternalLogin>();
        }

        public string Cnp { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string NumarCi { get; set; }
        public DateTime RegDate { get; set; }
        public string SerieCi { get; set; }

        public virtual ICollection<ClientClaim> ClientClaim { get; set; }
        public virtual ICollection<ClientRole> ClientRole { get; set; }
        public virtual ICollection<ExternalLogin> ExternalLogin { get; set; }
    }
}

Note that the only properties remaining from the old implementation are the custom columns that were added to the user table and the new navigation properties for the custom types.

ClientClaim.cs

This class (and some of the other supporting classes) doesn’t hold any changes from the what is default implemented into the framework with the exception of being called differently.

using System;
using System.Collections.Generic;

namespace WebApplication1.Models
{
    public partial class ClientClaim
    {
        public int Id { get; set; }
        public string ClaimType { get; set; }
        public string ClaimValue { get; set; }
        public string ClientId { get; set; }

        public virtual Client Client { get; set; }
    }
}

Got changed to this:

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;

namespace WebApplication1.Models
{
    public class ClientClaim : IdentityUserClaim<string>
    {
        public string ClientId { get; set; }

        public virtual Client Client { get; set; }
    }
}

ClientRole.cs

From this:

using System;
using System.Collections.Generic;

namespace WebApplication1.Models
{
    public partial class ClientRole
    {
        public string ClientId { get; set; }
        public string RoleId { get; set; }

        public virtual Client Client { get; set; }
        public virtual Role Role { get; set; }
    }
}

To this:

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;

namespace WebApplication1.Models
{
    public class ClientRole : IdentityUserRole<string>
    {
        public string ClientId { get; set; }

        public virtual Client Client { get; set; }
        public virtual Role Role { get; set; }
    }
}

ClientToken.cs

From this:

using System;
using System.Collections.Generic;

namespace WebApplication1.Models
{
    public partial class ClientToken
    {
        public string ClientId { get; set; }
        public string LoginProvider { get; set; }
        public string Name { get; set; }
        public string Value { get; set; }
    }
}

To this:

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;

namespace WebApplication1.Models
{
    public class ClientToken : IdentityUserToken<string>
    {
        public string ClientId { get; set; }
    }
}

ExternalLogin.cs

From this:

using System;
using System.Collections.Generic;

namespace WebApplication1.Models
{
    public partial class ExternalLogin
    {
        public string LoginProvider { get; set; }
        public string ProviderKey { get; set; }
        public string ProviderDisplayName { get; set; }
        public string ClientId { get; set; }

        public virtual Client Client { get; set; }
    }
}

To this:

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;

namespace WebApplication1.Models
{
    public class ExternalLogin : IdentityUserLogin<string>
    {
        public string ClientId { get; set; }

        public virtual Client Client { get; set; }
    }
}

Role.cs

From this:

using System;
using System.Collections.Generic;

namespace WebApplication1.Models
{
    public partial class Role
    {
        public Role()
        {
            ClientRole = new HashSet<ClientRole>();
            RoleClaim = new HashSet<RoleClaim>();
        }

        public string Id { get; set; }
        public string ConcurrencyStamp { get; set; }
        public string Name { get; set; }
        public string NormalizedName { get; set; }

        public virtual ICollection<ClientRole> ClientRole { get; set; }
        public virtual ICollection<RoleClaim> RoleClaim { get; set; }
    }
}

To this:

using System.Collections.Generic;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;

namespace WebApplication1.Models
{
    public class Role : IdentityRole<string, ClientRole, RoleClaim>
    {
        public Role()
        {
            ClientRole = new HashSet<ClientRole>();
            RoleClaim = new HashSet<RoleClaim>();
        }

        public virtual ICollection<ClientRole> ClientRole { get; set; }
        public virtual ICollection<RoleClaim> RoleClaim { get; set; }
    }
}

Note that in this case, just like for the client, we’re not just implementing IdentityRole but we’re using the form with generics for its dependent classes.

And finally

RoleClaims.cs

From this:

using System;
using System.Collections.Generic;

namespace WebApplication1.Models
{
    public partial class RoleClaim
    {
        public int Id { get; set; }
        public string ClaimType { get; set; }
        public string ClaimValue { get; set; }
        public string RoleId { get; set; }

        public virtual Role Role { get; set; }
    }
}

To this:

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;

namespace WebApplication1.Models
{
    public class RoleClaim : IdentityRoleClaim<string>
    {
        public virtual Role Role { get; set; }
    }
}

Pfffewww…But we’re not done yet. When we did the import of the models, a new database context was created which if you used the command we did earlier, it will be called the name of the database suffixed with Context and be placed inside the same models folder.

Updating the dabase context

What we need to do now are the following:

  • Move all the properties (the DbSets) created in this context and move them in the default database context that came with the project template, called ApplicationDbContext.

  • Move the OnModelCreating method entirely.

  • Delete the created context since we got everything we needed from it (we can ignore the OnConfiguring method since the ApplicationDbContext already receives the connection string in Startup.cs).

Now we need to do some updates in the *ApplicationDbContext.cs *file, intially it looked like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using WebApplication1.Models;

namespace WebApplication1.Data
{
    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);
            // Customize the ASP.NET Identity model and override the defaults if needed.
            // For example, you can rename the ASP.NET Identity table names and more.
            // Add your customizations after calling base.OnModelCreating(builder);
        }
    }
}

Besides the updates we added from the generated context earlier we also need to change the interface from IdentityDbContext to a more explicit interface declaring all of our custom classes, and in the end, it will look like this:

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using WebApplication1.Models;

namespace WebApplication1.Data
{
    public class ApplicationDbContext : IdentityDbContext<Client, Role, string, ClientClaim, ClientRole, ExternalLogin, RoleClaim, ClientToken>
    {
        public virtual DbSet<Client> Client { get; set; }
        public virtual DbSet<ClientClaim> ClientClaim { get; set; }
        public virtual DbSet<ClientRole> ClientRole { get; set; }
        public virtual DbSet<ClientToken> ClientToken { get; set; }
        public virtual DbSet<ExternalLogin> ExternalLogin { get; set; }
        public virtual DbSet<Role> Role { get; set; }
        public virtual DbSet<RoleClaim> RoleClaim { get; set; }


        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Client>(entity =>
            {
                entity.ToTable("Client", "Security");

                entity.HasIndex(e => e.NormalizedEmail)
                    .HasName("EmailIndex");

                entity.HasIndex(e => e.NormalizedUserName)
                    .HasName("UserNameIndex")
                    .IsUnique();

                entity.Property(e => e.Id)
                    .HasColumnName("ID")
                    .HasMaxLength(450);

                entity.Property(e => e.Cnp)
                    .HasColumnName("CNP")
                    .HasMaxLength(30);

                entity.Property(e => e.Email).HasMaxLength(256);

                entity.Property(e => e.FirstName)
                    .IsRequired()
                    .HasMaxLength(50);

                entity.Property(e => e.LastName)
                    .IsRequired()
                    .HasMaxLength(50);

                entity.Property(e => e.NormalizedEmail).HasMaxLength(256);

                entity.Property(e => e.NormalizedUserName)
                    .IsRequired()
                    .HasMaxLength(256);

                entity.Property(e => e.NumarCi)
                    .HasColumnName("NumarCI")
                    .HasMaxLength(20);

                entity.Property(e => e.RegDate).HasDefaultValueSql("getdate()");

                entity.Property(e => e.SerieCi)
                    .HasColumnName("SerieCI")
                    .HasMaxLength(2);

                entity.Property(e => e.UserName).HasMaxLength(256);
            });

            modelBuilder.Entity<ClientClaim>(entity =>
            {
                entity.ToTable("ClientClaim", "Security");

                entity.HasIndex(e => e.ClientId)
                    .HasName("IX_ClientClaim_ClientID");

                entity.Property(e => e.Id).HasColumnName("ID");

                entity.Property(e => e.ClientId)
                    .IsRequired()
                    .HasColumnName("ClientID")
                    .HasMaxLength(450);

                entity.HasOne(d => d.Client)
                    .WithMany(p => p.ClientClaim)
                    .HasForeignKey(d => d.ClientId);
            });

            modelBuilder.Entity<ClientRole>(entity =>
            {
                entity.HasKey(e => new { e.ClientId, e.RoleId })
                    .HasName("PK_ClientRole");

                entity.ToTable("ClientRole", "Security");

                entity.HasIndex(e => e.RoleId)
                    .HasName("IX_ClientRole_RoleID");

                entity.Property(e => e.ClientId)
                    .HasColumnName("ClientID")
                    .HasMaxLength(450);

                entity.Property(e => e.RoleId)
                    .HasColumnName("RoleID")
                    .HasMaxLength(450);

                entity.HasOne(d => d.Client)
                    .WithMany(p => p.ClientRole)
                    .HasForeignKey(d => d.ClientId);

                entity.HasOne(d => d.Role)
                    .WithMany(p => p.ClientRole)
                    .HasForeignKey(d => d.RoleId);
            });

            modelBuilder.Entity<ClientToken>(entity =>
            {
                entity.HasKey(e => new { e.ClientId, e.LoginProvider, e.Name })
                    .HasName("PK_ClientToken");

                entity.ToTable("ClientToken", "Security");

                entity.Property(e => e.ClientId)
                    .HasColumnName("ClientID")
                    .HasMaxLength(450);

                entity.Property(e => e.LoginProvider).HasMaxLength(450);

                entity.Property(e => e.Name).HasMaxLength(450);
            });

            modelBuilder.Entity<ExternalLogin>(entity =>
            {
                entity.HasKey(e => new { e.LoginProvider, e.ProviderKey })
                    .HasName("PK_ExternalLogin");

                entity.ToTable("ExternalLogin", "Security");

                entity.HasIndex(e => e.ClientId)
                    .HasName("IX_ExternalLogin_ClientID");

                entity.Property(e => e.LoginProvider).HasMaxLength(450);

                entity.Property(e => e.ProviderKey).HasMaxLength(450);

                entity.Property(e => e.ClientId)
                    .IsRequired()
                    .HasColumnName("ClientID")
                    .HasMaxLength(450);

                entity.HasOne(d => d.Client)
                    .WithMany(p => p.ExternalLogin)
                    .HasForeignKey(d => d.ClientId);
            });

            modelBuilder.Entity<Role>(entity =>
            {
                entity.ToTable("Role", "Security");

                entity.HasIndex(e => e.NormalizedName)
                    .HasName("RoleNameIndex")
                    .IsUnique();

                entity.Property(e => e.Id)
                    .HasColumnName("ID")
                    .HasMaxLength(450);

                entity.Property(e => e.Name).HasMaxLength(256);

                entity.Property(e => e.NormalizedName)
                    .IsRequired()
                    .HasMaxLength(256);
            });

            modelBuilder.Entity<RoleClaim>(entity =>
            {
                entity.ToTable("RoleClaim", "Security");

                entity.HasIndex(e => e.RoleId)
                    .HasName("IX_RoleClaim_RoleID");

                entity.Property(e => e.Id).HasColumnName("ID");

                entity.Property(e => e.RoleId)
                    .IsRequired()
                    .HasColumnName("RoleID")
                    .HasMaxLength(450);

                entity.HasOne(d => d.Role)
                    .WithMany(p => p.RoleClaim)
                    .HasForeignKey(d => d.RoleId);
            });
        }

    }
}

One thing to note which was a big headache for us is in the OnModelCreating method, and that was the default line base.OnModelCreating(builder) . The reason this was a headache was that we grew accustomed to always call on the base implementations of virtual methods and not asking why, and every time we ran the migration for the new database we would end up not only with our own tables, but also the old AspNetXXX tables because this is what runs inside the OnModelCreating override in the IdentityDbContext class:

protected override void OnModelCreating(ModelBuilder builder)
    {
      builder.Entity<TUser>((Action<EntityTypeBuilder<TUser>>) (b =>
      {
        b.HasKey((Expression<Func<TUser, object>>) (u => (object) u.Id));
        b.HasIndex((Expression<Func<TUser, object>>) (u => u.NormalizedUserName)).HasName("UserNameIndex").IsUnique(true);
        b.HasIndex((Expression<Func<TUser, object>>) (u => u.NormalizedEmail)).HasName("EmailIndex");
        b.ToTable<TUser>("AspNetUsers");
        b.Property<string>((Expression<Func<TUser, string>>) (u => u.ConcurrencyStamp)).IsConcurrencyToken(true);
        b.Property<string>((Expression<Func<TUser, string>>) (u => u.UserName)).HasMaxLength(256);
        b.Property<string>((Expression<Func<TUser, string>>) (u => u.NormalizedUserName)).HasMaxLength(256);
        b.Property<string>((Expression<Func<TUser, string>>) (u => u.Email)).HasMaxLength(256);
        b.Property<string>((Expression<Func<TUser, string>>) (u => u.NormalizedEmail)).HasMaxLength(256);
        b.HasMany<TUserClaim>((Expression<Func<TUser, IEnumerable<TUserClaim>>>) (u => u.Claims)).WithOne((string) null).HasForeignKey((Expression<Func<TUserClaim, object>>) (uc => (object) uc.UserId)).IsRequired(true);
        b.HasMany<TUserLogin>((Expression<Func<TUser, IEnumerable<TUserLogin>>>) (u => u.Logins)).WithOne((string) null).HasForeignKey((Expression<Func<TUserLogin, object>>) (ul => (object) ul.UserId)).IsRequired(true);
        b.HasMany<TUserRole>((Expression<Func<TUser, IEnumerable<TUserRole>>>) (u => u.Roles)).WithOne((string) null).HasForeignKey((Expression<Func<TUserRole, object>>) (ur => (object) ur.UserId)).IsRequired(true);
      }));
      builder.Entity<TRole>((Action<EntityTypeBuilder<TRole>>) (b =>
      {
        b.HasKey((Expression<Func<TRole, object>>) (r => (object) r.Id));
        b.HasIndex((Expression<Func<TRole, object>>) (r => r.NormalizedName)).HasName("RoleNameIndex").IsUnique(true);
        b.ToTable<TRole>("AspNetRoles");
        b.Property<string>((Expression<Func<TRole, string>>) (r => r.ConcurrencyStamp)).IsConcurrencyToken(true);
        b.Property<string>((Expression<Func<TRole, string>>) (u => u.Name)).HasMaxLength(256);
        b.Property<string>((Expression<Func<TRole, string>>) (u => u.NormalizedName)).HasMaxLength(256);
        b.HasMany<TUserRole>((Expression<Func<TRole, IEnumerable<TUserRole>>>) (r => r.Users)).WithOne((string) null).HasForeignKey((Expression<Func<TUserRole, object>>) (ur => (object) ur.RoleId)).IsRequired(true);
        b.HasMany<TRoleClaim>((Expression<Func<TRole, IEnumerable<TRoleClaim>>>) (r => r.Claims)).WithOne((string) null).HasForeignKey((Expression<Func<TRoleClaim, object>>) (rc => (object) rc.RoleId)).IsRequired(true);
      }));
      builder.Entity<TUserClaim>((Action<EntityTypeBuilder<TUserClaim>>) (b =>
      {
        b.HasKey((Expression<Func<TUserClaim, object>>) (uc => (object) uc.Id));
        b.ToTable<TUserClaim>("AspNetUserClaims");
      }));
      builder.Entity<TRoleClaim>((Action<EntityTypeBuilder<TRoleClaim>>) (b =>
      {
        b.HasKey((Expression<Func<TRoleClaim, object>>) (rc => (object) rc.Id));
        b.ToTable<TRoleClaim>("AspNetRoleClaims");
      }));
      builder.Entity<TUserRole>((Action<EntityTypeBuilder<TUserRole>>) (b =>
      {
        b.HasKey((Expression<Func<TUserRole, object>>) (r => new
        {
          UserId = r.UserId,
          RoleId = r.RoleId
        }));
        b.ToTable<TUserRole>("AspNetUserRoles");
      }));
      builder.Entity<TUserLogin>((Action<EntityTypeBuilder<TUserLogin>>) (b =>
      {
        b.HasKey((Expression<Func<TUserLogin, object>>) (l => new
        {
          LoginProvider = l.LoginProvider,
          ProviderKey = l.ProviderKey
        }));
        b.ToTable<TUserLogin>("AspNetUserLogins");
      }));
      builder.Entity<TUserToken>((Action<EntityTypeBuilder<TUserToken>>) (b =>
      {
        b.HasKey((Expression<Func<TUserToken, object>>) (l => new
        {
          UserId = l.UserId,
          LoginProvider = l.LoginProvider,
          Name = l.Name
        }));
        b.ToTable<TUserToken>("AspNetUserTokens");
      }));
    }

So in our implementation, we needed to remove the call to the base implementation so that it doesn’t interfere with our migration.

Running the application?

So after all this changes we hit Build and yep, we found the build was failing in all places because we removed the ApplicationUser class (to make things easier we could have just moved the Client implementation in that class and then renamed it, but that would be a bit tangled), we went to each location where *ApplicationUser *was used and replaced it with our own Client class.

Ok, now we build and everything seems ok, right? Wrong… We run the application and we get the following error:

GenericArguments\[0], ‘WebApplication1.Models.Client’, on ‘Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore\`4\[TUser,TRole,TContext,TKey]’ violates the constraint of type ‘TUser’.

When trying to run the following line in the *Startup.cs *file:

services.AddIdentity<Client, IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();

This is where we got stuck for a while (actually we got stuck in quite a few of the previous steps since we were doing something for which we had no proper documentation), so we started looking into the .Net Core documentation, and StackOverflow and such, but no clean-cut fixes for why this is happening, let along how to fix it (you know my policy, understand first, fix after).

So i took a look into the source code of the AddEntityFrameworkStores()method. And I found this:

private static IServiceCollection GetDefaultServices(Type userType, Type roleType, Type contextType, Type keyType = null)
    {
      Type type = keyType;
      if ((object) type == null)
        type = typeof (string);
      keyType = type;
      Type implementationType1 = typeof (UserStore<,,,>).MakeGenericType(userType, roleType, contextType, keyType);
      Type implementationType2 = typeof (RoleStore<,,>).MakeGenericType(roleType, contextType, keyType);
      ServiceCollection services = new ServiceCollection();
      services.AddScoped(typeof (IUserStore<>).MakeGenericType(userType), implementationType1);
      services.AddScoped(typeof (IRoleStore<>).MakeGenericType(roleType), implementationType2);
      return (IServiceCollection) services;
    }

Which then later got me to look into the definition of the UserStore to see what constraint we were violating. Well, it didn’t seem very obvious but the default UserStore and RoleStore implementations did now know how to use our custom classes (after some later research into the Github repo I found this issue and they mention this should be a lot easier to implement in the .Net Core 2 iteration, if you’re curious the issue can be found here). To fix it, we needed to implement our own stores and managers.

Implementing the “fix”

So this is what we did next, we updated the startup line as follows:

services.AddIdentity<Client, Role>()
    .AddUserStore<ApplicationUserStore>()
    .AddUserManager<ApplicationUserManager>()
    .AddRoleStore<ApplicationRoleStore>()
    .AddRoleManager<ApplicationRoleManager>()
    .AddSignInManager<ApplicationSignInManager>()
    //.AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();

As you can see we needed to add custom stores and managers (we will show you their implementation next) and commented out the AddEntityFrameworkStores line (kept here to emphasize that it needed to be removed).

Next this is the implementation for each one of those custom classes:

using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using WebApplication1.Models;

namespace WebApplication1.Data
{
    public class ApplicationRoleManager : RoleManager<Role>
    {
        public ApplicationRoleManager(IRoleStore<Role> store, IEnumerable<IRoleValidator<Role>> roleValidators, ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors, ILogger<RoleManager<Role>> logger, IHttpContextAccessor contextAccessor) 
            : base(store, roleValidators, keyNormalizer, errors, logger, contextAccessor)
        {
        }
    }
}
using System.Security.Claims;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using WebApplication1.Models;

namespace WebApplication1.Data
{
    public class ApplicationRoleStore : RoleStore<Role, ApplicationDbContext, string, ClientRole, RoleClaim>
    {
        public ApplicationRoleStore(ApplicationDbContext context, IdentityErrorDescriber describer = null) : base(context, describer)
        {
        }

        protected override RoleClaim CreateRoleClaim(Role role, Claim claim)
        {
            var roleClaim = new RoleClaim
            {
                Role = role,
                RoleId = role.Id
            };
            roleClaim.InitializeFromClaim(claim);
            return roleClaim;
        }
    }
}
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using WebApplication1.Models;

namespace WebApplication1.Data
{
    public class ApplicationSignInManager : SignInManager<Client>
    {
        public ApplicationSignInManager(UserManager<Client> userManager, IHttpContextAccessor contextAccessor, IUserClaimsPrincipalFactory<Client> claimsFactory, IOptions<IdentityOptions> optionsAccessor, ILogger<SignInManager<Client>> logger) 
            : base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger)
        {
        }
    }
}
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using WebApplication1.Models;

namespace WebApplication1.Data
{
    public class ApplicationUserManager : UserManager<Client>
    {
        public ApplicationUserManager(IUserStore<Client> store, IOptions<IdentityOptions> optionsAccessor, IPasswordHasher<Client> passwordHasher, IEnumerable<IUserValidator<Client>> userValidators, IEnumerable<IPasswordValidator<Client>> passwordValidators, ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors, IServiceProvider services, ILogger<UserManager<Client>> logger) 
            : base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
        {
        }
    }
}
using System.Security.Claims;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using WebApplication1.Models;

namespace WebApplication1.Data
{
    public class ApplicationUserStore : UserStore<Client, Role, ApplicationDbContext, string, ClientClaim, ClientRole, ExternalLogin, ClientToken, RoleClaim>
    {
        public ApplicationUserStore(ApplicationDbContext context, IdentityErrorDescriber describer = null)
            : base(context, describer)
        {
        }

        protected override ClientRole CreateUserRole(Client user, Role role)
        {
            return new ClientRole
            {
                Client = user,
                Role = role,
                ClientId = user.Id,
                RoleId = role.Id,
                UserId = user.Id
            };
        }

        protected override ClientClaim CreateUserClaim(Client user, Claim claim)
        {
            var clientClaim = new ClientClaim
            {
                Client = user,
                ClientId = user.Id,
                UserId = user.Id,
            };
            clientClaim.InitializeFromClaim(claim);
            return clientClaim;
        }

        protected override ExternalLogin CreateUserLogin(Client user, UserLoginInfo login)
        {
            return new ExternalLogin
            {
                Client = user,
                ClientId = user.Id,
                UserId = user.Id,
                LoginProvider = login.LoginProvider,
                ProviderDisplayName = login.ProviderDisplayName,
                ProviderKey = login.ProviderKey
            };
        }

        protected override ClientToken CreateUserToken(Client user, string loginProvider, string name, string value)
        {
            return new ClientToken
            {
                ClientId = user.Id,
                UserId = user.Id,
                LoginProvider = loginProvider,
                Value = value,
                Name = name
            };
        }
    }
}

Will it work now?

Well the answer is yes and no.

Yes, the application ran without any issue, but we forgot one more step. Since we only used our old database for scaffolding and by default a new application creates its own database, we need to add a migration for all the changes we made so that they can be applied to any new database we connect to.

Creating the migration

So we opened up the Package Manager Console again and typed in cd ./WebApplication1 (the reason for this is because we need to be in the project folder if we want to run any dotnet command lines) then dotnet ef migrations add Initial -o "Data\Migrations”.

This created our migration in the Data->Migrations folder just like it was when we first created the project, and for those curious so to how such a migration looks like after all our changes, this is what is outputted:

using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Metadata;

namespace WebApplication1.Data.Migrations
{
    public partial class Initial : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.EnsureSchema(
                name: "Security");

            migrationBuilder.CreateTable(
                name: "Client",
                schema: "Security",
                columns: table => new
                {
                    ID = table.Column<string>(maxLength: 450, nullable: false),
                    AccessFailedCount = table.Column<int>(nullable: false),
                    CNP = table.Column<string>(maxLength: 30, nullable: true),
                    ConcurrencyStamp = table.Column<string>(nullable: true),
                    Email = table.Column<string>(maxLength: 256, nullable: true),
                    EmailConfirmed = table.Column<bool>(nullable: false),
                    FirstName = table.Column<string>(maxLength: 50, nullable: false),
                    LastName = table.Column<string>(maxLength: 50, nullable: false),
                    LockoutEnabled = table.Column<bool>(nullable: false),
                    LockoutEnd = table.Column<DateTimeOffset>(nullable: true),
                    NormalizedEmail = table.Column<string>(maxLength: 256, nullable: true),
                    NormalizedUserName = table.Column<string>(maxLength: 256, nullable: false),
                    NumarCI = table.Column<string>(maxLength: 20, nullable: true),
                    PasswordHash = table.Column<string>(nullable: true),
                    PhoneNumber = table.Column<string>(nullable: true),
                    PhoneNumberConfirmed = table.Column<bool>(nullable: false),
                    RegDate = table.Column<DateTime>(nullable: false, defaultValueSql: "getdate()"),
                    SecurityStamp = table.Column<string>(nullable: true),
                    SerieCI = table.Column<string>(maxLength: 2, nullable: true),
                    TwoFactorEnabled = table.Column<bool>(nullable: false),
                    UserName = table.Column<string>(maxLength: 256, nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Client", x => x.ID);
                });

            migrationBuilder.CreateTable(
                name: "ClientToken",
                schema: "Security",
                columns: table => new
                {
                    ClientID = table.Column<string>(maxLength: 450, nullable: false),
                    LoginProvider = table.Column<string>(maxLength: 450, nullable: false),
                    Name = table.Column<string>(maxLength: 450, nullable: false),
                    UserId = table.Column<string>(nullable: true),
                    Value = table.Column<string>(nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_ClientToken", x => new { x.ClientID, x.LoginProvider, x.Name });
                });

            migrationBuilder.CreateTable(
                name: "Role",
                schema: "Security",
                columns: table => new
                {
                    ID = table.Column<string>(maxLength: 450, nullable: false),
                    ConcurrencyStamp = table.Column<string>(nullable: true),
                    Name = table.Column<string>(maxLength: 256, nullable: true),
                    NormalizedName = table.Column<string>(maxLength: 256, nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Role", x => x.ID);
                });

            migrationBuilder.CreateTable(
                name: "ClientClaim",
                schema: "Security",
                columns: table => new
                {
                    ID = table.Column<int>(nullable: false)
                        .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                    ClaimType = table.Column<string>(nullable: true),
                    ClaimValue = table.Column<string>(nullable: true),
                    ClientID = table.Column<string>(maxLength: 450, nullable: false),
                    ClientId1 = table.Column<string>(nullable: true),
                    UserId = table.Column<string>(nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_ClientClaim", x => x.ID);
                    table.ForeignKey(
                        name: "FK_ClientClaim_Client_ClientID",
                        column: x => x.ClientID,
                        principalSchema: "Security",
                        principalTable: "Client",
                        principalColumn: "ID",
                        onDelete: ReferentialAction.Cascade);
                    table.ForeignKey(
                        name: "FK_ClientClaim_Client_ClientId1",
                        column: x => x.ClientId1,
                        principalSchema: "Security",
                        principalTable: "Client",
                        principalColumn: "ID",
                        onDelete: ReferentialAction.Restrict);
                });

            migrationBuilder.CreateTable(
                name: "ExternalLogin",
                schema: "Security",
                columns: table => new
                {
                    LoginProvider = table.Column<string>(maxLength: 450, nullable: false),
                    ProviderKey = table.Column<string>(maxLength: 450, nullable: false),
                    ClientID = table.Column<string>(maxLength: 450, nullable: false),
                    ClientId1 = table.Column<string>(nullable: true),
                    ProviderDisplayName = table.Column<string>(nullable: true),
                    UserId = table.Column<string>(nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_ExternalLogin", x => new { x.LoginProvider, x.ProviderKey });
                    table.ForeignKey(
                        name: "FK_ExternalLogin_Client_ClientID",
                        column: x => x.ClientID,
                        principalSchema: "Security",
                        principalTable: "Client",
                        principalColumn: "ID",
                        onDelete: ReferentialAction.Cascade);
                    table.ForeignKey(
                        name: "FK_ExternalLogin_Client_ClientId1",
                        column: x => x.ClientId1,
                        principalSchema: "Security",
                        principalTable: "Client",
                        principalColumn: "ID",
                        onDelete: ReferentialAction.Restrict);
                });

            migrationBuilder.CreateTable(
                name: "ClientRole",
                schema: "Security",
                columns: table => new
                {
                    ClientID = table.Column<string>(maxLength: 450, nullable: false),
                    RoleID = table.Column<string>(maxLength: 450, nullable: false),
                    ClientId1 = table.Column<string>(nullable: true),
                    RoleId1 = table.Column<string>(nullable: true),
                    UserId = table.Column<string>(nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_ClientRole", x => new { x.ClientID, x.RoleID });
                    table.ForeignKey(
                        name: "FK_ClientRole_Client_ClientID",
                        column: x => x.ClientID,
                        principalSchema: "Security",
                        principalTable: "Client",
                        principalColumn: "ID",
                        onDelete: ReferentialAction.Cascade);
                    table.ForeignKey(
                        name: "FK_ClientRole_Client_ClientId1",
                        column: x => x.ClientId1,
                        principalSchema: "Security",
                        principalTable: "Client",
                        principalColumn: "ID",
                        onDelete: ReferentialAction.Restrict);
                    table.ForeignKey(
                        name: "FK_ClientRole_Role_RoleID",
                        column: x => x.RoleID,
                        principalSchema: "Security",
                        principalTable: "Role",
                        principalColumn: "ID",
                        onDelete: ReferentialAction.Cascade);
                    table.ForeignKey(
                        name: "FK_ClientRole_Role_RoleId1",
                        column: x => x.RoleId1,
                        principalSchema: "Security",
                        principalTable: "Role",
                        principalColumn: "ID",
                        onDelete: ReferentialAction.Restrict);
                });

            migrationBuilder.CreateTable(
                name: "RoleClaim",
                schema: "Security",
                columns: table => new
                {
                    ID = table.Column<int>(nullable: false)
                        .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                    ClaimType = table.Column<string>(nullable: true),
                    ClaimValue = table.Column<string>(nullable: true),
                    RoleID = table.Column<string>(maxLength: 450, nullable: false),
                    RoleId1 = table.Column<string>(nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_RoleClaim", x => x.ID);
                    table.ForeignKey(
                        name: "FK_RoleClaim_Role_RoleID",
                        column: x => x.RoleID,
                        principalSchema: "Security",
                        principalTable: "Role",
                        principalColumn: "ID",
                        onDelete: ReferentialAction.Cascade);
                    table.ForeignKey(
                        name: "FK_RoleClaim_Role_RoleId1",
                        column: x => x.RoleId1,
                        principalSchema: "Security",
                        principalTable: "Role",
                        principalColumn: "ID",
                        onDelete: ReferentialAction.Restrict);
                });

            migrationBuilder.CreateIndex(
                name: "EmailIndex",
                schema: "Security",
                table: "Client",
                column: "NormalizedEmail");

            migrationBuilder.CreateIndex(
                name: "UserNameIndex",
                schema: "Security",
                table: "Client",
                column: "NormalizedUserName",
                unique: true);

            migrationBuilder.CreateIndex(
                name: "IX_ClientClaim_ClientID",
                schema: "Security",
                table: "ClientClaim",
                column: "ClientID");

            migrationBuilder.CreateIndex(
                name: "IX_ClientClaim_ClientId1",
                schema: "Security",
                table: "ClientClaim",
                column: "ClientId1");

            migrationBuilder.CreateIndex(
                name: "IX_ClientRole_ClientId1",
                schema: "Security",
                table: "ClientRole",
                column: "ClientId1");

            migrationBuilder.CreateIndex(
                name: "IX_ClientRole_RoleID",
                schema: "Security",
                table: "ClientRole",
                column: "RoleID");

            migrationBuilder.CreateIndex(
                name: "IX_ClientRole_RoleId1",
                schema: "Security",
                table: "ClientRole",
                column: "RoleId1");

            migrationBuilder.CreateIndex(
                name: "IX_ExternalLogin_ClientID",
                schema: "Security",
                table: "ExternalLogin",
                column: "ClientID");

            migrationBuilder.CreateIndex(
                name: "IX_ExternalLogin_ClientId1",
                schema: "Security",
                table: "ExternalLogin",
                column: "ClientId1");

            migrationBuilder.CreateIndex(
                name: "RoleNameIndex",
                schema: "Security",
                table: "Role",
                column: "NormalizedName",
                unique: true);

            migrationBuilder.CreateIndex(
                name: "IX_RoleClaim_RoleID",
                schema: "Security",
                table: "RoleClaim",
                column: "RoleID");

            migrationBuilder.CreateIndex(
                name: "IX_RoleClaim_RoleId1",
                schema: "Security",
                table: "RoleClaim",
                column: "RoleId1");
        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropTable(
                name: "ClientClaim",
                schema: "Security");

            migrationBuilder.DropTable(
                name: "ClientRole",
                schema: "Security");

            migrationBuilder.DropTable(
                name: "ClientToken",
                schema: "Security");

            migrationBuilder.DropTable(
                name: "ExternalLogin",
                schema: "Security");

            migrationBuilder.DropTable(
                name: "RoleClaim",
                schema: "Security");

            migrationBuilder.DropTable(
                name: "Client",
                schema: "Security");

            migrationBuilder.DropTable(
                name: "Role",
                schema: "Security");
        }
    }
}

How about now? Will it work now?

Again yes and no again, we’re getting closer though, final push.

The application ran, we went to register a user, we got prompted to apply the migration, we applied it and it worked, what’s missing?

In our Client* model, we added 2 new fields that are mandatory. Those are FirstName and LastName. This is our **Register method inside the *AccountController.

// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
{
    ViewData["ReturnUrl"] = returnUrl;
    if (ModelState.IsValid)
    {
        var user = new Client { UserName = model.Email, Email = model.Email };
        var result = await _userManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
            await _signInManager.SignInAsync(user, isPersistent: false);
            _logger.LogInformation(3, "User created a new account with password.");
            return RedirectToLocal(returnUrl);
        }
        AddErrors(result);
    }
    return View(model);
}

As you can see this is where our Client is created, since we were losing our patience and we didn’t want to star chaging the register form, we just harcoded a few values like this:

 var user = new Client { UserName = model.Email, Email = model.Email, FirstName = "RandomFirstName", LastName = "RandomLastName"};

Classy right?

And guess what? IT WORKED !!! WOOHOO.

Excuse my enthusiasm, but we were finally through this ordeal, all that was left now is to migrate the rest of the application (this was the hard part), update the register form of course and continue on our way.

Conclusion

I hope you liked our adventures, and I also hope it helped you if you by change encountered this issue.

Thank you and see you next time,

Vlad V.

Leave a comment