How to Create SessionStorage in Blazor Server?

Introduction

Blazor Server apps need a safe and easy way to handle user sessions and private info. In this article, we'll learn How to create session storage using Protected Session Storage in Blazor Server. It's like a secret locker for your app data, adding an extra layer of security with the ProtectedSessionStorage service.

What is Session Storage?

In a Blazor Server application, session storage refers to a client-side storage mechanism that allows developers to store and retrieve data on the user's browser during a single session. Unlike local storage, which persists data even after the browser is closed and reopened, session storage is designed to hold information only for the duration of the user's session.

Create session storage in the Blazor Server

Let's create session storage in the Blazor Server Project. For creating Session Storage we need to follow these steps.

Step 1. Create a Blazor Server project

First, we need to create a Blazor Server project then you'll have a default page named index.razor. If you wish to customize this default page, you can do so by adjusting the route. For instance, you can change the route by adding @page "/your-custom-route" at the top of your desired page, allowing you to create your unique default page.

Step 2. Create a model named UserDetailsModel.cs

Create a model named UserDetailsModel.cs inside the 'Data' Folder which was already created when we initially set up the project, and write this code.

using System.ComponentModel.DataAnnotations;
namespace BlazorDemo.Data
{
    public class UserDetailsModel
    {
        public Int64 UserId { get; set; }
        public string  FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
        public string Password { get; set; }
        public int Age { get; set; }
        public int result { get; set; }     
    }
    public class LoginModel
    {
        [EmailAddress(ErrorMessage = $"Email Address is not a valid.")]
        [Required(ErrorMessage = $"Email Address is required.")]
        [StringLength(50)]
        public string Email { get; set; }
        [Required(ErrorMessage = $"Password is required.")]
        [StringLength(50)]
        public string password { get; set; }
    }
}

Step 3. Create a default page named login.razor

Now, we need to add a page named 'login.razor' inside the 'Pages' folder, which was already created when we initially set up the project, and write this code.

@page "/"
@using BlazorDemo.Data
@using BlazorDemo.Interfaces
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage;
@inject ProtectedSessionStorage ProtectedSessionStore
@inject AuthenticationStateProvider AuthenticationStateProvider
@inject NavigationManager navigationManager

<div class="login-container">
    <h3 class="heading mt-5">Login</h3>
    <div class="separator mt-4"></div>
    <EditForm Model="@loginmodel" OnValidSubmit="OnValidSubmit">
        <DataAnnotationsValidator />

        <div class="form-floating mb-15">
            <InputText @bind-Value="loginmodel.Email" class="form-control input-username is-invalid" required="true" id="username" placeholder="[email protected]" autocomplete="off" />
            <label for="Email">Username</label>
            <div id="usernameFeedback" class="invalid-feedback">
                <ValidationMessage For="@(() => loginmodel.Email)" />
            </div>
        </div>

        <div class="form-floating mb-10">
            <InputText @bind-Value="loginmodel.password" type="password" class="form-control input-password is-invalid" id="password" autocomplete="off" required="true" placeholder="Password" />
            <label for="password">Password</label>
            <div id="passwordFeedback" class="invalid-feedback">
                <ValidationMessage For="@(() => loginmodel.password)" />
            </div>
        </div>

        <div class="col-sm-12 mt-4 mb-10">
            <button class="btn btn-warning d-block mx-auto w-25" disabled="@loading">
                @if (loading)
                {
                    <span class="spinner-border spinner-border-sm mr-1"></span>
                }
                Login
            </button>
        </div>
    </EditForm>
</div>

@code {
    UserDetailsModel userDetailsModels = new UserDetailsModel();
    public LoginModel loginmodel = new LoginModel();
    protected bool loading = false;
    [Inject] IUserDetails iuserDetails { get; set; }
    public async Task OnValidSubmit()
    {
        userDetailsModels = await iuserDetails.LoginClick(loginmodel);
        await ProtectedSessionStore.SetAsync("UserSessionUserID", userDetailsModels.UserId);
        await ProtectedSessionStore.SetAsync("UserSessionPassword", userDetailsModels.Password);
        await ProtectedSessionStore.SetAsync("UserSessionFirstName", userDetailsModels.FirstName);
        await ProtectedSessionStore.SetAsync("UserSessionLastName", userDetailsModels.LastName);
        await ProtectedSessionStore.SetAsync("UserSessionEmailId", userDetailsModels.Email);

        if (userDetailsModels.result == 1)
        {
            navigationManager.NavigateTo("/ViewUserDetails");
        }
        else
        {
            navigationManager.NavigateTo("/");
        }

    }
}

Code Explanation

@page "/"

This sets the URL route for this Blazor component to be the root ("/") of the application.

@using BlazorDemo.Data
@using BlazorDemo.Interfaces
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage;

These statements define the namespaces to be used in the Razor component.

@inject ProtectedSessionStorage ProtectedSessionStore
@inject AuthenticationStateProvider AuthenticationStateProvider
@inject NavigationManager navigationManager

These lines use the @inject directive to inject services into the component. Here, it injects ProtectedSessionStorage for secure session storage, AuthenticationStateProvider for managing authentication state, and NavigationManager for programmatic navigation.

<EditForm Model="@loginmodel" OnValidSubmit="OnValidSubmit">

This sets up a Blazor EditForm component that wraps the form fields. It binds to the loginmodel and triggers the OnValidSubmit method when the form is submitted.

<InputText></InputText>

The form uses InputText components for email and password. DataAnnotationsValidator is used for client-side validation, and ValidationMessage displays error messages.

<button class="btn btn-warning d-block mx-auto w-25" disabled="@loading">

A button is provided for submitting the form. If the loading flag is true, a spinner indicates that the login process is ongoing.

@code {
    // Code block containing the component's C# logic.
}

This is the code block where C# logic resides. It declares variables, sets up dependency injection for IUserDetails, and defines the OnValidSubmit method.

UserDetailsModel userDetailsModels = new UserDetailsModel();

userDetailsModels is an instance of UserDetailsModel to store information about the user.

public LoginModel loginmodel = new LoginModel();

loginmodel is an instance of LoginModel representing the user's login credentials.

protected bool loading = false;

loading is a boolean flag to track whether the login process is currently loading.

[Inject] IUserDetails iuserDetails { get; set; }

This line uses the [Inject] attribute to inject the IUserDetails interface, presumably representing user-related functionality.

public async Task OnValidSubmit()
{
    userDetailsModels = await iuserDetails.LoginClick(loginmodel);
    await ProtectedSessionStore.SetAsync("UserSessionUserID", userDetailsModels.UserId);
    await ProtectedSessionStore.SetAsync("UserSessionPassword", userDetailsModels.Password);
    await ProtectedSessionStore.SetAsync("UserSessionFirstName", userDetailsModels.FirstName);
    await ProtectedSessionStore.SetAsync("UserSessionLastName", userDetailsModels.LastName);
    await ProtectedSessionStore.SetAsync("UserSessionEmailId", userDetailsModels.Email);

    if (userDetailsModels.result == 1)
    {
        navigationManager.NavigateTo("/ViewUserDetails");
    }
    else
    {
        navigationManager.NavigateTo("/");
    }
}
  • This method is triggered when the form is submitted (OnValidSubmit).
  • It calls the LoginClick method on the injected iuserDetails service, passing the loginmodel.
  • User details are then stored in the session using ProtectedSessionStore.
  • Depending on the result property of userDetailsModels, the page is navigated to either "/ViewUserDetails" or the root ("/").

Step 4. Create an Interface named IUserDetails.cs

Inside the Interface folder, we need to add an interface named IUserDetails.cs and write this code.

using BlazorDemo.Data;
namespace BlazorDemo.Interfaces
{
    public interface IUserDetails
    {
        public Task <UserDetailsModel> LoginClick(LoginModel loginmodel);
    }
}

Step 5. Add a class inside the Data Folder

Create a class Inside the Date folder named UserDetails.cs and write this code to check whether the user login details are correct to not and if the details are correct get all UserDetails from the database.

using BlazorDemo.Data;
using BlazorDemo.Interfaces;
using Dapper;
using Microsoft.Exchange.WebServices.Data;
using System.Data;

namespace BlazorDemo.Interfaces
{
    public class UserDetails : IUserDetails
    {
        private readonly DapperContext _context;
        private readonly ILogRepo _logRepo;

        public UserDetails(DapperContext dapperContext)
        {
            _context = dapperContext;
            _logRepo = new LogRepo(dapperContext);
        }
        public async Task<UserDetailsModel>LoginClick(LoginModel loginmodel)
        {
            UserDetailsModel lst = new UserDetailsModel();
            try
            {
                using (var connection = _context.CreateConnection())
                {
                    var procedure = "[Project_UserLogin]";
                    var values = new { EmailId = loginmodel.Email, Password= loginmodel.password};
                    lst = await connection.QueryFirstAsync<UserDetailsModel>(procedure, values, commandType: CommandType.StoredProcedure);           
                }
            }
            catch (Exception ex)
            {

            }
            return lst;
        }   
    }
}

Step 6. Create a class named Session.cs

Inside the Data folder create a Session.cs class and write this code here.

using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage;
namespace BlazorDemo.Data
{
    public class Session
    {
        public readonly ProtectedSessionStorage ProtectedSessionStorage;

        public Session(ProtectedSessionStorage _ProtectedSessionStore)
        {
            ProtectedSessionStorage = _ProtectedSessionStore;
        }

        public async Task<String> GetUserFirstName()
        {
            var val = await ProtectedSessionStorage.GetAsync<String>("UserSessionFirstName");
            return val.Value;
        }
        public async Task<String> GetUserLastName()
        {
            var val = await ProtectedSessionStorage.GetAsync<String>("UserSessionLastName");
            return val.Value;
        }
        public async Task<String> GetUserName()
        {
            var val = await ProtectedSessionStorage.GetAsync<String>("UserSessionFirstName");
            var val1 = await ProtectedSessionStorage.GetAsync<String>("UserSessionLastName");
            return val.Value + " " + val1.Value;
        }
        public async Task<Int64> GetUserId()
        {
            var val = await ProtectedSessionStorage.GetAsync<Int64>("UserSessionUserID");
            return val.Value;
        }
        public async Task<String> GetEmailId()
        {
            var val = await ProtectedSessionStorage.GetAsync<String>("UserSessionEmailId");
            return val.Value;
        }

        public async Task<String> GetPassword()
        {
            var val = await ProtectedSessionStorage.GetAsync<String>("UserSessionPassword");
            return val.Value;
        }
        public async Task RemoveSession()
        {
            await ProtectedSessionStorage.DeleteAsync("UserSessionFirstName");
            await ProtectedSessionStorage.DeleteAsync("UserSessionLastName");
            await ProtectedSessionStorage.DeleteAsync("UserSessionUserID");
            await ProtectedSessionStorage.DeleteAsync("UserSessionEmailId");
            await ProtectedSessionStorage.DeleteAsync("UserSessionPassword");
        }
    }
}

Code Explanation

public Session(ProtectedSessionStorage _ProtectedSessionStore)
{
    ProtectedSessionStorage = _ProtectedSessionStore;
}

The constructor takes an instance of ProtectedSessionStorage and assigns it to the ProtectedSessionStorage field.

public async Task<String> GetUserFirstName()
{
    var val = await ProtectedSessionStorage.GetAsync<String>("UserSessionFirstName");
    return val.Value;
}
public async Task<String> GetUserLastName()
{
    var val = await ProtectedSessionStorage.GetAsync<String>("UserSessionLastName");
    return val.Value;
}
public async Task<String> GetUserName()
{
    var val = await ProtectedSessionStorage.GetAsync<String>("UserSessionFirstName");
    var val1 = await ProtectedSessionStorage.GetAsync<String>("UserSessionLastName");
    return val.Value + " " + val1.Value;
}
public async Task<Int64> GetUserId()
{
    var val = await ProtectedSessionStorage.GetAsync<Int64>("UserSessionUserID");
    return val.Value;
}
public async Task<String> GetEmailId()
{
    var val = await ProtectedSessionStorage.GetAsync<String>("UserSessionEmailId");
    return val.Value;
}
public async Task<String> GetPassword()
{
    var val = await ProtectedSessionStorage.GetAsync<String>("UserSessionPassword");
    return val.Value;
}

GetUserFirstName, GetUserLastName, GetUserName, GetUserId, GetEmailId, and GetPassword methods retrieve session values for user details.

public async Task RemoveSession()
{
    await ProtectedSessionStorage.DeleteAsync("UserSessionFirstName");
    await ProtectedSessionStorage.DeleteAsync("UserSessionLastName");
    await ProtectedSessionStorage.DeleteAsync("UserSessionUserID");
    await ProtectedSessionStorage.DeleteAsync("UserSessionEmailId");
    await ProtectedSessionStorage.DeleteAsync("UserSessionPassword");
}

The removeSession method deletes all session values related to user details and the methods use await to asynchronously retrieve values from the ProtectedSessionStorage.This class provides a convenient way to manage and retrieve user session information using the ProtectedSessionStorage. It encapsulates the logic for getting and removing session values related to user details.

Step 7. Add a component named ViewUserDetails

As we can see when the user login details are correct we will get the 1 as a result and navigate the ViewUserDetails page so we need to add a component named ViewUserDetails and write this code here.

@page "/ViewUserDetails"
@using BlazorDemo.Data;
@using BlazorDemo.Interfaces;
@inject IUserDetails iuserDetails
@inject Session session
@inject NavigationManager navigationManager

<div class="row">
    <div class="col-6">
        <h3>ViewUserDetails</h3>
    </div>
    <div class="col-6" style="padding-left:40%">
        <button class="btn btn-warning" title="View" @onclick="() => LogOut()">
            LogOut
        </button>
    </div>
</div>
<div class="row">
    <div class="container mt-3">
        <div class="card mx-4" style="width:1104px;">
            <div class="card-body">
                <div class="row">
                    <div class="col-6">
                        <div class="row mx-3">
                            <div class="col-md-6 mb-2">
                                <label class="fw-700">First Name :</label>
                                <span> @userDetailsModel.FirstName</span>
                            </div>
                        </div>

                        <div class="row mx-3">
                            <div class="col-md-6 mb-2">
                                <label class="fw-700">Last Name :</label>
                                <span> @userDetailsModel.LastName</span>
                            </div>
                        </div>

                        <div class="row mx-3">
                            <div class="col-md-6 mb-2">
                                <label class="fw-700">Email :</label>
                                <span> @userDetailsModel.Email</span>
                            </div>
                        </div>

                        <div class="row mx-3">
                            <div class="col-md-6 mb-2">
                                <label class="fw-700">Age :</label>
                                <span> @userDetailsModel.Age</span>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

@code {
    UserDetailsModel userDetailsModel = new UserDetailsModel();
    protected bool loading = false;
    protected override async Task OnInitializedAsync()
    {
        loading = true;
        try
        {
            userDetailsModel.UserId = await session.GetUserId();
            userDetailsModel = await iuserDetails.GetUserDetailsByUserId(userDetailsModel.UserId);
        }
        catch (JSDisconnectedException ex)
        {
            // Ignore
        }
        catch (Exception ex)
        {

        }
        finally
        {
            StateHasChanged();
            loading = false;
        }
    }
    public async void LogOut()
    {
        await session.RemoveSession();
        navigationManager.NavigateTo("/");
    }
}

Create a method inside the IUserDetails.cs page named GetUserDetailsByUserId.

using BlazorDemo.Data;
namespace BlazorDemo.Interfaces
{
    public interface IUserDetails
    {
        public Task<UserDetailsModel>GetUserDetailsByUserId(Int64 UserId);
        public Task <UserDetailsModel> LoginClick(LoginModel loginmodel);
    }
}

And implement this method in the UserDetails.cs page.

public async Task<UserDetailsModel> GetUserDetailsByUserId(Int64 UserId)
{
   UserDetailsModel lst = new UserDetailsModel();
    try
    {
        using (var connection = _context.CreateConnection())
        {
            var procedure = "[GetUserByUserId]";
            var values = new { UserId = UserId };
            lst = (await connection.QueryFirstAsync<UserDetailsModel>(procedure, values, commandType: CommandType.StoredProcedure));
        }
    }
    catch (Exception ex)
    {

    }
    return lst;
}

Note. You must add services to the Program.cs file for all the interfaces and classes, similar to the example provided. just like this.

builder.Services.AddTransient<IUserDetails, UserDetails>();
builder.Services.AddScoped<Session>();

Then run the project and you can see this type of output.

session storage

Conclusion

In this article, we have learned about emphasizing the importance of using Protected Session Storage in Blazor Server applications for secure user session management. Encourage developers to implement these practices to enhance the overall security of their applications.

FAQ's

Q 1. What is Blazor?

Ans. Blazor is an open-source web framework developed by Microsoft that allows developers to build interactive web applications using C# and .NET instead of JavaScript. Blazor enables full-stack web development with a single programming language and runtime.

Q 2. What are the two primary hosting models in Blazor?

Ans. There are two primary hosting models for Blazor.

Blazor WebAssembly

This model allows you to run Blazor applications directly in the web browser using WebAssembly, enabling code execution in the browser.

Blazor Server

In this model, the application's user interface is rendered on the server, and UI updates are sent to the client over a SignalR connection.

Q 3. What is the role of ProtectedSessionStorage?

Ans. ProtectedSessionStorage is used to securely store and retrieve user session data such as user ID, first name, last name, email, and password.

Q 4. How does the Session class contribute to the application?

Ans. The Session class encapsulates methods for retrieving user session data and removing session values related to user details.


Similar Articles