Migrate Microsoft LightSwitch application

In this blog post I will show you how to migrate a Microsoft LightSwitch application with Radzen.

1. Create а new Microsoft LightSwitch application that displays the Products table from the Northwind database:

2. Enable Forms authentication for application access control:

3. Publish the app to IIS using an admin user as the application administrator:

This will create the ASP.NET membership tables. LightSwitch uses those to store users, passwords, roles, etc:

4. Create a new database as a copy of the existing one used in the LightSwitch application:

5. Create a new Radzen application that displays the Products table:

6. Enable security with email confirmation:

7. Deploy the application to IIS from Radzen:

We now have the ASP.NET Core 2.0 Identity tables in our new database:

and we can use the following SQL script to migrate our users, passwords, roles, etc. to the new database:

-- THIS SCRIPT NEEDS TO RUN FROM THE CONTEXT OF THE MEMBERSHIP DB
BEGIN TRANSACTION MigrateUsersAndRoles
USE Northwind

-- INSERT USERS
INSERT INTO NorthwindNew.dbo.AspNetUsers
          (Id,
           UserName,
           NormalizedUserName,
           PasswordHash,
           SecurityStamp,
           EmailConfirmed,
           PhoneNumber,
           PhoneNumberConfirmed,
           TwoFactorEnabled,
           LockoutEnd,
           LockoutEnabled,
           AccessFailedCount,
           Email,
           NormalizedEmail)
SELECT aspnet_Users.UserId,
     aspnet_Users.UserName,
     -- The NormalizedUserName value is upper case in ASP.NET Core Identity
     UPPER(aspnet_Users.UserName),
     -- Creates an empty password since passwords don't map between the 2 schemas
     '',
     /*
      The SecurityStamp token is used to verify the state of an account and
      is subject to change at any time. It should be initialized as a new ID.
     */
     NewID(),
     /*
      EmailConfirmed is set when a new user is created and confirmed via email.
      Users must have this set during migration to reset passwords.
     */
     1,
     aspnet_Users.MobileAlias,
     CASE
       WHEN aspnet_Users.MobileAlias IS NULL THEN 0
       ELSE 1
     END,
     -- 2FA likely wasn't setup in Membership for users, so setting as false.
     0,
     CASE
       -- Setting lockout date to time in the future (1,000 years)
       WHEN aspnet_Membership.IsLockedOut = 1 THEN Dateadd(year, 1000,
                                                   Sysutcdatetime())
       ELSE NULL
     END,
     aspnet_Membership.IsLockedOut,
     /*
      AccessFailedAccount is used to track failed logins. This is stored in
      Membership in multiple columns. Setting to 0 arbitrarily.
     */
     0,
     aspnet_Membership.Email,
     -- The NormalizedEmail value is upper case in ASP.NET Core Identity
     UPPER(aspnet_Membership.Email)
FROM   aspnet_Users
     LEFT OUTER JOIN aspnet_Membership
                  ON aspnet_Membership.ApplicationId =
                     aspnet_Users.ApplicationId
                     AND aspnet_Users.UserId = aspnet_Membership.UserId
     LEFT OUTER JOIN NorthwindNew.dbo.AspNetUsers
                  ON aspnet_Membership.UserId = AspNetUsers.Id
WHERE  AspNetUsers.Id IS NULL

-- INSERT ROLES
INSERT INTO NorthwindNew.dbo.AspNetRoles(Id, Name)
SELECT RoleId, RoleName
FROM aspnet_Roles;

-- INSERT USER ROLES
INSERT INTO NorthwindNew.dbo.AspNetUserRoles(UserId, RoleId)
SELECT UserId, RoleId
FROM aspnet_UsersInRoles;

IF @@ERROR <> 0
BEGIN
  ROLLBACK TRANSACTION MigrateUsersAndRoles
  RETURN
END

COMMIT TRANSACTION MigrateUsersAndRoles

We are done!

Now we only need to reset the password from the deployed Radzen application. Microsoft LightSwitch apps needed either a desktop app or custom pages for user management. For the purpose of this blog post I’ll take the shortcut and I will enter my admin user email directly in the database:

Now we can reset the password from the deployed Radzen application:

You will receive email with a link to generate new password and you can use it to login:

If you don’t want your users to reset their passwords you can use a custom password hasher which makes ASP.NET Membership (LightSwitch) passwords work with ASP.NET Core Identity (Radzen):

We need first to copy both Password and PasswordSalt from old aspnet_Membership table:

-- THIS SCRIPT NEEDS TO RUN FROM THE CONTEXT OF THE MEMBERSHIP DB
BEGIN TRANSACTION MigrateUsersAndRoles
USE Northwind

-- INSERT USERS
INSERT INTO NorthwindNew.dbo.AspNetUsers
          (Id,
           UserName,
           NormalizedUserName,
           PasswordHash,
           SecurityStamp,
           EmailConfirmed,
           PhoneNumber,
           PhoneNumberConfirmed,
           TwoFactorEnabled,
           LockoutEnd,
           LockoutEnabled,
           AccessFailedCount,
           Email,
           NormalizedEmail)
SELECT aspnet_Users.UserId,
     aspnet_Users.UserName,
     -- The NormalizedUserName value is upper case in ASP.NET Core Identity
     UPPER(aspnet_Users.UserName),
     -- Copy both Password and PasswordSalt
     aspnet_Membership.Password + ':' + aspnet_Membership.PasswordSalt,
     /*
      The SecurityStamp token is used to verify the state of an account and
      is subject to change at any time. It should be initialized as a new ID.
     */
     NewID(),
     /*
      EmailConfirmed is set when a new user is created and confirmed via email.
      Users must have this set during migration to reset passwords.
     */
     1,
     aspnet_Users.MobileAlias,
     CASE
       WHEN aspnet_Users.MobileAlias IS NULL THEN 0
       ELSE 1
     END,
     -- 2FA likely wasn't setup in Membership for users, so setting as false.
     0,
     CASE
       -- Setting lockout date to time in the future (1,000 years)
       WHEN aspnet_Membership.IsLockedOut = 1 THEN Dateadd(year, 1000,
                                                   Sysutcdatetime())
       ELSE NULL
     END,
     aspnet_Membership.IsLockedOut,
     /*
      AccessFailedAccount is used to track failed logins. This is stored in
      Membership in multiple columns. Setting to 0 arbitrarily.
     */
     0,
     aspnet_Membership.Email,
     -- The NormalizedEmail value is upper case in ASP.NET Core Identity
     UPPER(aspnet_Membership.Email)
FROM   aspnet_Users
     LEFT OUTER JOIN aspnet_Membership
                  ON aspnet_Membership.ApplicationId =
                     aspnet_Users.ApplicationId
                     AND aspnet_Users.UserId = aspnet_Membership.UserId
     LEFT OUTER JOIN NorthwindNew.dbo.AspNetUsers
                  ON aspnet_Membership.UserId = AspNetUsers.Id
WHERE  AspNetUsers.Id IS NULL

-- INSERT ROLES
INSERT INTO NorthwindNew.dbo.AspNetRoles(Id, Name)
SELECT RoleId, RoleName
FROM aspnet_Roles;

-- INSERT USER ROLES
INSERT INTO NorthwindNew.dbo.AspNetUserRoles(UserId, RoleId)
SELECT UserId, RoleId
FROM aspnet_UsersInRoles;

IF @@ERROR <> 0
BEGIN
  ROLLBACK TRANSACTION MigrateUsersAndRoles
  RETURN
END

COMMIT TRANSACTION MigrateUsersAndRoles

Just create a server\Startup.Custom.cs file in the Radzen application with the following content:

namespace NorthwindNew
{
    public partial class Startup
    {
        partial void OnConfigureServices(IServiceCollection services)
        {
            services.AddScoped<IPasswordHasher<ApplicationUser>, CustomPasswordHasher>();
        }
    }

    public class CustomPasswordHasher : PasswordHasher<ApplicationUser>
    {
        public static string GenerateHash(string providedPassword, byte[] saltAsByteArray)
        {
            var sha = new System.Security.Cryptography.SHA1CryptoServiceProvider();

            byte[] p1 = saltAsByteArray;
            byte[] p2 = System.Text.Encoding.Unicode.GetBytes(providedPassword);

            byte[] data = new byte[p1.Length + p2.Length];

            p1.CopyTo(data, 0);
            p2.CopyTo(data, p1.Length);

            return Convert.ToBase64String(sha.ComputeHash(data));
        }

        public override PasswordVerificationResult VerifyHashedPassword(ApplicationUser user, string hashedPasswordAndSalt,
           string providedPassword)
        {
            if (hashedPasswordAndSalt.IndexOf(":") != -1)
            {
                var hashedPassword = hashedPasswordAndSalt.Split(':')[0];
                var salt = hashedPasswordAndSalt.Split(':')[1];

                var hashedProvidedPassword = GenerateHash(providedPassword, Convert.FromBase64String(salt));

                return hashedPassword == hashedProvidedPassword
                    ? PasswordVerificationResult.SuccessRehashNeeded
                    : base.VerifyHashedPassword(user, hashedPassword, providedPassword);
            }
            else
            {
                return base.VerifyHashedPassword(user, hashedPasswordAndSalt, providedPassword);
            }
        }
    }
}

Enjoy!

Leverage Radzen on LinkedIn

Yes, we are on LinkedIn and you should follow us!

Now, you have the opportunity to showcase your expertise by adding Radzen Blazor Studio and Radzen Blazor Components as skills to your LinkedIn profile. Present your skills to employers, collaborators, and clients.

All you have to do is go to our Products page on LinkedIn and click the + Add as skill button.

by Vladimir Enchev

Radzen applications on a mobile device

Radzen applications are responsive by default which means that they work on a variety of devices - desktops, laptops, tablets or phones.
Read more