How to Distributed Sessions ASP.NET Core – Windows ASP.NET Core Hosting 2024 | Review and Comparison

You will discover what sessions are in this post as well as how to use them in your ASP.NET Core apps. You will next be shown the default session’s restrictions and how to get around them by dispersing your sessions.

In previous post, we have written an article about how to use sessions and HTTPContext in ASP.NET 5, you may refer to that link for further reading.

How to add Session State to ASP.NET Core

1. Create ASP.NET Core MVC App

The ASP.NET Core MVC app must be made initially. You can enter the following commands into the terminal:

dotnet new mvc -o DistributedSessions
cd DistributedSessions

When your app is created, you can run it with this command:

dotnet run

You should have a similar output in your terminal window:

info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://localhost:7106
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5050
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
A straightforward ASP.NET Core MVC template should be visible at the link provided in your terminal window.

2. Add the Session middleware to the pipeline

You are prepared. To add the session middleware to the pipeline, you can open the solution in your IDE. Add the two highlighted lines of code to the program.cs file:
var builder = WebApplication.CreateBuilder(args);

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

builder.Services.AddSession();

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.UseAuthorization();

app.UseSession();

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

app.Run();

To add a simple session state management to your app, you only need to add these two lines of code. By changing the session parameters, it is possible to alter the default behavior.

The official Microsoft documentation contains more information about the session state options.

It is crucial to add the UseSession middleware after UseRouting and before MapControllerRoute. Please refer to the official Microsoft documentation if you want to know more about the middleware pipeline’s order.

3. Add data to the session

This article’s sample project will replicate an internet shopping cart for a bookseller. To keep the chosen books in the cart, use session state.

First, create the Book.cs file to define your Book class:

namespace DistributedSessions;

public class Book
{
    public int Id { get; set; }
    public string Title { get; set; }
    public double Price { get; set; }

    public Book(int id, string title, double price)
    {
        Id = id;
        Title = title;
        Price = price;
    }
}

Also, add another class called Data in a separate Data.cs file:

namespace DistributedSessions;

public class Data
{
    public static List<Book> Books = new()
    {
        new Book(1, "Case of the Laughing Goose", 18.90),
        new Book(2, "Call of Lords", 25),
        new Book(3, "Strike the Future",23.16),
        new Book(4, "Wild and Wicked", 14.45)
    };
}

A list of books makes up the only property of the Data class. The information in a real-world app will originate from a data source (a database, file, etc.). We’ll suppose for the purposes of this example that the Data class’s list of books fills that function.

The books will be visible on the home page. Go to the HomeController.cs file in the Controllers folder. As shown below, update the Index() method:

    public IActionResult Index()
    {
        var books = Data.Books;
        return View(books);
    }

Getting all the books and passing them to the home page view are the two things the code above does. Since a list of books is being used as the model, the home page view is tightly written. You must therefore change the view code by providing it with a model. Go to Views/Home/Index.cshtml and make the following changes to the file’s content:

@model List<Book>

@{
    ViewData["Title"] = "Home Page";
}

<div class="container">
    <div class="row">
        <div class="col-10 text-center">
            <h1 class="display-4">Welcome</h1>
            <p>Here are our books:</p>
        </div>
        <div class="col-2 pt-5">
             <a class="btn btn-outline-success btn-lg" 
                asp-controller="Cart" 
                asp-action="Index">
                Cart
            </a>
        </div>
    </div>
</div>

<div class="text-center">
    <table class="table table-hover">
        <thead>
            <tr class="table-secondary">
                <th scope="col">Id</th>
                <th scope="col">Title</th>
                <th scope="col">Price</th>
                <th scope="col">Actions</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var book in Model)
            {
                <tr>
                    <td>@book.Id</td>
                    <td>@book.Title</td>
                    <td>@book.Price</td>
                    <td>
                        <a class="btn btn-success btn-sm p-lg-1" 
                            asp-controller="Cart" 
                            asp-action="AddToCart" 
                            asp-route-id="@book.Id">
                            Add to cart
                        </a>
                    </td>
                </tr>
            }
        </tbody>
    </table>
</div>

Navigate to your project folder at the command line and use the dotnet run command to launch your application. At this point, your app’s home page’s book list ought to look something like this:

The associated book will be added to the cart when you click the Add to cart button. You’ll carry out that behavior during the session. Create a controller with the name CartController with the following code in the Controllers folder:

using Microsoft.AspNetCore.Mvc;
using System.Text;
using System.Text.Json;

namespace DistributedSessions.Controllers;

public class CartController : Controller
{
    public async Task<IActionResult> Index()
    {
        // Get the value of the session
        var data = await GetBooksFromSession();

        //Pass the list to the view to render
        return View(data);
    }

    private async Task<List<Book>> GetBooksFromSession()
    {
        await HttpContext.Session.LoadAsync();
        var sessionString = HttpContext.Session.GetString("cart");
        if (sessionString is not null)
        {
            return JsonSerializer.Deserialize<List<Book>>(sessionString);
        }

        return Enumerable.Empty<Book>().ToList();
    }
}

As suggested by its name, the HttpContext obtains the context of the active HTTP request. The session object is located there. One key-value pair is used to store the session state. You choose the key you want to use to save and retrieve the data. If a session cookie doesn’t already exist, one will be generated automatically. The data in a session is linked to the browser and that browser only because the session cookie is stored in the browser. This indicates that two users who are using different browsers do not share session data.

The data from the session will be retrieved via the Index() action result and shown in the view. An empty list will appear if there is no data in the session.

The session’s data, if any, will be a JSON-formatted string. The JSON must be transformed into.NET objects in this case, a list of books. Deserialization is the name of this conversion.

You’ll now create the capability to add a book to the cart. Create a new AddToCart function in the CartController to handle this.

    public async Task<IActionResult> AddToCart(int id)
    {
        var data = await GetBooksFromSession();

        var book = Data.Books.FirstOrDefault(b => b.Id == id);

        if (book is not null)
        {
            data.Add(book);

            HttpContext.Session.SetString("cart", JsonSerializer.Serialize(data));

            TempData["Success"] = "The book is added successfully";

            return RedirectToAction("Index");
        }

        return NotFound();
    }

The session value string is obtained by this code, which then deserializes it into a list of books. The chosen book is then added to the list, the list is serialized to JSON, and the JSON is saved back into the session.

You will then create the cart page. Create a folder called Cart under the Views directory. Make an Index.cshtml file in that folder with the following information:

@model List<Book>

<div class="container">

    @if (TempData["Success"] is not null)
    {
        <div class="alert alert-success col-12 mt-4"><strong> @TempData["Success"]</strong></div>
    }

    <div class="row">
        <div class="col-10 text-center">
            <h1>Cart from session</h1>
            <p>Here's your cart (retrieved from session)</p>
        </div>
        <div class="col-2 pt-3">
            <a class="btn btn-outline-success btn-lg" asp-controller="Home" asp-action="Index">Home</a>
        </div>
    </div>
</div>

<div class="text-center">
    <table class="table table-hover">
        <thead>
            <tr class="table-secondary">
                <th scope="col">Id</th>
                <th scope="col">Title</th>
                <th scope="col">Price</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var book in Model)
            {
                <tr>
                    <td>@book.Id</td>
                    <td>@book.Title</td>
                    <td>@book.Price</td>
                </tr>
            }
        </tbody>
    </table>
</div>

The home page view is comparable to this view. Furthermore, it will use a list of books as a model and display the list on the page.

Restart the app. The chosen book should appear on the Cart page when you click the Add to cart button in the home page’s book list. The information is now flowing from the session.
Congratulations, you’ve finished implementing ASP.NET Core MVC’s fundamental session management!

Limitations of the default in-memory session

For a quick start, the default in-memory session is fantastic. It quickly puts the data in the server’s memory, where the app is now operating. However, there is a downside. You might need to deploy your web application to numerous servers and use a load balancer to direct traffic to the servers if your web application receives a lot of traffic. HTTP queries sent by users while using the program will be forwarded amongst various application servers. As a result, when a user requests another page, their cart is suddenly empty because it was delivered by a different server and they may have books in it on one server but not on another. Due to the fact that each server keeps its own session data in memory, this occurs when the data is out of sync. You’ll discover how to use dispersed sessions to resolve this issue in the following portion of this article.

How to distribute Session State in ASP.NET Core

By storing session data in a single data store that is used by all instances of your apps, a distributed session allows you to preserve session data. It can be beneficial to maintain the customer’s shopping cart across all instances of your applications for your fictitious online bookstore.

Let’s now configure a distributed session within the ASP.NET Core application. Distributed caching in ASP.NET Core must be configured in order to configure your session to be distributed; after that, the session will automatically use the IDistributedCache. You can utilize a variety of distributed cache providers. The session data will be configured to be stored in a Redis database as your distributed caching in this article.

Popular open-source data store Redis is designed for a variety of purposes, such as session data management and caching. Visit the official documentation website to find out more about Redis.

Test the connection to Redis

Use the redis-cli to check your Redis server’s connectivity first. To connect to a Redis server that is being run locally and does not require authentication, simply run redis-cli. To specify the server address and login information in the absence of it, consult the redis-cli documentation. Use the ping command after connecting to the server.

You should see “PONG” in your command prompt if Redis has been properly installed and launched.

Configure distributed caching with Redis

It’s time to communicate with the Redis server now that you have confirmed it is operating properly. For that purpose, there is a NuGet package: Microsoft.Extensions.Caching.StackExchangeRedis. Go to your terminal window and type the following command inside the project folder to install the package:

dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis
Configure your Redis database’s connection string next. You can update the appsettings if you’re running the server locally without authentication.similar to this development.json file:
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "ConnectionStrings": {
    "RedisSession": "localhost:6379"
  }
}

6379 is the default port Redis will run on. Change this if you configured your Redis server to use a different port.

If you’re using a username and password, you should use user-secrets, environment variables, or a vault service to hold the connection string.

Next, put the highlighted code in front of AddSession in Program.cs:

builder.Services.AddStackExchangeRedisCache(options =>
    options.Configuration = builder.Configuration.GetConnectionString("RedisSession"));

builder.Services.AddSession();

var app = builder.Build();

With the RedisSession connection string, the IDistributedCache will be configured to use the Redis server.

Relaunch the app, then add some books to the shopping cart. The session state information is currently kept in the Redis database. The data can be accessed with the redis-cli. The keys are GUIDs, so list them first using the KEYS * command, then copy one of the keys. Use the HGET command to obtain the session data:

HGET 8c4ab198-6b5f-b096-6467-faa42fda2fc4 data
8c4ab198-6b5f-b096-6467-faa42fda2fc4 is the key I copied, so replace that with the key you copied. The result will be hash because multiple values are stored as a hash under this key.
The result will be a coded representation of the JSON string containing the books in your cart, which will appear as follows:
"\x02\x00\x00\x01\x82Q\xce\x1bM\x1a\xc04\xca\x95\xef\x1c<\xbf&w\x00\x04cart\x00\x00\x00m[{\"Id\":1,\"Title\":\"Case of the Laughing Goose\",\"Price\":18.9},{\"Id\":4,\"Title\":\"Wild and Wicked\",\"Price\":14.45}]"

Conclusion

You learnt how to use ASP.NET Core to fetch and store data in session state. Additionally, you learnt that the server’s RAM is where the data is saved by default, which can be problematic if you run numerous instances of your application behind a load balancer. Finally, you discovered a solution to these problems by sharing your session, in this example by storing your session data in a Redis database.