Food Ordering System Part 14 | How to Build a User Profile Page in ASP.NET Core MVC

 


The Profile GET action is the engine that retrieves a customer's personal details from the database and displays them in a professional, editable dashboard. This step is critical for any ASP.NET Core MVC application that requires user account management.

Step-by-Step Code Explanation

  1. Session Security Check: The code first looks for a UserId in the HttpContext.Session. If no ID is found, it means the user isn't logged in, so it securely redirects them to the Login page.

  2. Database Retrieval: Using _context.Users.Find(userId), the system queries your SQL database to find the specific record matching the logged-in user. If the user doesn't exist in the database, it returns a NotFound() error.

  3. ViewModel Mapping: We don't pass the database "User" model directly to the view for security reasons. Instead, we map the data to a ProfileViewModel. This object carries the Username, Email, FullName, Address, and Phone to the frontend.

  4. View Delivery: Finally, the populated model is sent to the Profile.cshtml view, where we will build our two-column layout: the update form on the left and the user status card (with "My Orders" and "Change Password" buttons) on the right.

ACCOUNTCONTROLLER.CS (PROFILE GET)
[HttpGet]
public IActionResult Profile()
{
    var userId = HttpContext.Session.GetInt32("UserId");
    if (userId == null) return RedirectToAction("Login");

    var user = _context.Users.Find(userId);
    if (user == null) return NotFound();

    var model = new ProfileViewModel
    {
        Id = user.Id,
        Username = user.Username,
        Email = user.Email,
        FullName = user.FullName,
        Address = user.Address,
        Phone = user.Phone
    };

    return View(model);
}

The ProfileViewModel is a critical component because it acts as the data carrier between your SQL database and the user interface. By using a ViewModel instead of your raw database entity, you ensure that your application remains secure and follows the MVC Best Practices.

Step-by-Step Code Explanation

  1. public int Id { get; set; }: This property stores the unique primary key of the user. We need this "hidden" ID so that when the user clicks "Save," the database knows exactly which record to update.

  2. [Display(Name = "Username")]: This attribute tells ASP.NET Core exactly what label to show in the UI. It makes your code cleaner by separating the variable name from the front-facing text.

  3. [Required] and [EmailAddress]: These are Data Annotations. They provide instant server-side validation. The [Required] tag ensures the email field isn't empty, and [EmailAddress] verifies the string follows the correct format (e.g., name@domain.com).

  4. Customer Details: Properties like FullName, Address, and Phone allow the user to manage their delivery information. Since these are in the ViewModel, they can be easily mapped to the input fields in our Bootstrap form.

MODELS/VIEWMODELS/PROFILEVIEWMODEL.CS
using System.ComponentModel.DataAnnotations;

namespace FoodOrderingSystem.Models.ViewModels
{
    public class ProfileViewModel
    {
        public int Id { get; set; }

        [Display(Name = "Username")]
        public string Username { get; set; }

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

        public string FullName { get; set; }

        public string Address { get; set; }

        public string Phone { get; set; }
    }
}

This view is a perfect example of modern UI design, combining a clean sidebar for status and navigation with a robust form for account updates. It’s designed to give your food ordering app a high-end, personalized feel.

Step-by-Step Code Explanation

  1. Two-Column Layout: We use the Bootstrap grid system (col-md-4 and col-md-8). The left side acts as a User Summary Card and navigation hub, while the right side is the Data Management area.

  2. User Status Sidebar: This section displays the user's name, username, and an "Active Member" badge. It also includes a list-group with quick-access links to My Orders and Change Password, improving site navigation.

  3. Dynamic Feedback (TempData): The code includes alerts for Success and Error messages. When a user saves their changes, they get instant visual confirmation—crucial for a good user experience (UX).

  4. Security Measures:

    • Hidden Fields: Id and Username are passed as hidden inputs to ensure the backend knows which record to update.

    • Disabled Username: The username field is marked as disabled because, in most professional systems, the unique login ID remains permanent for security.

  5. Bootstrap Icons: We’ve integrated BI Icons (like bi-person-circle and bi-save) to make the interface modern and intuitive for the customer.

VIEWS/ACCOUNT/PROFILE.CSHTML
@model FoodOrderingSystem.Models.ViewModels.ProfileViewModel
@{
    ViewData["Title"] = "My Profile";
}

<div class="container my-5">
    <div class="row">
        <div class="col-md-4">
            <div class="card shadow mb-4">
                <div class="card-body text-center">
                    <div class="mb-3">
                        <i class="bi bi-person-circle display-1 text-primary"></i>
                    </div>
                    <h4>@Model.FullName</h4>
                    <p class="text-muted">@Model.Username</p>
                    <span class="badge bg-success">Active Member</span>
                </div>
            </div>

        </div>

        <div class="col-md-8">
            <div class="card shadow">
                <div class="card-header bg-primary text-white">
                    <h5 class="mb-0"><i class="bi bi-pencil-square"></i> Edit Profile</h5>
                </div>
                <div class="card-body">
                    @if (TempData["Success"] != null)
                    {
                        <div class="alert alert-success alert-dismissible fade show" role="alert">
                            @TempData["Success"]
                            <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
                        </div>
                    }

                    @if (TempData["Error"] != null)
                    {
                        <div class="alert alert-danger alert-dismissible fade show" role="alert">
                            @TempData["Error"]
                            <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
                        </div>
                    }

                    <form asp-action="Profile" method="post">
                        <div asp-validation-summary="ModelOnly" class="text-danger"></div>
                        <input type="hidden" asp-for="Id" />
                        <input type="hidden" asp-for="Username" />

                        <div class="mb-3">
                            <label class="form-label">Username</label>
                            <input asp-for="Username" class="form-control" disabled />
                            <small class="text-muted">Username cannot be changed</small>
                        </div>

                        <div class="mb-3">
                            <label asp-for="FullName" class="form-label"></label>
                            <input asp-for="FullName" class="form-control" />
                            <span asp-validation-for="FullName" class="text-danger"></span>
                        </div>

                        <div class="mb-3">
                            <label asp-for="Email" class="form-label"></label>
                            <input asp-for="Email" class="form-control" />
                            <span asp-validation-for="Email" class="text-danger"></span>
                        </div>

                        <div class="mb-3">
                            <label asp-for="Phone" class="form-label"></label>
                            <input asp-for="Phone" class="form-control" />
                            <span asp-validation-for="Phone" class="text-danger"></span>
                        </div>

                        <div class="mb-3">
                            <label asp-for="Address" class="form-label"></label>
                            <textarea asp-for="Address" class="form-control" rows="3"></textarea>
                            <span asp-validation-for="Address" class="text-danger"></span>
                        </div>

                        <div class="d-flex justify-content-between">
                            <button type="submit" class="btn btn-primary">
                                <i class="bi bi-save"></i> Save Changes
                            </button>

                            <button type="button" class="btn btn-outline-danger">
                                <i class="bi bi-trash"></i> Delete Account
                            </button>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>

This method is the "worker" that takes the updated information from the user's form, validates it for security and uniqueness, and commits those changes to your SQL database.

Step-by-Step Code Explanation

  1. Session & Security: The code first verifies that a UserId exists in the session. This prevents unauthorized "anonymous" updates to user data.

  2. ModelState.IsValid: Before touching the database, the system checks if the data matches the rules we set in our ProfileViewModel (like valid email formats and required fields).

  3. Unique Email Validation: This is a critical professional step. The line _context.Users.Any(...) checks if the new email address is already being used by another user. This prevents two accounts from sharing the same email.

  4. Database Update: We find the existing user record using the ID from the session and map the new values (FullName, Email, Phone, Address) onto the database object.

  5. _context.SaveChanges(): This executes the actual SQL UPDATE command.

  6. User Feedback (TempData): After a successful save, we store a message in TempData["Success"]. Because we use RedirectToAction, this message will "survive" the redirect and appear on the refreshed profile page to notify the user of their success.

ACCOUNTCONTROLLER.CS (POST PROFILE)
[HttpPost]
public IActionResult Profile(ProfileViewModel model)
{
    var userId = HttpContext.Session.GetInt32("UserId");
    if (userId == null) return RedirectToAction("Login");

    if (ModelState.IsValid)
    {
        var user = _context.Users.Find(userId);
        if (user == null) return NotFound();

        // Check if email is already taken by another user
        var emailExists = _context.Users.Any(u => u.Email == model.Email && u.Id != userId);
        if (emailExists)
        {
            ModelState.AddModelError("Email", "Email is already registered to another account.");
            return View(model);
        }

        user.FullName = model.FullName;
        user.Email = model.Email;
        user.Phone = model.Phone;
        user.Address = model.Address;

        _context.SaveChanges();
        TempData["Success"] = "Profile updated successfully!";
        return RedirectToAction("Profile");
    }

    return View(model);
}


Comments