DEV Community

Christopher C. Johnson
Christopher C. Johnson

Posted on • Originally published at thatamazingprogrammer.com on

2

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.

]]>

Retry later

Top comments (0)

Retry later
Retry later