Gmail Registration Process using ASP.NET Core MVC

Gmail-Like Registration Process using ASP.NET Core MVC and EF Core

Creating a multi-step registration process similar to Google’s Gmail registration involves several components, including multiple views, models, controllers, services, and client-server interactions. In this article, I will discuss building a robust Gmail-like registration system using ASP.NET Core MVC, Entity Framework (EF) Core, Remote Validation, and Data Annotations. Each step will mirror Gmail’s registration process.

Project Overview

The goal is to create a Gmail-like registration system with the following steps:

  • Step 1: Enter First Name and Last Name
  • Step 2: Enter Date of Birth and Gender
  • Step 3: Choose a Gmail Address
  • Step 4: Create a Strong Password
  • Step 5: Verify Phone Number
  • Step 6: Enter Verification Code
  • Step 7: Add Recovery Email (Optional)
  • Step 8: Review Account Information
  • Step 9: Accept Privacy and Terms
  • Step 10: Create an Account and Show the Dashboard

Each step will have its own view and corresponding backend logic to handle data and validations. Before implementing, let us first look at the pages and flow we will develop as part of our Gmail-like registration system.

Step1: Enter First Name and Last Name

In the first step, the users will be asked to provide their first and last names. Both fields are required and designed to validate user input to ensure no special characters or numbers are included. After the user enters this information and clicks the “Next” button, the application validates the input and progresses to the next step.

Enter First Name and Last Name

Step2: Enter Date of Birth and Gender

In this step, the user is asked to provide their date of birth using separate dropdowns for the month and text boxes for day and year, facilitating easier input and error-checking for valid dates. Additionally, users select their gender from predefined options (e.g., Male, Female, or Other). The system checks for valid dates, and upon validation, users can proceed to the next step by clicking the Next button.

Enter Date of Birth and Gender

Step3: Choose a Gmail Address

This step involves email selection or creation. The system offers suggested email addresses based on the user’s name or allows the user to create a custom email address if preferred. This page includes client-side validation to check the uniqueness of the custom email using Remote Validation, ensuring the email address is not already in use. This will enhance the user’s experience by preventing registration delays due to email duplication.

Choose a Gmail Address

Step4: Create a Strong Password

Users are asked to create a strong password that meets the provided security criteria, including a mix of upper- and lowercase letters, numbers, and special characters, and is at least eight characters long. An optional checkbox is also provided that allows the users to toggle the visibility of their password, enabling them to verify their input before moving forward.

Create a Strong Password

Step5: Verify Phone Number

At this stage, users must enter a valid mobile phone number. The system uses this number to send a One-Time Password (OTP) as part of the verification process, ensuring the phone number is valid through format validation based on the selected country. This step is essential for establishing a secure account recovery method and additional verification.

Verify Phone Number

Step6: Enter Verification Code

Users must enter the code in the provided field after receiving the OTP on their registered mobile number. This process is essential for multi-factor authentication, enhancing account security by verifying the ownership of the mobile phone number provided in the previous step.

Enter Verification Code

Step7: Add Recovery Email (Optional)

Users have the option to provide a recovery email address to reset their account if they forget their password. This step is optional and can be skipped if the user prefers not to provide an additional email address.

Add Recovery Email (Optional)

Step8: Review Account Information

This step provides users with a summary of all the information they have entered before finalizing the registration. It allows them to review and ensure accuracy or return to previous steps to make corrections. This final review process is essential for user satisfaction and accuracy before account creation.

Review Account Information

Step9: Accept Privacy and Terms

Users must check the checkbox to agree to the privacy policy and terms of service before creating an account, ensuring they are informed of their rights and obligations.

Accept Privacy and Terms

Step10: Create an Account and Show the Dashboard

Once the user agrees to the terms and completes all registration steps, the account is created, and the user is redirected to a dashboard. This final page displays the user’s profile information and provides access to various features, indicating successful registration.

Create an Account and Show the Dashboard

These steps provide a comprehensive process for user registration, similar to the flow of Google’s Gmail registration.

Setting Up the ASP.NET Core MVC Project

Create a New ASP.NET Core Project using the Model View Controller template and name it GmailRegistrationDemo. Then, Install the following Necessary Packages. You can install these packages using NuGet Package Manager for solution or by executing the following commands in the Package Manager Console:

  • Install-Package Microsoft.EntityFrameworkCore.SqlServer
  • Install-Package Microsoft.EntityFrameworkCore.Tools
Defining the Data Models

We will create all our Models and View Models inside the Models folder. If it doesn’t exist, please create a folder named Models in the project root directory.

Gender Enum:

Create a class file named Gender.cs within the Models folder, then copy and paste the following code.

namespace GmailRegistrationDemo.Models
{
    // Enum for Gender
    public enum Gender
    {
        Male,
        Female,
        Other
    }
}
User Model

Create a class file named User.cs within the Models folder and then copy and paste the following code. The User class defines the structure of user data stored in the database. It represents a user in the system.

using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
namespace GmailRegistrationDemo.Models
{
    // Represents a user in the system.
    [Index(nameof(Email), Name ="UX_Users_Email_Unque", IsUnique =true)]
    public class User
    {
        [Key]
        public int Id { get; set; } //PK

        [Required]
        public string Email { get; set; }

        [Required]
        public string Password { get; set; }

        [Required]
        [StringLength(50)]
        public string FirstName { get; set; }

        [Required]
        [StringLength(50)]
        public string LastName { get; set; }

        [Required]
        public DateTime DateOfBirth { get; set; }

        [Required]
        public Gender Gender { get; set; }

        [Required]
        public string CountryCode { get; set; }

        [Required]
        [Phone]
        public long PhoneNumber { get; set; }

        [EmailAddress]
        public string? RecoveryEmail { get; set; }

        // Timestamps
        public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
    }
}

Create Registration View Models for Each Step

Let us proceed and create the View Models for each step.

Step 1: ViewModel for the First and Last Name

Create a class file named RegisterStep1ViewModel.cs within the Models folder, and then copy and paste the following code. This View Model Captures the user’s first and last names during the initial registration step.

using System.ComponentModel.DataAnnotations;
namespace GmailRegistrationDemo.Models
{
    // ViewModel for Step 1: Enter First and Last Name.
    public class RegisterStep1ViewModel
    {
        [Required(ErrorMessage = "First Name is required.")]
        [RegularExpression(@"^[a-zA-Z]+$", ErrorMessage = "First name must contain only letters.")]
        [Display(Name = "First Name")]
        public string FirstName { get; set; }

        [Required(ErrorMessage = "Last Name is required.")]
        [RegularExpression(@"^[a-zA-Z]+$", ErrorMessage = "Last name must contain only letters.")]
        [Display(Name = "Last Name")]
        public string LastName { get; set; }
    }
}
Step 2: Date of Birth and Gender View Model

Create a class file named RegisterStep2ViewModel.cs within the Models folder, and then copy and paste the following code. This View Model Collects the user’s date of birth and gender information in the second registration step.

using System.ComponentModel.DataAnnotations;
namespace GmailRegistrationDemo.Models
{
    // ViewModel for Step 2: Enter Date of Birth and Gender.
    public class RegisterStep2ViewModel
    {
        [Required(ErrorMessage = "Month is required.")]
        public string Month { get; set; }

        [Required(ErrorMessage = "Day is required.")]
        [Range(1, 31, ErrorMessage = "Please enter a valid day.")]
        public int? Day { get; set; }

        [Required(ErrorMessage = "Year is required.")]
        [Range(1900, 2100, ErrorMessage = "Please enter a valid year.")]
        public int? Year { get; set; }

        [Required(ErrorMessage = "Gender is required.")]
        public Gender? Gender { get; set; }
    }
}
Step 3: Choose Gmail Address View Model

Create a class file named RegisterStep3ViewModel.cs within the Models folder, and then copy and paste the following code. This View Model Allows the user to choose a unique Gmail address and validate its availability.

using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
namespace GmailRegistrationDemo.Models
{
    // ViewModel for Step 3: Choose Gmail Address.
    public class RegisterStep3ViewModel
    {
        [Required(ErrorMessage = "Gmail address is required.")]
        [EmailAddress(ErrorMessage = "Please enter a valid email address.")]
        public string Email { get; set; }

        // Initialize SuggestedEmails to prevent null references
        public List<string> SuggestedEmails { get; set; } = new List<string>();

        // Selected Suggested Email
        public string? SuggestedEmail { get; set; }

        // Custom Email Input 
        [EmailAddress(ErrorMessage = "Please enter a valid email address.")]
        public string? CustomEmail { get; set; }
    }
}
Step 4: Create Password View Model

Create a class file named RegisterStep4ViewModel.cs within the Models folder, and then copy and paste the following code. This View Model Enables the user to create a strong password with confirmation and toggle visibility.

using System.ComponentModel.DataAnnotations;
namespace GmailRegistrationDemo.Models
{
    // ViewModel for Step 4: Create a Strong Password.
    public class RegisterStep4ViewModel
    {
        [Required(ErrorMessage = "Password is required.")]
        [DataType(DataType.Password)]
        [MinLength(8, ErrorMessage = "Password must be at least 8 characters long.")]
        [RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@#$!%*?&])[A-Za-z\d@#$!%*?&]{8,}$",
            ErrorMessage = "Password must include uppercase, lowercase, number, and special character.")]
        public string Password { get; set; }

        [Required(ErrorMessage = "Confirm Password is required.")]
        [DataType(DataType.Password)]
        [Compare("Password", ErrorMessage = "Passwords do not match.")]
        [Display(Name = "Confirm Password")]
        public string ConfirmPassword { get; set; }

        // Property to toggle password visibility
        [Display(Name = "Show Password")]
        public bool ShowPassword { get; set; }
    }
}
Step 5: Phone Number View Model

Create a class file named RegisterStep5ViewModel.cs within the Models folder, and then copy and paste the following code. This View Model Gathers the user’s phone number for verification purposes.

using Microsoft.AspNetCore.Mvc.Rendering;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;

namespace GmailRegistrationDemo.Models
{
    // ViewModel for Step 5: Enter Phone Number.
    // Implementing IValidatableObject to handle custom validation
    public class RegisterStep5ViewModel : IValidatableObject
    {
        [Required(ErrorMessage = "Country code is required.")]
        public string CountryCode { get; set; }

        [Required(ErrorMessage = "Phone number is required.")]
        [Display(Name = "Phone Number")]
        public long? PhoneNumber { get; set; }

        // List of country codes for the dropdown, typically provided in the view
        public IEnumerable<SelectListItem>? CountryCodes { get; set; }

        // Implementing the Validate method from IValidatableObject for custom validation logic
        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            // If the phone number is not valid for the selected country, return a validation error
            if (!IsValidPhoneNumber(CountryCode, PhoneNumber))
            {
                // Yield a validation result if the phone number is invalid
                yield return new ValidationResult("Please enter a valid phone number.", new[] { "PhoneNumber" });
            }
        }

        // Helper method to validate the phone number based on the country code
        private bool IsValidPhoneNumber(string countryCode, long? phoneNumber)
        {
            // If either the country code or phone number is null/empty, return false (invalid)
            if (string.IsNullOrEmpty(countryCode) || phoneNumber <= 0)
                return false;

            // Define regular expression patterns based on the country code
            string pattern = countryCode switch
            {
                "+1" => @"^[2-9]\d{9}$",      // USA/Canada: 10 digits, starting with digits between 2 and 9
                "+44" => @"^7\d{9}$",         // UK: 10 digits, starting with 7
                "+91" => @"^[6-9]\d{9}$",     // India: 10 digits, starting with 6-9
                _ => @"^\d{7,15}$",           // Default: Allows 7-15 digits for other countries
            };

            // Return whether the phone number matches the regex pattern
            return Regex.IsMatch(Convert.ToString(phoneNumber.Value), pattern);
        }

        // Static method to generate a list of country codes for dropdown selection
        public static List<SelectListItem> GetCountryCodes()
        {
            // Returning a list of countries and their respective country codes
            return new List<SelectListItem>
            {
                 new SelectListItem { Value = "+1", Text = "US (+1)" },  // US with country code +1
                 new SelectListItem { Value = "+1", Text = "CA (+1)" },  // Canada with country code +1
                 new SelectListItem { Value = "+44", Text = "UK (+44)" }, // UK with country code +44
                 new SelectListItem { Value = "+91", Text = "IND (+91)" } // India with country code +91
                // More countries can be added to this list as needed
            };
        }
    }
}
Step 6: Phone Verification View Model

Create a class file named RegisterStep6ViewModel.cs within the Models folder, and then copy and paste the following code. This View Model Receives and validates the verification code sent to the user’s phone.

using System.ComponentModel.DataAnnotations;
namespace GmailRegistrationDemo.Models
{
    // ViewModel for Step 6: Enter Verification Code.
    public class RegisterStep6ViewModel
    {
        [Required(ErrorMessage = "Verification code is required.")]
        [RegularExpression(@"^\d{6}$", ErrorMessage = "Please enter a valid 6-digit code.")]
        [Display(Name = "Verification Code")]
        public string VerificationCode { get; set; }
    }
}
Step 7: Add Recovery Email View Model

Create a class file named RegisterStep7ViewModel.cs within the Models folder, and then copy and paste the following code. This View Model Optionally collects a recovery email address from the user.

using System.ComponentModel.DataAnnotations;
namespace GmailRegistrationDemo.Models
{
    // ViewModel for Step 7: Add Recovery Email.
    public class RegisterStep7ViewModel
    {
        [EmailAddress(ErrorMessage = "Please enter a valid recovery email address.")]
        [Display(Name = "Recovery Email (Optional)")]
        public string? RecoveryEmail { get; set; }
    }
}
Step 8: Review Account Info View Model

Create a class file named RegisterStep8ViewModel.cs within the Models folder, and then copy and paste the following code. This View Model presents a summary of the account information entered for user review before final submission.

namespace GmailRegistrationDemo.Models
{
    // ViewModel for Step 8: Review Account Information.
    public class RegisterStep8ViewModel
    {
        public string FullName { get; set; }
        public string Email { get; set; }
        public string Gender { get; set; }
        public string PhoneNumber { get; set; }
        public string RecoveryEmail { get; set; }
    }
}

Step 9: Privacy and Terms View Model

Create a class file named RegisterStep9ViewModel.cs within the Models folder, and then copy and paste the following code. This View Model Ensures the user agrees to the Privacy Policy and Terms of Service before account creation.

using System.ComponentModel.DataAnnotations;
namespace GmailRegistrationDemo.Models
{
    // ViewModel for Step 9: Privacy and Terms.
    public class RegisterStep9ViewModel
    {
        [Required(ErrorMessage = "You must agree to the terms and privacy policy to proceed.")]
        [Display(Name = "I agree to the Terms and Privacy Policy")]
        public bool Agree { get; set; }
    }
}
Configuring the Database Context

Create a class file named GmailDBContext.cs within the Models folder, and then copy and paste the following code:

using Microsoft.EntityFrameworkCore;
namespace GmailRegistrationDemo.Models
{
    // Database context for the GmailRegistrationDemo application.
    public class GmailDBContext : DbContext
    {
        public GmailDBContext(DbContextOptions<GmailDBContext> options)
            : base(options)
        {
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            // Configure Gender to be stored as string
            modelBuilder.Entity<User>()
                .Property(u => u.Gender)
                .HasConversion<string>()
                .IsRequired();
        }

        // DbSet representing the Users table in the database.
        public DbSet<User> Users { get; set; }
    }
}
Creating Services

Services help encapsulate business logic. We will create services for generating unique email suggestions and handling phone verification.

Generate Email Suggestions Service

Create a Services folder in the project root directory. Add a class file named GenerateEmailSuggestions.cs inside the Services folder, and then copy and paste the following code. The GenerateEmailSuggestions service provides methods to create unique email suggestions by appending random numbers, ensuring uniqueness in the database.

using Microsoft.EntityFrameworkCore;  
using GmailRegistrationDemo.Models;   

namespace GmailRegistrationDemo.Services
{
    // Service for generating unique email suggestions.
    // This service is responsible for generating a list of unique email suggestions
    public class GenerateEmailSuggestions
    {
        private readonly GmailDBContext _context;

        public GenerateEmailSuggestions(GmailDBContext context)
        {
            _context = context;
        }

        // Asynchronously generates a list of unique email suggestions based on the base email provided
        // baseEmail: The base email to generate suggestions from (e.g., pranaya.rout@example.com)
        // count: The number of unique email suggestions to generate (default is 2)
        public async Task<List<string>> GenerateUniqueEmailsAsync(string baseEmail, int count = 2)
        {
            var suggestions = new List<string>();  // List to store email suggestions

            // If the base email is null or doesn't contain the '@' symbol, return an empty list
            if (string.IsNullOrEmpty(baseEmail) || !baseEmail.Contains("@"))
                return suggestions;

            // Split the base email into prefix and domain (e.g., "pranaya.rout" and "example.com")
            string emailPrefix = baseEmail.Split('@')[0];  // Extracts the part before '@'
            string emailDomain = baseEmail.Split('@')[1];  // Extracts the part after '@'

            string suggestion;  // Variable to store the generated suggestion

            // Continue generating suggestions until we have the desired number (specified by 'count')
            while (suggestions.Count < count)
            {
                do
                {
                    // Generate a random suggestion by appending a random number (100-999) to the prefix
                    // pranaya.rout124@example.com
                    suggestion = $"{emailPrefix}{new Random().Next(100, 999)}@{emailDomain}"; 

                    // Use AnyAsync to asynchronously check if the email already exists in the database
                    // Also ensure that the suggestion is not already in the suggestions list
                } while (await _context.Users.AnyAsync(u => u.Email == suggestion) || suggestions.Contains(suggestion));

                // Add the new unique suggestion to the list
                suggestions.Add(suggestion);
            }

            // Return the list of unique email suggestions
            return suggestions;
        }
    }
}
PhoneVerification Service

For simplicity, we will simulate phone verification. The Phone Verification service simulates sending and validating verification codes. In a real-world scenario, integrate with an SMS provider like Twilio to send actual SMS messages. So, create a class file named PhoneVerification.cs within the Services folder and copy and paste the following code.

using System.Collections.Concurrent;
namespace GmailRegistrationDemo.Services
{
    // Service for handling phone verification.
    public class PhoneVerification
    {
        // Simulated storage for verification codes.
        private static ConcurrentDictionary<string, string> _verificationCodes = new ConcurrentDictionary<string, string>();

        // Sends a verification code to the specified phone number.
        // In a real application, integrate with an SMS service.
        // phoneNumber: The phone number to send the code to.
        // returns the generated verification code.
        public async Task<string> SendVerificationCodeAsync(string phoneNumber)
        {
            // Generate a 6-digit code.
            var code = new Random().Next(100000, 999999).ToString();

            // Store the code with the phone number.
            _verificationCodes[phoneNumber] = code;

            //This is the time taken by system to send the SMS to User Mobile Number
            await Task.Delay(TimeSpan.FromMilliseconds(100));

            // Simulate sending SMS by writing to console (replace with SMS API in production).
            Console.WriteLine($"Verification code for {phoneNumber}: {code}");

            return code;
        }

        // Validates the verification code for the specified phone number.
        // phoneNumber: The phone number associated with the code.</param>
        // code: The code to validate
        // returns true if valid; otherwise, false
        public bool ValidateCode(string phoneNumber, string code)
        {
            if (_verificationCodes.TryGetValue(phoneNumber, out var storedCode))
            {
                // return storedCode == code;

                // For testing purpose, you can return true
                return true;
            }
            return false;
        }
    }
}
Implementing Remote Validation

Remote Validation allows server-side validation from the client side using AJAX without reloading the page. We will implement Remote Validation for email uniqueness.

Create the RemoteValidationController

Create an empty MVC Controller named RemoteValidationController within the Controllers folder, then copy and paste the following code. The RemoteValidationController handles AJAX requests to verify the uniqueness of email inputs. If a value is already in use, it returns alternative suggestions generated by the GenerateEmailSuggestions service.

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using GmailRegistrationDemo.Models;
using GmailRegistrationDemo.Services;
using System.ComponentModel.DataAnnotations;

namespace GmailRegistrationDemo.Controllers
{
    // Controller responsible for handling remote validation requests.
    public class RemoteValidationController : Controller
    {
        private readonly GmailDBContext _context;
        private readonly GenerateEmailSuggestions _generateSuggestions;

        public RemoteValidationController(GmailDBContext context, GenerateEmailSuggestions generateSuggestions)
        {
            _context = context;
            _generateSuggestions = generateSuggestions;
        }

        // Checks if the provided email is available. If not, returns suggestions.
        // Email: The email to validate
        // Returns a JSON result indicating availability or suggestions
        [AcceptVerbs("GET", "POST")]
        public async Task<IActionResult> IsEmailAvailable(string CustomEmail)
        {
            // Check if the email is empty
            if (string.IsNullOrEmpty(CustomEmail))
            {
                return Json("Please enter a valid email address.");
            }

            // Validate the email format
            var emailAttribute = new EmailAddressAttribute();
            if (!emailAttribute.IsValid(CustomEmail))
            {
                return Json("Please enter a valid email address.");
            }

            // Check if the email is already in use (case-insensitive)
            var emailExists = await _context.Users.AnyAsync(u => u.Email.ToLower() == CustomEmail.ToLower());

            if (emailExists)
            {
                // Optionally, provide alternative suggestions
                //var suggestedEmails = await _generateSuggestions.GenerateUniqueEmailsAsync(CustomEmail, 3);
                //var suggestions = string.Join(", ", suggestedEmails);
                //return Json($"This email address is already in use. Try one of these: {suggestions}");
                return Json($"This email address is already in use.");
            }

            // If the email is available
            return Json(true);  // Indicates success to jQuery Unobtrusive Validation
        }
    }
}
Apply Remote Attributes in View Model

Please apply the Remote attribute on the CustomEmail Property of the RegisterStep3ViewModel. Please modify RegisterStep3ViewModel.cs class file as follows:

using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
namespace GmailRegistrationDemo.Models
{
    // ViewModel for Step 3: Choose Gmail Address.
    public class RegisterStep3ViewModel
    {
        [Required(ErrorMessage = "Gmail address is required.")]
        [EmailAddress(ErrorMessage = "Please enter a valid email address.")]
        public string Email { get; set; }

        // Initialize SuggestedEmails to prevent null references
        public List<string> SuggestedEmails { get; set; } = new List<string>();

        // Selected Suggested Email
        public string? SuggestedEmail { get; set; }

        // Custom Email Input 
        [EmailAddress(ErrorMessage = "Please enter a valid email address.")]
        [Remote(action: "IsEmailAvailable", controller: "RemoteValidation", ErrorMessage = "Email is already in use.")]
        public string? CustomEmail { get; set; }
    }
}
Creating Registration Controller

We will create a RegistrationController to handle the multi-step registration process, managing the flow from one step to another. So, create an empty MVC Controller named RegistrationController within the Controllers folder and then copy and paste the following code:

using Microsoft.AspNetCore.Mvc;
using GmailRegistrationDemo.Models;
using GmailRegistrationDemo.Services;
using Microsoft.EntityFrameworkCore;

namespace GmailRegistrationDemo.Controllers
{
    // Controller responsible for managing the multi-step registration process.
    public class RegistrationController : Controller
    {
        private readonly GmailDBContext _context;
        private readonly GenerateEmailSuggestions _generateSuggestions;
        private readonly PhoneVerification _phoneVerification;

        public RegistrationController(
            GmailDBContext context,
            GenerateEmailSuggestions generateSuggestions,
            PhoneVerification phoneVerification)
        {
            _context = context;
            _generateSuggestions = generateSuggestions;
            _phoneVerification = phoneVerification;
        }

        /// Displays Step 1: Enter First and Last Name.
        [HttpGet]
        public IActionResult Step1()
        {
            try
            {
                // Retrieve existing first and last names from session, if any
                var model = new RegisterStep1ViewModel
                {
                    FirstName = HttpContext.Session.GetString("FirstName"),
                    LastName = HttpContext.Session.GetString("LastName")
                };

                return View(model);
            }
            catch (Exception ex)
            {
                // Log the exception (implementation depends on logging setup)
                return View("Error");
            }
        }

        // Handles submission of Step 1: First and Last Name.
        [HttpPost]
        public IActionResult Step1(RegisterStep1ViewModel model)
        {
            try
            {
                if (ModelState.IsValid)
                {
                    // Store first and last names in session
                    HttpContext.Session.SetString("FirstName", model.FirstName);
                    HttpContext.Session.SetString("LastName", model.LastName);
                    return RedirectToAction("Step2");
                }

                return View(model);
            }
            catch (Exception ex)
            {
                // Log the exception
                return View("Error");
            }
        }

        // Displays Step 2: Enter Date of Birth and Gender.
        [HttpGet]
        public IActionResult Step2()
        {
            try
            {
                var model = new RegisterStep2ViewModel();

                // Pre-populate Date of Birth from session, if available
                var dateOfBirthString = HttpContext.Session.GetString("DateOfBirth");

                if (!string.IsNullOrEmpty(dateOfBirthString))
                {
                    var dateOfBirth = DateTime.Parse(dateOfBirthString);
                    model.Month = dateOfBirth.ToString("MMMM");
                    model.Day = dateOfBirth.Day;
                    model.Year = dateOfBirth.Year;
                }

                // Pre-populate Gender from session, if available
                var genderString = HttpContext.Session.GetString("Gender");
                if (!string.IsNullOrEmpty(genderString))
                {
                    model.Gender = Enum.Parse<Gender>(genderString);
                }

                return View(model);
            }
            catch (Exception ex)
            {
                // Log the exception
                return View("Error");
            }
        }

        // Handles submission of Step 2: Date of Birth and Gender.
        [HttpPost]
        public IActionResult Step2(RegisterStep2ViewModel model)
        {
            try
            {
                if (ModelState.IsValid)
                {
                    // Combine Month, Day, Year into a single DateOfBirth string
                    var dateOfBirth = $"{model.Month} {model.Day}, {model.Year}";

                    // Store DateOfBirth and Gender in session
                    HttpContext.Session.SetString("DateOfBirth", dateOfBirth);
                    HttpContext.Session.SetString("Gender", model.Gender.ToString());

                    return RedirectToAction("Step3");
                }

                return View(model);
            }
            catch (Exception ex)
            {
                // Log the exception
                return View("Error");
            }
        }

        // Displays Step 3: Choose Gmail Address.
        [HttpGet]
        public async Task<IActionResult> Step3()
        {
            try
            {
                // Retrieve first and last names from session
                var firstName = HttpContext.Session.GetString("FirstName");
                var lastName = HttpContext.Session.GetString("LastName");

                // If first or last name is missing, redirect to Step1
                if (string.IsNullOrEmpty(firstName) || string.IsNullOrEmpty(lastName))
                {
                    return RedirectToAction("Step1");
                }

                // Generate base email (e.g., john.doe@example.com)
                var baseEmail = $"{firstName.ToLower()}.{lastName.ToLower()}@example.com";

                // Generate suggested emails
                // 1: pranaya.rout123@example.com 
                // 2: pranaya.rout456@example.com 
                var suggestedEmails = await _generateSuggestions.GenerateUniqueEmailsAsync(baseEmail, 3);

                // Retrieve the selected email from the session
                var selectedEmail = HttpContext.Session.GetString("Email"); //pranaya.rout@example.com 

                // Determine if the selected email is one of the suggested emails or a custom email
                var model = new RegisterStep3ViewModel
                {
                    SuggestedEmails = suggestedEmails,
                    Email = selectedEmail // This will be used to set the selected email in the view
                };

                // Check if the selected email is one of the suggested emails
                if (suggestedEmails.Contains(selectedEmail))
                {
                    model.SuggestedEmail = selectedEmail; //pranaya.rout123 @example.com
                }
                else if (!string.IsNullOrEmpty(selectedEmail))
                {
                    // If it's a custom email
                    model.SuggestedEmail = "createOwn";
                    model.CustomEmail = selectedEmail;
                }

                return View(model);
            }
            catch (Exception ex)
            {
                // Log the exception
                return View("Error");
            }
        }

        // Handles submission of Step 3: Choose Gmail Address.
        [HttpPost]
        public async Task<IActionResult> Step3(RegisterStep3ViewModel model)
        {
            try
            {
                if (ModelState.IsValid)
                {
                    // Check if the email already exists (server-side validation)
                    var emailExists = await _context.Users.AnyAsync(u => u.Email.ToLower() == model.Email.ToLower());
                    if (emailExists)
                    {
                        // Add model error if email is already taken
                        ModelState.AddModelError("Email", "This email address is already taken. Please choose another one.");

                        // Regenerate email suggestions
                        var firstName = HttpContext.Session.GetString("FirstName");
                        var lastName = HttpContext.Session.GetString("LastName");

                        var baseEmail = $"{firstName?.ToLower()}.{lastName?.ToLower()}@example.com";
                        model.SuggestedEmails = await _generateSuggestions.GenerateUniqueEmailsAsync(baseEmail, 3);

                        return View(model);
                    }

                    // Save the selected email to session
                    HttpContext.Session.SetString("Email", model.Email);

                    // Proceed to the next step
                    return RedirectToAction("Step4");
                }

                // If model state is invalid, regenerate suggestions to display again
                var userFirstName = HttpContext.Session.GetString("FirstName");
                var userLastName = HttpContext.Session.GetString("LastName");

                var baseEmailSuggestion = $"{userFirstName?.ToLower()}.{userLastName?.ToLower()}@gmail.com";
                model.SuggestedEmails = await _generateSuggestions.GenerateUniqueEmailsAsync(baseEmailSuggestion, 3);

                return View(model);
            }
            catch (Exception ex)
            {
                // Log the exception
                return View("Error");
            }
        }

        // Displays Step 4: Create a Strong Password.
        [HttpGet]
        public IActionResult Step4()
        {
            try
            {
                return View();
            }
            catch (Exception ex)
            {
                // Log the exception
                return View("Error");
            }
        }

        // Handles submission of Step 4: Create a Strong Password.
        [HttpPost]
        public IActionResult Step4(RegisterStep4ViewModel model)
        {
            try
            {
                if (ModelState.IsValid)
                {
                    // Store the password in session
                    HttpContext.Session.SetString("Password", model.Password);
                    return RedirectToAction("Step5");
                }

                return View(model);
            }
            catch (Exception ex)
            {
                // Log the exception
                return View("Error");
            }
        }

        // Displays Step 5: Enter Phone Number.
        [HttpGet]
        public IActionResult Step5()
        {
            try
            {
                var model = new RegisterStep5ViewModel();

                // Pre-populate CountryCode and PhoneNumber from session
                model.CountryCode = HttpContext.Session.GetString("CountryCode") ?? string.Empty;
                model.PhoneNumber = HttpContext.Session.GetString("PhoneNumber") != null ? Convert.ToInt64(HttpContext.Session.GetString("PhoneNumber")) : null;

                // Get the list of countries and their codes
                model.CountryCodes = RegisterStep5ViewModel.GetCountryCodes();

                return View(model);
            }
            catch (Exception ex)
            {
                // Log the exception
                return View("Error");
            }
        }

        // Handles submission of Step 5: Enter Phone Number.
        [HttpPost]
        public async Task<IActionResult> Step5(RegisterStep5ViewModel model)
        {
            try
            {
                if (ModelState.IsValid)
                {
                    // Combine country code and phone number
                    var fullPhoneNumber = $"{model.CountryCode}{model.PhoneNumber}";

                    HttpContext.Session.SetString("CountryCode", model.CountryCode);
                    HttpContext.Session.SetString("PhoneNumber", Convert.ToString(model.PhoneNumber.Value)); // Store only the number without country code
                    HttpContext.Session.SetString("FullPhoneNumber", fullPhoneNumber);

                    // Send verification code (simulated)
                    await _phoneVerification.SendVerificationCodeAsync(fullPhoneNumber);

                    return RedirectToAction("Step6");
                }

                // Get the list of countries and their codes in case of validation failure
                model.CountryCodes = RegisterStep5ViewModel.GetCountryCodes();

                return View(model);
            }
            catch (Exception ex)
            {
                // Log the exception
                return View("Error");
            }
        }

        // Displays Step 6: Enter Verification Code.
        [HttpGet]
        public IActionResult Step6()
        {
            try
            {
                return View();
            }
            catch (Exception ex)
            {
                // Log the exception
                return View("Error");
            }
        }

        // Handles submission of Step 6: Enter Verification Code.
        [HttpPost]
        public IActionResult Step6(RegisterStep6ViewModel model)
        {
            try
            {
                if (ModelState.IsValid && model.VerificationCode != null)
                {
                    var fullPhoneNumber = HttpContext.Session.GetString("FullPhoneNumber");
                    
                    // Validate the verification code
                    if (_phoneVerification.ValidateCode(fullPhoneNumber, model.VerificationCode))
                    {
                        return RedirectToAction("Step7");
                    }
                    else
                    {
                        // Add model error if verification code is invalid
                        ModelState.AddModelError("VerificationCode", "Invalid verification code.");
                    }
                }

                return View(model);
            }
            catch (Exception ex)
            {
                // Log the exception
                return View("Error");
            }
        }

        // Displays Step 7: Add Recovery Email.
        [HttpGet]
        public IActionResult Step7()
        {
            try
            {
                // Retrieve existing recovery email from session, if any
                RegisterStep7ViewModel model = new RegisterStep7ViewModel();
                model.RecoveryEmail = HttpContext.Session.GetString("RecoveryEmail");

                return View(model);
            }
            catch (Exception ex)
            {
                // Log the exception
                return View("Error");
            }
        }

        // Handles submission of Step 7: Add Recovery Email.
        [HttpPost]
        public IActionResult Step7(RegisterStep7ViewModel model)
        {
            try
            {
                if (ModelState.IsValid && model.RecoveryEmail != null)
                {
                    // Store the recovery email in session
                    HttpContext.Session.SetString("RecoveryEmail", model.RecoveryEmail);
                    return RedirectToAction("Step8");
                }

                return View(model);
            }
            catch (Exception ex)
            {
                // Log the exception
                return View("Error");
            }
        }

        // Displays Step 8: Review Account Information.
        [HttpGet]
        public IActionResult Step8()
        {
            try
            {
                var model = new RegisterStep8ViewModel
                {
                    FullName = $"{HttpContext.Session.GetString("FirstName")} {HttpContext.Session.GetString("LastName")}",
                    Email = HttpContext.Session.GetString("Email") ?? string.Empty,
                    PhoneNumber = HttpContext.Session.GetString("FullPhoneNumber") ?? string.Empty,
                    Gender = HttpContext.Session.GetString("Gender") ?? string.Empty,
                    RecoveryEmail = HttpContext.Session.GetString("RecoveryEmail") ?? string.Empty
                };

                return View(model);
            }
            catch (Exception ex)
            {
                // Log the exception
                return View("Error");
            }
        }

        // Handles submission of Step 8: Review Account Information.
        [HttpPost]
        public IActionResult Step8Confirm()
        {
            try
            {
                return RedirectToAction("Step9");
            }
            catch (Exception ex)
            {
                // Log the exception
                return View("Error");
            }
        }

        // Displays Step 9: Privacy and Terms.
        [HttpGet]
        public IActionResult Step9()
        {
            try
            {
                return View();
            }
            catch (Exception ex)
            {
                // Log the exception
                return View("Error");
            }
        }

        // Handles submission of Step 9: Privacy and Terms.
        [HttpPost]
        public IActionResult Step9(RegisterStep9ViewModel model)
        {
            try
            {
                if (ModelState.IsValid && model.Agree == true)
                {
                    return RedirectToAction("Step10");
                }

                return View(model);
            }
            catch (Exception ex)
            {
                // Log the exception
                return View("Error");
            }
        }

        // Final Step: Create Account and Show Dashboard.
        [HttpGet]
        public IActionResult Step10()
        {
            try
            {
                // Retrieve all registration data from session
                var firstName = HttpContext.Session.GetString("FirstName");
                var lastName = HttpContext.Session.GetString("LastName");
                var email = HttpContext.Session.GetString("Email");
                var password = HttpContext.Session.GetString("Password");
                var dateOfBirth = HttpContext.Session.GetString("DateOfBirth");
                var gender = HttpContext.Session.GetString("Gender");
                var countryCode = HttpContext.Session.GetString("CountryCode");
                var phoneNumber = HttpContext.Session.GetString("PhoneNumber");
                var recoveryEmail = HttpContext.Session.GetString("RecoveryEmail");

                // Validate that all required data is present
                if (string.IsNullOrEmpty(firstName) || string.IsNullOrEmpty(lastName) ||
                    string.IsNullOrEmpty(email) || string.IsNullOrEmpty(password) ||
                    string.IsNullOrEmpty(dateOfBirth) || string.IsNullOrEmpty(gender) ||
                    string.IsNullOrEmpty(countryCode) || string.IsNullOrEmpty(phoneNumber))
                {
                    // Redirect to Step1 if any required data is missing
                    return RedirectToAction("Step1");
                }

                // Create a new User object with the collected data
                var user = new User
                {
                    FirstName = firstName,
                    LastName = lastName,
                    Email = email,
                    Password = password, // In production, ensure to hash passwords
                    DateOfBirth = DateTime.Parse(dateOfBirth),
                    Gender = Enum.Parse<Gender>(gender),
                    CountryCode = countryCode,
                    PhoneNumber = Convert.ToInt64(phoneNumber),
                    RecoveryEmail = recoveryEmail
                };

                // Save the user to the database
                _context.Users.Add(user);
                _context.SaveChanges();

                // Optionally, clear the session data after successful registration
                HttpContext.Session.Clear();

                // Display the Dashboard view with the user information
                return View("Dashboard", user);
            }
            catch (Exception ex)
            {
                // Log the exception
                return View("Error");
            }
        }
    }
}
Configure the Database Connection String

Next, we will store the connection string in the appsettings.json file. So, add your database connection string in the appsettings.json file as follows:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },

  "AllowedHosts": "*",
  "ConnectionStrings": {
    "EFCoreDBConnection": "Server=LAPTOP-6P5NK25R\\SQLSERVER2022DEV;Database=GmailRegistrationDB;Trusted_Connection=True;TrustServerCertificate=True;"
  }
}

Note: It will use the GmailRegistrationDB data if it exists in the SQL Server; otherwise, it will create and use the GmailRegistrationDB.

Configure Services, DbContext, and Session in Program Class:

Please modify the Program class as follows to register the Services, DbContext, and the connection, as well as enable sessions for our application.

using GmailRegistrationDemo.Models;
using GmailRegistrationDemo.Services;
using Microsoft.EntityFrameworkCore;

namespace GmailRegistrationDemo
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            // Add services to the container.
            builder.Services.AddControllersWithViews();

            //Configure the ConnectionString and DbContext class
            builder.Services.AddDbContext<GmailDBContext>(options =>
            {
                options.UseSqlServer(builder.Configuration.GetConnectionString("EFCoreDBConnection"));
            });

            // Add Session services
            builder.Services.AddDistributedMemoryCache();
            builder.Services.AddSession(options =>
            {
                options.IdleTimeout = TimeSpan.FromMinutes(30); // Set appropriate timeout
                options.Cookie.HttpOnly = true;
                options.Cookie.IsEssential = true;
            });

            // Register Services
            builder.Services.AddScoped<GenerateEmailSuggestions>();
            builder.Services.AddSingleton<PhoneVerification>();

            var app = builder.Build();

            // Configure the HTTP request pipeline.
            if (!app.Environment.IsDevelopment())
            {
                app.UseExceptionHandler("/Home/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseSession(); // Enable Session

            app.UseAuthorization();

            app.MapControllerRoute(
                name: "default",
                pattern: "{controller=Registration}/{action=Step1}/{id?}");

            app.Run();
        }
    }
}
Generating and Applying Database Migration

Next, we need to generate and execute the Migration so that it creates and updates the database schema. So, open the Package Manager Console and Execute the Add-Migration and Update-Database commands as follows.

Gmail Registration Process using ASP.NET Core MVC and Entity Framework Core

With this, the GmailRegistrationDB Database with Users table should be created as shown in the below image:

Gmail Registration Process using ASP.NET Core MVC and Entity Framework Core

Designing the Views

We will create separate views for each registration step. First, create a folder named Registration inside the Views folder where we will create all our cshtml files:

Add a Progress Bar

A progress indicator will be added to show the current step. Create a partial view _ProgressBar.cshtml within the Views/Shared folder to display the registration steps. Once you create the _ProgressBar.cshtml view, please copy and paste the following code:

@{
    var currentStep = ViewBag.CurrentStep ?? 1;
    var totalSteps = 9;
    var progressPercentage = (currentStep / (double)totalSteps) * 100;
}

<div class="progress mb-4" style="height: 30px;">
    <div class="progress-bar" role="progressbar" style="width: @progressPercentage%;" aria-valuenow="@progressPercentage" aria-valuemin="0" aria-valuemax="100">
        Step @currentStep of @totalSteps
    </div>
</div>
Step1.cshtml: Enter First and Last Name View

Create a view file named Step1.cshtml inside the Views/Registration folder and then copy and paste the following code. This View display a form to collect the user’s first and last names.

@model GmailRegistrationDemo.Models.RegisterStep1ViewModel

@{
    ViewData["Title"] = "Step 1: Enter Your Name";
    ViewBag.CurrentStep = 1;
}

<h2 class="mb-4 text-center">@ViewData["Title"]</h2>

@await Html.PartialAsync("_ProgressBar")

<div class="card shadow-sm">
    <div class="card-body">
        <form asp-action="Step1" asp-controller="Registration" method="post">
            <div class="form-group mt-3">
                <label asp-for="FirstName"></label>
                <input asp-for="FirstName" class="form-control" placeholder="First Name" />
                <span asp-validation-for="FirstName" class="text-danger"></span>
            </div>
            <div class="form-group mt-3">
                <label asp-for="LastName"></label>
                <input asp-for="LastName" class="form-control" placeholder="Last Name" />
                <span asp-validation-for="LastName" class="text-danger"></span>
            </div>
            <div class="form-group mt-3 text-end">
                <button type="submit" class="btn btn-primary">Next</button>
            </div>
        </form>
    </div>
</div>

@section Scripts {
    @{
        await Html.RenderPartialAsync("_ValidationScriptsPartial");
    }
}
Step2.cshtml: Enter Date of Birth and Gender View

Create a view file named Step2.cshtml inside the Views/Registration folder and then copy and paste the following code. This View provides inputs for the user’s date of birth and gender selection.

@model GmailRegistrationDemo.Models.RegisterStep2ViewModel

@{
    ViewData["Title"] = "Step 2: Enter Your Date of Birth and Gender";
    ViewBag.CurrentStep = 2;
}

<h2 class="mb-4 text-center">@ViewData["Title"]</h2>

@await Html.PartialAsync("_ProgressBar")

<div class="card shadow-sm">
    <div class="card-body">
        <form asp-action="Step2" asp-controller="Registration" method="post">
            <!-- Row for Month, Day, Year -->
            <div class="row mb-4">
                <!-- Month Field -->
                <div class="form-group col-md-4">
                    <label asp-for="Month" class="form-label"></label>
                    <select asp-for="Month" class="form-select">
                        <option value="">Select Month</option>
                        @foreach (var month in System.Globalization.DateTimeFormatInfo.InvariantInfo.MonthNames.Take(12))
                        {
                            <option value="@month">@month</option>
                        }
                    </select>
                    <span asp-validation-for="Month" class="text-danger"></span>
                </div>

                <!-- Day Field -->
                <div class="form-group col-md-4">
                    <label asp-for="Day" class="form-label"></label>
                    <input asp-for="Day" class="form-control" placeholder="Day" type="number" min="1" max="31"
                           oninput="if(this.value.length > 2) this.value = this.value.slice(0,2)" />
                    <span asp-validation-for="Day" class="text-danger"></span>
                </div>
               
                <!-- Year Field -->
                <div class="form-group col-md-4">
                    <label asp-for="Year" class="form-label"></label>
                    <input asp-for="Year" class="form-control" placeholder="Year" type="number" min="1900" max="2100"
                           oninput="if(this.value.length > 4) this.value = this.value.slice(0,4)" />
                    <span asp-validation-for="Year" class="text-danger"></span>
                </div>
            </div>
            <!-- End of Row for Month, Day, Year -->
            <!-- Gender Field -->
            <div class="form-group mb-4">
                <label asp-for="Gender" class="form-label"></label>
                <select asp-for="Gender" class="form-select">
                    <option value="">Select Gender</option>
                    @foreach (var gender in Enum.GetValues(typeof(Gender)).Cast<Gender>())
                    {
                        <option value="@gender">@gender</option>
                    }
                </select>
                <span asp-validation-for="Gender" class="text-danger"></span>
            </div>

            <!-- Back and Next Buttons -->
            <div class="form-group text-end">
                <a asp-action="Step1" class="btn btn-secondary">Back</a>
                <button type="submit" class="btn btn-primary px-4">Next</button>
            </div>
        </form>
    </div>
</div>

@section Scripts {
    @{
        await Html.RenderPartialAsync("_ValidationScriptsPartial");
    }
}
Step3.cshtml: Choose Gmail Address View

Create a view file named Step3.cshtml inside the Views/Registration folder and then copy and paste the following code. This View allow the user to enter and validate their desired Gmail address.

@model GmailRegistrationDemo.Models.RegisterStep3ViewModel

@{
    ViewData["Title"] = "Step 3: Choose Your Gmail Address";
    ViewBag.CurrentStep = 3;

    // Determine whether to display the custom email input based on the selected option
    var displayStyle = Model.SuggestedEmail == "createOwn" ? "block" : "none";
}

<h2 class="mb-4 text-center">@ViewData["Title"]</h2>

@await Html.PartialAsync("_ProgressBar")

<div class="card shadow-sm">
    <div class="card-body">
        <!-- Form to select or create a Gmail address -->
        <form asp-action="Step3" asp-controller="Registration" method="post">
            <!-- Email Selection Section -->
            <div class="form-group mb-4">
                <label class="form-label">Pick a Gmail address or create your own:</label>

                <!-- Suggested Emails as Radio Buttons -->
                <div class="form-check">
                    @foreach (var email in Model.SuggestedEmails)
                    {
                        // Check if the current email is the one selected by the user
                        if (Model.SuggestedEmail == email)
                        {
                        // Render the radio button as checked
                            <input class="form-check-input" type="radio" name="SuggestedEmail" id="email_@email" value="@email" checked="checked" />
                        }
                        else
                        {
                        // Render the radio button unchecked
                            <input class="form-check-input" type="radio" name="SuggestedEmail" id="email_@email" value="@email" />
                        }
                        // Label for the radio button
                        <label class="form-check-label" for="email_@email">
                            @email
                        </label>
                        <br />
                    }
                </div>

                <!-- Option to Create Own Email -->
                <div class="form-check mt-2">
                    @if (Model.SuggestedEmail == "createOwn")
                    {
                        @* If "Create your own" is selected, render the radio button as checked *@
                        <input class="form-check-input" type="radio" name="SuggestedEmail" id="createOwn" value="createOwn" checked>
                    }
                    else
                    {
                        @* Render the radio button unchecked *@
                        <input class="form-check-input" type="radio" name="SuggestedEmail" id="createOwn" value="createOwn">
                    }
                    <label class="form-check-label" for="createOwn">
                        Create your own Gmail address
                    </label>
                </div>

                <!-- Custom Email Input Field -->
                <div class="mt-3" id="customEmailDiv" style="display:@displayStyle;">
                    <label for="CustomEmail" class="form-label">Your Custom Gmail Address:</label>
                    <!-- Input for custom email address -->
                    <input id="CustomEmail" asp-for="CustomEmail" class="form-control" placeholder="Enter your desired Gmail address" type="email" />
                    <!-- Validation message for custom email -->
                    <span asp-validation-for="CustomEmail" class="text-danger"></span>
                </div>

                <!-- Hidden Field to Store the Final Email Selection -->
                <input type="hidden" id="Email" name="Email" value="@Model.Email" />
                <!-- Validation message for the Email field -->
                <span asp-validation-for="Email" class="text-danger"></span>

            </div>

            <!-- Navigation Buttons -->
            <div class="form-group mt-3 text-end">
                <!-- Back button to navigate to the previous step -->
                <a asp-action="Step2" class="btn btn-secondary">Back</a>
                <!-- Submit button to proceed to the next step -->
                <button type="submit" class="btn btn-primary">Next</button>
            </div>
        </form>
    </div>
</div>

@section Scripts {
    @{
        // Include validation scripts
        await Html.RenderPartialAsync("_ValidationScriptsPartial");
    }
    <script>
        $(document).ready(function () {
            // Function to show or hide the custom email input based on the initial selection
            if ($('input[name="SuggestedEmail"]:checked').val() === 'createOwn') {
                $('#customEmailDiv').show(); // Show the custom email input
            } else {
                $('#customEmailDiv').hide(); // Hide the custom email input
            }

            // Event handler for changes to the SuggestedEmail radio buttons
            $('input[name="SuggestedEmail"]').change(function () {
                if ($('#createOwn').is(':checked')) {
                    // If "Create your own" is selected
                    $('#customEmailDiv').show(); // Show the custom email input
                    $('#CustomEmail').attr('required', true); // Make the custom email input required
                } else {
                    // If a suggested email is selected
                    $('#customEmailDiv').hide(); // Hide the custom email input
                    $('#CustomEmail').removeAttr('required'); // Remove the required attribute
                    $('#CustomEmail').val(''); // Clear the custom email input
                }

                updateEmailField(); // Update the hidden Email field
                clearValidationErrors(); // Clear any validation errors
            });

            // Function to update the hidden Email field based on the selection
            function updateEmailField() {
                var selectedValue = $('input[name="SuggestedEmail"]:checked').val();
                if (selectedValue === 'createOwn') {
                    var customEmail = $('#CustomEmail').val();
                    $('#Email').val(customEmail); // Set the hidden Email field to the custom email
                } else {
                    $('#Email').val(selectedValue); // Set the hidden Email field to the selected suggested email
                }
            }

            // Event handler for input changes in the custom email field
            $('#CustomEmail').on('input', function () {
                updateEmailField(); // Update the hidden Email field
                clearValidationErrors(); // Clear validation errors when typing
            });
        });
    </script>
}
Step4.cshtml: Create a Strong Password View

Create a view file named Step4.cshtml inside the Views/Registration folder and then copy and paste the following code. This View enable the user to create and confirm a strong password, with an option to show/hide the password.

@model GmailRegistrationDemo.Models.RegisterStep4ViewModel

@{
    ViewData["Title"] = "Step 4: Create a Strong Password";
    ViewBag.CurrentStep = 4;
}

<h2 class="mb-4 text-center">@ViewData["Title"]</h2>

@await Html.PartialAsync("_ProgressBar")

<div class="card shadow-sm">
    <div class="card-body">
        <form asp-action="Step4" asp-controller="Registration" method="post">
            <div class="form-group">
                <label asp-for="Password"></label>
                <input asp-for="Password" class="form-control" placeholder="Create your password" />
                <span asp-validation-for="Password" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="ConfirmPassword"></label>
                <input asp-for="ConfirmPassword" class="form-control" placeholder="Confirm your password" />
                <span asp-validation-for="ConfirmPassword" class="text-danger"></span>
            </div>
            <div class="form-group form-check">
                <input asp-for="ShowPassword" id="showPassword" class="form-check-input" type="checkbox" />
                <label asp-for="ShowPassword" class="form-check-label">Show Password</label>
            </div>
            <!-- Back and Next Buttons -->
            <div class="form-group mt-3 text-end">
                <a asp-action="Step3" class="btn btn-secondary">Back</a>
                <button type="submit" class="btn btn-primary">Next</button>
            </div>
        </form>
    </div>
</div>

@section Scripts {
    @{
        await Html.RenderPartialAsync("_ValidationScriptsPartial");
    }
    <script>
        $(document).ready(function () {
            // Toggle password visibility
            $('#showPassword').change(function () {
                const type = $(this).is(':checked') ? 'text' : 'password';
                $('input[name="Password"]').attr('type', type);
                $('input[name="ConfirmPassword"]').attr('type', type);
            });
        });
    </script>
}
Step5.cshtml: Enter Phone Number View

Create a view file named Step5.cshtml inside the Views/Registration folder and then copy and paste the following code. This View collect the user’s phone number and inform them about SMS verification.

@model GmailRegistrationDemo.Models.RegisterStep5ViewModel

@{
    ViewData["Title"] = "Step 5: Enter Your Phone Number";
    ViewBag.CurrentStep = 5;
}

<h2 class="mb-4 text-center">@ViewData["Title"]</h2>

@await Html.PartialAsync("_ProgressBar")

<div class="card shadow-sm">
    <div class="card-body">
        <p>Google will verify this number via SMS (charges may apply).</p>
        <form asp-action="Step5" asp-controller="Registration" method="post">
            <div class="form-group row">
                <!-- Country Code -->
                <div class="col-md-4">
                    <label asp-for="CountryCode"></label>
                    <select asp-for="CountryCode" asp-items="Model.CountryCodes" class="form-select">
                        <option value="">Select Country</option>
                    </select>
                    <span asp-validation-for="CountryCode" class="text-danger"></span>
                </div>
                <!-- Phone Number -->
                <div class="col-md-8">
                    <label asp-for="PhoneNumber"></label>
                    <input asp-for="PhoneNumber" class="form-control" placeholder="Enter your phone number" />
                    <span asp-validation-for="PhoneNumber" class="text-danger"></span>
                </div>
            </div>

            <!-- Back and Next Buttons -->
            <div class="form-group mt-3 text-end">
                <a asp-action="Step4" class="btn btn-secondary">Back</a>
                <button type="submit" class="btn btn-primary">Next</button>
            </div>
        </form>
    </div>
</div>

@section Scripts {
    @{
        await Html.RenderPartialAsync("_ValidationScriptsPartial");
    }
}
Step6.cshtml: Phone Verification Code View

Create a view file named Step6.cshtml inside the Views/Registration folder and then copy and paste the following code. This View provide a field for the user to enter the received verification code.

@model GmailRegistrationDemo.Models.RegisterStep6ViewModel

@{
    ViewData["Title"] = "Step 6: Enter Verification Code";
    ViewBag.CurrentStep = 6;
}

<h2 class="mb-4 text-center">@ViewData["Title"]</h2>

@await Html.PartialAsync("_ProgressBar")

<div class="card shadow-sm">
    <div class="card-body">
        <p>Enter the 6-digit verification code sent to your phone.</p>
        <form asp-action="Step6" asp-controller="Registration" method="post">
            <div class="form-group">
                <label asp-for="VerificationCode"></label>
                <input asp-for="VerificationCode" maxlength="6" class="form-control" placeholder="Enter verification code" />
                <span asp-validation-for="VerificationCode" class="text-danger"></span>
            </div>

            <!-- Back and Next Buttons -->
            <div class="form-group mt-3 text-end">
                <a asp-action="Step5" asp-controller="Registration" class="btn btn-secondary">Back</a>
                <button type="submit" class="btn btn-primary">Verify</button>
            </div>
        </form>
    </div>
</div>

@section Scripts {
    @{
        await Html.RenderPartialAsync("_ValidationScriptsPartial");
    }
}
Step7.cshtml: Add Recovery Email View

Create a view file named Step7.cshtml inside the Views/Registration folder and then copy and paste the following code. This View optionally allows the user to add a recovery email address for account security. Users can skip adding a recovery email by clicking the Skip button, which redirects to Step 8.

@model GmailRegistrationDemo.Models.RegisterStep7ViewModel

@{
    ViewData["Title"] = "Step 7: Add Recovery Email";
    ViewBag.CurrentStep = 7;
}

<h2 class="mb-4 text-center">@ViewData["Title"]</h2>

@await Html.PartialAsync("_ProgressBar")

<div class="card shadow-sm">
    <div class="card-body">
        <p>The address where Google can contact you if there’s unusual activity in your account or if you get locked out.</p>
        <form asp-action="Step7" asp-controller="Registration" method="post">
            <div class="form-group">
                <label asp-for="RecoveryEmail"></label>
                <input asp-for="RecoveryEmail" class="form-control" placeholder="Enter recovery email (optional)" />
                <span asp-validation-for="RecoveryEmail" class="text-danger"></span>
            </div>

            <!-- Back, Next and Skip button Buttons -->
            <a asp-action="Step6" asp-controller="Registration" class="btn btn-secondary">Back</a>
            <button type="submit" class="btn btn-primary">Next</button>
            <a asp-action="Step8" asp-controller="Registration" class="btn btn-secondary">Skip</a>
        </form>
    </div>
</div>

@section Scripts {
    @{
        await Html.RenderPartialAsync("_ValidationScriptsPartial");
    }
}
Step8.cshtml: Review Account Information

Create a view file named Step8.cshtml inside the Views/Registration folder and then copy and paste the following code. This View present a summary of all entered information for the user to review before finalizing registration.

@model GmailRegistrationDemo.Models.RegisterStep8ViewModel

@{
    ViewData["Title"] = "Step 8: Review Your Account Info";
    ViewBag.CurrentStep = 8;
}

<h2 class="mb-4 text-center">@ViewData["Title"]</h2>

@await Html.PartialAsync("_ProgressBar")

<div class="card shadow-sm">
    <div class="card-body">
        <form asp-action="Step8Confirm" asp-controller="Registration" method="post">
            <div class="row mb-3">
                <div class="col-md-4 font-weight-bold">Full Name:</div>
                <div class="col-md-8">@Model.FullName</div>
            </div>
            <div class="row mb-3">
                <div class="col-md-4 font-weight-bold">Gmail Address:</div>
                <div class="col-md-8">@Model.Email</div>
            </div>
            <div class="row mb-3">
                <div class="col-md-4 font-weight-bold">Phone Number:</div>
                <div class="col-md-8">@Model.PhoneNumber</div>
            </div>
            <div class="row mb-3">
                <div class="col-md-4 font-weight-bold">Gender:</div>
                <div class="col-md-8">@Model.Gender</div>
            </div>
            @if (!string.IsNullOrEmpty(Model.RecoveryEmail))
            {
                <div class="row mb-3">
                    <div class="col-md-4 font-weight-bold">Recovery Email:</div>
                    <div class="col-md-8">@Model.RecoveryEmail</div>
                </div>
            }
            <div class="form-group mt-3 text-end">
                <a asp-action="Step7" asp-controller="Registration" class="btn btn-secondary">Back</a>
                <button type="submit" class="btn btn-primary">Next</button>
            </div>
        </form>
    </div>
</div>
Step9.cshtml: Privacy and Terms

Create a view file named Step9.cshtml inside the Views/Registration folder and then copy and paste the following code. This View displays the Privacy Policy and Terms of Service, requiring the user to agree before proceeding. Users must agree to the Privacy and Terms to proceed.

@model GmailRegistrationDemo.Models.RegisterStep9ViewModel

@{
    ViewData["Title"] = "Step 9: Privacy and Terms";
    ViewBag.CurrentStep = 9;
}

<h2 class="mb-4 text-center">@ViewData["Title"]</h2>

@await Html.PartialAsync("_ProgressBar")

<div class="card shadow-sm">
    <div class="card-body">

        <div class="privacy-terms">
            <p>To create a Google Account, you’ll need to agree to the Terms of Service below.</p>
            <p>[Insert Privacy Policy and Terms of Service Content Here]</p>
        </div>

        <form asp-action="Step9" asp-controller="Registration" method="post">
            <div class="form-group form-check">
                <input asp-for="Agree" id="agreeCheckbox" class="form-check-input" type="checkbox" />
                <label asp-for="Agree" class="form-check-label"></label>
                <span asp-validation-for="Agree" class="text-danger"></span>
            </div>
            <div class="form-group mt-3 text-end">
                <button type="submit" id="agreeButton" disabled class="btn btn-primary">I Agree</button>
            </div>
        </form>
    </div>
</div>

@section Scripts {
    @{
        await Html.RenderPartialAsync("_ValidationScriptsPartial");
    }
    <script>
        $(document).ready(function () {
            // Toggle button state based on checkbox
            $('#agreeCheckbox').change(function () {
                if ($(this).is(':checked')) {
                    $('#agreeButton').prop('disabled', false);
                } else {
                    $('#agreeButton').prop('disabled', true);
                }
            });
        });
    </script>
}
Dashboard.cshtml: Registration Success and Dashboard

Create a view file named Dashboard.cshtml inside the Views/Registration folder, and then copy and paste the following code. This View confirms successful registration and displays the user’s account details on a dashboard.

@model GmailRegistrationDemo.Models.User

@{
    ViewData["Title"] = "Dashboard";
}

<div class="card shadow-sm">
    <div class="card-body">
        <div class="text-center mb-4">
            <h3 class="mb-2">Welcome, @Model.FirstName!</h3>
            <p class="text-success">Your Gmail account has been created successfully.</p>
        </div>

        <h4 class="mb-3">Your Account Details:</h4>
        <ul class="list-group mb-4">
            <li class="list-group-item d-flex justify-content-between align-items-center">
                <strong>Full Name:</strong>
                <span>@Model.FirstName @Model.LastName</span>
            </li>
            <li class="list-group-item d-flex justify-content-between align-items-center">
                <strong>Email:</strong>
                <span>@Model.Email</span>
            </li>
            <li class="list-group-item d-flex justify-content-between align-items-center">
                <strong>Phone Number:</strong>
                <span>@Model.CountryCode@Model.PhoneNumber</span>
            </li>
            @if (!string.IsNullOrEmpty(Model.RecoveryEmail))
            {
                <li class="list-group-item d-flex justify-content-between align-items-center">
                    <strong>Recovery Email:</strong>
                    <span>@Model.RecoveryEmail</span>
                </li>
            }
            <li class="list-group-item d-flex justify-content-between align-items-center">
                <strong>Date of Birth:</strong>
                <span>@Model.DateOfBirth.ToString("MMMM dd, yyyy")</span>
            </li>
            <li class="list-group-item d-flex justify-content-between align-items-center">
                <strong>Gender:</strong>
                <span>@Model.Gender</span>
            </li>
        </ul>
    </div>
</div>
Error View

Create an Error.cshtml view within the Views/Shared folder to display user-friendly error messages. Once you create the View, copy and paste the following code:

@{
    ViewData["Title"] = "Error";
}
<h1 class="text-danger">An error occurred while processing your request.</h1>
<p>Please try again later or contact support if the problem persists.</p>
_ValidationScriptsPartial Partial view:

Create a partial view named _ValidationScriptsPartial.cshtml within the Views/Shared folder and then copy and paste the following code:

<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
Modifying _Layout.cshtml file:

Finally, modify the _Layout.cshtml file as follows:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - GmailRegistrationDemo</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
    <link rel="stylesheet" href="~/GmailRegistrationDemo.styles.css" asp-append-version="true" />
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
            <div class="container-fluid">
                <a class="navbar-brand" href="/">Gmail Registration</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="collapse navbar-collapse" id="navbarNav">
                    <ul class="navbar-nav">
                        <li class="nav-item">
                            <a class="nav-link" asp-controller="Home" asp-action="Index">Home</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" asp-controller="Registration" asp-action="Step1">Register</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>

    <div class="container my-5">
        <div class="row justify-content-center">
            <div class="col-md-8">
                @RenderBody()
            </div>
        </div>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2024 - GmailRegistrationDemo - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
        </div>
    </footer>
    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>
    <!-- Bootstrap 5 JS Bundle with Popper -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
    @await RenderSectionAsync("Scripts", required: false)
</body>
</html>

That’s it. We are done with our implementation. Now, run the application and test the functionalities, and it should work as expected.

That’s it. We have completed our Gmail Like Registration Process implementation using ASP.NET Core MVC and Entity Framework Core. Run the application and test the functionalities; it should work as expected. I hope this will give you a very good idea of how the Gmail Registration Process is developed and Implemented using ASP.NET Core MVC and EF Core.

In the next article, I will discuss developing a Blog Management Application using ASP.NET Core MVC and Entity Framework Core. I hope you enjoy this in-depth article on Gmail Like Registration Process using ASP.NET Core MVC and Entity Framework Core. Please give your valuable feedback about this Gmail Registration using ASP.NET Core MVC article and tell me how we can improve this project.

Leave a Reply

Your email address will not be published. Required fields are marked *