<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ravi Vishwakarma</title>
    <description>The latest articles on DEV Community by Ravi Vishwakarma (@ravi-vishwakarma-hash).</description>
    <link>https://dev.to/ravi-vishwakarma-hash</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F927623%2Fc560ed5b-5722-4b25-b9f7-36205b08c154.jpeg</url>
      <title>DEV Community: Ravi Vishwakarma</title>
      <link>https://dev.to/ravi-vishwakarma-hash</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ravi-vishwakarma-hash"/>
    <language>en</language>
    <item>
      <title>How Do You Delete Billions of WhatsApp Statuses Every Day Without Bringing Your Database to Its Knees?</title>
      <dc:creator>Ravi Vishwakarma</dc:creator>
      <pubDate>Sat, 22 Aug 2026 17:55:29 +0000</pubDate>
      <link>https://dev.to/ravi-vishwakarma-hash/how-do-you-delete-billions-of-whatsapp-statuses-every-day-without-bringing-your-database-to-its-4aja</link>
      <guid>https://dev.to/ravi-vishwakarma-hash/how-do-you-delete-billions-of-whatsapp-statuses-every-day-without-bringing-your-database-to-its-4aja</guid>
      <description>&lt;p&gt;Imagine you run a messaging app used by billions of people.&lt;/p&gt;

&lt;p&gt;Every day, people upload photos, videos, and text as &lt;strong&gt;Status updates&lt;/strong&gt;. But there is one important rule: a Status should disappear after about 24 hours.&lt;/p&gt;

&lt;p&gt;Now imagine you have to clean up &lt;strong&gt;billions of expired Statuses every day&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Your first thought might be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Easy. Just run a query that deletes everything older than 24 hours.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;statuses&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;expires_at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple, right?&lt;/p&gt;

&lt;p&gt;Unfortunately, at WhatsApp's scale, this can be a terrible idea.&lt;/p&gt;

&lt;p&gt;A giant delete can put enormous pressure on the database, causing slow queries, high disk usage, replication delays, locks, and even outages.&lt;/p&gt;

&lt;p&gt;So how do large systems actually approach this problem?&lt;/p&gt;

&lt;p&gt;Let's understand it from the ground up.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. First: What Is a WhatsApp Status?
&lt;/h2&gt;

&lt;p&gt;A Status is basically a temporary piece of content.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Alice posts a photo at 10:00 AM.&lt;/li&gt;
&lt;li&gt;Bob posts a video at 2:00 PM.&lt;/li&gt;
&lt;li&gt;Carol posts some text at 8:00 PM.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After roughly 24 hours, these Statuses are no longer supposed to be visible.&lt;/p&gt;

&lt;p&gt;From a database perspective, you might store something like:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;status_id&lt;/th&gt;
&lt;th&gt;user_id&lt;/th&gt;
&lt;th&gt;content&lt;/th&gt;
&lt;th&gt;created_at&lt;/th&gt;
&lt;th&gt;expires_at&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;td&gt;photo.jpg&lt;/td&gt;
&lt;td&gt;10:00&lt;/td&gt;
&lt;td&gt;10:00 next day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;td&gt;video.mp4&lt;/td&gt;
&lt;td&gt;14:00&lt;/td&gt;
&lt;td&gt;14:00 next day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;103&lt;/td&gt;
&lt;td&gt;Carol&lt;/td&gt;
&lt;td&gt;"Hello!"&lt;/td&gt;
&lt;td&gt;20:00&lt;/td&gt;
&lt;td&gt;20:00 next day&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The database now has a simple job:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Find expired records and remove them.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But there's a catch.&lt;/p&gt;

&lt;p&gt;At massive scale, there can be an enormous number of expired records.&lt;/p&gt;




&lt;h1&gt;
  
  
  2. Why Can't We Just Delete Everything at Once?
&lt;/h1&gt;

&lt;p&gt;Suppose there are &lt;strong&gt;1 billion expired records&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You might write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;statuses&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;expires_at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The database now has to find and delete a huge number of rows.&lt;/p&gt;

&lt;p&gt;That can cause several problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem #1: Huge amount of work
&lt;/h3&gt;

&lt;p&gt;Deleting a row isn't always as simple as removing a line from a file.&lt;/p&gt;

&lt;p&gt;The database may need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Locate the row.&lt;/li&gt;
&lt;li&gt;Modify indexes.&lt;/li&gt;
&lt;li&gt;Write transaction information.&lt;/li&gt;
&lt;li&gt;Update storage structures.&lt;/li&gt;
&lt;li&gt;Replicate the changes.&lt;/li&gt;
&lt;li&gt;Eventually reclaim the space.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Multiply that by billions.&lt;/p&gt;

&lt;p&gt;That's a lot of work.&lt;/p&gt;




&lt;h1&gt;
  
  
  3. The Database Has Other Jobs Too
&lt;/h1&gt;

&lt;p&gt;This is perhaps the most important idea for beginners.&lt;/p&gt;

&lt;p&gt;Your database isn't sitting around waiting for the cleanup job.&lt;/p&gt;

&lt;p&gt;While the cleanup system is deleting old Statuses, users are still:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;uploading new Statuses,&lt;/li&gt;
&lt;li&gt;viewing Statuses,&lt;/li&gt;
&lt;li&gt;sending messages,&lt;/li&gt;
&lt;li&gt;opening chats,&lt;/li&gt;
&lt;li&gt;checking contacts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The database needs to handle those requests too.&lt;/p&gt;

&lt;p&gt;Think of a restaurant.&lt;/p&gt;

&lt;p&gt;Imagine a restaurant has one kitchen.&lt;/p&gt;

&lt;p&gt;During dinner, hundreds of customers are ordering food.&lt;/p&gt;

&lt;p&gt;Now someone says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Before serving any more customers, let's completely clean the kitchen.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The kitchen might become extremely busy cleaning instead of cooking.&lt;/p&gt;

&lt;p&gt;A massive database deletion can create a similar problem.&lt;/p&gt;




&lt;h1&gt;
  
  
  4. The Basic Solution: Delete in Small Batches
&lt;/h1&gt;

&lt;p&gt;Instead of deleting one billion rows in one operation, divide the work into small pieces.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;statuses&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;expires_at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then repeat.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Delete 10,000
       ↓
Delete 10,000
       ↓
Delete 10,000
       ↓
Delete 10,000
       ↓
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the database gets many small jobs instead of one enormous job.&lt;/p&gt;

&lt;p&gt;This is called &lt;strong&gt;batch deletion&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  5. Why Batches Are Better
&lt;/h1&gt;

&lt;p&gt;Suppose you need to remove 100 million records.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100,000,000 rows
       ↓
ONE MASSIVE DELETE
       ↓
Database suffers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you could do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10,000 rows
10,000 rows
10,000 rows
10,000 rows
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each transaction is relatively small.&lt;/p&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;transactions finish faster,&lt;/li&gt;
&lt;li&gt;locks are held for less time,&lt;/li&gt;
&lt;li&gt;memory pressure is lower,&lt;/li&gt;
&lt;li&gt;replication can keep up more easily,&lt;/li&gt;
&lt;li&gt;other queries get opportunities to run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal isn't merely to delete data.&lt;/p&gt;

&lt;p&gt;The goal is to &lt;strong&gt;delete data without disturbing the rest of the system&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  6. But There's Another Problem
&lt;/h1&gt;

&lt;p&gt;You might now say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Fine. We'll delete 10,000 rows at a time.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But how do we find those 10,000 expired rows?&lt;/p&gt;

&lt;p&gt;Suppose the table looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;statuses
--------------------------------
id
user_id
content
created_at
expires_at
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;expires_at&lt;/code&gt; isn't indexed, the database may have to scan a huge portion of the table to discover which rows are expired.&lt;/p&gt;

&lt;p&gt;That's expensive.&lt;/p&gt;

&lt;p&gt;So we usually want an index.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_status_expiry&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;statuses&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expires_at&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the database can efficiently find records whose expiration time has passed.&lt;/p&gt;

&lt;p&gt;Think of an index like the index at the back of a textbook.&lt;/p&gt;

&lt;p&gt;Without the index, you might have to read every page to find a topic.&lt;/p&gt;

&lt;p&gt;With the index, you can jump closer to where the information lives.&lt;/p&gt;




&lt;h1&gt;
  
  
  7. Don't Delete Randomly — Delete in Order
&lt;/h1&gt;

&lt;p&gt;There's another optimization.&lt;/p&gt;

&lt;p&gt;Suppose expired records have IDs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1001
1002
1003
1004
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of repeatedly asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Give me any 10,000 expired rows.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;we can process them in a predictable order.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;statuses&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;expires_at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;expires_at&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, in some database designs, we can use an ID/cursor-based approach.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Start at record 0
       ↓
Process 10,000
       ↓
Remember where we stopped
       ↓
Process the next 10,000
       ↓
Repeat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is often called &lt;strong&gt;cursor-based processing&lt;/strong&gt; or &lt;strong&gt;keyset pagination&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  8. The Secret Weapon: Don't Wake Up Every Millisecond
&lt;/h1&gt;

&lt;p&gt;Here's another beginner mistake.&lt;/p&gt;

&lt;p&gt;You might create a worker that constantly asks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Anything expired?"
"Anything expired?"
"Anything expired?"
"Anything expired?"
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's unnecessary.&lt;/p&gt;

&lt;p&gt;Instead, a cleanup system can work on a schedule.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;12:00 → clean expired data
12:01 → clean expired data
12:02 → clean expired data
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or it can continuously process a queue at a controlled rate.&lt;/p&gt;

&lt;p&gt;The important concept is &lt;strong&gt;backpressure&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  9. What Is Backpressure?
&lt;/h1&gt;

&lt;p&gt;Backpressure simply means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Slow down when the database is under heavy load.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Imagine your cleanup worker can delete 100,000 records per second.&lt;/p&gt;

&lt;p&gt;But the database is already extremely busy.&lt;/p&gt;

&lt;p&gt;Instead of saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I must delete 100,000 per second!”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;the cleanup system can say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The database is busy. I'll reduce my deletion rate.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database healthy
→ delete faster

Database busy
→ delete slower

Database overloaded
→ pause cleanup temporarily
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is extremely useful in large distributed systems.&lt;/p&gt;

&lt;p&gt;Cleanup is important.&lt;/p&gt;

&lt;p&gt;But serving users is usually more important.&lt;/p&gt;




&lt;h1&gt;
  
  
  10. Even Better: Use a Queue
&lt;/h1&gt;

&lt;p&gt;Instead of having one giant cleanup process constantly scanning the database, you can use a queue.&lt;/p&gt;

&lt;p&gt;Imagine every Status gets an expiration event.&lt;/p&gt;

&lt;p&gt;When Alice creates a Status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Alice creates Status
        ↓
Status expires in 24 hours
        ↓
Create expiration task
        ↓
Queue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Queue
 ↓
Worker
 ↓
Delete Status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can have many workers processing the queue.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 ┌── Worker 1
Queue ───────────┼── Worker 2
                 ├── Worker 3
                 └── Worker 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now cleanup can scale horizontally.&lt;/p&gt;

&lt;p&gt;Need more capacity?&lt;/p&gt;

&lt;p&gt;Add more workers.&lt;/p&gt;

&lt;p&gt;Need less capacity?&lt;/p&gt;

&lt;p&gt;Remove workers.&lt;/p&gt;




&lt;h1&gt;
  
  
  11. But What If a Worker Crashes?
&lt;/h1&gt;

&lt;p&gt;Distributed systems assume something will eventually fail.&lt;/p&gt;

&lt;p&gt;A worker might crash halfway through its work.&lt;/p&gt;

&lt;p&gt;That's why cleanup jobs should generally be &lt;strong&gt;idempotent&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Don't worry about the fancy word.&lt;/p&gt;

&lt;p&gt;It basically means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Running the same cleanup operation again should not cause a disaster.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Delete Status 123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If Status 123 is already gone, trying to delete it again shouldn't break the system.&lt;/p&gt;

&lt;p&gt;This makes retries much safer.&lt;/p&gt;




&lt;h1&gt;
  
  
  12. A Very Important Idea: Soft Delete vs Hard Delete
&lt;/h1&gt;

&lt;p&gt;There are actually two different meanings of "delete."&lt;/p&gt;

&lt;h3&gt;
  
  
  Soft delete
&lt;/h3&gt;

&lt;p&gt;Instead of physically removing the row, mark it as deleted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;deleted = true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application simply ignores deleted records.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hard delete
&lt;/h3&gt;

&lt;p&gt;Actually remove the database record.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;statuses&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Large systems may sometimes use a combination of both approaches.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Status expires
      ↓
Immediately stop showing it
      ↓
Physical cleanup happens later
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful because the user-facing requirement is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Don't show expired content.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It doesn't necessarily mean:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The physical database bytes must disappear at exactly 24:00:00.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Those are two different requirements.&lt;/p&gt;




&lt;h1&gt;
  
  
  13. This Difference Is Huge
&lt;/h1&gt;

&lt;p&gt;Suppose a Status expires at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10:00:00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application can immediately treat it as unavailable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;expires_at &amp;lt;= current_time
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the user can't see it anymore.&lt;/p&gt;

&lt;p&gt;The physical deletion might happen at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10:02
10:05
10:20
11:00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;depending on system load.&lt;/p&gt;

&lt;p&gt;That's often perfectly acceptable.&lt;/p&gt;

&lt;p&gt;This gives the cleanup system flexibility.&lt;/p&gt;




&lt;h1&gt;
  
  
  14. What About Photos and Videos?
&lt;/h1&gt;

&lt;p&gt;There's another important detail.&lt;/p&gt;

&lt;p&gt;The database might not actually contain the entire photo or video.&lt;/p&gt;

&lt;p&gt;A database record might simply contain metadata:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;status_id
user_id
storage_location
created_at
expires_at
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual video could live in an object-storage system.&lt;/p&gt;

&lt;p&gt;So deleting a Status may involve multiple steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database record
      +
Media object
      +
Caches
      +
Indexes
      +
Metadata
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You need to make sure all of these eventually get cleaned up.&lt;/p&gt;




&lt;h1&gt;
  
  
  15. Don't Delete Everything at the Exact Expiration Time
&lt;/h1&gt;

&lt;p&gt;Here's a subtle but important design idea.&lt;/p&gt;

&lt;p&gt;Imagine 100 million people uploaded Statuses between 10:00 AM and 10:01 AM.&lt;/p&gt;

&lt;p&gt;Twenty-four hours later, a huge number of objects become eligible for deletion at roughly the same time.&lt;/p&gt;

&lt;p&gt;If your system tries to delete all of them immediately, you get a &lt;strong&gt;thundering herd&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead, you can spread the cleanup work out.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10:00 → 100,000 deletions
10:01 → 100,000 deletions
10:02 → 100,000 deletions
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The work is distributed over time.&lt;/p&gt;

&lt;p&gt;This makes the system much smoother.&lt;/p&gt;




&lt;h1&gt;
  
  
  16. Partitioning Can Help Too
&lt;/h1&gt;

&lt;p&gt;At very large scale, a single enormous table can become difficult to manage.&lt;/p&gt;

&lt;p&gt;One technique is &lt;strong&gt;partitioning&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Imagine splitting Status data by time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;statuses_2026_08_20
statuses_2026_08_21
statuses_2026_08_22
statuses_2026_08_23
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now old data is grouped together.&lt;/p&gt;

&lt;p&gt;Instead of deleting billions of individual rows one by one, a database may be able to remove or detach an entire old partition.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Old partition
      ↓
No longer needed
      ↓
Drop partition
      ↓
Large amount of data removed efficiently
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can be dramatically cheaper than row-by-row deletion, depending on the database architecture.&lt;/p&gt;

&lt;p&gt;However, partitioning isn't a magic solution. It has its own design and operational trade-offs.&lt;/p&gt;




&lt;h1&gt;
  
  
  17. A Realistic Architecture
&lt;/h1&gt;

&lt;p&gt;A simplified large-scale design might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;              User creates Status
                       │
                       ▼
                 Status Service
                       │
             ┌─────────┴─────────┐
             ▼                   ▼
        Database             Media Storage
             │
             ▼
      Expiration Scheduler
             │
             ▼
       Cleanup Queue
             │
       ┌─────┼─────┐
       ▼     ▼     ▼
    Worker Worker Worker
       │     │     │
       └─────┼─────┘
             ▼
        Batch Delete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key idea is that &lt;strong&gt;expiration and physical deletion are separate concerns&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  18. What Happens If the Database Is Overloaded?
&lt;/h1&gt;

&lt;p&gt;A good cleanup system should be able to say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Not now.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cleanup worker
      ↓
Check database health
      ↓
Is database overloaded?
   /          \
 Yes           No
  ↓             ↓
Slow down    Continue
or pause     deleting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a crucial production principle:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Background work should be a good citizen.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;User-facing traffic gets priority.&lt;/p&gt;

&lt;p&gt;Cleanup can wait.&lt;/p&gt;




&lt;h1&gt;
  
  
  19. Monitoring Is Just as Important
&lt;/h1&gt;

&lt;p&gt;You don't want to build the cleanup system and forget about it.&lt;/p&gt;

&lt;p&gt;You need metrics.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;h3&gt;
  
  
  Expired items waiting
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;50 million
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cleanup rate
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;200,000 items/sec
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Database CPU
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;45%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Queue size
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2 million jobs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cleanup latency
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Average: 3 minutes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can see whether the system is keeping up.&lt;/p&gt;




&lt;h1&gt;
  
  
  20. What If Cleanup Falls Behind?
&lt;/h1&gt;

&lt;p&gt;Suppose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;New expired Statuses:
1 billion/day

Cleanup capacity:
800 million/day
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You're losing the race.&lt;/p&gt;

&lt;p&gt;The queue will grow forever.&lt;/p&gt;

&lt;p&gt;So the system needs to scale.&lt;/p&gt;

&lt;p&gt;Possible solutions include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;increase worker count,&lt;/li&gt;
&lt;li&gt;increase batch size carefully,&lt;/li&gt;
&lt;li&gt;optimize indexes,&lt;/li&gt;
&lt;li&gt;partition the data,&lt;/li&gt;
&lt;li&gt;move cleanup to replicas or specialized stores where appropriate,&lt;/li&gt;
&lt;li&gt;spread work over a longer window,&lt;/li&gt;
&lt;li&gt;improve storage architecture.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important thing is to measure the system instead of guessing.&lt;/p&gt;




&lt;h1&gt;
  
  
  21. The Beginner's Mental Model
&lt;/h1&gt;

&lt;p&gt;You don't need to understand every database engine to understand the core idea.&lt;/p&gt;

&lt;p&gt;Think of expired Statuses as garbage.&lt;/p&gt;

&lt;p&gt;Imagine a city produces &lt;strong&gt;billions of pieces of garbage every day&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You wouldn't wait until the end of the month and tell one truck:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Collect everything.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You'd probably use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;many trucks,&lt;/li&gt;
&lt;li&gt;scheduled pickups,&lt;/li&gt;
&lt;li&gt;different neighborhoods,&lt;/li&gt;
&lt;li&gt;manageable loads,&lt;/li&gt;
&lt;li&gt;monitoring,&lt;/li&gt;
&lt;li&gt;extra capacity during busy periods.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Database cleanup works in a similar way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't create one giant cleanup operation. Build a controlled garbage-collection system.&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  22. The Big Lessons
&lt;/h1&gt;

&lt;p&gt;If you remember only a few things, remember these:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Don't perform gigantic deletes
&lt;/h3&gt;

&lt;p&gt;Break the work into batches.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10,000
10,000
10,000
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Make finding expired data cheap
&lt;/h3&gt;

&lt;p&gt;Indexes on expiration-related fields can be important.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Don't block user traffic
&lt;/h3&gt;

&lt;p&gt;Cleanup should run as background work.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Use queues and workers when appropriate
&lt;/h3&gt;

&lt;p&gt;They allow cleanup to scale horizontally.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Use backpressure
&lt;/h3&gt;

&lt;p&gt;When the database is busy, slow down.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Consider partitioning
&lt;/h3&gt;

&lt;p&gt;Time-based data can sometimes be efficiently managed through partitions.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Separate logical expiration from physical deletion
&lt;/h3&gt;

&lt;p&gt;A Status can stop being visible immediately while physical cleanup happens later.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Make cleanup retry-safe
&lt;/h3&gt;

&lt;p&gt;Failures are normal in distributed systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Monitor everything
&lt;/h3&gt;

&lt;p&gt;Know how much data is waiting and how quickly you're processing it.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Picture
&lt;/h1&gt;

&lt;p&gt;So, how can a service like WhatsApp deal with billions of temporary Statuses?&lt;/p&gt;

&lt;p&gt;The answer isn't one magical SQL query.&lt;/p&gt;

&lt;p&gt;It's an &lt;strong&gt;architecture&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Billions of expired records
          ↓
     ONE DELETE
          ↓
     💥 Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you want something closer to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;              Expired Statuses
                     │
                     ▼
              Cleanup Queue
                     │
          ┌──────────┼──────────┐
          ▼          ▼          ▼
       Worker      Worker      Worker
          │          │          │
          └──────────┼──────────┘
                     ▼
               Small Batches
                     │
                     ▼
              Database / Storage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trick is not to make the database &lt;strong&gt;delete billions of things instantly&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The trick is to make the system &lt;strong&gt;continuously and safely remove expired data at a controlled rate&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That's one of the fundamental lessons of large-scale backend engineering:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;At massive scale, the question isn't just “Can we do this?” It's “Can we do this continuously without hurting everything else?”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>database</category>
      <category>performance</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Optimistic and Pessimistic Locking in .NET with SQL Server</title>
      <dc:creator>Ravi Vishwakarma</dc:creator>
      <pubDate>Sat, 22 Aug 2026 14:31:54 +0000</pubDate>
      <link>https://dev.to/ravi-vishwakarma-hash/optimistic-and-pessimistic-locking-in-net-with-sql-server-29nj</link>
      <guid>https://dev.to/ravi-vishwakarma-hash/optimistic-and-pessimistic-locking-in-net-with-sql-server-29nj</guid>
      <description>&lt;p&gt;When multiple users access the same application at the same time, they may also try to read or update the &lt;strong&gt;same database record&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User A opens a product and changes its price.&lt;/li&gt;
&lt;li&gt;User B opens the same product at the same time and also changes its price.&lt;/li&gt;
&lt;li&gt;Both users click &lt;strong&gt;Save&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What happens?&lt;/p&gt;

&lt;p&gt;Without a strategy to handle concurrent updates, one user's changes may silently overwrite the other's. This is where &lt;strong&gt;concurrency control&lt;/strong&gt; and database locking strategies become important.&lt;/p&gt;

&lt;p&gt;Two common approaches are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Optimistic locking&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pessimistic locking&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this article, we'll learn both approaches using &lt;strong&gt;.NET and SQL Server&lt;/strong&gt;, with beginner-friendly examples and commented code.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Problem Are We Trying to Solve?
&lt;/h2&gt;

&lt;p&gt;Imagine we have a simple &lt;code&gt;Products&lt;/code&gt; table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;Products&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="n"&gt;NVARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Price&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The table contains this record:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Id&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Laptop&lt;/td&gt;
&lt;td&gt;50000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Now imagine two users open this product at exactly the same time.&lt;/p&gt;

&lt;h3&gt;
  
  
  User A
&lt;/h3&gt;

&lt;p&gt;Reads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Laptop - ₹50,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;User A changes the price to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;₹55,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  User B
&lt;/h3&gt;

&lt;p&gt;At the same time, User B also reads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Laptop - ₹50,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;User B changes the price to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;₹48,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If User A saves first and User B saves afterward, User B's update may overwrite User A's change.&lt;/p&gt;

&lt;p&gt;This is often called a &lt;strong&gt;lost update&lt;/strong&gt; problem.&lt;/p&gt;

&lt;p&gt;We need a way to detect or prevent this conflict.&lt;/p&gt;




&lt;h1&gt;
  
  
  1. What Is Optimistic Locking?
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Optimistic locking assumes that conflicts are rare.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The basic idea is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Let everyone read and work with the data. When someone tries to save, check whether the data has changed since they originally read it."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the data has not changed, the update succeeds.&lt;/p&gt;

&lt;p&gt;If the data has changed, the application detects a &lt;strong&gt;concurrency conflict&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Does It Work?
&lt;/h2&gt;

&lt;p&gt;A common approach is to add a &lt;code&gt;rowversion&lt;/code&gt; column to the SQL Server table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;Products&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="n"&gt;NVARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Price&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="c1"&gt;-- SQL Server automatically changes this value&lt;/span&gt;
    &lt;span class="c1"&gt;-- whenever the row is updated.&lt;/span&gt;
    &lt;span class="n"&gt;RowVersion&lt;/span&gt; &lt;span class="n"&gt;ROWVERSION&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;RowVersion&lt;/code&gt; column acts like a version identifier for the row.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Product before update:

Id: 1
Price: 50000
RowVersion: 0x00000000000007D1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the product is updated, SQL Server changes the &lt;code&gt;RowVersion&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Product after update:

Id: 1
Price: 55000
RowVersion: 0x00000000000007D2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when a user attempts to update the record, we can check whether the version they originally read is still the current version.&lt;/p&gt;




&lt;h1&gt;
  
  
  Optimistic Locking with ADO.NET
&lt;/h1&gt;

&lt;p&gt;First, let's create a simple C# model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Price&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// This stores the row version returned by SQL Server.&lt;/span&gt;
    &lt;span class="c1"&gt;// It helps us detect whether another user changed the row.&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;RowVersion&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 1: Read the Product
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Data.SqlClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetProductAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Replace this with your actual connection string.&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;connectionString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="s"&gt;"Server=.;Database=ShopDb;Trusted_Connection=True;TrustServerCertificate=True;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SqlConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connectionString&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OpenAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;@"
        SELECT Id, Name, Price, RowVersion
        FROM Products
        WHERE Id = @Id"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SqlCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Use parameters instead of string concatenation.&lt;/span&gt;
    &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"@Id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteReaderAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAsync&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetInt32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Price&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetDecimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

        &lt;span class="c1"&gt;// Save the row version that existed when we read the record.&lt;/span&gt;
        &lt;span class="n"&gt;RowVersion&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"RowVersion"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the user loads the product, we also store its current &lt;code&gt;RowVersion&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Later, when the user clicks &lt;strong&gt;Save&lt;/strong&gt;, we use that version in the &lt;code&gt;UPDATE&lt;/code&gt; statement.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Update the Product Safely
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;UpdateProductAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;connectionString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="s"&gt;"Server=.;Database=ShopDb;Trusted_Connection=True;TrustServerCertificate=True;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SqlConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connectionString&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OpenAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;@"
        UPDATE Products
        SET
            Name = @Name,
            Price = @Price
        WHERE
            Id = @Id

            -- Only update if the row version is still the same
            -- as when the user originally read the record.
            AND RowVersion = @RowVersion"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SqlCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"@Id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"@Name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"@Price"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Price&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"@RowVersion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SqlDbType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Timestamp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RowVersion&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// ExecuteNonQueryAsync returns the number of affected rows.&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;affectedRows&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteNonQueryAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// If 1 row was updated, the operation succeeded.&lt;/span&gt;
    &lt;span class="c1"&gt;// If 0 rows were updated, someone may have changed&lt;/span&gt;
    &lt;span class="c1"&gt;// the record before us.&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;affectedRows&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now imagine this sequence:&lt;/p&gt;

&lt;h3&gt;
  
  
  User A reads the product
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Price: 50000
RowVersion: Version 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  User B reads the product
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Price: 50000
RowVersion: Version 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  User A updates the product
&lt;/h3&gt;

&lt;p&gt;The product becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Price: 55000
RowVersion: Version 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  User B tries to update
&lt;/h3&gt;

&lt;p&gt;But User B still has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RowVersion: Version 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SQL query checks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;RowVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;RowVersion&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since the database now contains Version 2, no row matches the condition.&lt;/p&gt;

&lt;p&gt;Therefore:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;affectedRows = 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application can now tell User B:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This product was modified by another user. Please reload the latest data and try again."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is &lt;strong&gt;optimistic locking&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Optimistic Locking with Entity Framework Core
&lt;/h1&gt;

&lt;p&gt;Entity Framework Core makes optimistic concurrency easier.&lt;/p&gt;

&lt;p&gt;First, create the model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.ComponentModel.DataAnnotations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Price&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// [Timestamp] tells EF Core that this property&lt;/span&gt;
    &lt;span class="c1"&gt;// should be used for optimistic concurrency checking.&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Timestamp&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;RowVersion&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also configure it using Fluent API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnModelCreating&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModelBuilder&lt;/span&gt; &lt;span class="n"&gt;modelBuilder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;modelBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Entity&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RowVersion&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsRowVersion&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When EF Core detects that another user changed the row before &lt;code&gt;SaveChangesAsync()&lt;/code&gt;, it can throw a concurrency exception.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Get the product.&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FindAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Change the price.&lt;/span&gt;
    &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Price&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;55000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// EF Core generates an UPDATE statement that checks&lt;/span&gt;
    &lt;span class="c1"&gt;// the original concurrency value.&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveChangesAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Product updated successfully."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DbUpdateConcurrencyException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// This happens when another user changed or deleted&lt;/span&gt;
    &lt;span class="c1"&gt;// the record before our update was completed.&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"The product was modified by another user. "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
        &lt;span class="s"&gt;"Reload the latest data and try again."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one reason &lt;code&gt;rowversion&lt;/code&gt; is commonly used with EF Core applications.&lt;/p&gt;




&lt;h1&gt;
  
  
  Advantages of Optimistic Locking
&lt;/h1&gt;

&lt;p&gt;Optimistic locking works well when conflicts are relatively uncommon.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Users are not blocked while reading data.&lt;/li&gt;
&lt;li&gt;Long-running locks are avoided.&lt;/li&gt;
&lt;li&gt;It generally scales well for many read operations.&lt;/li&gt;
&lt;li&gt;It is useful for web applications where users may keep a page open for several minutes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Disadvantages
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The application must handle concurrency conflicts.&lt;/li&gt;
&lt;li&gt;A user may spend time editing data and then discover that another user changed it.&lt;/li&gt;
&lt;li&gt;Users may need to reload and merge changes.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  2. What Is Pessimistic Locking?
&lt;/h1&gt;

&lt;p&gt;Pessimistic locking takes the opposite approach.&lt;/p&gt;

&lt;p&gt;It assumes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"A conflict might happen, so lock the data while it is being used."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In simple terms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User A accesses a row.&lt;/li&gt;
&lt;li&gt;The application places a lock on that row.&lt;/li&gt;
&lt;li&gt;User B tries to access or modify the same row.&lt;/li&gt;
&lt;li&gt;User B may have to wait until User A finishes and releases the lock.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The lock usually exists within a &lt;strong&gt;database transaction&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Pessimistic Locking Example with SQL Server
&lt;/h1&gt;

&lt;p&gt;Suppose we want to reserve a product row for update.&lt;/p&gt;

&lt;p&gt;We can use locking hints such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;UPDLOCK&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;ROWLOCK&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Products&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UPDLOCK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ROWLOCK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's understand these hints.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;UPDLOCK&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This requests an update lock while reading the row.&lt;/p&gt;

&lt;p&gt;The intention is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I am reading this row because I plan to update it."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;ROWLOCK&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This asks SQL Server to prefer row-level locking rather than locking a larger resource.&lt;/p&gt;

&lt;p&gt;However, it is important to understand that SQL Server manages locking internally, and locking hints are requests rather than a guarantee of a particular physical locking behavior in every situation.&lt;/p&gt;




&lt;h1&gt;
  
  
  Pessimistic Locking with ADO.NET
&lt;/h1&gt;

&lt;p&gt;Here is a simple example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Data.SqlClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;UpdateProductWithLockAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;newPrice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;connectionString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="s"&gt;"Server=.;Database=ShopDb;Trusted_Connection=True;TrustServerCertificate=True;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SqlConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connectionString&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OpenAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Start a database transaction.&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SqlTransaction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BeginTransactionAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;selectSql&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;@"
            SELECT Id, Name, Price
            FROM Products WITH (UPDLOCK, ROWLOCK)
            WHERE Id = @Id"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;selectCommand&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SqlCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selectSql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;selectCommand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"@Id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;selectCommand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteReaderAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAsync&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Product not found."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Read the current values while the transaction&lt;/span&gt;
        &lt;span class="c1"&gt;// is still holding the requested lock.&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;productName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;currentPrice&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetDecimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;$"Current price of &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;productName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;currentPrice&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Close the reader before executing another command.&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CloseAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;updateSql&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;@"
            UPDATE Products
            SET Price = @Price
            WHERE Id = @Id"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;updateCommand&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SqlCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;updateSql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;updateCommand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"@Id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;updateCommand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"@Price"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newPrice&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;updateCommand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteNonQueryAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Commit releases the transaction's locks.&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CommitAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Product updated successfully."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;catch&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// If something goes wrong, undo the transaction.&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RollbackAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;throw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;SELECT&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt;
&lt;span class="n"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Products&lt;/span&gt; &lt;span class="nf"&gt;WITH&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UPDLOCK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ROWLOCK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;@Id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;combined with a transaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SqlTransaction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BeginTransactionAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The lock remains relevant for the transaction according to SQL Server's locking and transaction behavior, helping protect the read-then-update operation from competing modifications.&lt;/p&gt;




&lt;h1&gt;
  
  
  A Simple Pessimistic Locking Scenario
&lt;/h1&gt;

&lt;p&gt;Imagine User A starts this transaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BEGIN TRANSACTION

Read Product 1 with an update lock

Update Product 1

COMMIT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While User A's transaction is still active, User B tries to perform a conflicting operation on the same data.&lt;/p&gt;

&lt;p&gt;Depending on the operation, isolation level, and locks involved, User B may wait until User A commits or rolls back.&lt;/p&gt;

&lt;p&gt;After User A executes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;COMMIT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the transaction completes and its locks are released.&lt;/p&gt;

&lt;p&gt;Then User B can continue.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Danger of Long Transactions
&lt;/h1&gt;

&lt;p&gt;Pessimistic locking can cause problems if transactions stay open for too long.&lt;/p&gt;

&lt;p&gt;For example, this is usually a bad idea:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Start transaction
2. Lock product
3. Wait for user to fill out a form
4. User goes for lunch
5. Commit transaction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That could cause other users to wait unnecessarily.&lt;/p&gt;

&lt;p&gt;Instead, transactions should generally be as short as possible.&lt;/p&gt;

&lt;p&gt;A better pattern is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. User fills out the form
2. Start transaction
3. Read and validate the current data
4. Update the data
5. Commit immediately
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Pessimistic Locking and Deadlocks
&lt;/h1&gt;

&lt;p&gt;Pessimistic locking can increase the possibility of &lt;strong&gt;blocking&lt;/strong&gt; and, in some situations, &lt;strong&gt;deadlocks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A deadlock can look like this:&lt;/p&gt;

&lt;h3&gt;
  
  
  Transaction A
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Locks Product 1
Waits for Product 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Transaction B
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Locks Product 2
Waits for Product 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now both transactions are waiting for each other.&lt;/p&gt;

&lt;p&gt;SQL Server detects the deadlock and chooses one transaction as the deadlock victim, rolling it back.&lt;/p&gt;

&lt;p&gt;One useful strategy for reducing deadlocks is to access resources in a consistent order.&lt;/p&gt;

&lt;p&gt;For example, if you always update products in ID order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Product 1
Product 2
Product 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rather than sometimes doing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Product 3
Product 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you can reduce certain deadlock scenarios.&lt;/p&gt;




&lt;h1&gt;
  
  
  Optimistic vs Pessimistic Locking
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Optimistic Locking&lt;/th&gt;
&lt;th&gt;Pessimistic Locking&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Assumption&lt;/td&gt;
&lt;td&gt;Conflicts are rare&lt;/td&gt;
&lt;td&gt;Conflicts are likely or costly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Locks held while user edits&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Usually no user-edit-duration lock; locks are held during the transaction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conflict handling&lt;/td&gt;
&lt;td&gt;Detect conflict during update&lt;/td&gt;
&lt;td&gt;Block or serialize conflicting access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scalability&lt;/td&gt;
&lt;td&gt;Often better for read-heavy apps&lt;/td&gt;
&lt;td&gt;Can suffer from blocking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complexity&lt;/td&gt;
&lt;td&gt;Requires conflict resolution&lt;/td&gt;
&lt;td&gt;Requires careful transaction management&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;Web apps, normal CRUD systems&lt;/td&gt;
&lt;td&gt;Short critical sections with high contention&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  Which One Should You Use?
&lt;/h1&gt;

&lt;p&gt;There is no universal answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Optimistic Locking When
&lt;/h2&gt;

&lt;p&gt;Choose optimistic locking when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Many users mostly read data.&lt;/li&gt;
&lt;li&gt;Simultaneous updates are relatively rare.&lt;/li&gt;
&lt;li&gt;Your application can ask users to reload or resolve conflicts.&lt;/li&gt;
&lt;li&gt;You are building a typical web application or business application.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For many CRUD applications, optimistic concurrency using SQL Server &lt;code&gt;rowversion&lt;/code&gt; is a practical starting point.&lt;/p&gt;




&lt;h2&gt;
  
  
  Use Pessimistic Locking When
&lt;/h2&gt;

&lt;p&gt;Consider pessimistic locking when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple processes frequently modify the same data.&lt;/li&gt;
&lt;li&gt;Conflicting updates would be especially problematic.&lt;/li&gt;
&lt;li&gt;You need to protect a short, critical read-modify-write operation.&lt;/li&gt;
&lt;li&gt;Waiting is preferable to allowing concurrent conflicting work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples might include certain inventory, reservation, or financial workflows—but the exact design should depend on the business rules and transaction requirements.&lt;/p&gt;




&lt;h1&gt;
  
  
  A Real-World Example: Booking the Last Seat
&lt;/h1&gt;

&lt;p&gt;Suppose there is only &lt;strong&gt;one seat left&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Two users try to book it at the same time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimistic Approach
&lt;/h2&gt;

&lt;p&gt;Both users may initially see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Available seats: 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first user successfully updates the record.&lt;/p&gt;

&lt;p&gt;The second user's update detects that the data changed.&lt;/p&gt;

&lt;p&gt;The second user receives a message:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Sorry, this seat is no longer available."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Pessimistic Approach
&lt;/h2&gt;

&lt;p&gt;The first transaction locks the relevant data, checks availability, and creates the booking.&lt;/p&gt;

&lt;p&gt;A competing transaction attempting a conflicting operation may wait.&lt;/p&gt;

&lt;p&gt;Once the first transaction completes, the next transaction checks the latest availability.&lt;/p&gt;

&lt;p&gt;If no seat remains, it does not create a booking.&lt;/p&gt;

&lt;p&gt;The important point is that the entire operation should be designed as a &lt;strong&gt;short atomic transaction&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  A Safer Inventory Pattern
&lt;/h1&gt;

&lt;p&gt;For inventory-like scenarios, you can often avoid a separate read followed by an update.&lt;/p&gt;

&lt;p&gt;Instead, perform the business rule directly in the &lt;code&gt;UPDATE&lt;/code&gt; statement.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;Products&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;Stock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Stock&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;Stock&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then check how many rows were affected.&lt;/p&gt;

&lt;p&gt;In C#:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;TryBuyProductAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;connectionString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="s"&gt;"Server=.;Database=ShopDb;Trusted_Connection=True;TrustServerCertificate=True;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SqlConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connectionString&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OpenAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;@"
        UPDATE Products
        SET Stock = Stock - 1
        WHERE Id = @Id
          AND Stock &amp;gt; 0"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SqlCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"@Id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// If affectedRows is 1, stock was available.&lt;/span&gt;
    &lt;span class="c1"&gt;// If affectedRows is 0, either the product does not exist&lt;/span&gt;
    &lt;span class="c1"&gt;// or there was no stock remaining.&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;affectedRows&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteNonQueryAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;affectedRows&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern is often simpler and safer than manually locking a row and then performing application-side logic.&lt;/p&gt;




&lt;h1&gt;
  
  
  A Simple Way to Remember the Difference
&lt;/h1&gt;

&lt;p&gt;Think of a shared document.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimistic Locking
&lt;/h2&gt;

&lt;p&gt;Two people can open and edit the document.&lt;/p&gt;

&lt;p&gt;When someone saves, the system checks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Has somebody else changed this since you opened it?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If yes, there is a conflict.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pessimistic Locking
&lt;/h2&gt;

&lt;p&gt;The first person effectively reserves the document for the critical operation.&lt;/p&gt;

&lt;p&gt;Other conflicting operations may need to wait until the first person is finished.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;Concurrency can initially sound complicated, but the core idea is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What should happen when two users try to change the same data at the same time?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Optimistic locking&lt;/strong&gt; says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Let them work independently. Detect the conflict when saving."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Pessimistic locking&lt;/strong&gt; says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Protect the critical section so conflicting operations are coordinated."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For many .NET applications using SQL Server, a good starting point is &lt;strong&gt;optimistic concurrency with a &lt;code&gt;rowversion&lt;/code&gt; column&lt;/strong&gt;. It avoids holding long-running locks and works naturally with Entity Framework Core.&lt;/p&gt;

&lt;p&gt;For operations where contention is high or correctness requires carefully coordinated access, a &lt;strong&gt;short transaction with appropriate locking or an atomic SQL operation&lt;/strong&gt; may be more suitable.&lt;/p&gt;

&lt;p&gt;The key is not simply choosing "optimistic" or "pessimistic." The best solution depends on your application's business rules, how frequently conflicts occur, and what should happen when two users attempt the same operation at the same time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Takeaways
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency&lt;/strong&gt; means multiple users or processes accessing data at the same time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimistic locking&lt;/strong&gt; detects conflicts when data is updated.&lt;/li&gt;
&lt;li&gt;SQL Server's &lt;code&gt;rowversion&lt;/code&gt; is commonly used for optimistic concurrency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pessimistic locking&lt;/strong&gt; coordinates conflicting access during a transaction.&lt;/li&gt;
&lt;li&gt;Keep database transactions as short as possible.&lt;/li&gt;
&lt;li&gt;Long-running locks can cause blocking and increase the risk of deadlocks.&lt;/li&gt;
&lt;li&gt;For some scenarios, an atomic SQL statement is better than manually implementing a lock.&lt;/li&gt;
&lt;li&gt;Always design concurrency around the actual business rule you need to protect.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>sqlserver</category>
      <category>dotnet</category>
      <category>sql</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Why HTTP Needed the QUERY Method</title>
      <dc:creator>Ravi Vishwakarma</dc:creator>
      <pubDate>Sat, 11 Jul 2026 09:17:42 +0000</pubDate>
      <link>https://dev.to/ravi-vishwakarma-hash/why-http-needed-the-query-method-3p1h</link>
      <guid>https://dev.to/ravi-vishwakarma-hash/why-http-needed-the-query-method-3p1h</guid>
      <description>&lt;h1&gt;
  
  
  HTTP Finally Gets a New Request Method: Meet &lt;code&gt;QUERY&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;For years, HTTP developers have worked with a familiar set of request methods—&lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt;, &lt;code&gt;PATCH&lt;/code&gt;, and &lt;code&gt;DELETE&lt;/code&gt;. These methods have served the web remarkably well, but one common problem has always been awkward to solve:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do you send a complex query to the server without pretending it's a state-changing operation?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The answer was usually one of two compromises:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stuff everything into a long &lt;code&gt;GET&lt;/code&gt; URL.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;POST&lt;/code&gt; even though you're only reading data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, HTTP has a better answer.&lt;/p&gt;

&lt;p&gt;With &lt;strong&gt;RFC 10008&lt;/strong&gt;, the HTTP ecosystem introduces a new standardized request method: &lt;strong&gt;&lt;code&gt;QUERY&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with GET
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;GET&lt;/code&gt; is the natural choice for retrieving data because it is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Safe (doesn't modify server state)&lt;/li&gt;
&lt;li&gt;Idempotent&lt;/li&gt;
&lt;li&gt;Cache-friendly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, &lt;code&gt;GET&lt;/code&gt; requests carry parameters in the URL.&lt;/p&gt;

&lt;p&gt;Imagine searching an e-commerce website with dozens of filters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /products?category=laptops&amp;amp;brand=Dell&amp;amp;brand=Lenovo&amp;amp;priceMin=500&amp;amp;priceMax=1500&amp;amp;sort=-rating&amp;amp;page=3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As filters become more complex, URLs become:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Difficult to read&lt;/li&gt;
&lt;li&gt;Hard to maintain&lt;/li&gt;
&lt;li&gt;Limited by URL length restrictions in some environments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even worse, expressing deeply nested filters in a query string can become nearly impossible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Developers Used POST
&lt;/h2&gt;

&lt;p&gt;To avoid enormous URLs, many APIs switched to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /products/search
Content-Type: application/json

{
  "category": "laptops",
  "brands": ["Dell", "Lenovo"],
  "price": {
    "min": 500,
    "max": 1500
  },
  "sort": "-rating"
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works technically.&lt;/p&gt;

&lt;p&gt;But semantically, it's misleading.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;POST&lt;/code&gt; is intended for operations that may change server state, while a search is simply retrieving information.&lt;/p&gt;

&lt;p&gt;This mismatch affects caching, retries, API documentation, and HTTP semantics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter &lt;code&gt;QUERY&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The new &lt;code&gt;QUERY&lt;/code&gt; method is designed specifically for read-only operations that require a request body.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;QUERY /products
Content-Type: application/json

{
  "category": "laptops",
  "brands": ["Dell", "Lenovo"],
  "price": {
    "min": 500,
    "max": 1500
  },
  "sort": "-rating"
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the intent is clear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The client is retrieving data.&lt;/li&gt;
&lt;li&gt;The request can include a rich JSON body.&lt;/li&gt;
&lt;li&gt;The server promises not to modify server state.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Characteristics
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;QUERY&lt;/code&gt; is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Safe&lt;/li&gt;
&lt;li&gt;Idempotent&lt;/li&gt;
&lt;li&gt;Designed to support a request body&lt;/li&gt;
&lt;li&gt;Intended for complex retrieval operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In many situations, it becomes the ideal middle ground between &lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;POST&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  GET vs POST vs QUERY
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;GET&lt;/th&gt;
&lt;th&gt;POST&lt;/th&gt;
&lt;th&gt;QUERY&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Read data&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request body&lt;/td&gt;
&lt;td&gt;No (not generally used)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Safe&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Idempotent&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complex filters&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Correct HTTP semantics&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No (for reads)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl357bzbb94o6k1ahs2cr.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl357bzbb94o6k1ahs2cr.jpg" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Use Cases
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;QUERY&lt;/code&gt; is especially useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Advanced product searches&lt;/li&gt;
&lt;li&gt;Analytics dashboards&lt;/li&gt;
&lt;li&gt;Business intelligence queries&lt;/li&gt;
&lt;li&gt;Graph-style filtering&lt;/li&gt;
&lt;li&gt;GIS and map searches&lt;/li&gt;
&lt;li&gt;Large reporting APIs&lt;/li&gt;
&lt;li&gt;Log search platforms&lt;/li&gt;
&lt;li&gt;AI and vector database queries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Anywhere a client needs to send a structured query without changing data, &lt;code&gt;QUERY&lt;/code&gt; is a natural fit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Will It Replace POST?
&lt;/h2&gt;

&lt;p&gt;Not immediately.&lt;/p&gt;

&lt;p&gt;The web ecosystem is vast. Browsers, HTTP libraries, proxies, API gateways, CDNs, and frameworks all need to support the new method. Existing APIs that already use &lt;code&gt;POST /search&lt;/code&gt; will likely continue to do so until support for &lt;code&gt;QUERY&lt;/code&gt; becomes widespread.&lt;/p&gt;

&lt;p&gt;Over time, however, new APIs may adopt &lt;code&gt;QUERY&lt;/code&gt; to better express read-only operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Means for API Designers
&lt;/h2&gt;

&lt;p&gt;For years, developers had to choose between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Correct semantics (&lt;code&gt;GET&lt;/code&gt;) with awkward URLs&lt;/li&gt;
&lt;li&gt;Convenient request bodies (&lt;code&gt;POST&lt;/code&gt;) with incorrect semantics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;QUERY&lt;/code&gt; removes that trade-off.&lt;/p&gt;

&lt;p&gt;When an endpoint retrieves data but requires a structured request body, developers no longer have to misuse &lt;code&gt;POST&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;New HTTP methods are introduced very rarely.&lt;/p&gt;

&lt;p&gt;The introduction of &lt;code&gt;PATCH&lt;/code&gt; was a significant milestone, and now &lt;code&gt;QUERY&lt;/code&gt; represents another meaningful evolution of the HTTP protocol.&lt;/p&gt;

&lt;p&gt;It is a small addition with a significant impact, making HTTP more expressive while preserving the principles that have made it successful for decades.&lt;/p&gt;

&lt;p&gt;As support spreads across frameworks, servers, proxies, and client libraries, &lt;code&gt;QUERY&lt;/code&gt; has the potential to become the standard choice for complex, read-only operations.&lt;/p&gt;

&lt;p&gt;HTTP doesn't evolve often, but when it does, it's worth paying attention.&lt;/p&gt;

</description>
      <category>http</category>
      <category>development</category>
      <category>rfc</category>
      <category>programming</category>
    </item>
    <item>
      <title>Building a Live Data Dashboard with Angular and ASP.NET Core: A Complete Step-by-Step Guide (Beginner to Advanced)</title>
      <dc:creator>Ravi Vishwakarma</dc:creator>
      <pubDate>Tue, 30 Jun 2026 06:39:12 +0000</pubDate>
      <link>https://dev.to/ravi-vishwakarma-hash/building-a-live-data-dashboard-with-angular-and-aspnet-core-a-complete-step-by-step-guide-35l0</link>
      <guid>https://dev.to/ravi-vishwakarma-hash/building-a-live-data-dashboard-with-angular-and-aspnet-core-a-complete-step-by-step-guide-35l0</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Learn how to build a real-time, live-updating data dashboard using Angular on the frontend and ASP.NET Core with SignalR on the backend. This guide walks through every step — from project setup to production deployment — with full code examples, best practices, and SEO-friendly structure for your own blog or documentation.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;What You Will Build&lt;/li&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Understanding the Architecture&lt;/li&gt;
&lt;li&gt;Part 1 (Beginner): Setting Up the ASP.NET Core Backend&lt;/li&gt;
&lt;li&gt;Part 2 (Beginner): Adding SignalR for Real-Time Communication&lt;/li&gt;
&lt;li&gt;Part 3 (Beginner): Setting Up the Angular Frontend&lt;/li&gt;
&lt;li&gt;Part 4 (Intermediate): Connecting Angular to SignalR&lt;/li&gt;
&lt;li&gt;Part 5 (Intermediate): Building the Dashboard UI with Live Charts&lt;/li&gt;
&lt;li&gt;Part 6 (Intermediate): Styling the Dashboard&lt;/li&gt;
&lt;li&gt;Part 7 (Advanced): Handling Reconnection and Error States&lt;/li&gt;
&lt;li&gt;Part 8 (Advanced): Adding Authentication with JWT&lt;/li&gt;
&lt;li&gt;Part 9 (Advanced): Scaling SignalR with Redis Backplane&lt;/li&gt;
&lt;li&gt;Part 10 (Advanced): Performance Optimization&lt;/li&gt;
&lt;li&gt;Part 11 (Advanced): Testing the Application&lt;/li&gt;
&lt;li&gt;Part 12 (Advanced): Deploying to Production&lt;/li&gt;
&lt;li&gt;Common Errors and Troubleshooting&lt;/li&gt;
&lt;li&gt;Best Practices Checklist&lt;/li&gt;
&lt;li&gt;Frequently Asked Questions (FAQ)&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;Real-time dashboards have become a core requirement in modern web applications — whether you're tracking stock prices, monitoring IoT sensor data, displaying server health metrics, or showing live sales numbers. Instead of forcing users to refresh a page to see updated data, a &lt;strong&gt;live data dashboard&lt;/strong&gt; pushes new information to the browser the moment it becomes available.&lt;/p&gt;

&lt;p&gt;In this guide, you will learn how to build a complete live data dashboard using two of the most popular technologies in the enterprise web development world: &lt;strong&gt;Angular&lt;/strong&gt; for the frontend and &lt;strong&gt;ASP.NET Core&lt;/strong&gt; for the backend, connected through &lt;strong&gt;SignalR&lt;/strong&gt;, Microsoft's real-time communication library.&lt;/p&gt;

&lt;p&gt;This tutorial is structured to take you from absolute beginner concepts all the way to advanced, production-ready practices including authentication, scaling, and deployment. Whether you are a student building your first real-time app or a professional developer architecting a production dashboard, this guide has something for you.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. What You Will Build
&lt;/h2&gt;

&lt;p&gt;By the end of this tutorial, you will have a fully functional application consisting of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;strong&gt;ASP.NET Core Web API&lt;/strong&gt; backend that generates and broadcasts live data (for example, simulated stock prices, server metrics, or sensor readings).&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;SignalR Hub&lt;/strong&gt; that pushes updates to connected clients instantly, without polling.&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;Angular application&lt;/strong&gt; that connects to the SignalR hub and updates its UI in real time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live charts&lt;/strong&gt; built with a charting library (Chart.js or ngx-charts) that animate as new data arrives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JWT authentication&lt;/strong&gt; to secure the dashboard so only logged-in users can view live data.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;production deployment&lt;/strong&gt; setup using Docker and IIS/Azure.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before starting, make sure you have the following installed and a basic understanding of these technologies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Software Requirements
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Node.js&lt;/strong&gt; (LTS version) and npm&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Angular CLI&lt;/strong&gt; (&lt;code&gt;npm install -g @angular/cli&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;.NET SDK&lt;/strong&gt; (latest LTS version of .NET)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visual Studio Code&lt;/strong&gt; or &lt;strong&gt;Visual Studio 2022&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Git&lt;/strong&gt; for version control&lt;/li&gt;
&lt;li&gt;(Optional, for advanced sections) &lt;strong&gt;Docker Desktop&lt;/strong&gt; and a &lt;strong&gt;Redis&lt;/strong&gt; instance&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Knowledge Requirements
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Basic understanding of TypeScript and Angular components/services&lt;/li&gt;
&lt;li&gt;Basic understanding of C# and ASP.NET Core controllers&lt;/li&gt;
&lt;li&gt;Familiarity with REST APIs (helpful but not mandatory)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are completely new to Angular or ASP.NET Core, don't worry — each step includes explanations of &lt;em&gt;why&lt;/em&gt; we are doing something, not just &lt;em&gt;how&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Understanding the Architecture
&lt;/h2&gt;

&lt;p&gt;Before writing any code, it's important to understand how the pieces fit together.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; ┌─────────────────────┐         WebSocket / SignalR        ┌──────────────────────┐
 │                      │ &amp;lt;---------------------------------&amp;gt; │                      │
 │   Angular Frontend   │                                     │  ASP.NET Core Backend│
 │  (Dashboard UI +     │         REST API (initial data)     │  (Web API + SignalR  │
 │   SignalR Client)    │ &amp;lt;---------------------------------- │   Hub + Data Service)│
 │                      │                                     │                      │
 └─────────────────────┘                                     └──────────────────────┘
                                                                         │
                                                                         ▼
                                                              ┌──────────────────────┐
                                                              │  Data Source (DB,    │
                                                              │  IoT feed, simulated │
                                                              │  data generator, etc)│
                                                              └──────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key concept:&lt;/strong&gt; Instead of the Angular app repeatedly calling an API (polling) every few seconds to check for new data, the ASP.NET Core backend uses &lt;strong&gt;SignalR&lt;/strong&gt; to push data directly to connected clients over a persistent WebSocket connection. This is far more efficient and provides true real-time updates.&lt;/p&gt;

&lt;p&gt;We will use a &lt;strong&gt;REST API call&lt;/strong&gt; for the initial page load (to get historical/starting data) and &lt;strong&gt;SignalR&lt;/strong&gt; for all subsequent live updates.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Part 1 (Beginner): Setting Up the ASP.NET Core Backend
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1.1: Create the Web API Project
&lt;/h3&gt;

&lt;p&gt;Open your terminal and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new webapi &lt;span class="nt"&gt;-n&lt;/span&gt; LiveDashboard.Api
&lt;span class="nb"&gt;cd &lt;/span&gt;LiveDashboard.Api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a new ASP.NET Core Web API project. If you're prompted to choose between minimal APIs and controllers, this guide uses &lt;strong&gt;controllers&lt;/strong&gt; for clarity, but the concepts apply equally to minimal API style.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1.2: Project Structure
&lt;/h3&gt;

&lt;p&gt;Organize your project with the following folders for clarity as the app grows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LiveDashboard.Api/
 ├── Controllers/
 ├── Hubs/
 ├── Models/
 ├── Services/
 ├── Program.cs
 └── appsettings.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create these folders inside your project directory.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1.3: Define the Data Model
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;Models/MetricData.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;LiveDashboard.Api.Models&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MetricData&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;MetricName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt; &lt;span class="n"&gt;Timestamp&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This simple model represents a single data point — for example, a CPU usage percentage, a stock price, or a temperature reading.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1.4: Create a Simulated Data Service
&lt;/h3&gt;

&lt;p&gt;In a real application, this data might come from a database, a message queue, or an external API. For this tutorial, we'll simulate live data so you can see the dashboard working immediately.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;Services/DataGeneratorService.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;LiveDashboard.Api.Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;LiveDashboard.Api.Services&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DataGeneratorService&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Random&lt;/span&gt; &lt;span class="n"&gt;_random&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;MetricData&lt;/span&gt; &lt;span class="nf"&gt;GenerateMetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;metricName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;baseValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;variance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;change&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NextDouble&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;variance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;MetricData&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;MetricName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;metricName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baseValue&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;change&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;Timestamp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register this service in &lt;code&gt;Program.cs&lt;/code&gt; (we'll come back to &lt;code&gt;Program.cs&lt;/code&gt; again shortly):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DataGeneratorService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 1.5: Create a Basic REST Endpoint
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;Controllers/MetricsController.cs&lt;/code&gt; to allow the Angular app to fetch the initial set of data when it first loads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Mvc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;LiveDashboard.Api.Services&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;LiveDashboard.Api.Controllers&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ApiController&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api/[controller]"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MetricsController&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ControllerBase&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;DataGeneratorService&lt;/span&gt; &lt;span class="n"&gt;_generator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;MetricsController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DataGeneratorService&lt;/span&gt; &lt;span class="n"&gt;generator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_generator&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;generator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"snapshot"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;GetSnapshot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;_generator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GenerateMetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CPU"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;_generator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GenerateMetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Memory"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;_generator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GenerateMetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"RequestsPerSecond"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 1.6: Enable CORS
&lt;/h3&gt;

&lt;p&gt;Since Angular (running on port 4200 in development) and ASP.NET Core (running on a different port) are different origins, you need to enable CORS. Add this to &lt;code&gt;Program.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddCors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AllowAngularDev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithOrigins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:4200"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AllowAnyHeader&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
              &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AllowAnyMethod&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
              &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AllowCredentials&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And later, in the middleware pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseCors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AllowAngularDev"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &lt;code&gt;AllowCredentials()&lt;/code&gt; is required for SignalR connections to work correctly with CORS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1.7: Run and Verify
&lt;/h3&gt;

&lt;p&gt;Run the backend with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visit &lt;code&gt;https://localhost:&amp;lt;port&amp;gt;/api/metrics/snapshot&lt;/code&gt; in your browser or via a tool like Postman to confirm you receive a JSON array of metric data.&lt;/p&gt;

&lt;p&gt;At this point, you have a working REST API. Next, we'll add real-time capability using SignalR.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Part 2 (Beginner): Adding SignalR for Real-Time Communication
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 2.1: What Is SignalR?
&lt;/h3&gt;

&lt;p&gt;SignalR is a library built into ASP.NET Core that simplifies adding real-time web functionality. It abstracts away the complexity of WebSockets, automatically falling back to other transport methods (like Server-Sent Events or long polling) when WebSockets aren't available, and handles connection management for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2.2: Create the Hub
&lt;/h3&gt;

&lt;p&gt;A "Hub" in SignalR is the core component that handles communication between server and clients. Create &lt;code&gt;Hubs/DashboardHub.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.SignalR&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;LiveDashboard.Api.Hubs&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DashboardHub&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Hub&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;OnConnectedAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Clients&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Caller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ReceiveMessage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Connected to live dashboard hub."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnConnectedAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This hub doesn't need many methods yet — most of the data pushing will happen from a background service, not from client-invoked methods. The hub mainly serves as the "pipe" through which data flows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2.3: Register SignalR in Program.cs
&lt;/h3&gt;

&lt;p&gt;Update &lt;code&gt;Program.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;LiveDashboard.Api.Hubs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;LiveDashboard.Api.Services&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddControllers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSignalR&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DataGeneratorService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddCors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AllowAngularDev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithOrigins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:4200"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AllowAnyHeader&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
              &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AllowAnyMethod&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
              &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AllowCredentials&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseCors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AllowAngularDev"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseRouting&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapControllers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MapHub&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DashboardHub&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"/hubs/dashboard"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key line here is &lt;code&gt;app.MapHub&amp;lt;DashboardHub&amp;gt;("/hubs/dashboard")&lt;/code&gt;, which exposes the hub at a specific URL that the Angular client will connect to.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2.4: Create a Background Service to Push Live Data
&lt;/h3&gt;

&lt;p&gt;This is where the "live" part of the dashboard comes to life. We'll create a hosted background service that runs continuously, generates new data every few seconds, and broadcasts it to all connected clients via the hub.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;Services/MetricBroadcastService.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.SignalR&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;LiveDashboard.Api.Hubs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;LiveDashboard.Api.Services&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MetricBroadcastService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BackgroundService&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IHubContext&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DashboardHub&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_hubContext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;DataGeneratorService&lt;/span&gt; &lt;span class="n"&gt;_generator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;MetricBroadcastService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IHubContext&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DashboardHub&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;hubContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DataGeneratorService&lt;/span&gt; &lt;span class="n"&gt;generator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_hubContext&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hubContext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;_generator&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;generator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCancellationRequested&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cpu&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_generator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GenerateMetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CPU"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_generator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GenerateMetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Memory"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rps&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_generator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GenerateMetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"RequestsPerSecond"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_hubContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Clients&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;All&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ReceiveMetric"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cpu&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_hubContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Clients&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;All&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ReceiveMetric"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_hubContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Clients&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;All&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ReceiveMetric"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// push every 2 seconds&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register the background service in &lt;code&gt;Program.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MetricBroadcastService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;How this works:&lt;/strong&gt; &lt;code&gt;IHubContext&amp;lt;DashboardHub&amp;gt;&lt;/code&gt; lets you send messages to clients from anywhere in your application (not just from within the hub itself) — perfect for a background service that has no direct connection to a specific client, but needs to broadcast to all of them. &lt;code&gt;Clients.All.SendAsync("ReceiveMetric", ...)&lt;/code&gt; sends a message named &lt;code&gt;"ReceiveMetric"&lt;/code&gt; with the metric data as a payload to every connected client.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2.5: Verify with a Test Client (Optional)
&lt;/h3&gt;

&lt;p&gt;Before building the Angular frontend, you can test the SignalR hub using a tool like the &lt;a href="https://learning.postman.com/docs/sending-requests/signalr/signalr/" rel="noopener noreferrer"&gt;SignalR test client in Postman&lt;/a&gt; or a simple HTML page with the SignalR JavaScript client. This isn't required, but it can help isolate backend issues from frontend issues if something doesn't work later.&lt;/p&gt;

&lt;p&gt;At this point, your backend is complete for the beginner phase: it has a REST endpoint for the initial snapshot and a SignalR hub broadcasting new data every two seconds.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Part 3 (Beginner): Setting Up the Angular Frontend
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 3.1: Create the Angular Project
&lt;/h3&gt;

&lt;p&gt;In a separate folder (sibling to your backend project), run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ng new live-dashboard-client &lt;span class="nt"&gt;--routing&lt;/span&gt; &lt;span class="nt"&gt;--style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;scss
&lt;span class="nb"&gt;cd &lt;/span&gt;live-dashboard-client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Choose the default options when prompted, or customize as you prefer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3.2: Install the SignalR Client Library
&lt;/h3&gt;

&lt;p&gt;Angular communicates with the SignalR hub using Microsoft's official JavaScript/TypeScript client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @microsoft/signalr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3.3: Install a Charting Library
&lt;/h3&gt;

&lt;p&gt;For this tutorial we'll use &lt;strong&gt;Chart.js&lt;/strong&gt; with the &lt;code&gt;ng2-charts&lt;/code&gt; Angular wrapper, since it's lightweight and beginner-friendly. (Advanced alternative: &lt;code&gt;ngx-charts&lt;/code&gt;, covered briefly later.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;chart.js ng2-charts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3.4: Project Structure
&lt;/h3&gt;

&lt;p&gt;Generate the core building blocks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ng generate service services/signalr
ng generate service services/metrics
ng generate component components/dashboard
ng generate component components/metric-card
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you a clean separation: a service to manage the SignalR connection, a service to manage metric state, and components for the visual layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3.5: Define the Metric Model
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;src/app/models/metric-data.model.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;MetricData&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;metricName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This mirrors the C# &lt;code&gt;MetricData&lt;/code&gt; class on the backend, ensuring type safety across the stack.&lt;/p&gt;

&lt;p&gt;At this point, your Angular project is scaffolded and ready to be wired up to the live backend. In the next section, we'll connect it to SignalR.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Part 4 (Intermediate): Connecting Angular to SignalR
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 4.1: Build the SignalR Service
&lt;/h3&gt;

&lt;p&gt;This service will manage the connection lifecycle (connect, disconnect, reconnect) and expose an Observable stream of incoming metric data that any component can subscribe to.&lt;/p&gt;

&lt;p&gt;Edit &lt;code&gt;src/app/services/signalr.service.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Injectable&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;signalR&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@microsoft/signalr&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Subject&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rxjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MetricData&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../models/metric-data.model&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../environments/environment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;providedIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SignalrService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;hubConnection&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;signalR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HubConnection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;metricSubject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Subject&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MetricData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;metric$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metricSubject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asObservable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;startConnection&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hubConnection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;signalR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HubConnectionBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiBaseUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/hubs/dashboard`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withAutomaticReconnect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configureLogging&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signalR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LogLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Information&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hubConnection&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SignalR connection started&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error starting SignalR connection:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerHandlers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;registerHandlers&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hubConnection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ReceiveMetric&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MetricData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metricSubject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hubConnection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ReceiveMessage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Server message:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;stopConnection&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hubConnection&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key concepts explained:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;HubConnectionBuilder().withUrl(...)&lt;/code&gt; configures the connection to point at the hub endpoint we exposed on the backend (&lt;code&gt;/hubs/dashboard&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.withAutomaticReconnect()&lt;/code&gt; tells SignalR to automatically attempt reconnection if the connection drops — essential for a reliable live dashboard.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;this.hubConnection.on('ReceiveMetric', ...)&lt;/code&gt; registers a handler for the exact same message name (&lt;code&gt;"ReceiveMetric"&lt;/code&gt;) that the backend uses in &lt;code&gt;SendAsync("ReceiveMetric", ...)&lt;/code&gt;. These names &lt;strong&gt;must match exactly&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;We use an RxJS &lt;code&gt;Subject&lt;/code&gt; to convert SignalR's callback-based API into an Observable stream, which fits naturally into Angular's reactive patterns.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 4.2: Create the Environment Configuration
&lt;/h3&gt;

&lt;p&gt;Update &lt;code&gt;src/environments/environment.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;production&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;apiBaseUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://localhost:7000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// match your backend's actual port&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;code&gt;src/environments/environment.prod.ts&lt;/code&gt; for production builds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;production&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;apiBaseUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://your-production-api.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4.3: Build the Metrics Service
&lt;/h3&gt;

&lt;p&gt;This service will fetch the initial snapshot via REST and maintain the current state of each metric as live updates arrive.&lt;/p&gt;

&lt;p&gt;Edit &lt;code&gt;src/app/services/metrics.service.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Injectable&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;HttpClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/common/http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BehaviorSubject&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rxjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MetricData&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../models/metric-data.model&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../environments/environment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SignalrService&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./signalr.service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;providedIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MetricsService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;metricsMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MetricData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;metricsSubject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;BehaviorSubject&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MetricData&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;metrics$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metricsSubject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asObservable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;signalr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SignalrService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signalr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metric$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;metric&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upsertMetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;metric&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;loadInitialSnapshot&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MetricData&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiBaseUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/api/metrics/snapshot`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initialData&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;initialData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metricsMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metricName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emitMetrics&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;upsertMetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;metric&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MetricData&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metricsMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;metric&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metricName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;metric&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emitMetrics&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;emitMetrics&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metricsSubject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metricsMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why a &lt;code&gt;Map&lt;/code&gt; keyed by metric name?&lt;/strong&gt; Each new live update replaces the latest value for that specific metric (e.g., CPU), rather than appending to an ever-growing list. This keeps the current-state view of the dashboard accurate and memory-efficient. (We'll handle historical trend data separately for charts in the next section.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4.4: Enable HttpClient
&lt;/h3&gt;

&lt;p&gt;Make sure &lt;code&gt;HttpClient&lt;/code&gt; is available. In a standalone Angular app (&lt;code&gt;app.config.ts&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ApplicationConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;provideHttpClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/common/http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;provideRouter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app.routes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ApplicationConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;provideRouter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;provideHttpClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're using an NgModule-based app instead, import &lt;code&gt;HttpClientModule&lt;/code&gt; into your &lt;code&gt;AppModule&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4.5: Initialize the Connection in the Dashboard Component
&lt;/h3&gt;

&lt;p&gt;Edit &lt;code&gt;src/app/components/dashboard/dashboard.component.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;OnInit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;OnDestroy&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CommonModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MetricsService&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../services/metrics.service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SignalrService&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../services/signalr.service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MetricData&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../models/metric-data.model&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;standalone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;CommonModule&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./dashboard.component.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;styleUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./dashboard.component.scss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DashboardComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnInit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;OnDestroy&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MetricData&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;metricsService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MetricsService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;signalrService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SignalrService&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="nf"&gt;ngOnInit&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signalrService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startConnection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metricsService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadInitialSnapshot&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metricsService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metrics$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;ngOnDestroy&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signalrService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stopConnection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this stage, your Angular app connects to the backend, fetches an initial snapshot, and listens for live updates. Next, we'll render this data visually.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Part 5 (Intermediate): Building the Dashboard UI with Live Charts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 5.1: Basic Metric Cards (Numbers View)
&lt;/h3&gt;

&lt;p&gt;Update &lt;code&gt;dashboard.component.html&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"dashboard"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Live Data Dashboard&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"metric-grid"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;app-metric-card&lt;/span&gt;
      &lt;span class="na"&gt;*ngFor=&lt;/span&gt;&lt;span class="s"&gt;"let metric of metrics"&lt;/span&gt;
      &lt;span class="na"&gt;[name]=&lt;/span&gt;&lt;span class="s"&gt;"metric.metricName"&lt;/span&gt;
      &lt;span class="na"&gt;[value]=&lt;/span&gt;&lt;span class="s"&gt;"metric.value"&lt;/span&gt;
      &lt;span class="na"&gt;[timestamp]=&lt;/span&gt;&lt;span class="s"&gt;"metric.timestamp"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/app-metric-card&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Edit &lt;code&gt;metric-card.component.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Input&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CommonModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-metric-card&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;standalone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;CommonModule&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./metric-card.component.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;styleUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./metric-card.component.scss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MetricCardComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;metric-card.component.html&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"metric-card"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;{{ name }}&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"value"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ value }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;small&amp;gt;&lt;/span&gt;Updated: {{ timestamp | date:'mediumTime' }}&lt;span class="nt"&gt;&amp;lt;/small&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you a simple, working live-updating numeric dashboard. Now let's add line charts to visualize trends over time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5.2: Setting Up ng2-charts
&lt;/h3&gt;

&lt;p&gt;In your standalone component or module, import the chart provider. With standalone components, register it in &lt;code&gt;app.config.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;provideCharts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;withDefaultRegisterables&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ng2-charts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ApplicationConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;provideRouter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;provideHttpClient&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nf"&gt;provideCharts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;withDefaultRegisterables&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5.3: Create a Live Chart Component
&lt;/h3&gt;

&lt;p&gt;Generate a new component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ng generate component components/live-chart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This component needs to maintain a rolling history of values for a given metric (e.g., the last 30 data points) so the chart shows a trend rather than just the latest number.&lt;/p&gt;

&lt;p&gt;Edit &lt;code&gt;live-chart.component.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;OnChanges&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SimpleChanges&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CommonModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BaseChartDirective&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ng2-charts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ChartConfiguration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ChartData&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chart.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-live-chart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;standalone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;CommonModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;BaseChartDirective&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./live-chart.component.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;styleUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./live-chart.component.scss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LiveChartComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnChanges&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;maxPoints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;lineChartData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ChartData&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;line&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="na"&gt;datasets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
        &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;borderColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#3f8cff&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rgba(63, 140, 255, 0.15)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;tension&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;pointRadius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;lineChartOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ChartConfiguration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;options&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;responsive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;scales&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;beginAtZero&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;legend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nf"&gt;ngOnChanges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SimpleChanges&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;firstChange&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addDataPoint&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;firstChange&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addDataPoint&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;addDataPoint&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lineChartData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;labels&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dataset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lineChartData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;datasets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;

    &lt;span class="nx"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLocaleTimeString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxPoints&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Reassign to trigger change detection in ng2-charts&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lineChartData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lineChartData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;datasets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lineChartData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;datasets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;live-chart.component.html&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"chart-wrapper"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h4&amp;gt;&lt;/span&gt;{{ label }} Trend&lt;span class="nt"&gt;&amp;lt;/h4&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;canvas&lt;/span&gt;
    &lt;span class="na"&gt;baseChart&lt;/span&gt;
    &lt;span class="na"&gt;[data]=&lt;/span&gt;&lt;span class="s"&gt;"lineChartData"&lt;/span&gt;
    &lt;span class="na"&gt;[options]=&lt;/span&gt;&lt;span class="s"&gt;"lineChartOptions"&lt;/span&gt;
    &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"line"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/canvas&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5.4: Wire the Chart into the Dashboard
&lt;/h3&gt;

&lt;p&gt;Update &lt;code&gt;dashboard.component.html&lt;/code&gt; to include the chart alongside the cards:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"dashboard"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Live Data Dashboard&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"metric-grid"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;app-metric-card&lt;/span&gt;
      &lt;span class="na"&gt;*ngFor=&lt;/span&gt;&lt;span class="s"&gt;"let metric of metrics"&lt;/span&gt;
      &lt;span class="na"&gt;[name]=&lt;/span&gt;&lt;span class="s"&gt;"metric.metricName"&lt;/span&gt;
      &lt;span class="na"&gt;[value]=&lt;/span&gt;&lt;span class="s"&gt;"metric.value"&lt;/span&gt;
      &lt;span class="na"&gt;[timestamp]=&lt;/span&gt;&lt;span class="s"&gt;"metric.timestamp"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/app-metric-card&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"chart-grid"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;app-live-chart&lt;/span&gt;
      &lt;span class="na"&gt;*ngFor=&lt;/span&gt;&lt;span class="s"&gt;"let metric of metrics"&lt;/span&gt;
      &lt;span class="na"&gt;[label]=&lt;/span&gt;&lt;span class="s"&gt;"metric.metricName"&lt;/span&gt;
      &lt;span class="na"&gt;[value]=&lt;/span&gt;&lt;span class="s"&gt;"metric.value"&lt;/span&gt;
      &lt;span class="na"&gt;[timestamp]=&lt;/span&gt;&lt;span class="s"&gt;"metric.timestamp"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/app-live-chart&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't forget to add &lt;code&gt;LiveChartComponent&lt;/code&gt; to the &lt;code&gt;imports&lt;/code&gt; array of &lt;code&gt;DashboardComponent&lt;/code&gt; since both are standalone.&lt;/p&gt;

&lt;p&gt;At this point, your dashboard displays both live numeric values and animated trend lines that update in real time as the backend pushes new data every two seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5.5: Alternative — Using ngx-charts (Advanced Option)
&lt;/h3&gt;

&lt;p&gt;If you prefer a more "Angular-native" charting library with declarative templates instead of imperative chart configuration objects, consider &lt;code&gt;@swimlane/ngx-charts&lt;/code&gt;. It integrates well with Angular's change detection and offers built-in chart types (line, bar, gauge, pie) ideal for dashboards. The setup process is similar: install the package, import the chart module, and bind data arrays to chart components via &lt;code&gt;[results]&lt;/code&gt; inputs. Many production dashboards mix both libraries depending on the visualization needed — for example, ngx-charts for gauges and Chart.js for dense time-series lines.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Part 6 (Intermediate): Styling the Dashboard
&lt;/h2&gt;

&lt;p&gt;A functional dashboard is only half the job — visual clarity matters, especially for monitoring tools that may be displayed on a large screen for extended periods (e.g., a NOC wall display).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6.1: Grid Layout
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;dashboard.component.scss&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nc"&gt;.dashboard&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#0f172a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;min-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100vh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#e2e8f0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'Inter'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="mi"&gt;.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.metric-grid&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="na"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auto-fit&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;minmax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;200px&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1fr&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="na"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.chart-grid&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="na"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auto-fit&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;minmax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;320px&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1fr&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="na"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="mi"&gt;.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6.2: Card Styling
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;metric-card.component.scss&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nc"&gt;.metric-card&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#1e293b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="mi"&gt;.25rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt; &lt;span class="nf"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="mi"&gt;.25&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="mi"&gt;.2s&lt;/span&gt; &lt;span class="n"&gt;ease&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;translateY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-2px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nt"&gt;h3&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="mi"&gt;.85rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;text-transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;uppercase&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;letter-spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="mi"&gt;.05em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#94a3b8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="mi"&gt;.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.value&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;700&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#3f8cff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nt"&gt;small&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#64748b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6.3: Visual Feedback on Update
&lt;/h3&gt;

&lt;p&gt;A nice UX touch for live dashboards is briefly highlighting a value when it changes, so users can visually notice an update without staring at numbers. You can achieve this with a CSS animation triggered by an Angular class binding tied to a "just updated" flag, toggled with a short &lt;code&gt;setTimeout&lt;/code&gt; whenever new data arrives. This kind of micro-interaction significantly improves the perceived "liveness" of the dashboard.&lt;/p&gt;




&lt;h2&gt;
  
  
  11. Part 7 (Advanced): Handling Reconnection and Error States
&lt;/h2&gt;

&lt;p&gt;Production dashboards must gracefully handle network interruptions. SignalR's &lt;code&gt;.withAutomaticReconnect()&lt;/code&gt; handles basic reconnection, but you should also surface connection status to users and handle edge cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7.1: Expose Connection State
&lt;/h3&gt;

&lt;p&gt;Update &lt;code&gt;signalr.service.ts&lt;/code&gt; to track and expose connection status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BehaviorSubject&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rxjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;connectionState$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;BehaviorSubject&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;connected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reconnecting&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;disconnected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;disconnected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;startConnection&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hubConnection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;signalR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HubConnectionBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiBaseUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/hubs/dashboard`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withAutomaticReconnect&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30000&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hubConnection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onreconnecting&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connectionState$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reconnecting&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hubConnection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onreconnected&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connectionState$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;connected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hubConnection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onclose&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connectionState$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;disconnected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hubConnection&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connectionState$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;connected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connectionState$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;disconnected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerHandlers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The array &lt;code&gt;[0, 2000, 5000, 10000, 30000]&lt;/code&gt; passed to &lt;code&gt;withAutomaticReconnect&lt;/code&gt; defines custom retry delays (in milliseconds): retry immediately, then after 2s, 5s, 10s, and 30s, after which SignalR stops retrying automatically (you can add logic to manually retry beyond this).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7.2: Display a Connection Banner
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;dashboard.component.html&lt;/code&gt;, add a status banner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"connection-banner"&lt;/span&gt; &lt;span class="na"&gt;*ngIf=&lt;/span&gt;&lt;span class="s"&gt;"connectionState !== 'connected'"&lt;/span&gt; &lt;span class="na"&gt;[ngClass]=&lt;/span&gt;&lt;span class="s"&gt;"connectionState"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;*ngIf=&lt;/span&gt;&lt;span class="s"&gt;"connectionState === 'reconnecting'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Reconnecting to live data...&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;*ngIf=&lt;/span&gt;&lt;span class="s"&gt;"connectionState === 'disconnected'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Disconnected. Attempting to reconnect...&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Subscribe to &lt;code&gt;connectionState$&lt;/code&gt; in the component class just like you did for metrics. This small addition dramatically improves trust in the dashboard — users immediately know if the data they're looking at might be stale.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7.3: Handle Manual Reconnection
&lt;/h3&gt;

&lt;p&gt;If automatic reconnection exhausts its retries, implement a manual fallback, for instance using &lt;code&gt;setInterval&lt;/code&gt; to attempt &lt;code&gt;hubConnection.start()&lt;/code&gt; again every 30 seconds while in the &lt;code&gt;disconnected&lt;/code&gt; state, with exponential backoff capped at a reasonable maximum to avoid hammering the server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7.4: Graceful Degradation
&lt;/h3&gt;

&lt;p&gt;Consider what happens if SignalR/WebSockets are entirely blocked (e.g., by a restrictive corporate proxy). SignalR automatically falls back to Server-Sent Events or long polling, but if you want a guaranteed fallback, you could implement a periodic REST polling mechanism as a last resort, triggered only when the &lt;code&gt;connectionState$&lt;/code&gt; remains &lt;code&gt;disconnected&lt;/code&gt; for an extended period.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. Part 8 (Advanced): Adding Authentication with JWT
&lt;/h2&gt;

&lt;p&gt;Most production dashboards display sensitive business data and need to be secured. Here's how to add JWT-based authentication to both the backend and the SignalR connection.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 8.1: Backend — Add JWT Authentication
&lt;/h3&gt;

&lt;p&gt;Install the JWT package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure it in &lt;code&gt;Program.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Authentication.JwtBearer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.IdentityModel.Tokens&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;jwtKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Jwt:Key"&lt;/span&gt;&lt;span class="p"&gt;]!;&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAuthentication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JwtBearerDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthenticationScheme&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddJwtBearer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TokenValidationParameters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TokenValidationParameters&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;ValidateIssuer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ValidateAudience&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ValidateLifetime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ValidateIssuerSigningKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ValidIssuer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Jwt:Issuer"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;ValidAudience&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Jwt:Audience"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;IssuerSigningKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SymmetricSecurityKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTF8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jwtKey&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="c1"&gt;// Required so SignalR can read the access token from the query string,&lt;/span&gt;
        &lt;span class="c1"&gt;// since WebSocket connections cannot set custom Authorization headers.&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Events&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;JwtBearerEvents&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;OnMessageReceived&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;accessToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"access_token"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartsWithSegments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/hubs"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletedTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the middleware:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseAuthentication&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Secure the hub and controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Authorize&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DashboardHub&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Hub&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Authorize&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ApiController&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api/[controller]"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MetricsController&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ControllerBase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Important detail:&lt;/strong&gt; Browsers cannot attach custom &lt;code&gt;Authorization&lt;/code&gt; headers to native WebSocket connections, so SignalR's JavaScript client sends the token as a query string parameter (&lt;code&gt;access_token&lt;/code&gt;) instead. The &lt;code&gt;OnMessageReceived&lt;/code&gt; event above reads that token specifically for requests to the &lt;code&gt;/hubs&lt;/code&gt; path.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 8.2: Create a Simple Login Endpoint (for demo purposes)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ApiController&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api/[controller]"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuthController&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ControllerBase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IConfiguration&lt;/span&gt; &lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;AuthController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IConfiguration&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"login"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;Login&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;FromBody&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;LoginRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Replace with real user validation against your identity store&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Username&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"demo"&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Password&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Unauthorized&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;claims&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Claim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ClaimTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SymmetricSecurityKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTF8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Jwt:Key"&lt;/span&gt;&lt;span class="p"&gt;]!));&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SigningCredentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SecurityAlgorithms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HmacSha256&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;JwtSecurityToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;issuer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Jwt:Issuer"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;audience&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Jwt:Audience"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;expires&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHours&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;signingCredentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;JwtSecurityTokenHandler&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;WriteToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;LoginRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Password&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the corresponding &lt;code&gt;Jwt:Key&lt;/code&gt;, &lt;code&gt;Jwt:Issuer&lt;/code&gt;, and &lt;code&gt;Jwt:Audience&lt;/code&gt; values to &lt;code&gt;appsettings.json&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 8.3: Angular — Send the Token with SignalR
&lt;/h3&gt;

&lt;p&gt;Update &lt;code&gt;signalr.service.ts&lt;/code&gt; to pass the access token when building the connection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hubConnection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;signalR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HubConnectionBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiBaseUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/hubs/dashboard`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;accessTokenFactory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authToken&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withAutomaticReconnect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 8.4: Add an Angular HTTP Interceptor for REST Calls
&lt;/h3&gt;

&lt;p&gt;To attach the token to your regular REST API calls (like &lt;code&gt;/api/metrics/snapshot&lt;/code&gt;), add an &lt;code&gt;HttpInterceptorFn&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;HttpInterceptorFn&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/common/http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authInterceptor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HttpInterceptorFn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authToken&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cloned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;setHeaders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cloned&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register it in &lt;code&gt;app.config.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;provideHttpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;withInterceptors&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;authInterceptor&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 8.5: Build a Minimal Login Component
&lt;/h3&gt;

&lt;p&gt;Create a simple login form that calls &lt;code&gt;/api/auth/login&lt;/code&gt;, stores the returned token in &lt;code&gt;localStorage&lt;/code&gt; (or, for better security, a memory-based store with refresh tokens in an HttpOnly cookie for production-grade apps), and redirects to the dashboard route. Use an Angular route guard (&lt;code&gt;CanActivateFn&lt;/code&gt;) to prevent unauthenticated access to the dashboard route.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security note:&lt;/strong&gt; Storing JWTs in &lt;code&gt;localStorage&lt;/code&gt; is convenient but vulnerable to XSS attacks. For production systems handling sensitive data, consider using HttpOnly, Secure cookies combined with short-lived access tokens and a refresh token rotation strategy.&lt;/p&gt;




&lt;h2&gt;
  
  
  13. Part 9 (Advanced): Scaling SignalR with Redis Backplane
&lt;/h2&gt;

&lt;p&gt;If you deploy your ASP.NET Core backend across multiple server instances (for load balancing or high availability), SignalR connections will be distributed across those instances. By default, a message broadcast from one server instance won't reach clients connected to a different instance. This is solved using a &lt;strong&gt;backplane&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 9.1: Install the Redis Backplane Package
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Microsoft.AspNetCore.SignalR.StackExchangeRedis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 9.2: Configure It in Program.cs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSignalR&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddStackExchangeRedis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Redis:ConnectionString"&lt;/span&gt;&lt;span class="p"&gt;]!,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChannelPrefix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StackExchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RedisChannel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"LiveDashboard"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this in place, when &lt;code&gt;MetricBroadcastService&lt;/code&gt; calls &lt;code&gt;Clients.All.SendAsync(...)&lt;/code&gt; on one server instance, Redis ensures the message is relayed to all other instances, which then forward it to their own connected clients. This makes your dashboard horizontally scalable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 9.3: When Do You Need This?
&lt;/h3&gt;

&lt;p&gt;For a single-instance deployment (most small to mid-size applications), you do &lt;strong&gt;not&lt;/strong&gt; need a backplane. Only add this complexity once you're running multiple instances behind a load balancer — typically in higher-traffic production environments.&lt;/p&gt;




&lt;h2&gt;
  
  
  14. Part 10 (Advanced): Performance Optimization
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 10.1: Throttle High-Frequency Updates
&lt;/h3&gt;

&lt;p&gt;If your real data source produces updates faster than the UI needs to render them (e.g., hundreds of updates per second), avoid pushing every single update to clients. Instead, batch or throttle on the backend — for example, aggregate updates and broadcast at a fixed interval (every 500ms–1s) using a buffer, rather than sending on every change.&lt;/p&gt;

&lt;p&gt;On the Angular side, you can also throttle using RxJS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;throttleTime&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rxjs/operators&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signalrService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metric$&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;throttleTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;metric&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upsertMetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;metric&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 10.2: Use OnPush Change Detection
&lt;/h3&gt;

&lt;p&gt;For dashboard components receiving frequent updates, switch to &lt;code&gt;ChangeDetectionStrategy.OnPush&lt;/code&gt; to avoid unnecessary re-renders across the entire component tree:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-metric-card&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;changeDetection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ChangeDetectionStrategy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OnPush&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Combined with immutable data patterns (creating new objects/arrays rather than mutating existing ones, as we did in the chart component), &lt;code&gt;OnPush&lt;/code&gt; significantly reduces CPU usage on busy dashboards.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 10.3: Limit Chart History
&lt;/h3&gt;

&lt;p&gt;Capping chart data to a fixed window (we used &lt;code&gt;maxPoints = 30&lt;/code&gt; earlier) prevents memory growth and keeps rendering fast, especially important if the dashboard runs unattended for hours or days (e.g., on an office wall display).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 10.4: Use trackBy for *ngFor
&lt;/h3&gt;

&lt;p&gt;When rendering lists of metric cards or charts, always use &lt;code&gt;trackBy&lt;/code&gt; to prevent Angular from destroying and recreating DOM elements unnecessarily on every update:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;app-metric-card&lt;/span&gt; &lt;span class="na"&gt;*ngFor=&lt;/span&gt;&lt;span class="s"&gt;"let metric of metrics; trackBy: trackByName"&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt; &lt;span class="nt"&gt;&amp;gt;&amp;lt;/app-metric-card&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;trackByName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;metric&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MetricData&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;metric&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metricName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 10.5: Compress SignalR Messages (Advanced)
&lt;/h3&gt;

&lt;p&gt;For very data-heavy dashboards, consider using MessagePack instead of the default JSON protocol for SignalR, which reduces payload size and improves throughput:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Microsoft.AspNetCore.SignalR.Protocols.MessagePack
npm &lt;span class="nb"&gt;install&lt;/span&gt; @microsoft/signalr-protocol-msgpack
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure it on both server (&lt;code&gt;AddSignalR().AddMessagePackProtocol()&lt;/code&gt;) and client (&lt;code&gt;.withHubProtocol(new MessagePackHubProtocol())&lt;/code&gt;).&lt;/p&gt;




&lt;h2&gt;
  
  
  15. Part 11 (Advanced): Testing the Application
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 11.1: Backend Unit Tests
&lt;/h3&gt;

&lt;p&gt;Use xUnit to test your services in isolation. For example, test that &lt;code&gt;DataGeneratorService.GenerateMetric&lt;/code&gt; returns values within the expected variance range:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;GenerateMetric_ReturnsValueWithinVarianceRange&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;DataGeneratorService&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GenerateMetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CPU"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CPU"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MetricName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 11.2: Integration Testing the Hub
&lt;/h3&gt;

&lt;p&gt;ASP.NET Core supports integration testing using &lt;code&gt;WebApplicationFactory&lt;/code&gt; combined with a real SignalR client connecting to an in-memory test server, allowing you to verify that messages broadcast from the hub are actually received by a connected client.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 11.3: Angular Unit Tests
&lt;/h3&gt;

&lt;p&gt;Use Jasmine/Karma (or Jest, if configured) to test that &lt;code&gt;MetricsService&lt;/code&gt; correctly upserts metrics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should update existing metric value on new data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;upsertMetric&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]({&lt;/span&gt; &lt;span class="na"&gt;metricName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CPU&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-01-01T00:00:00Z&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;upsertMetric&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]({&lt;/span&gt; &lt;span class="na"&gt;metricName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CPU&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-01-01T00:00:05Z&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metrics$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;metrics&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cpu&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metricName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CPU&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cpu&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 11.4: End-to-End Testing
&lt;/h3&gt;

&lt;p&gt;Use Playwright or Cypress to verify the full flow: load the dashboard, confirm the initial snapshot renders, wait for a live update, and assert that displayed values change without a page reload.&lt;/p&gt;




&lt;h2&gt;
  
  
  16. Part 12 (Advanced): Deploying to Production
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 16.1: Build the Angular App for Production
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ng build &lt;span class="nt"&gt;--configuration&lt;/span&gt; production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This outputs static files to &lt;code&gt;dist/live-dashboard-client&lt;/code&gt;, which can be served by any static file host or reverse proxy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 16.2: Publish the ASP.NET Core Backend
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet publish &lt;span class="nt"&gt;-c&lt;/span&gt; Release &lt;span class="nt"&gt;-o&lt;/span&gt; ./publish
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 16.3: Dockerize Both Applications
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Backend Dockerfile:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/dotnet/sdk:8.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /src&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet publish &lt;span class="nt"&gt;-c&lt;/span&gt; Release &lt;span class="nt"&gt;-o&lt;/span&gt; /app/publish

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; mcr.microsoft.com/dotnet/aspnet:8.0&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/publish .&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 80&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["dotnet", "LiveDashboard.Api.dll"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Frontend Dockerfile (served via Nginx):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run build &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--configuration&lt;/span&gt; production

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; nginx:alpine&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/dist/live-dashboard-client /usr/share/nginx/html&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; nginx.conf /etc/nginx/conf.d/default.conf&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A minimal &lt;code&gt;nginx.conf&lt;/code&gt; should include a WebSocket-friendly proxy configuration if Nginx also handles routing to the backend, ensuring &lt;code&gt;Upgrade&lt;/code&gt; and &lt;code&gt;Connection&lt;/code&gt; headers are forwarded correctly for SignalR's WebSocket transport.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 16.4: docker-compose for Local Orchestration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.8'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./LiveDashboard.Api&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5000:80"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ASPNETCORE_ENVIRONMENT=Production&lt;/span&gt;

  &lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./live-dashboard-client&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4200:80"&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 16.5: Deploying to Azure
&lt;/h3&gt;

&lt;p&gt;For Azure App Service deployments, deploy the ASP.NET Core backend to an &lt;strong&gt;App Service&lt;/strong&gt; (with &lt;strong&gt;WebSockets enabled&lt;/strong&gt; in the Configuration settings — this is required for SignalR to function correctly) and the Angular frontend to &lt;strong&gt;Azure Static Web Apps&lt;/strong&gt; or a separate App Service serving static files. If you expect multiple backend instances, enable &lt;strong&gt;Azure SignalR Service&lt;/strong&gt; instead of self-hosting SignalR, which handles scaling and connection management for you and removes the need for a Redis backplane.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 16.6: Deploying to IIS
&lt;/h3&gt;

&lt;p&gt;If deploying to IIS, ensure the &lt;strong&gt;WebSocket Protocol&lt;/strong&gt; feature is enabled in Windows Server roles/features, and that the &lt;strong&gt;ASP.NET Core Module v2&lt;/strong&gt; is installed. Without WebSocket support enabled at the IIS/server level, SignalR will silently fall back to long polling, which still works but is less efficient.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 16.7: HTTPS and Security Headers
&lt;/h3&gt;

&lt;p&gt;In production, always serve both frontend and backend over HTTPS — mixed content (HTTPS frontend calling an HTTP backend) will be blocked by browsers, and WebSocket connections (&lt;code&gt;wss://&lt;/code&gt;) require a secure context when the page itself is loaded over HTTPS.&lt;/p&gt;




&lt;h2&gt;
  
  
  17. Common Errors and Troubleshooting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Error: "Failed to complete negotiation with the server"&lt;/strong&gt;&lt;br&gt;
Usually caused by CORS misconfiguration or the backend not running. Confirm &lt;code&gt;AllowCredentials()&lt;/code&gt; is set and that the origin matches exactly (including protocol and port).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Error: SignalR connects but no data arrives&lt;/strong&gt;&lt;br&gt;
Double-check that the event name in &lt;code&gt;hubConnection.on('ReceiveMetric', ...)&lt;/code&gt; exactly matches the string used in &lt;code&gt;SendAsync("ReceiveMetric", ...)&lt;/code&gt; on the backend — these are case-sensitive string matches, not type-checked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Error: Connection works locally but fails after deployment&lt;/strong&gt;&lt;br&gt;
Almost always a missing WebSocket configuration at the hosting/proxy layer (IIS, Nginx, or a load balancer not configured for WebSocket passthrough).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Charts not updating despite new data arriving&lt;/strong&gt;&lt;br&gt;
Verify you are creating new array/object references when updating chart data (as shown in Step 5.3) rather than mutating existing objects in place — Angular and Chart.js both rely on reference changes to detect updates efficiently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;401 Unauthorized when connecting to the hub after adding JWT auth&lt;/strong&gt;&lt;br&gt;
Confirm the &lt;code&gt;accessTokenFactory&lt;/code&gt; is correctly returning a valid, non-expired token, and that the &lt;code&gt;OnMessageReceived&lt;/code&gt; handler on the backend is scoped to the &lt;code&gt;/hubs&lt;/code&gt; path.&lt;/p&gt;


&lt;h2&gt;
  
  
  18. Best Practices Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use SignalR for live updates; use REST only for the initial data snapshot.&lt;/li&gt;
&lt;li&gt;Always implement &lt;code&gt;.withAutomaticReconnect()&lt;/code&gt; and surface connection status in the UI.&lt;/li&gt;
&lt;li&gt;Cap historical data arrays to avoid unbounded memory growth in long-running dashboard sessions.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;OnPush&lt;/code&gt; change detection and &lt;code&gt;trackBy&lt;/code&gt; for performance on data-heavy dashboards.&lt;/li&gt;
&lt;li&gt;Secure both the REST API and the SignalR hub with &lt;code&gt;[Authorize]&lt;/code&gt; once authentication is added.&lt;/li&gt;
&lt;li&gt;Enable WebSockets explicitly on your hosting environment (IIS, Azure App Service, Nginx).&lt;/li&gt;
&lt;li&gt;Use a Redis backplane or Azure SignalR Service only once you scale beyond a single backend instance.&lt;/li&gt;
&lt;li&gt;Throttle extremely high-frequency data sources before broadcasting to clients.&lt;/li&gt;
&lt;li&gt;Always test reconnection behavior by manually killing and restarting the backend during development.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  19. Frequently Asked Questions (FAQ)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: Do I need SignalR, or can I just poll the API every few seconds?&lt;/strong&gt;&lt;br&gt;
Polling works for simple cases but is less efficient and introduces latency. SignalR provides true push-based real-time updates with lower overhead once a connection is established, making it the better choice for dashboards needing sub-second responsiveness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Can I use this approach with React or Vue instead of Angular?&lt;/strong&gt;&lt;br&gt;
Yes. The backend (ASP.NET Core + SignalR) is frontend-agnostic. Microsoft provides the same &lt;code&gt;@microsoft/signalr&lt;/code&gt; JavaScript client that works with any frontend framework, not just Angular.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: What's the difference between SignalR and raw WebSockets?&lt;/strong&gt;&lt;br&gt;
SignalR is a higher-level abstraction built on top of WebSockets (with automatic fallback to other transports). It handles connection management, reconnection, and message serialization for you, whereas raw WebSockets require you to build all of that yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: How do I handle dashboards with many different data types (not just numeric metrics)?&lt;/strong&gt;&lt;br&gt;
Use a more generic message envelope (e.g., a &lt;code&gt;DashboardEvent&lt;/code&gt; with a &lt;code&gt;type&lt;/code&gt; field and a polymorphic payload) instead of a single &lt;code&gt;MetricData&lt;/code&gt; model, and route messages to different handlers/components based on &lt;code&gt;type&lt;/code&gt; on the client side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Is SignalR suitable for very high-frequency data (thousands of updates per second)?&lt;/strong&gt;&lt;br&gt;
For extremely high-frequency data, consider batching updates on the server before broadcasting (as discussed in the performance section) rather than sending every single data point individually.&lt;/p&gt;


&lt;h2&gt;
  
  
  20. Conclusion
&lt;/h2&gt;

&lt;p&gt;You've now built a complete, real-time live data dashboard from the ground up — starting with a simple ASP.NET Core Web API, layering in SignalR for true real-time push updates, building a reactive Angular frontend with live-updating charts, and progressing through advanced topics like authentication, scaling, performance optimization, and production deployment.&lt;/p&gt;

&lt;p&gt;This architecture pattern — REST for initial state, SignalR for live updates, RxJS for reactive state management on the client — is a robust foundation you can extend to almost any real-time use case: IoT monitoring, financial dashboards, server health monitors, live order tracking, or collaborative applications.&lt;/p&gt;

&lt;p&gt;From here, consider exploring related topics such as integrating a real database with Entity Framework Core instead of simulated data, adding role-based dashboards where different users see different metrics, or building alerting rules that trigger notifications when metric thresholds are crossed.&lt;/p&gt;


&lt;h2&gt;
  
  
  Source Code
&lt;/h2&gt;

&lt;p&gt;The complete source code for this project is available on GitHub.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://github.com/ravi-vishwakarma-hash/Live_Data_Dashboard" rel="noopener noreferrer"&gt;Live Data Dashboard – GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The repository contains both the &lt;strong&gt;ASP.NET Core backend&lt;/strong&gt; and &lt;strong&gt;Angular frontend&lt;/strong&gt;, including the SignalR implementation used for real-time metric updates.&lt;/p&gt;

&lt;p&gt;You can clone the repository and follow the setup instructions in the README to run the project locally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/ravi-vishwakarma-hash/Live_Data_Dashboard.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa2p49icuojx69d0zmc36.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa2p49icuojx69d0zmc36.png" alt=" " width="799" height="416"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you found this guide helpful, consider bookmarking it as a reference architecture for your next real-time Angular + ASP.NET Core project.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>core</category>
      <category>programming</category>
    </item>
    <item>
      <title>The Complete Guide to Types of Testing in Software Engineering</title>
      <dc:creator>Ravi Vishwakarma</dc:creator>
      <pubDate>Sun, 07 Jun 2026 13:49:25 +0000</pubDate>
      <link>https://dev.to/ravi-vishwakarma-hash/the-complete-guide-to-types-of-testing-in-software-engineering-3lmj</link>
      <guid>https://dev.to/ravi-vishwakarma-hash/the-complete-guide-to-types-of-testing-in-software-engineering-3lmj</guid>
      <description>&lt;p&gt;Software testing is primarily divided into two main categories: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Functional Testing&lt;/strong&gt;, which verifies what the system does against business requirements&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-Functional Testing&lt;/strong&gt;, which evaluates how the system performs regarding usability, speed, and security. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These categories are executed across different stages, methods, and levels to ensure the final product is stable and reliable.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Functional Testing (The "What")
&lt;/h2&gt;

&lt;p&gt;Functional testing focuses on the business requirements of an application, validating that every feature operates according to specified expectations. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unit Testing:&lt;/strong&gt; Checks individual blocks, methods, or functions of code in strict isolation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration Testing:&lt;/strong&gt; Verifies that different modules or units work together seamlessly when combined.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;System Testing:&lt;/strong&gt; Evaluates the complete, fully integrated software system as a whole to check overall business compliance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Acceptance Testing (UAT):&lt;/strong&gt; Conducted by clients or end-users to confirm if the application is ready for real-world deployment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regression Testing:&lt;/strong&gt; Re-tests existing features after updates or code modifications to ensure no new bugs were introduced.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smoke Testing:&lt;/strong&gt; Runs rapid, high-level checks on core functionalities to verify if a build is stable enough for deeper testing. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Non-Functional Testing (The "How")
&lt;/h2&gt;

&lt;p&gt;Non-functional testing evaluates the quality, technical behaviors, and operational readiness of the software rather than specific features.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Performance Testing:&lt;/strong&gt; Measures system responsiveness, speed, and resource usage under normal and peak conditions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Load Testing:&lt;/strong&gt; Evaluates how the application behaves under a specific, expected volume of simultaneous users.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stress Testing:&lt;/strong&gt; Pushes the system past its normal performance capacity to find its breaking point.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Testing:&lt;/strong&gt; Scans the application for internal or external vulnerabilities and cyber threats.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Usability Testing:&lt;/strong&gt; Assesses user experience, interface layout, and overall user-friendliness.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compatibility Testing:&lt;/strong&gt; Verifies the software functions correctly across various operating systems, browsers, and devices. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Testing Approaches (The "Knowledge")
&lt;/h2&gt;

&lt;p&gt;Software tests are also classified by how much visibility the tester has into the underlying programming code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Black-Box Testing:&lt;/strong&gt; Testing is done completely from the outside, with no knowledge of the internal code or logic structure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;White-Box Testing:&lt;/strong&gt; Testing is executed with deep knowledge of the code, loops, data flows, and internal architectures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grey-Box Testing:&lt;/strong&gt; A hybrid approach where the tester has limited or partial knowledge of the internal system backend.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Methods of Execution
&lt;/h2&gt;

&lt;p&gt;Depending on the engineering team's goals and resources, the testing types above can be carried out through two primary execution methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Manual Testing:&lt;/strong&gt; Human QA specialists manually interact with the user interface and tools to log unexpected defects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated Testing:&lt;/strong&gt; Writing scripts and utilizing testing frameworks to execute repetitive test suites with speed and precision.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Keep coding.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>softwareengineering</category>
      <category>testing</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Implement SSE for Notifications in .NET in a Production-Ready Way?</title>
      <dc:creator>Ravi Vishwakarma</dc:creator>
      <pubDate>Thu, 16 Apr 2026 02:43:20 +0000</pubDate>
      <link>https://dev.to/ravi-vishwakarma-hash/how-to-implement-sse-for-notifications-in-net-in-a-production-ready-way-4a9i</link>
      <guid>https://dev.to/ravi-vishwakarma-hash/how-to-implement-sse-for-notifications-in-net-in-a-production-ready-way-4a9i</guid>
      <description>&lt;p&gt;This is &lt;strong&gt;not a demo&lt;/strong&gt; – this is the &lt;strong&gt;same pattern used in real chat/notification systems&lt;/strong&gt; (WhatsApp-like browser notifications, dashboards, etc.).&lt;/p&gt;

&lt;h2&gt;
  
  
  1. When to Use SSE (Production Decision)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.mindstick.com/interview/34423/what-is-sse" rel="noopener noreferrer"&gt;Use &lt;strong&gt;SSE&lt;/strong&gt;&lt;/a&gt; when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  One-way server → browser notifications&lt;/li&gt;
&lt;li&gt;  Chat message alerts (not full chat transport)&lt;/li&gt;
&lt;li&gt;  Live counters, alerts, status updates&lt;/li&gt;
&lt;li&gt;  Browser-based apps (no mobile socket requirement)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do &lt;strong&gt;NOT&lt;/strong&gt; use SSE for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Binary streaming&lt;/li&gt;
&lt;li&gt;  High-frequency bidirectional chat typing events&lt;/li&gt;
&lt;li&gt;  Mobile background connections (WebSocket better)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Architecture (Production)
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser (EventSource)
        ↓
SSE Controller (long-lived HTTP)
        ↓
ConcurrentDictionary&amp;lt;UserId, SseClient&amp;gt;
        ↓
Notification Publisher (Chat / Job / API)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Key rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;One&lt;/strong&gt; &lt;a href="https://answers.mindstick.com/qa/116201/how-to-implement-the-sse-in-dot-net" rel="noopener noreferrer"&gt;&lt;strong&gt;SSE connection&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;per user&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;No DB polling&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;No blocking threads&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Heartbeat required&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Auto-reconnect handled by browser&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Core SSE Infrastructure (IMPORTANT)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  3.1 SSE Client Model
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class SseClient
{
    public int UserId { get; set; }
    public HttpResponse Response { get; set; }
    public DateTime ConnectedAt { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  3.2 SSE Connection Manager (Thread-Safe)
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public static class SseConnectionManager
{
    private static readonly ConcurrentDictionary&amp;lt;int, SseClient&amp;gt; _clients
        = new ConcurrentDictionary&amp;lt;int, SseClient&amp;gt;();

    public static bool AddClient(int userId, HttpResponse response)
    {
        RemoveClient(userId); // ensure single tab policy (optional)
        return _clients.TryAdd(userId, new SseClient
        {
            UserId = userId,
            Response = response,
            ConnectedAt = DateTime.UtcNow
        });
    }

    public static void RemoveClient(int userId)
    {
        _clients.TryRemove(userId, out _);
    }

    public static bool SendToUser(int userId, string eventName, string data)
    {
        if (!_clients.TryGetValue(userId, out var client))
            return false;

        try
        {
            client.Response.Write($"event: {eventName}\n");
            client.Response.Write($"data: {data}\n\n");
            client.Response.Flush();
            return true;
        }
        catch
        {
            RemoveClient(userId);
            return false;
        }
    }

    public static void Broadcast(string eventName, string data)
    {
        foreach (var userId in _clients.Keys)
        {
            SendToUser(userId, eventName, data);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;  Thread-safe&lt;/li&gt;
&lt;li&gt;  Memory-safe&lt;/li&gt;
&lt;li&gt;  Handles broken connections&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. SSE Controller (Production-Safe)
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class SseController : Controller
{
    [HttpGet]
    public void Subscribe()
    {
        int userId = GetLoggedInUserId();

        Response.ContentType = "text/event-stream";
        Response.CacheControl = "no-cache";
        Response.BufferOutput = false;

        SseConnectionManager.AddClient(userId, Response);

        try
        {
            while (Response.IsClientConnected)
            {
                // Heartbeat every 15 sec
                Response.Write("event: ping\n");
                Response.Write("data: {}\n\n");
                Response.Flush();

                Thread.Sleep(15000);
            }
        }
        finally
        {
            SseConnectionManager.RemoveClient(userId);
        }
    }

    private int GetLoggedInUserId()
    {
        return int.Parse(User.Identity.Name);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Why heartbeat?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  Prevents proxy/load-balancer timeout&lt;/li&gt;
&lt;li&gt;  Detects dead connections&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  5. Sending Notifications (Chat / API / Jobs)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Example: Chat Message Notification&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class NotificationService
{
    public static void NotifyUser(int userId, object payload)
    {
        string json = Newtonsoft.Json.JsonConvert.SerializeObject(payload);
        SseConnectionManager.SendToUser(userId, "message", json);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Usage:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NotificationService.NotifyUser(671496, new
{
    fromUserId = 655093,
    text = "Hello 👋",
    createdAt = DateTime.UtcNow
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  6. Browser Client (Production JS)
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script&amp;gt;
let source = new EventSource("/sse/subscribe");

source.addEventListener("message", function (e) {
    let data = JSON.parse(e.data);
    console.log("Message:", data);
});

source.addEventListener("ping", function () {
    console.log("heartbeat");
});

source.onerror = function () {
    console.log("SSE disconnected. Reconnecting...");
};
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;  Auto-reconnect handled by browser&lt;/li&gt;
&lt;li&gt;  No polling&lt;/li&gt;
&lt;li&gt;  Low memory footprint&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. Multi-Tab Handling (WhatsApp-Like Behavior)
&lt;/h2&gt;

&lt;p&gt;If &lt;strong&gt;same user opens multiple tabs&lt;/strong&gt;, enforce &lt;strong&gt;single active tab&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public static bool AddClient(int userId, HttpResponse response)
{
    RemoveClient(userId); // kicks old tab
    return _clients.TryAdd(userId, new SseClient
    {
        UserId = userId,
        Response = response,
        ConnectedAt = DateTime.UtcNow
    });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Other tabs will automatically disconnect and can show popup:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“This account is active in another tab”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  8. Production Hardening Checklist
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;IIS / Hosting&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Disable response buffering&lt;/li&gt;
&lt;li&gt;  Increase request timeout&lt;/li&gt;
&lt;li&gt;  Enable keep-alive&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Security&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Auth-based user binding&lt;/li&gt;
&lt;li&gt;  No userId in query string&lt;/li&gt;
&lt;li&gt;  Rate-limit notification sends&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Scalability
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  In-memory only → &lt;strong&gt;single server&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  Multi-server → use &lt;strong&gt;Redis Pub/Sub&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  9. Multi-Server (Redis) Architecture
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Server A  ─┐
Server B  ─┼── Redis Pub/Sub ──► SSE Clients
Server C  ─┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Each server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Subscribes to Redis channel&lt;/li&gt;
&lt;li&gt;  Pushes events to connected users&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  10. Why Big Companies Use SSE
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  GitHub notifications&lt;/li&gt;
&lt;li&gt;  Slack web notifications&lt;/li&gt;
&lt;li&gt;  Trading dashboards&lt;/li&gt;
&lt;li&gt;  Monitoring systems&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;  Lower cost than WebSockets&lt;/li&gt;
&lt;li&gt;  Works perfectly behind proxies&lt;/li&gt;
&lt;li&gt;  Auto-reconnect built-in&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  11. SSE vs WebSocket (Quick Decision)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;SSE&lt;/th&gt;
&lt;th&gt;WebSocket&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Direction&lt;/td&gt;
&lt;td&gt;Server → Client&lt;/td&gt;
&lt;td&gt;Two-way&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complexity&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scaling&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;td&gt;Harder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser support&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chat notifications&lt;/td&gt;
&lt;td&gt;Best&lt;/td&gt;
&lt;td&gt;Overkill&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Final Recommendation (For Your Chat App)
&lt;/h2&gt;

&lt;p&gt;Use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;SSE&lt;/strong&gt; → notifications, unread count, alerts&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;API&lt;/strong&gt; → send message&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Optional WebSocket&lt;/strong&gt; → typing indicators only&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Read Also&lt;/strong&gt;&amp;nbsp;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://www.mindstick.com/interview/34423/what-is-sse" rel="noopener noreferrer"&gt;&lt;strong&gt;What is SSE?&lt;/strong&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://www.mindstick.com/interview/34422/how-to-implement-websocket-ap" rel="noopener noreferrer"&gt;&lt;strong&gt;How to implement Websocket AP?&lt;/strong&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>backend</category>
      <category>dotnet</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>EmailSpamChecker class for .NET Framework 4.7</title>
      <dc:creator>Ravi Vishwakarma</dc:creator>
      <pubDate>Wed, 18 Jun 2025 16:53:15 +0000</pubDate>
      <link>https://dev.to/ravi-vishwakarma-hash/emailspamchecker-class-for-net-framework-47-59h8</link>
      <guid>https://dev.to/ravi-vishwakarma-hash/emailspamchecker-class-for-net-framework-47-59h8</guid>
      <description>&lt;p&gt;Here’s a complete and reusable EmailSpamChecker class for .NET Framework 4.7, which performs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Syntax validation&lt;/li&gt;
&lt;li&gt;Blacklisted domain check&lt;/li&gt;
&lt;li&gt;MX record verification (requires DnsClient NuGet)&lt;/li&gt;
&lt;li&gt;Scoring logic for spam suspicion&lt;/li&gt;
&lt;li&gt;Customizable domain block list&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Step 1: Install DnsClient NuGet Package
&lt;/h3&gt;

&lt;p&gt;From NuGet Package Manager Console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Install-Package DnsClient
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Step 2: EmailSpamChecker Class
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Mail;
using System.Text.RegularExpressions;
using DnsClient;

public class EmailSpamChecker
{
    private static readonly string[] _blacklistedDomains = new[]
    {
        "mailinator.com", "10minutemail.com", "guerrillamail.com",
        "tempmail.com", "desy.xyz", "yopmail.com", "trashmail.com"
    };

    private static readonly string[] _suspiciousTlds = new[]
    {
        ".xyz", ".tk", ".gq", ".ml", ".cf"
    };

    public class SpamCheckResult
    {
        public bool IsValid { get; set; }
        public int SpamScore { get; set; }
        public List&amp;lt;string&amp;gt; Issues { get; set; } = new List&amp;lt;string&amp;gt;();
    }

    public static SpamCheckResult CheckEmail(string email)
    {
        var result = new SpamCheckResult();

        // Check basic syntax
        if (!IsValidSyntax(email))
        {
            result.Issues.Add("Invalid email syntax.");
            result.SpamScore += 2;
            result.IsValid = false;
            return result;
        }

        string domain = email.Split('@')[1].ToLower();

        // Check blacklist
        if (_blacklistedDomains.Contains(domain))
        {
            result.Issues.Add("Domain is blacklisted.");
            result.SpamScore += 3;
        }

        // Check suspicious TLD
        if (_suspiciousTlds.Any(tld =&amp;gt; domain.EndsWith(tld)))
        {
            result.Issues.Add("Suspicious top-level domain.");
            result.SpamScore += 1;
        }

        // Check if it looks random (e.g., xas21@abc.xyz)
        if (IsRandomLooking(email))
        {
            result.Issues.Add("Username looks auto-generated or random.");
            result.SpamScore += 1;
        }

        // Check MX record
        if (!HasValidMxRecord(domain))
        {
            result.Issues.Add("Domain has no MX record (may not receive emails).");
            result.SpamScore += 2;
        }

        result.IsValid = result.SpamScore &amp;lt; 3;
        return result;
    }

    private static bool IsValidSyntax(string email)
    {
        try
        {
            var addr = new MailAddress(email);
            return addr.Address == email;
        }
        catch
        {
            return false;
        }
    }

    private static bool HasValidMxRecord(string domain)
    {
        try
        {
            var lookup = new LookupClient();
            var result = lookup.Query(domain, QueryType.MX);
            return result.Answers.MxRecords().Any();
        }
        catch
        {
            return false;
        }
    }

    private static bool IsRandomLooking(string email)
    {
        string user = email.Split('@')[0];

        // Count digits and special chars
        int digitCount = user.Count(char.IsDigit);
        int symbolCount = user.Count(c =&amp;gt; !char.IsLetterOrDigit(c));

        // Very short or too random
        return user.Length &amp;lt; 5 || digitCount + symbolCount &amp;gt; 4;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Usage Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;string inputEmail = "xyz@desy.xyz";
var result = EmailSpamChecker.CheckEmail(inputEmail);

if (!result.IsValid)
{
    Console.WriteLine("Potential spam or invalid email:");
    foreach (var issue in result.Issues)
        Console.WriteLine($"- {issue}");
}
else
{
    Console.WriteLine("Email looks clean.");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Output Example
&lt;/h3&gt;

&lt;p&gt;Potential spam or invalid email:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Domain is blacklisted.&lt;/li&gt;
&lt;li&gt;Suspicious top-level domain.&lt;/li&gt;
&lt;li&gt;Domain has no MX record (may not receive emails).&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Thanks&lt;br&gt;
Keep Coding.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>microsoft</category>
    </item>
    <item>
      <title>Clean Architecture in .net application step by step</title>
      <dc:creator>Ravi Vishwakarma</dc:creator>
      <pubDate>Sat, 12 Apr 2025 13:54:40 +0000</pubDate>
      <link>https://dev.to/ravi-vishwakarma-hash/clean-architecture-in-net-application-step-by-step-2ol0</link>
      <guid>https://dev.to/ravi-vishwakarma-hash/clean-architecture-in-net-application-step-by-step-2ol0</guid>
      <description>&lt;p&gt;&lt;strong&gt;Clean Architecture&lt;/strong&gt; is a software design philosophy introduced by &lt;strong&gt;Robert C. Martin (Uncle Bob)&lt;/strong&gt;. Its goal is to create systems that are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Independent of frameworks&lt;/li&gt;
&lt;li&gt;Testable&lt;/li&gt;
&lt;li&gt;Independent of UI or database&lt;/li&gt;
&lt;li&gt;Flexible and maintainable&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Principles
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Separation of Concerns&lt;/strong&gt;: Each layer of the application has a specific responsibility and doesn't mix with others.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency Rule&lt;/strong&gt;: Code dependencies must point inward, from outer layers (UI, DB) to inner layers (Use Cases, Entities). Inner layers know nothing about the outer layers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inversion of Control&lt;/strong&gt;: High-level policies (business rules) shouldn't depend on low-level details (DB, APIs). Instead, interfaces should be used to invert control.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Independence&lt;/strong&gt;: 

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Framework Independent&lt;/strong&gt;: Can change from ASP.NET to another without affecting core logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database Independent&lt;/strong&gt;: Can switch from SQL Server to MongoDB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UI Independent&lt;/strong&gt;: Can change from Web to Mobile easily.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Layers
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Domain Layer (Entities)&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Contains core business rules&lt;/li&gt;
&lt;li&gt;Framework and UI agnostic&lt;/li&gt;
&lt;li&gt;Pure business objects (e.g., User, Order)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application Layer (Use Cases)&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Contains application-specific logic&lt;/li&gt;
&lt;li&gt;Coordinates the flow of data&lt;/li&gt;
&lt;li&gt;Interfaces with repositories&lt;/li&gt;
&lt;li&gt;E.g., CreateUser, GetOrders&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interface Adapters Layer&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Adapts data between use cases and frameworks&lt;/li&gt;
&lt;li&gt;Includes Controllers, Gateways, ViewModels&lt;/li&gt;
&lt;li&gt;Implements interfaces from Application Layer&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frameworks &amp;amp; Drivers&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;External tools and frameworks (e.g., ASP.NET Core, EF Core)&lt;/li&gt;
&lt;li&gt;Dependency injection and infrastructure&lt;/li&gt;
&lt;li&gt;Least stable and most replaceable&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Flow of Control
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;UI (Controller) → Use Case → Domain Logic → Output/Result&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Controllers receive the input (e.g., HTTP request)&lt;/li&gt;
&lt;li&gt;Pass data to the Use Case&lt;/li&gt;
&lt;li&gt;Use Case performs business logic using the Domain&lt;/li&gt;
&lt;li&gt;Returns results (e.g., DTOs or ViewModels)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ⚖️ Advantages
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High Testability&lt;/strong&gt; – Business logic can be tested without UI or DB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy to Maintain&lt;/strong&gt; – Changes in UI/DB won’t affect core logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability&lt;/strong&gt;– Modular design helps teams work independently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reusability&lt;/strong&gt;– Domain and Use Cases are reusable in different apps.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ⚠️ Common Mistakes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Violating the dependency rule (e.g., use case calling Entity Framework directly)&lt;/li&gt;
&lt;li&gt;Mixing business logic with framework code&lt;/li&gt;
&lt;li&gt;Skipping interfaces (tight coupling)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧱 Project Structure
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: CleanArchitectureDemo.sln&lt;br&gt;
&lt;strong&gt;Projects&lt;/strong&gt;:&lt;br&gt;
├── CleanArchitectureDemo.Domain         // &lt;strong&gt;Entities&lt;/strong&gt;&lt;br&gt;
├── CleanArchitectureDemo.Application    // &lt;strong&gt;Use Cases&lt;/strong&gt;&lt;br&gt;
├── CleanArchitectureDemo.Infrastructure // &lt;strong&gt;DB / Repositories&lt;/strong&gt;&lt;br&gt;
├── CleanArchitectureDemo.API            // &lt;strong&gt;Web API (Controllers)&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  🔧 Create Projects
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet new sln -n CleanArchitectureDemo

dotnet new classlib -n CleanArchitectureDemo.Domain
dotnet new classlib -n CleanArchitectureDemo.Application
dotnet new classlib -n CleanArchitectureDemo.Infrastructure
dotnet new webapi   -n CleanArchitectureDemo.API

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Add them to the solution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet sln add CleanArchitectureDemo.Domain
dotnet sln add CleanArchitectureDemo.Application
dotnet sln add CleanArchitectureDemo.Infrastructure
dotnet sln add CleanArchitectureDemo.API

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add project references:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet add CleanArchitectureDemo.Application reference CleanArchitectureDemo.Domain
dotnet add CleanArchitectureDemo.Infrastructure reference CleanArchitectureDemo.Application
dotnet add CleanArchitectureDemo.API reference CleanArchitectureDemo.Application
dotnet add CleanArchitectureDemo.API reference CleanArchitectureDemo.Infrastructure

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  📁 Layer Implementations
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Domain/Entities/User.cs&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace CleanArchitectureDemo.Domain.Entities
{
    public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Application/Interfaces/IUserRepository.cs&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using CleanArchitectureDemo.Domain.Entities;
using System.Collections.Generic;

namespace CleanArchitectureDemo.Application.Interfaces
{
    public interface IUserRepository
    {
        IEnumerable&amp;lt;User&amp;gt; GetAll();
        User GetById(int id);
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Application/UseCases/UserService.cs&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using CleanArchitectureDemo.Application.Interfaces;
using CleanArchitectureDemo.Domain.Entities;
using System.Collections.Generic;

namespace CleanArchitectureDemo.Application.UseCases
{
    public class UserService
    {
        private readonly IUserRepository _userRepository;

        public UserService(IUserRepository userRepository)
        {
            _userRepository = userRepository;
        }

        public IEnumerable&amp;lt;User&amp;gt; GetUsers() =&amp;gt; _userRepository.GetAll();

        public User GetUser(int id) =&amp;gt; _userRepository.GetById(id);
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Infrastructure/Repositories/InMemoryUserRepository.cs&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using CleanArchitectureDemo.Application.Interfaces;
using CleanArchitectureDemo.Domain.Entities;
using System.Collections.Generic;
using System.Linq;

namespace CleanArchitectureDemo.Infrastructure.Repositories
{
    public class InMemoryUserRepository : IUserRepository
    {
        private readonly List&amp;lt;User&amp;gt; _users = new()
        {
            new User { Id = 1, Name = "Alice", Email = "alice@example.com" },
            new User { Id = 2, Name = "Bob", Email = "bob@example.com" }
        };

        public IEnumerable&amp;lt;User&amp;gt; GetAll() =&amp;gt; _users;

        public User GetById(int id) =&amp;gt; _users.FirstOrDefault(u =&amp;gt; u.Id == id);
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. API/Controllers/UserController.cs&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using Microsoft.AspNetCore.Mvc;
using CleanArchitectureDemo.Application.UseCases;
using CleanArchitectureDemo.Domain.Entities;
using System.Collections.Generic;

namespace CleanArchitectureDemo.API.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class UserController : ControllerBase
    {
        private readonly UserService _userService;

        public UserController(UserService userService)
        {
            _userService = userService;
        }

        [HttpGet]
        public IEnumerable&amp;lt;User&amp;gt; Get() =&amp;gt; _userService.GetUsers();

        [HttpGet("{id}")]
        public ActionResult&amp;lt;User&amp;gt; Get(int id)
        {
            var user = _userService.GetUser(id);
            if (user == null)
                return NotFound();
            return user;
        }
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6. API/Program.cs – Register Dependencies&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using CleanArchitectureDemo.Application.Interfaces;
using CleanArchitectureDemo.Application.UseCases;
using CleanArchitectureDemo.Infrastructure.Repositories;

var builder = WebApplication.CreateBuilder(args);

// Dependency Injection
builder.Services.AddScoped&amp;lt;IUserRepository, InMemoryUserRepository&amp;gt;();
builder.Services.AddScoped&amp;lt;UserService&amp;gt;();

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

app.UseSwagger();
app.UseSwaggerUI();

app.MapControllers();
app.Run();

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Thanks &lt;br&gt;
Keep Coding.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>dotnet</category>
      <category>cleancode</category>
      <category>cleancoding</category>
    </item>
    <item>
      <title>Reduce the page execution time</title>
      <dc:creator>Ravi Vishwakarma</dc:creator>
      <pubDate>Sun, 06 Apr 2025 18:32:50 +0000</pubDate>
      <link>https://dev.to/ravi-vishwakarma-hash/reduce-the-time-of-page-execution-15gh</link>
      <guid>https://dev.to/ravi-vishwakarma-hash/reduce-the-time-of-page-execution-15gh</guid>
      <description>&lt;p&gt;Alright, you're aiming for top-notch Core Web Vitals — crisp, smooth, fast-loading webpages that make Lighthouse go “WOW” and Google rank you like royalty 👑. Let’s break down the best practices to write HTML, CSS, JS, fonts, etc. that give you green scores across all Web Vitals:&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;HTML: Keep it Clean, Semantic &amp;amp; Lightweight&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang="en"&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;meta charset="UTF-8" /&amp;gt;
  &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1" /&amp;gt;
  &amp;lt;title&amp;gt;Your Awesome Page&amp;lt;/title&amp;gt;

  &amp;lt;!-- Preconnects for performance --&amp;gt;
  &amp;lt;link rel="preconnect" href="https://fonts.googleapis.com" /&amp;gt;
  &amp;lt;link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /&amp;gt;

  &amp;lt;!-- Inline Critical CSS --&amp;gt;
  &amp;lt;style&amp;gt;
    /* Only critical styles here (for above-the-fold content) */
    body {
      font-family: 'Inter', sans-serif;
      margin: 0;
      padding: 0;
      background: #fff;
      color: #333;
    }
    h1 {
      font-size: 2rem;
      text-align: center;
      margin-top: 2rem;
    }
  &amp;lt;/style&amp;gt;

  &amp;lt;!-- Load fonts properly --&amp;gt;
  &amp;lt;link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&amp;amp;display=swap" rel="stylesheet" /&amp;gt;

  &amp;lt;!-- Defer non-critical CSS --&amp;gt;
  &amp;lt;link rel="stylesheet" href="styles.css" media="print" onload="this.media='all'" /&amp;gt;
  &amp;lt;noscript&amp;gt;&amp;lt;link rel="stylesheet" href="styles.css" /&amp;gt;&amp;lt;/noscript&amp;gt;

  &amp;lt;!-- Important: Avoid large JS in &amp;lt;head&amp;gt; --&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;

  &amp;lt;h1&amp;gt;Hello, Web Vitals!&amp;lt;/h1&amp;gt;
  &amp;lt;button id="btn"&amp;gt;Click Me&amp;lt;/button&amp;gt;

  &amp;lt;!-- Minimal Critical JS (inlined) --&amp;gt;
  &amp;lt;script&amp;gt;
    document.addEventListener('DOMContentLoaded', () =&amp;gt; {
      document.getElementById('btn').addEventListener('click', () =&amp;gt; {
        alert('Hi there!');
      });
    });
  &amp;lt;/script&amp;gt;

  &amp;lt;!-- Async/defer all non-critical JS --&amp;gt;
  &amp;lt;script src="main.js" defer&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;CSS: Optimize, Minify &amp;amp; Use Only What’s Needed&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Extract Critical CSS and inline it (manually or with tools like critical)&lt;/li&gt;
&lt;li&gt;Load the rest asynchronously (media="print" trick)&lt;/li&gt;
&lt;li&gt;Avoid unused styles (Lighthouse can show you these)&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;:where()&lt;/code&gt; or &lt;code&gt;:is()&lt;/code&gt; instead of long selector chains (faster parsing)&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;strong&gt;JavaScript: Split, Defer, and Delay&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Inline only critical JS&lt;/li&gt;
&lt;li&gt;Use defer for everything else&lt;/li&gt;
&lt;li&gt;Use code splitting (in frameworks like React, Vue, etc.)&lt;/li&gt;
&lt;li&gt;Avoid large JS bundles — aim for &amp;lt;150KB (gzipped) for initial load&lt;/li&gt;
&lt;li&gt;Lazy-load expensive features like charts, sliders, chat widgets&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;strong&gt;Fonts: Load Fast and Avoid Layout Shifts&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use &lt;code&gt;font-display: swap&lt;/code&gt; (default with Google Fonts)&lt;/li&gt;
&lt;li&gt;Preconnect to &lt;code&gt;fonts.googleapis.com&lt;/code&gt; and &lt;code&gt;fonts.gstatic.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Prefer system fonts for super-fast load (optional)
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700&amp;amp;display=swap');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OR use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;Images: Compress, Lazy Load, Responsive&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;img src="banner.jpg" loading="lazy" alt="Banner" width="600" height="400" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Use &lt;code&gt;loading="lazy"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add width and height attributes to prevent CLS (layout shift)&lt;/li&gt;
&lt;li&gt;Prefer &lt;code&gt;WebP&lt;/code&gt; or &lt;code&gt;AVIF&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;srcset&lt;/code&gt;for responsive images if needed&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;strong&gt;Core Web Vitals Targets (What You’re Chasing)&lt;/strong&gt;&lt;br&gt;
Metric                          Goal&lt;br&gt;
LCP (Largest Contentful Paint)  &amp;lt; 2.5s&lt;br&gt;
FID (First Input Delay)         &amp;lt; 100ms&lt;br&gt;
CLS (Cumulative Layout Shift)   &amp;lt; 0.1&lt;br&gt;
TTI (Time to Interactive)   As fast as possible&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Read More&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://dev.to/ravivis13370227/comprehensive-guide-to-documentation-comments-in-c-379d"&gt;Comprehensive Guide to Documentation Comments in C#&lt;/a&gt;&lt;br&gt;
&lt;a href="https://dev.to/ravivis13370227/understanding-strong-password-regex-in-c-from-basic-to-advanced-25cf"&gt;Understanding Strong Password Regex in C#: From Basic to Advanced&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Thank You,&lt;br&gt;
Keep coding&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Understanding Strong Password Regex in C#: From Basic to Advanced</title>
      <dc:creator>Ravi Vishwakarma</dc:creator>
      <pubDate>Mon, 03 Feb 2025 13:09:57 +0000</pubDate>
      <link>https://dev.to/ravi-vishwakarma-hash/understanding-strong-password-regex-in-c-from-basic-to-advanced-25cf</link>
      <guid>https://dev.to/ravi-vishwakarma-hash/understanding-strong-password-regex-in-c-from-basic-to-advanced-25cf</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;In today’s digital age, ensuring password security is a critical aspect of software development. One way to enforce security standards is by using regular expressions (Regex) to validate passwords. In this article, we will explore different levels of password complexity, ranging from basic to advanced, using C#.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;1. Basic Password Validation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A basic password validation rule simply enforces a minimum length. The following Regex pattern ensures that a password is at least 8 characters long:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;string basicRegex = "^.{8,}$";
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Explanation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;^&lt;/code&gt; and &lt;code&gt;$&lt;/code&gt; mark the start and end of the string.&lt;/li&gt;
&lt;li&gt;.{8,} ensures the password has at least 8 characters of any type.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While this rule prevents extremely short passwords, it does not enforce complexity.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;2. Intermediate Password Validation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To increase security, we can require passwords to contain at least one letter and one digit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;string intermediateRegex = "^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$";
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Explanation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;(?=.*[A-Za-z])&lt;/code&gt; ensures at least one letter (uppercase or lowercase).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;(?=.*\d)&lt;/code&gt; ensures at least one digit.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[A-Za-z\d]{8,}&lt;/code&gt; allows only letters and digits, with a minimum of 8 characters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This regex prevents purely numeric or alphabetical passwords but still lacks special character enforcement.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;3. Advanced Password Validation (Strong Passwords)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A strong password should meet the following criteria:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;At least 8 characters long.&lt;/li&gt;
&lt;li&gt;Contains at least one lowercase letter.&lt;/li&gt;
&lt;li&gt;Contains at least one uppercase letter.&lt;/li&gt;
&lt;li&gt;Contains at least one digit.&lt;/li&gt;
&lt;li&gt;Contains at least one special character from a predefined set (e.g., @$!%*?&amp;amp;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Regex for Strong Passwords:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;string advancedRegex = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&amp;amp;])[A-Za-z\d@$!%*?&amp;amp;]{8,}$";
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;string extendedAdvancedRegex = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&amp;amp;^#()[\]{}|\\/\-+_.:;=,~`])[^\s&amp;lt;&amp;gt;]{8,}$";
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Explanation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;(?=.*[a-z])&lt;/code&gt; ensures at least one lowercase letter.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;(?=.*[A-Z])&lt;/code&gt; ensures at least one uppercase letter.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;(?=.*\d)&lt;/code&gt; ensures at least one digit.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;(?=.*[@$!%*?&amp;amp;])&lt;/code&gt; ensures at least one special character.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;(?=.*[@$!%*?&amp;amp;^#()[\]{}|\\/\-+_.:;=,~])&lt;/code&gt; ensures at least one special character from an extended set.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[^\s&amp;lt;&amp;gt;]{8,}&lt;/code&gt; ensures a minimum length of 8 characters while restricting whitespace and certain unsafe characters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This pattern significantly enhances password security and meets most modern security standards.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;4. Implementing Password Validation in C#&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Below is a simple C# program that validates passwords using Regex:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using System;
using System.Text.RegularExpressions;

public class PasswordValidator
{
    public static bool IsValidPassword(string password, string pattern)
    {
        return Regex.IsMatch(password, pattern);
    }

    public static void Main()
    {
        string password = "StrongP@ss1";

        // Use one of the regex patterns defined above
        string pattern = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&amp;amp;^#()[\]{}|\\/\-+_.:;=,~`])[^\s&amp;lt;&amp;gt;]{8,}$";

        if (IsValidPassword(password, pattern))
            Console.WriteLine("Password is valid!");
        else
            Console.WriteLine("Password does not meet the requirements.");
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Implementing password validation using Regex in C# ensures strong security policies for user authentication. By progressively enforcing length, character variety, and complexity, we can significantly reduce vulnerabilities related to weak passwords.&lt;/p&gt;

&lt;p&gt;For enterprise-level applications, additional security measures such as multi-factor authentication (MFA) and rate-limiting login attempts should also be implemented alongside Regex-based password validation.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Connect with Me!&lt;/strong&gt;&lt;br&gt;
Thanks for reading! If you found this helpful, consider following me for more content like this.&lt;/p&gt;

&lt;p&gt;🌟 &lt;a href="https://www.linkedin.com/in/ravi-vishwakarma-a20b451b6/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;br&gt;
💻 &lt;a href="https://github.com/ravivish786" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your support motivates me to keep sharing valuable insights. Stay tuned for more! 🚀&lt;/p&gt;

&lt;p&gt;Thanks for Reading.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>AddMVC() vs AddControllersWithViews() vs AddControllers() vs AddRazorPages() | ASP.Net Core</title>
      <dc:creator>Ravi Vishwakarma</dc:creator>
      <pubDate>Tue, 28 Jan 2025 02:42:18 +0000</pubDate>
      <link>https://dev.to/ravi-vishwakarma-hash/addmvc-vs-addcontrollerswithviews-vs-addcontrollers-vs-addrazorpages-aspnet-core-bgo</link>
      <guid>https://dev.to/ravi-vishwakarma-hash/addmvc-vs-addcontrollerswithviews-vs-addcontrollers-vs-addrazorpages-aspnet-core-bgo</guid>
      <description>&lt;p&gt;In ASP.NET Core, the methods &lt;code&gt;AddMvc()&lt;/code&gt;, &lt;code&gt;AddControllersWithViews()&lt;/code&gt;, &lt;code&gt;AddControllers()&lt;/code&gt;, and &lt;code&gt;AddRazorPages()&lt;/code&gt; are used to configure services for various application architectures. Each of these methods serves a specific purpose depending on the type of application you are building.&lt;/p&gt;




&lt;h3&gt;
  
  
  AddMvc()
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Configures the app to use the complete ASP.NET Core MVC framework, including support for controllers, views, Razor Pages, and API features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Status&lt;/strong&gt;: This method is obsolete in recent ASP.NET Core versions (3.0 and later). It's replaced by more granular methods like AddControllersWithViews() or AddRazorPages().&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Case&lt;/strong&gt;: Used in older versions (before ASP.NET Core 3.0) to set up both MVC and Razor Pages in a single step.&lt;/p&gt;




&lt;h3&gt;
  
  
  AddControllersWithViews()
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Configures the app to support controllers and views (for MVC-style web apps). Does not include Razor Pages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Case&lt;/strong&gt;: Ideal for traditional MVC web applications where controllers and views are the primary focus.&lt;br&gt;
Suitable for server-rendered HTML with routing, model binding, and validation.&lt;br&gt;
&lt;strong&gt;Features&lt;/strong&gt;: Adds support for controllers and views.&lt;br&gt;
Does not support Razor Pages (e.g., .cshtml files under the /Pages folder).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;builder.Services.AddControllersWithViews();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  AddControllers()
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Configures the app to support controllers only, for building API applications without views.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Case&lt;/strong&gt;: Suitable for building Web APIs. Does not include Razor Pages or support for views.&lt;br&gt;
&lt;strong&gt;Features&lt;/strong&gt;: Adds support for controllers, but excludes Razor Pages and views. Focused on API endpoints, ideal for RESTful services.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;builder.Services.AddControllers();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  AddRazorPages()
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Configures the app to support Razor Pages, a page-focused programming model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Case&lt;/strong&gt;: Ideal for applications primarily using Razor Pages for server-side rendering. Suitable for smaller apps or apps with a page-oriented architecture (e.g., intranet applications).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Features&lt;/strong&gt;:&lt;br&gt;
Supports Razor Pages (e.g., .cshtml files under the /Pages folder).&lt;br&gt;
Does not support controllers or views unless explicitly added.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;builder.Services.AddRazorPages();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  .NET Core Middleware Comparison Table
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Method&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Controllers&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Views&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Razor Pages&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;APIs (JSON/XML responses)&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AddMvc()&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AddControllersWithViews()&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AddControllers()&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AddRazorPages()&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  Choosing the Right Method
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;For APIs:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;AddControllers()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Example: Building a RESTful API that does not require views or Razor Pages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For Traditional MVC Applications:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;AddControllersWithViews()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Example: Building a web app with controllers and views for server-side rendering.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For Razor Pages Applications:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;AddRazorPages()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Example: Building a simple page-oriented application with &lt;code&gt;.cshtml&lt;/code&gt; files under &lt;code&gt;/Pages&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For Hybrid Scenarios:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Combine &lt;code&gt;AddControllersWithViews()&lt;/code&gt; and &lt;code&gt;AddRazorPages()&lt;/code&gt; for apps that need both controllers and Razor Pages.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Best Practices
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Use specific configurations (e.g., &lt;code&gt;AddControllersWithViews()&lt;/code&gt;, &lt;code&gt;AddRazorPages()&lt;/code&gt;) instead of &lt;code&gt;AddMvc()&lt;/code&gt; for better clarity and performance.&lt;/li&gt;
&lt;li&gt;Choose the appropriate method based on your application's architecture and the required features.&lt;/li&gt;
&lt;li&gt;If your app includes API endpoints and views, combine &lt;code&gt;AddControllersWithViews()&lt;/code&gt; with features like &lt;code&gt;JsonOptions&lt;/code&gt; to configure API behavior.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Connect with Me!&lt;/strong&gt;&lt;br&gt;
Thanks for reading! If you found this helpful, consider following me for more content like this.&lt;/p&gt;

&lt;p&gt;🌟 &lt;a href="https://www.linkedin.com/in/ravi-vishwakarma-a20b451b6/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;br&gt;
💻 &lt;a href="https://github.com/ravivish786" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your support motivates me to keep sharing valuable insights. Stay tuned for more! 🚀&lt;/p&gt;

&lt;p&gt;Thanks for Reading.&lt;/p&gt;

</description>
      <category>netcore</category>
      <category>dotnet</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Comprehensive Guide to Documentation Comments in C#</title>
      <dc:creator>Ravi Vishwakarma</dc:creator>
      <pubDate>Sun, 19 Jan 2025 11:20:09 +0000</pubDate>
      <link>https://dev.to/ravi-vishwakarma-hash/comprehensive-guide-to-documentation-comments-in-c-379d</link>
      <guid>https://dev.to/ravi-vishwakarma-hash/comprehensive-guide-to-documentation-comments-in-c-379d</guid>
      <description>&lt;p&gt;Documentation comments in C# are structured comments that describe code functionality and are processed by tools like Visual Studio and Sandcastle to generate user-friendly documentation. These comments are written in XML format and placed above code elements like classes, methods, properties, and fields.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Syntax for Documentation Comments in C#&lt;/strong&gt;&lt;br&gt;
In C#, documentation comments start with three slashes (&lt;code&gt;///&lt;/code&gt;) and support XML-based tags to provide detailed descriptions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/// &amp;lt;summary&amp;gt;
/// Represents a basic calculator for arithmetic operations.
/// &amp;lt;/summary&amp;gt;
public class Calculator
{
    // Class implementation
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Common XML Tags in C#&lt;/strong&gt;&lt;br&gt;
Here are the most commonly used XML tags in C# documentation comments:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;General Tags&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;summary&amp;gt;&lt;/code&gt;:   Provides a brief summary of a class, method, or property.&lt;br&gt;
&lt;code&gt;&amp;lt;remarks&amp;gt;&lt;/code&gt;:   Adds additional or detailed remarks about the element.&lt;br&gt;
&lt;code&gt;&amp;lt;example&amp;gt;&lt;/code&gt;:   Contains code examples to demonstrate usage.&lt;br&gt;
&lt;code&gt;&amp;lt;value&amp;gt;&lt;/code&gt;: Describes the value returned by a property.&lt;br&gt;
&lt;code&gt;&amp;lt;inheritdoc&amp;gt;&lt;/code&gt;:    Inherits documentation from the base class or interface.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Method-Specific Tags&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;param&amp;gt;&lt;/code&gt;: Describes a method parameter.&lt;br&gt;
&lt;code&gt;&amp;lt;returns&amp;gt;&lt;/code&gt;:   Documents the return value of a method.&lt;br&gt;
&lt;code&gt;&amp;lt;exception&amp;gt;&lt;/code&gt;: Describes exceptions thrown by the method.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-Referencing Tags&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;see&amp;gt;&lt;/code&gt;:   Links to another code element for inline reference.&lt;br&gt;
&lt;code&gt;&amp;lt;seealso&amp;gt;&lt;/code&gt;:   Refers to related code elements for more information.&lt;br&gt;
&lt;code&gt;&amp;lt;c&amp;gt;&lt;/code&gt;: Embeds inline code.&lt;br&gt;
&lt;code&gt;&amp;lt;code&amp;gt;&lt;/code&gt;:  Displays a block of code in the documentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Examples of Documentation Comments in C#&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Documenting a Class&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/// &amp;lt;summary&amp;gt;
/// Performs basic arithmetic operations such as addition and subtraction.
/// &amp;lt;/summary&amp;gt;
/// &amp;lt;remarks&amp;gt;
/// This class is intended for demonstration purposes only.
/// &amp;lt;/remarks&amp;gt;
public class Calculator
{
    // Class implementation
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Documenting a Method&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/// &amp;lt;summary&amp;gt;
/// Adds two integers and returns the result.
/// &amp;lt;/summary&amp;gt;
/// &amp;lt;param name="a"&amp;gt;The first integer.&amp;lt;/param&amp;gt;
/// &amp;lt;param name="b"&amp;gt;The second integer.&amp;lt;/param&amp;gt;
/// &amp;lt;returns&amp;gt;The sum of the two integers.&amp;lt;/returns&amp;gt;
/// &amp;lt;exception cref="ArgumentNullException"&amp;gt;Thrown if a or b is null.&amp;lt;/exception&amp;gt;
public int Add(int a, int b)
{
    if (a == null || b == null)
    {
        throw new ArgumentNullException("Parameters cannot be null.");
    }
    return a + b;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Documenting a Property&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/// &amp;lt;summary&amp;gt;
/// Gets or sets the name of the calculator's manufacturer.
/// &amp;lt;/summary&amp;gt;
/// &amp;lt;value&amp;gt;
/// A string representing the manufacturer's name.
/// &amp;lt;/value&amp;gt;
public string Manufacturer { get; set; }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Documenting an Example&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/// &amp;lt;example&amp;gt;
/// &amp;lt;code&amp;gt;
/// Calculator calc = new Calculator();
/// int result = calc.Add(5, 10);
/// Console.WriteLine(result); // Output: 15
/// &amp;lt;/code&amp;gt;
/// &amp;lt;/example&amp;gt;
public int Add(int a, int b)
{
    return a + b;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Best Practices for Documentation Comments in C#&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Write Clear Summaries&lt;/strong&gt;: Use  to describe the purpose of a code element in a concise and meaningful way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use  and  Tags Appropriately&lt;/strong&gt;: Ensure all method parameters and return values are documented.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provide Examples&lt;/strong&gt;: Use  tags to demonstrate usage, especially for complex methods.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Link to Related Elements&lt;/strong&gt;: Utilize  and  to refer readers to related parts of the codebase.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintain Consistency&lt;/strong&gt;: Follow consistent formatting and style for all documentation comments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Update Regularly&lt;/strong&gt;: Sync documentation comments with code changes to prevent inaccuracies.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For more go to Microsoft &lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/xmldoc/" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>aspdotnet</category>
      <category>netcore</category>
      <category>discuss</category>
    </item>
  </channel>
</rss>
