Skip to content
Go back

Building a Real-Time Santa's Workshop Tracker with SignalR and .NET 9

An image of Santa working at a computer

Table of contents

Open Table of contents

🎄✨ Getting the Workshop Ready! 🎅🛠️

Kick off the festive coding adventure by setting up Santa’s workshop project! First, you must create a shiny new ASP.NET Core Web Application using the magic of .NET 9. Grab your coding sleigh and run this command:

dotnet new web -n SantasWorkshopTracker -f net9.0

Dash into the project directory, where all the holiday magic will unfold. Once you’re there, it’s time to add the SignalR client library to bring the elves to life:

cd SantasWorkshopTracker

dotnet add package Microsoft.AspNetCore.SignalR.Client

With these steps, you’re laying the foundation for a good time in Santa’s Workshop! Get ready to watch the magic unfold in an interactive experience that’ll make even the North Pole proud! 🎁💻✨

🎄🔔 SantasWorkshopHub.cs: Santa’s Communication Network 🔔🎄

Create a new file, SantasWorkshopHub.cs, with the following content:

using Microsoft.AspNetCore.SignalR;

using System.Diagnostics;

namespace SantasWorkshopTracker;

public record ToyUpdate(int Count);

public record ElfActivity(string Name, string Activity);

public record SantaStatus(string Status);

public class SantasWorkshopHub : Hub

{

    private static readonly ActivitySource ActivitySource = new("SantasWorkshop.SignalR");

    

    private int _totalToys;

    public async Task UpdateToyProduction(ToyUpdate update)

    {

       using var activity = ActivitySource.StartActivity();

       activity?.SetTag("ToyCount", update.Count);

    

       var localTotal = _totalToys;

       await Task.Delay(100); // Simulating some async work

       localTotal += update.Count;

       _totalToys = localTotal;

    

       await Clients.All.SendAsync("ReceiveToyUpdate", new ToyUpdate(_totalToys));

    }

    public Task UpdateElfActivity(ElfActivity elfActivity)

    {

       using var activity = ActivitySource.StartActivity();

       activity?.SetTag("ElfName", elfActivity.Name);

       activity?.SetTag("Activity", elfActivity.Activity);

       return Clients.All.SendAsync("ReceiveElfUpdate", elfActivity);

    }

    public Task UpdateSantaStatus(SantaStatus status)

    {

       using var activity = ActivitySource.StartActivity();

       activity?.SetTag("Status", status.Status);

       return Clients.All.SendAsync("ReceiveSantaUpdate", status);

    }

}

🌟 Unwrapping the Festive Features 🌟

Record-Breaking Christmas Records:

Our ToyUpdate, ElfActivity, and SantaStatus are wrapped up neatly as records. They’re like perfectly packed presents, immutable and ready to spread joy!

Santa’s Magic Telescope (ActivitySource):

private static readonly ActivitySource ActivitySource = new("SantasWorkshop.SignalR");

It isn’t just any telescope; it’s Santa’s special way of watching all the workshop activities! 🔭

Tagging Toys and Elves:

Each method uses ActivitySource to create activities and set tags. It’s like Santa putting name tags on all the presents and elves!

activity?.SetTag("ToyCount", update.Count);

activity?.SetTag("ElfName", elfActivity.Name);

Async Christmas Magic:

Our UpdateToyProduction method works asynchronously, just like elves working through the night to prepare all the toys!

SignalR’s Christmas Broadcast:

Clients.All.SendAsync is like Santa’s magical announcement system, keeping everyone in the North Pole updated!

🎊 Why This Code Sleighs 🎊

This SantasWorkshopHub isn’t just a piece of code; it’s the heart of the virtual North Pole, pumping Christmas cheer and toy updates to all!🎄🎅🏻🧝‍♀️

🧝‍♂️ Elf Code Workshop: The Magical WorkshopSimulator 🧝‍♀️

Create a new file, WorkshopSimulator.cs:

using Microsoft.AspNetCore.SignalR;

namespace SantasWorkshopTracker;

public partial class WorkshopSimulator

{

    private int _toyCount;

    public partial int ToyCount { get; }

}

public partial class WorkshopSimulator(IHubContext<SantasWorkshopHub> hubContext) : BackgroundService

{

    private readonly Random _random = new();

    private const int ToyGoal = 100;

    private readonly Lock _wrappingStationLock = new();

    private int _availableWrappingStations = 3;

    

    public partial int ToyCount => _toyCount;

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)

    {

        string[] elfNames = ["Buddy", "Jingle", "Sparkle", "Twinkle", "Holly"];

        string[] elfActivities = ["making toys", "feeding reindeer", "baking cookies", "checking the list"];

        while (!stoppingToken.IsCancellationRequested && _toyCount < ToyGoal)

        {

            var tasks = new List<Task>();

            for (var i = 0; i < 5; i++) // Process 5 elves concurrently

            {

                var elfName = elfNames[_random.Next(elfNames.Length)];

                var activity = elfActivities[_random.Next(elfActivities.Length)];

                tasks.Add(ProcessElfActivity(elfName, activity, stoppingToken));

            }

            await Task.WhenAll(tasks);

            await Task.Delay(TimeSpan.FromSeconds(2), stoppingToken);

        }

        if (_toyCount >= ToyGoal)

        {

            await hubContext.Clients.All.SendAsync("ReceiveSantaStatus", new SantaStatus("Ho Ho Ho! All toys are ready! Santa has departed! Merry Christmas!"), stoppingToken);

        }

    }

    private async Task ProcessElfActivity(string elfName, string activity, CancellationToken stoppingToken)

    {

        if (activity == "making toys")

        {

            var toysProduced = _random.Next(1, 15);

            Interlocked.Add(ref _toyCount, toysProduced);

            await hubContext.Clients.All.SendAsync("ReceiveToyUpdate", new ToyUpdate(_toyCount), stoppingToken);

            await WrapToy(elfName, stoppingToken);

        }

        else

        {

            await hubContext.Clients.All.SendAsync("ReceiveElfUpdate", new ElfActivity(elfName, activity), stoppingToken);

        }

    }

    private async Task WrapToy(string elfName, CancellationToken stoppingToken)

    {

        var wrapped = false;

        while (!wrapped && !stoppingToken.IsCancellationRequested)

        {

            using (var lockScope = _wrappingStationLock.EnterScope())

            {

                if (_availableWrappingStations > 0)

                {

                    _availableWrappingStations--;

                    wrapped = true;

                }

            }

            if (wrapped)

            {

                await hubContext.Clients.All.SendAsync("ReceiveElfUpdate", new ElfActivity(elfName, "wrapping a gift"), stoppingToken);

                await Task.Delay(3000, stoppingToken); // Simulating wrapping time

                using var lockScope = _wrappingStationLock.EnterScope();

                _availableWrappingStations++;

            }

            else

            {

                await hubContext.Clients.All.SendAsync("ReceiveElfUpdate", new ElfActivity(elfName, "waiting for a wrapping station"), stoppingToken);

                await Task.Delay(1000, stoppingToken); // Wait before trying again

            }

        }

    }

}

🎁 Unwrapping the Festive Features 🎁

  1. Magical Lock Spell: The _wrappingStationLock is like Santa’s magic key, ensuring orderly access to gift-wrapping stations!

  2. Partial Properties - A Christmas Miracle: You’ve split the ToyCount property like sharing a Christmas cookie. Half the declaration, twice the joy!

  3. Collection Expressions - A Sack Full of Goodies: The elf names and activities are now as neatly packed as Santa’s gift bag, thanks to the new collection expression syntax!

  4. Concurrent Elf Magic: You’re processing five elves at once, spreading holiday cheer faster than Rudolph on Christmas Eve!

  5. Festive Randomness: Just like you never know what’s in a Christmas cracker, the _random adds a sprinkle of surprise to elf activities!

This code isn’t just functional; it’s a winter wonderland of C# 13 features! It’s as exciting as finding the biggest present under the tree on Christmas morning! 🎄🎁

🎅🎄🏗️ Program.cs: The North Pole’s Command Center

Update your Program.cs file to add SignalR services and map the hub:

using SantasWorkshopTracker;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSignalR();

builder.Services.AddHostedService<WorkshopSimulator>();

var app = builder.Build();

app.MapHub<SantasWorkshopHub>("/santasworkshop");

app.UseStaticFiles();

app.MapFallbackToFile("index.html");

app.Run();

This jolly Program.cs sets up the North Pole operations center, connecting Santa’s workshop to the digital world faster than you can say “Ho Ho Ho!” 🎅🏻🌟

🎄 Crafting Santa’s Digital Dashboard 🖥️

Create a festive index.html in the wwwroot folder:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Santa's Workshop Live Tracker</title>
    <style>
        body { font-family: Arial, sans-serif; background-color: #f0f0f0; }
        .container { max-width: 800px; margin: 0 auto; padding: 20px; background-color: #fff; border-radius: 10px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
        h1 { color: #c41e3a; font-size: 24px; text-align: center; }
        .update-section { margin-bottom: 20px; }
    </style>
</head>
<body>
<div class="container">
    <h1>Santa's Workshop Live Tracker</h1>
    <div class="update-section">
        <h2>Toy Production</h2>
        <p id="toyCount">Toys made: 0</p>
    </div>
    <div class="update-section">
        <h2>Elf Activities</h2>
        <ul id="elfActivities"></ul>
    </div>
    <div class="update-section">
        <h2>Santa's Status</h2>
        <p id="santaStatus">Preparing for Christmas Eve</p>
    </div>
    <div class="update-section">
        <h2>Workshop Updates</h2>
        <ul id="workshopUpdates"></ul>
    </div>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/6.0.1/signalr.min.js"></script>
<script>
    const connection = new signalR.HubConnectionBuilder()
        .withUrl("/santasworkshop")
        .configureLogging(signalR.LogLevel.Information)
        .build();

    connection.on("ReceiveToyUpdate", (update) => {
        document.getElementById("toyCount").textContent = `Toys made: ${update.count}`;
    });

    connection.on("ReceiveElfUpdate", (activity) => {
        const li = document.createElement("li");
        li.textContent = `${activity.name} is ${activity.activity}`;
        document.getElementById("elfActivities").appendChild(li);
    });

    connection.on("ReceiveSantaStatus", (status) => {
        const santaStatus = document.getElementById("santaStatus");
        santaStatus.textContent = status.status;
        if (status.status.includes("departed")) {
            santaStatus.style.color = "#c41e3a";
            santaStatus.style.fontWeight = "bold";
            connection.stop();
        }
    });

    connection.on("ReceiveMessage", (message) => {
        const li = document.createElement("li");
        li.textContent = message;
        document.getElementById("workshopUpdates").appendChild(li);
    });

    connection.start()
        .then(() => {
            console.log("Connected to Santa's Workshop!");
            document.getElementById("connectionStatus").textContent = "Connected";
        })
        .catch((err) => {
            console.error(`Connection error: ${err.toString()}`);
            document.getElementById("connectionStatus").textContent = "Connection failed";
            alert("Failed to connect to Santa's Workshop. Please try refreshing the page.");
        });

    connection.onclose((error) => {
        console.error(`Connection closed: ${error ? error.message : "Unknown error"}`);
        document.getElementById("connectionStatus").textContent = "Disconnected";
        alert("Connection to Santa's Workshop was lost. Please refresh the page to reconnect.");
    });

</script>
</body>
</html>

This magical interface brings Santa’s workshop to life, updating faster than Rudolph’s nose blinks! 🎅✨

🎅 Launching Santa’s Workshop! 🎄

Fire up the magic with:

dotnet run

Then, dash over to http://localhost:5000 in your browser to watch Santa’s Workshop Live Tracker sparkle into action! ✨🎁

You should see something that looks an awful lot like this!

alt text

🎁 Wrapping Up the Workshop

🎄🎅 Ho ho ho! Time to unwrap the festive .NET 9 features for each class.

🎅🎄🏗️ Program.cs: The North Pole’s Command Center

🧝‍♂️ Elf Code Workshop: The Magical WorkshopSimulator 🧝‍♀️

🔔 SantasWorkshopHub.cs: Santa’s Communication Network

🎁 General .NET 9 Goodies:

These magical .NET 9 features will make this the most productive Christmas season yet for developers worldwide! 🌟🎄

🔧 Troubleshooting

Here are some common issues developers might encounter and how to resolve them:

  1. SignalR Connection IssuesProblem: SignalR fails to establish a connection.
    Solution: Ensure your firewall isn’t blocking WebSocket connections. Check the console for any CORS-related errors and update your CORS policy if necessary.

    // In Program.cs
    builder.Services.AddCors(options =>
    {
        options.AddPolicy("CorsPolicy", builder => builder
            .WithOrigins("http://localhost:5000")
            .AllowAnyMethod()
            .AllowAnyHeader()
            .AllowCredentials());
    });
  2. Performance Bottlenecks in Toy ProductionProblem: Toy production simulation runs slowly.
    Solution: Utilize parallel processing for elf activities and consider using System.Threading.Channels for efficient producer-consumer scenarios.

    // In WorkshopSimulator.cs
    private Channel<ElfActivity> _elfActivityChannel = Channel.CreateUnbounded<ElfActivity>();
    
    // Producer
    await _elfActivityChannel.Writer.WriteAsync(new ElfActivity(elfName, activity));
    
    // Consumer
    while (await _elfActivityChannel.Reader.WaitToReadAsync())
    {
        if (_elfActivityChannel.Reader.TryRead(out var elfActivity))
        {
            await ProcessElfActivity(elfActivity);
        }
    }
  3. Memory Leaks in Long-Running SimulationsProblem: Application memory usage grows over time.
    Solution: Implement proper disposal of resources and use weak references for long-lived objects.

    // In WorkshopSimulator.cs
    private WeakReference<List<ElfActivity>> _recentActivities = new WeakReference<List<ElfActivity>>(new List<ElfActivity>());
    
    // Usage
    if (_recentActivities.TryGetTarget(out var activities))
    {
        activities.Add(new ElfActivity(elfName, activity));
    }

Important Tip*: Always monitor your application’s performance using tools like dotnet-counters or Application Insights to catch and address issues early.* Warning*: Be cautious when implementing parallel processing in SignalR hubs. Ensure thread-safety and consider using* SemaphoreSlim for controlled concurrency.


Share this post on:

Previous Post
Scheduling GUI Application Launch on Wayland with Systemd User Timers
Next Post
Building a Real-Time IoT Dashboard with HarperDB and Node.js