DEV Community

Christopher C. Johnson
Christopher C. Johnson

Posted on • Originally published at thatamazingprogrammer.com on

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

Image description

This blog post is part of the 2024 C# Advent Calendar. Go check out the other 49 great posts after you read mine, of course.

๐ŸŽ„๐ŸŽ… Ho Ho Ho! Welcome to Santa's Coding Workshop! ๐Ÿง๐Ÿ’ป

'Tis the season to be jolly... and to code! Grab your candy cane stylus and put on your coding mittens because you're about to embark on a magical journey to the North Pole's most high-tech workshop!

In this merry tutorial, you'll craft a real-time web application that brings Santa's workshop to life, powered by the enchanting duo of SignalR and .NET 9. Imagine a digital snow globe where you can peek inside and see:

  • ๐Ÿงธ Elves busily crafting toys

  • ๐ŸŽ Gift-wrapping stations buzzing with activity

  • ๐ŸฆŒ Reindeer munching on magical carrots

  • ๐Ÿช Ovens full of Mrs. Claus's famous cookies

  • ๐Ÿ“œ The Naughty-or-Nice list being checked twice

All updating in real-time, faster than you can say "Jingle Bells"!Our festive application showcases modern web development practices, all wrapped up in a bow of Christmas cheer. Through a clean, simple interface that would make even Frosty the Snowman proud, users can:

  • ๐Ÿงฎ Watch the toy count climb towards Santa's goal

  • ๐Ÿ‘€ Spy on the activities of elves like Buddy, Jingle, Sparkle, Twinkle, and Holly

  • ๐ŸŽ… Keep tabs on Santa's status

And the best part? No need to refresh the page - it's all as smooth as a sleigh ride!

As you journey through the code, you'll see how to harness the power of WebSocket connections, sprinkled in some C# 13 magic dust, and leveraged SignalR's real-time capabilities to create an engaging and interactive workshop experience.

So, hang your stockings by the chimney with care, and dive into the code to see how the magic happens! Remember, in Santa's workshop, every bug is just a feature in disguise, and every successful compile is like finding a present under the tree.

Ready to code up some Christmas cheer? Jingle all the way to a winter wonderland of web development! ๐ŸŽ„

๐ŸŽ„ 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
Enter fullscreen mode Exit fullscreen mode

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

cd SantasWorkshopTracker

dotnet add package Microsoft.AspNetCore.SignalR.Client
Enter fullscreen mode Exit fullscreen mode

With these steps, you're laying the foundation for a good time in Santas Workshop! Get ready to watch the magic unfold in an interactive experience thatll 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);

    }

}

Enter fullscreen mode Exit fullscreen mode

๐ŸŒŸ 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");
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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 ๐ŸŽŠ

  • Improved Tracing : With ActivitySource, you can track every toy made, every elf's activity, and Santa's status. It's like having a magical Christmas logbook! ๐Ÿ“–

  • Real-time Updates : SignalR ensures everyone gets updates faster than Rudolph can fly! ๐ŸฆŒ๐Ÿ’จ

  • Clean and Concise : Using records and modern C# features makes our code as neat as a wrapped gift!

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

            }

        }

    }

}

Enter fullscreen mode Exit fullscreen mode

๐ŸŽ 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 : Youve 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 : Youre 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();

Enter fullscreen mode Exit fullscreen mode

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>

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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!

๐ŸŽ 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

  • ๐Ÿš€ Native AOT compilation: Faster than Rudolph on Christmas Eve!

  • ๐ŸŽ Improved performance: Santa's sleigh just got a turbo boost!

  • ๐Ÿ”’ Enhanced security: Keeping those presents safe from the Grinch!

๐Ÿง Elf Code Workshop: The Magical WorkshopSimulator ๐Ÿง

  • JIT enhancements: Elves are working faster than ever!

  • ๐Ÿงฎ Loop optimizations: Counting toys quicker than you can say "Ho Ho Ho!"

  • ๐ŸŽจ PGO improvements: Painting toys with precision!

๐Ÿ”” SantasWorkshopHub.cs: Santa's Communication Network

  • ๐Ÿ“ก Improved tracing: Tracking elves better than Santa's naughty-or-nice list!

  • ๐Ÿš€ Native AOT compatibility: Messages flying faster than reindeer!

  • ๐Ÿงฉ Polymorphic hub method arguments: Handling different types of Christmas wishes!

๐ŸŽ General .NET 9 Goodies:

  • ๐Ÿง  AI building blocks: Helping elves make smarter toys!

  • ๐Ÿ“Š New Tensor types: Wrapping presents in multi-dimensional joy!

  • ๐Ÿ”‘ Improved cryptography: Keeping Santa's magic safe and sound!

  • ๐Ÿ“ฆ Enhanced ASP.NET Core: Delivering web presents smoother than ever!

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());
 });
Enter fullscreen mode Exit fullscreen mode
  1. 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);
     }
 }
Enter fullscreen mode Exit fullscreen mode
  1. 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));
 }
Enter fullscreen mode Exit fullscreen mode

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.

]]>

Top comments (0)