<?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: Farrukh Rehman</title>
    <description>The latest articles on DEV Community by Farrukh Rehman (@farrukh_rehman).</description>
    <link>https://dev.to/farrukh_rehman</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3574936%2Fbafd285a-00c8-4598-b8b8-25d42e98bbfe.jpg</url>
      <title>DEV Community: Farrukh Rehman</title>
      <link>https://dev.to/farrukh_rehman</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/farrukh_rehman"/>
    <language>en</language>
    <item>
      <title>🧱 Lesson 13B: Centralized Error Handling &amp; Validation (Frontend)</title>
      <dc:creator>Farrukh Rehman</dc:creator>
      <pubDate>Mon, 16 Feb 2026 05:47:43 +0000</pubDate>
      <link>https://dev.to/farrukh_rehman/lesson-13b-centralized-error-handling-validation-frontend-1b31</link>
      <guid>https://dev.to/farrukh_rehman/lesson-13b-centralized-error-handling-validation-frontend-1b31</guid>
      <description>&lt;p&gt;Series: From Code to Cloud: Building a Production-Ready .NET Application&lt;br&gt;
By: Farrukh Rehman - Senior .NET Full Stack Developer / Team Lead&lt;br&gt;
LinkedIn: &lt;a href="https://linkedin.com/in/farrukh-rehman" rel="noopener noreferrer"&gt;https://linkedin.com/in/farrukh-rehman&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/farrukh1212cs" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source Code Backend : &lt;a href="https://github.com/farrukh1212cs/ECommerce-Backend.git" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs/ECommerce-Backend.git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source Code Frontend : &lt;a href="https://github.com/farrukh1212cs/ECommerce-Frontend.git" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs/ECommerce-Frontend.git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🎯 Introduction&lt;/strong&gt;&lt;br&gt;
In the previous lesson (13A), we standardized the backend error responses. Now, in Lesson 13B, we will implement the Frontend counterpart using Angular Interceptors.&lt;/p&gt;

&lt;p&gt;Instead of handling .subscribe({ error: ... }) in every single component or service, we will use a Global HTTP Interceptor. This interceptor will catch all error responses from the API, parse the standardized JSON, and display a user-friendly notification (using PrimeNG Toasts).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Define the Error Response Interface&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We need a TypeScript interface that matches the C# ErrorResponse class we created in the backend. This ensures type safety when parsing errors.&lt;/p&gt;

&lt;p&gt;File: src/app/core/interceptors/error.interceptor.ts&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export interface ErrorResponse {
    statusCode: number;
    message: string;
    details?: any;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Implement the Error Interceptor&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The interceptor sits in the HTTP pipeline. It checks if an error occurs, parses the message from the backend, and uses MessageService to show a toast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File: src/app/core/interceptors/error.interceptor.ts&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;import { HttpErrorResponse, HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { MessageService } from 'primeng/api';
import { catchError, throwError } from 'rxjs';
export const errorInterceptor: HttpInterceptorFn = (req, next) =&amp;gt; {
    const messageService = inject(MessageService);
    return next(req).pipe(
        catchError((error: HttpErrorResponse) =&amp;gt; {
            let errorMessage = 'An unexpected error occurred';
            let summary = 'Error';

            // 1. Handle Network Errors (Server offline / No internet)
            if (error.status === 0) {
                errorMessage = 'Network error. Please check your connection.';
                summary = 'Network Error';
            }
            // 2. Handle Backend Standardized Errors
            else if (error.error &amp;amp;&amp;amp; typeof error.error === 'object' &amp;amp;&amp;amp; 'message' in error.error) {
                const backendError = error.error as ErrorResponse;
                errorMessage = backendError.message || errorMessage;

                // Optional: Log validation details if present
                if (backendError.statusCode === 400 &amp;amp;&amp;amp; backendError.details) {
                     console.error('Validation Details:', backendError.details);
                }
            } 
            // 3. Fallback for specific status codes
            else if (error.status === 404) {
                errorMessage = 'Resource not found';
            } else if (error.status === 401) {
                errorMessage = 'Unauthorized access';
                summary = 'Unauthorized';
            } else if (error.status === 403) {
                errorMessage = 'Forbidden access';
                summary = 'Forbidden';
            } else if (error.status === 500) {
                errorMessage = 'Internal Server Error';
                summary = 'Server Error';
            }
            // 4. Display Toast Notification
            messageService.add({ 
                severity: 'error', 
                summary: summary, 
                detail: errorMessage,
                life: 5000 
            });
            // Re-throw the error so specific components can still handle it if needed
            return throwError(() =&amp;gt; error);
        })
    );
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3: Register the Interceptor&lt;/strong&gt;&lt;br&gt;
For the interceptor to work, it must be registered in the application configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File: src/app.config.ts&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;import { errorInterceptor } from './core/interceptors/error.interceptor';
// ... other imports
export const appConfig: ApplicationConfig = {
    providers: [
        // ...
        provideHttpClient(
            withFetch(), 
            withInterceptors([
                authInterceptor, 
                errorInterceptor // &amp;lt;--- Add this here
            ])
        ),
        // ...
    ]
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4: Verification&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To verify the implementation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run the application (ng serve).&lt;/li&gt;
&lt;li&gt;Trigger an error (e.g., attempt to login with invalid credentials).&lt;/li&gt;
&lt;li&gt;Expected Result: A red toast notification appears at the top right with the exact error message returned by the backend (e.g., "Invalid credentials" or "Resource not found").&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This completes the Centralized Error Handling cycle. Both Backend and Frontend are now synchronized over a standard error protocol.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next Lecture Preview&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Lecture 14 : Cloud Deployment (Azure / AWS)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Deploying backend, frontend, and database to the cloud with managed infrastructure services.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>frontend</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>🧱 Lesson 13A: Centralized Error Handling &amp; Validation Backend</title>
      <dc:creator>Farrukh Rehman</dc:creator>
      <pubDate>Mon, 16 Feb 2026 05:39:09 +0000</pubDate>
      <link>https://dev.to/farrukh_rehman/lesson-13a-centralized-error-handling-validation-backend-29ip</link>
      <guid>https://dev.to/farrukh_rehman/lesson-13a-centralized-error-handling-validation-backend-29ip</guid>
      <description>&lt;p&gt;Series: From Code to Cloud: Building a Production-Ready .NET Application&lt;br&gt;
By: Farrukh Rehman - Senior .NET Full Stack Developer / Team Lead&lt;br&gt;
LinkedIn: &lt;a href="https://linkedin.com/in/farrukh-rehman" rel="noopener noreferrer"&gt;https://linkedin.com/in/farrukh-rehman&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/farrukh1212cs" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source Code Backend : &lt;a href="https://github.com/farrukh1212cs/ECommerce-Backend.git" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs/ECommerce-Backend.git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source Code Frontend : &lt;a href="https://github.com/farrukh1212cs/ECommerce-Frontend.git" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs/ECommerce-Frontend.git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🎯 Introduction&lt;/strong&gt;&lt;br&gt;
In this lesson, we implement a Centralized Error Handling mechanism for our ASP.NET Core Web API. Instead of using try-catch blocks in every controller action, we will use a Global Exception Middleware. This middleware will catch all unhandled exceptions, log them, and return a standardized JSON response to the client.&lt;/p&gt;

&lt;p&gt;This approach ensures:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Consistency: All API errors follow the same structure.&lt;/li&gt;
&lt;li&gt;Maintainability: Controllers remain clean and focused on business logic.&lt;/li&gt;
&lt;li&gt;Security: We avoid leaking sensitive stack traces in production.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Define the Standardized Error Response&lt;/strong&gt;&lt;br&gt;
First, we define a wrapper class that all error responses will use. This ensures the frontend always knows what format to expect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File: ECommerce.Application/Common/ErrorResponse.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 ECommerce.Application.Common;

public class ErrorResponse
{
    public int StatusCode { get; set; }
    public string Message { get; set; } = string.Empty;
    public object? Details { get; set; }

    public ErrorResponse(int statusCode, string message, object? details = null)
    {
        StatusCode = statusCode;
        Message = message;
        Details = details;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Create Custom Domain Exceptions&lt;/strong&gt;&lt;br&gt;
We create custom exceptions in the Domain layer. These exceptions represent specific business scenarios (e.g., "Resource Not Found") and are independent of HTTP status codes. The middleware will map these to HTTP codes later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File: ECommerce.Domain/Exceptions/NotFoundException.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 ECommerce.Domain.Exceptions;
public class NotFoundException : Exception
{
    public NotFoundException(string message) : base(message)
    {
    }
    public NotFoundException(string name, object key)
        : base($"Entity \"{name}\" ({key}) was not found.")
    {
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;File: ECommerce.Domain/Exceptions/BadRequestException.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 ECommerce.Domain.Exceptions;
public class BadRequestException : Exception
{
    public BadRequestException(string message) : base(message)
    {
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3: Implement the Global Middleware&lt;/strong&gt;&lt;br&gt;
This is the core component. It sits in the HTTP pipeline, catches exceptions, determines the appropriate HTTP status code, and writes the &lt;br&gt;
ErrorResponse&lt;br&gt;
 JSON.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File: ECommerce.API/Middleware/ExceptionHandlingMiddleware.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 System.Net;
using System.Text.Json;
using ECommerce.Application.Common;
using ECommerce.Domain.Exceptions;
namespace ECommerce.API.Middleware;
public class ExceptionHandlingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger&amp;lt;ExceptionHandlingMiddleware&amp;gt; _logger;
    private readonly IHostEnvironment _env;
    public ExceptionHandlingMiddleware(RequestDelegate next, ILogger&amp;lt;ExceptionHandlingMiddleware&amp;gt; logger, IHostEnvironment env)
    {
        _next = next;
        _logger = logger;
        _env = env;
    }
    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            await HandleExceptionAsync(context, ex);
        }
    }
    private async Task HandleExceptionAsync(HttpContext context, Exception exception)
    {
        _logger.LogError(exception, "An unhandled exception has occurred: {Message}", exception.Message);
        context.Response.ContentType = "application/json";

        // Map specific exceptions to HTTP Status Codes
        var response = exception switch
        {
            NotFoundException =&amp;gt; new ErrorResponse((int)HttpStatusCode.NotFound, exception.Message),
            BadRequestException =&amp;gt; new ErrorResponse((int)HttpStatusCode.BadRequest, exception.Message),
            _ =&amp;gt; new ErrorResponse((int)HttpStatusCode.InternalServerError, "An internal server error has occurred.", _env.IsDevelopment() ? exception.StackTrace : null)
        };
        context.Response.StatusCode = response.StatusCode;
        var json = JsonSerializer.Serialize(response, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
        await context.Response.WriteAsync(json);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4: Register the Middleware&lt;/strong&gt;&lt;br&gt;
Finally, we tell ASP.NET Core to use our middleware. The order matters! It should be registered early in the pipeline to catch errors from other middleware.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File: ECommerce.API/Program.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;// ... existing code ...
var app = builder.Build();
// ------------------------------------------------------
// Middleware Pipeline
// ------------------------------------------------------
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
// REGISTER MIDDLEWARE HERE
app.UseMiddleware&amp;lt;ECommerce.API.Middleware.ExceptionHandlingMiddleware&amp;gt;();
app.UseHttpsRedirection();
// ... rest of the pipeline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 5: Usage &amp;amp; Verification&lt;/strong&gt;&lt;br&gt;
Now, you can throw exceptions from anywhere in your Application or Domain layers, and they will be automatically handled.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public async Task&amp;lt;ProductDto&amp;gt; GetProductById(Guid id)
{
    var product = await _repository.GetByIdAsync(id);
    if (product == null)
    {
        throw new NotFoundException(nameof(Product), id);
    }
    return _mapper.Map&amp;lt;ProductDto&amp;gt;(product);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Response Example (404 Not Found):&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;{
  "statusCode": 404,
  "message": "Entity \"Product\" (d290f1ee-6c54-4b01-90e6-d701748f0851) was not found.",
  "details": null
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Next Lecture Preview&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Lecture 13B : Centralized Error Handling &amp;amp; Validation Frontend&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Using interceptor for error handling, implementing FluentValidation, and maintaining consistent API responses.&lt;/p&gt;

</description>
      <category>api</category>
      <category>backend</category>
      <category>dotnet</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>🧱 Lecture 12: Structured Logging &amp; Monitoring</title>
      <dc:creator>Farrukh Rehman</dc:creator>
      <pubDate>Thu, 12 Feb 2026 10:37:57 +0000</pubDate>
      <link>https://dev.to/farrukh_rehman/lecture-12-structured-logging-monitoring-ohi</link>
      <guid>https://dev.to/farrukh_rehman/lecture-12-structured-logging-monitoring-ohi</guid>
      <description>&lt;p&gt;Series: From Code to Cloud: Building a Production-Ready .NET Application&lt;br&gt;
By: Farrukh Rehman - Senior .NET Full Stack Developer / Team Lead&lt;br&gt;
LinkedIn: &lt;a href="https://linkedin.com/in/farrukh-rehman" rel="noopener noreferrer"&gt;https://linkedin.com/in/farrukh-rehman&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/farrukh1212cs" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source Code Backend : &lt;a href="https://github.com/farrukh1212cs/ECommerce-Backend.git" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs/ECommerce-Backend.git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source Code Frontend : &lt;a href="https://github.com/farrukh1212cs/ECommerce-Frontend.git" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs/ECommerce-Frontend.git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🎯 Introduction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Logging is the eyes and ears of your application. When things go wrong in production, a well-structured log file is often the only thing standing between a quick fix and a sleepless night. In this guide, we'll walk through how to implement Serilog in a .NET 8 Web API, configuring it to write structured JSON logs to separate files for Error, Information, and Debug levels.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Serilog?&lt;/strong&gt;&lt;br&gt;
Serilog is a diagnostic logging library for .NET applications. Unlike default logging, Serilog is built from the ground up to support structured data. This means your logs aren't just text strings; they are data objects that can be queried, filtered, and analyzed easily.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Install the Essentials&lt;/strong&gt;&lt;br&gt;
First, we need to add the necessary NuGet packages to our project. These packages allow Serilog to integrate with ASP.NET Core, write to files, and format logs as JSON.&lt;/p&gt;

&lt;p&gt;Run the following commands in your project directory:&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 package Serilog.AspNetCore
dotnet add package Serilog.Sinks.File
dotnet add package Serilog.Formatting.Compact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Serilog.AspNetCore: Integrates Serilog with the ASP.NET Core framework.&lt;/li&gt;
&lt;li&gt;Serilog.Sinks.File: Writes log events to files.&lt;/li&gt;
&lt;li&gt;Serilog.Formatting.Compact: Formats logs as compact JSON, saving space and making them machine-readable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Configuration Magic&lt;/strong&gt;&lt;br&gt;
One of the most powerful features of Serilog is its ability to be configured entirely via appsettings.json. We want to separate our logs into three distinct files based on severity:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Error-yyyyMMdd.json: Critical issues that need immediate attention.&lt;/li&gt;
&lt;li&gt;Info-yyyyMMdd.json: General application flow and operational events.&lt;/li&gt;
&lt;li&gt;Debug-yyyyMMdd.json: Detailed diagnostic information for development and troubleshooting.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Open your &lt;br&gt;
appsettings.json&lt;br&gt;
 and replace the default Logging section with this Serilog configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "System": "Warning"
      }
    },
    "WriteTo": [
      {
        "Name": "File",
        "Args": {
          "path": "logs/Error-.json",
          "restrictedToMinimumLevel": "Error",
          "rollingInterval": "Day",
          "formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact"
        }
      },
      {
        "Name": "File",
        "Args": {
          "path": "logs/Info-.json",
          "restrictedToMinimumLevel": "Information",
          "rollingInterval": "Day",
          "formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact"
        }
      },
      {
        "Name": "File",
        "Args": {
          "path": "logs/Debug-.json",
          "restrictedToMinimumLevel": "Debug",
          "rollingInterval": "Day",
          "formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact"
        }
      }
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key Configurations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rollingInterval: Sets the log file to roll over every Day, creating files like Error-20231025.json.&lt;/li&gt;
&lt;li&gt;restrictedToMinimumLevel: Ensures that the "Error" file only contains errors (and higher), while "Info" captures information and above.&lt;/li&gt;
&lt;li&gt;formatter: Uses the CompactJsonFormatter to output clean, structured JSON.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Wire It Up in Program.cs&lt;/strong&gt;&lt;br&gt;
Now, let's hook Serilog into the application startup pipeline. We'll configure it to load settings from our JSON configuration and replace the default logger. We also want to wrap our application startup in a try/catch block to capture any fatal errors that prevent the app from starting.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using Serilog;
// 1. Initialize the bootstrap logger
Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .CreateBootstrapLogger();
try
{
    var builder = WebApplication.CreateBuilder(args);

    // 2. Add Serilog to the Host
    builder.Host.UseSerilog((context, services, configuration) =&amp;gt; configuration
        .ReadFrom.Configuration(context.Configuration)
        .ReadFrom.Services(services)
        .Enrich.FromLogContext());
    // ... (Rest of your service registrations) ...
    var app = builder.Build();
    // ... (Middleware pipeline) ...
    app.Run();
}
catch (Exception ex)
{
    // 3. Capture fatal startup errors
    Log.Fatal(ex, "Application terminated unexpectedly");
}
finally
{
    // 4. Ensure logs are flushed before exit
    Log.CloseAndFlush();
}
&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.amazonaws.com%2Fuploads%2Farticles%2Fl96jeykck2uaxpze784x.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.amazonaws.com%2Fuploads%2Farticles%2Fl96jeykck2uaxpze784x.png" alt=" " width="615" height="313"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Result&lt;/strong&gt;&lt;br&gt;
Run your application, and you will see a logs folder appear in your project root. Inside, you'll find your structured JSON log files:&lt;/p&gt;

&lt;p&gt;logs/Error-20260206.json&lt;br&gt;
logs/Info-20260206.json&lt;br&gt;
logs/Debug-20260206.json&lt;br&gt;
Each line in these files is a valid JSON object, making it incredibly easy to ingest into log management tools like Datadog, Splunk,Seq, or the ELK stack (Elasticsearch, Logstash, Kibana).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
By implementing structured logging with Serilog, you've moved beyond simple text files to a robust, queryable logging infrastructure. Configuring separate files for different log levels ensures that critical errors don't get lost in the noise of information logs, helping you maintain a healthy and debuggable application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next Lecture Preview&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Lecture 13 : Centralized Error Handling &amp;amp; Validation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Using middleware for error handling, implementing FluentValidation, and maintaining consistent API responses.&lt;/p&gt;

</description>
      <category>seriallog</category>
      <category>csharp</category>
      <category>netcore</category>
      <category>webdev</category>
    </item>
    <item>
      <title>🧱 Lecture 11: Continuous Integration and Continuous Deployment (CI/CD) with Jenkins</title>
      <dc:creator>Farrukh Rehman</dc:creator>
      <pubDate>Tue, 20 Jan 2026 14:22:29 +0000</pubDate>
      <link>https://dev.to/farrukh_rehman/lecture-11-continuous-integration-and-continuous-deployment-cicd-with-jenkins-6om</link>
      <guid>https://dev.to/farrukh_rehman/lecture-11-continuous-integration-and-continuous-deployment-cicd-with-jenkins-6om</guid>
      <description>&lt;p&gt;Series: From Code to Cloud: Building a Production-Ready .NET Application&lt;br&gt;
By: Farrukh Rehman - Senior .NET Full Stack Developer / Team Lead&lt;br&gt;
LinkedIn: &lt;a href="https://linkedin.com/in/farrukh-rehman" rel="noopener noreferrer"&gt;https://linkedin.com/in/farrukh-rehman&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/farrukh1212cs" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source Code Backend : &lt;a href="https://github.com/farrukh1212cs/ECommerce-Backend.git" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs/ECommerce-Backend.git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source Code Frontend : &lt;a href="https://github.com/farrukh1212cs/ECommerce-Frontend.git" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs/ECommerce-Frontend.git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🎯 Introduction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In modern software development, manual builds and deployments are no longer sustainable. Teams need speed, reliability, and consistency. This is exactly what Continuous Integration (CI) and Continuous Deployment (CD) provide.&lt;/p&gt;

&lt;p&gt;Jenkins is one of the most powerful tools for implementing CI/CD. It enables teams to automate everything from code integration to production deployment.&lt;/p&gt;

&lt;p&gt;In this lecture, you will learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What CI/CD really means in practice&lt;/li&gt;
&lt;li&gt;How Jenkins works&lt;/li&gt;
&lt;li&gt;How to write pipelines using Jenkinsfile&lt;/li&gt;
&lt;li&gt;How to automate build, test, and deployment&lt;/li&gt;
&lt;li&gt;Best practices used in professional teams&lt;/li&gt;
&lt;li&gt;This is a practical, industry-aligned guide.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What is Continuous Integration (CI)?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Continuous Integration is the practice of frequently merging code into a shared repository and automatically validating every change.&lt;/p&gt;

&lt;p&gt;A typical CI workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Developer pushes code to GitHub&lt;/li&gt;
&lt;li&gt;Pipeline automatically triggers&lt;/li&gt;
&lt;li&gt;Application builds&lt;/li&gt;
&lt;li&gt;Tests run&lt;/li&gt;
&lt;li&gt;Feedback is provided immediately&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Why CI Matters&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bugs are caught early&lt;/li&gt;
&lt;li&gt;Integration conflicts are reduced&lt;/li&gt;
&lt;li&gt;Code quality improves&lt;/li&gt;
&lt;li&gt;Teams ship faster with confidence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without CI, teams often discover problems too late — during release.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Continuous Deployment (CD)?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Continuous Deployment goes one step further.&lt;/p&gt;

&lt;p&gt;After code passes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build&lt;/li&gt;
&lt;li&gt;Tests&lt;/li&gt;
&lt;li&gt;Quality checks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…it is automatically deployed to environments like staging or production.&lt;/p&gt;

&lt;p&gt;Some teams use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Continuous Delivery → Deployment requires manual approval&lt;/li&gt;
&lt;li&gt;Continuous Deployment → Deployment is fully automatic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both models are common in professional environments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Jenkins?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Jenkins&lt;/strong&gt; is an open-source automation server that allows you to build complete CI/CD pipelines.&lt;/p&gt;

&lt;p&gt;With Jenkins, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pull code from repositories&lt;/li&gt;
&lt;li&gt;Build applications&lt;/li&gt;
&lt;li&gt;Run automated tests&lt;/li&gt;
&lt;li&gt;Perform static code analysis&lt;/li&gt;
&lt;li&gt;Deploy applications&lt;/li&gt;
&lt;li&gt;Trigger workflows across tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Jenkins is popular because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is free and open-source&lt;/li&gt;
&lt;li&gt;It supports thousands of plugins&lt;/li&gt;
&lt;li&gt;It works with almost any tech stack&lt;/li&gt;
&lt;li&gt;It scales from small teams to large enterprises&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Jenkins Architecture (Simplified)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Jenkins typically consists of:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Jenkins Controller (Master)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manages jobs&lt;/li&gt;
&lt;li&gt;Provides UI&lt;/li&gt;
&lt;li&gt;Schedules pipelines&lt;/li&gt;
&lt;li&gt;Handles configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Jenkins Agents (Workers)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Execute the actual builds&lt;/li&gt;
&lt;li&gt;Can run on separate machines&lt;/li&gt;
&lt;li&gt;Useful for scaling pipelines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pipelines&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Define the automation workflow&lt;/li&gt;
&lt;li&gt;Written as code using a Jenkinsfile&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Lets Write our Jenkinsfile&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;for backend .net application&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pipeline {
    agent any
     options {
        disableConcurrentBuilds()
    }   
    stages {
        stage('Build') {
            steps {
                script {
                    // Remove previous image with the &amp;lt;none&amp;gt; tag  testing
                    sh 'docker rmi ecom-backend:latest || true'

                    // Build the new image with an explicit tag  
                    sh 'docker build --no-cache -t ecom-backend:latest -f ECommerce.API/Dockerfile .'

                    // Remove dangling images
                    sh 'docker images -q --filter "dangling=true" | xargs docker rmi || true'

                    // Clean up intermediate images
                    sh 'docker image prune -f'

                    // Custom cleanup script to remove any remaining &amp;lt;none&amp;gt; tagged images
                    sh '''
                        for image_id in $(docker images --filter "dangling=true" -q); do
                            docker rmi $image_id || true
                        done
                    '''
                }
            }
        }
        stage('Push and Deploy') {
            steps {                
                script {
                    // Stop and remove the container if it exists
                    sh 'docker stop ecom-backend || true'
                    sh 'docker rm ecom-backend || true'

                    // Run the new container
                    sh 'docker run -d --restart always --name ecom-backend --env "ASPNETCORE_ENVIRONMENT=Development" --network zohan -p 8202:8080 ecom-backend:latest'

                     sh 'docker image prune -f'
                }
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Lets Write for Frontend&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;pipeline {
    agent any
     options {
        disableConcurrentBuilds()
    }   
    stages {       

        stage('Build') {
            steps {
                sh 'docker build --no-cache -t ecom-front/ui -f Dockerfile .'
            }
        }
        stage('Push and Deploy') {
            steps {                
                sh 'docker stop ecom-front || true &amp;amp;&amp;amp; docker rm ecom-front || true'
                sh 'docker run -d --restart always --name ecom-front --network zohan -p 9201:80 ecom-front/ui:latest'

            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Now time to Work on Jenkin&lt;/strong&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2F82k5mhkgsw2cmprfv2ys.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.amazonaws.com%2Fuploads%2Farticles%2F82k5mhkgsw2cmprfv2ys.png" alt=" " width="800" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;New Item&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fnaw8yptba2vvleelpj4h.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.amazonaws.com%2Fuploads%2Farticles%2Fnaw8yptba2vvleelpj4h.png" alt=" " width="800" height="266"&gt;&lt;/a&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fapcqop6bfyme0y98dtmb.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.amazonaws.com%2Fuploads%2Farticles%2Fapcqop6bfyme0y98dtmb.png" alt=" " width="800" height="454"&gt;&lt;/a&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2F8cn4zzmmod65apbnjjkx.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.amazonaws.com%2Fuploads%2Farticles%2F8cn4zzmmod65apbnjjkx.png" alt=" " width="800" height="408"&gt;&lt;/a&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fh4h6q2fysnt56ledjax8.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.amazonaws.com%2Fuploads%2Farticles%2Fh4h6q2fysnt56ledjax8.png" alt=" " width="800" height="384"&gt;&lt;/a&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fql1rnis4xqvt803ywq82.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.amazonaws.com%2Fuploads%2Farticles%2Fql1rnis4xqvt803ywq82.png" alt=" " width="800" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For Frontend do same process , just update the Repo URL and branch.&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fixrdyi2megl5z5wo0nte.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.amazonaws.com%2Fuploads%2Farticles%2Fixrdyi2megl5z5wo0nte.png" alt=" " width="800" height="341"&gt;&lt;/a&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fxis5nc2gf2dky9y9xvv4.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.amazonaws.com%2Fuploads%2Farticles%2Fxis5nc2gf2dky9y9xvv4.png" alt=" " width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next Lecture Preview&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Lecture 12 : Structured Logging &amp;amp; Monitoring&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Configuring Serilog with Seq or ELK Stack for centralized structured logging and observability.&lt;/p&gt;

</description>
      <category>jenkins</category>
      <category>cicd</category>
      <category>containers</category>
      <category>docker</category>
    </item>
    <item>
      <title>🧱 Lecture 10 : Dockerizing the Full Stack Application</title>
      <dc:creator>Farrukh Rehman</dc:creator>
      <pubDate>Mon, 22 Dec 2025 14:47:51 +0000</pubDate>
      <link>https://dev.to/farrukh_rehman/lecture-10-dockerizing-the-full-stack-application-1dme</link>
      <guid>https://dev.to/farrukh_rehman/lecture-10-dockerizing-the-full-stack-application-1dme</guid>
      <description>&lt;p&gt;Series: From Code to Cloud: Building a Production-Ready .NET Application&lt;br&gt;
By: Farrukh Rehman - Senior .NET Full Stack Developer / Team Lead&lt;br&gt;
LinkedIn: &lt;a href="https://linkedin.com/in/farrukh-rehman" rel="noopener noreferrer"&gt;https://linkedin.com/in/farrukh-rehman&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/farrukh1212cs" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source Code Backend : &lt;a href="https://github.com/farrukh1212cs/ECommerce-Backend.git" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs/ECommerce-Backend.git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source Code Frontend : &lt;a href="https://github.com/farrukh1212cs/ECommerce-Frontend.git" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs/ECommerce-Frontend.git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🎯 Introduction&lt;br&gt;
In modern software engineering, containerization is a fundamental skill for building portable, scalable, and DevOps-friendly applications. By packaging both the .NET 8 backend API and the Angular Admin Panel inside Docker containers, we ensure that the entire application stack can run consistently across any environment—local, QA, staging, or production—without dependency conflicts or manual setup.&lt;/p&gt;

&lt;p&gt;This lesson focuses exclusively on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating a Dockerfile for the .NET 8 E-Commerce API&lt;/li&gt;
&lt;li&gt;Creating a Dockerfile for the Angular Admin App (using the Mantis free template)&lt;/li&gt;
&lt;li&gt;Running both applications in isolated Docker containers&lt;/li&gt;
&lt;li&gt;Configuring docker-compose to orchestrate both services together&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After completing this lesson, you will be able to launch the entire backend + admin panel with one command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker-compose up --build -d&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is an essential step toward production readiness and CI/CD automation in future lessons.&lt;/p&gt;

&lt;p&gt;Section 1 — Dockerizing the .NET 8 Backend API&lt;/p&gt;

&lt;p&gt;FIRST OF ALL WE NEED TO UPDATE PROGRAM FILE FOR AUTO RUN MIGRATIONS &lt;br&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.amazonaws.com%2Fuploads%2Farticles%2Fz9ry12ge4uvzs69trgey.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.amazonaws.com%2Fuploads%2Farticles%2Fz9ry12ge4uvzs69trgey.png" alt=" " width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We will create a multi-stage Dockerfile that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Restores dependencies&lt;/li&gt;
&lt;li&gt;Publishes the project &lt;/li&gt;
&lt;li&gt;Runs the optimized build inside ASP.NET Core runtime&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 1.1 — Create Dockerfile in your API project root&lt;/p&gt;

&lt;p&gt;Location:&lt;br&gt;
Path : "/ECommerce.API/Dockerfile"&lt;br&gt;
Right-click the API project, select Add, and then choose Container Support to enable containerization for the service.&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fkcw0kozxd304vkab946v.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.amazonaws.com%2Fuploads%2Farticles%2Fkcw0kozxd304vkab946v.png" alt=" " width="800" height="648"&gt;&lt;/a&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fpazwk8a0m05k4a6mgxut.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.amazonaws.com%2Fuploads%2Farticles%2Fpazwk8a0m05k4a6mgxut.png" alt=" " width="800" height="596"&gt;&lt;/a&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fa6tb8j5h5zgxzrwyremf.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.amazonaws.com%2Fuploads%2Farticles%2Fa6tb8j5h5zgxzrwyremf.png" alt=" " width="800" height="314"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lets Configure Docker Containers to connect each other.&lt;/p&gt;

&lt;p&gt;First Create Docker Network&lt;br&gt;
&lt;code&gt;docker network create ecommerce-net&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Lets Connect Postgres to this Network (ecommerce-net) &lt;br&gt;
&lt;code&gt;docker network connect ecommerce-net postgres&lt;/code&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2F8cbhr2pdo01ia0hoh3tc.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.amazonaws.com%2Fuploads%2Farticles%2F8cbhr2pdo01ia0hoh3tc.png" alt=" " width="800" height="144"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now time to connect RabbitMQ to network (ecommerce-net) &lt;br&gt;
&lt;code&gt;docker network connect ecommerce-net rabbitmq&lt;/code&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2F41wud5lw56havqk6h35r.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.amazonaws.com%2Fuploads%2Farticles%2F41wud5lw56havqk6h35r.png" alt=" " width="800" height="208"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Its time to connect Redis with the network (ecommerce-net) &lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker network connect ecommerce-net ecommerce-redis&lt;/code&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fwuzq2e3d1kc361h4wvab.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.amazonaws.com%2Fuploads%2Farticles%2Fwuzq2e3d1kc361h4wvab.png" alt=" " width="800" height="248"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Last setp lets verify Network&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker inspect ecommerce-net&lt;/code&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fzryfy0d5ich8j0pt0a1a.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.amazonaws.com%2Fuploads%2Farticles%2Fzryfy0d5ich8j0pt0a1a.png" alt=" " width="800" height="864"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All Containers are Connected with our network.now its time to work on api side.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Copy all configuration values from appsettings.json into appsettings.Development.json&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Update appsettings.Development.json Replace localhost from ConnectionStrings:PostgreSQL to ContainerName (postgres)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Update Redis localhost to ContainerName (ecommerce-redis)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Update RabbitMQ localhost to ContainerName (rabbitmq)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&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.amazonaws.com%2Fuploads%2Farticles%2Fwvrox6ho6jo4kcqoh3o5.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.amazonaws.com%2Fuploads%2Farticles%2Fwvrox6ho6jo4kcqoh3o5.png" alt=" " width="800" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lets try to Build Image (make sure docker is running)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker build -t ecommerce-api -f ECommerce.API/Dockerfile .&lt;/code&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fvcd5jdk8nsufl44ndsjq.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.amazonaws.com%2Fuploads%2Farticles%2Fvcd5jdk8nsufl44ndsjq.png" alt=" " width="800" height="109"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker images&lt;/code&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fpcong0uq5a216yk7jj13.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.amazonaws.com%2Fuploads%2Farticles%2Fpcong0uq5a216yk7jj13.png" alt=" " width="800" height="249"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Run the API Container&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker run -p 5000:8080 --name ecommerce-api-container --network ecommerce-net -e ASPNETCORE_ENVIRONMENT=Development ecommerce-api&lt;/code&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fott5vadk6orkpj40y70j.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.amazonaws.com%2Fuploads%2Farticles%2Fott5vadk6orkpj40y70j.png" alt=" " width="800" height="322"&gt;&lt;/a&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Flbkmvq5ogc11vb1b97fa.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.amazonaws.com%2Fuploads%2Farticles%2Flbkmvq5ogc11vb1b97fa.png" alt=" " width="800" height="137"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Its time to Test Swagger our container is accessable on port 5000&lt;/strong&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fydzzy7rkwy860zter24w.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.amazonaws.com%2Fuploads%2Farticles%2Fydzzy7rkwy860zter24w.png" alt=" " width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lets start working on angular containerization&lt;/strong&gt;&lt;br&gt;
we need to create four files&lt;br&gt;
Dockerfile,.dockerignore,nginx.conf,environment.prod.ts&lt;br&gt;
Path: "ecommerce-backoffice\Dockerfile"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM node:20.19-alpine AS build

WORKDIR /app

# Install dependencies (use npm ci for reproducible installs)
COPY package.json package-lock.json* ./
RUN npm ci --silent || npm install --silent

# Copy source and build the Angular app
COPY . .
RUN npm run build -- --configuration=production

FROM nginx:stable-alpine

# Remove default nginx static assets
RUN rm -rf /usr/share/nginx/html/*

# Copy built Angular app from the builder stage
COPY --from=build /app/dist/sakai-ng/browser  /usr/share/nginx/html

# Expose port 80
EXPOSE 80

# Start nginx in the foreground
CMD ["nginx", "-g", "daemon off;"]
&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.amazonaws.com%2Fuploads%2Farticles%2F7jnkhw5u03xsbwed3z1m.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.amazonaws.com%2Fuploads%2Farticles%2F7jnkhw5u03xsbwed3z1m.png" alt=" " width="800" height="650"&gt;&lt;/a&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fy2lsh0a12ib0r8pbtxi0.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.amazonaws.com%2Fuploads%2Farticles%2Fy2lsh0a12ib0r8pbtxi0.png" alt=" " width="800" height="662"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Path: "ecommerce-backoffice.dockerignore"&lt;br&gt;
&lt;strong&gt;.dockerignore&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;node_modules
dist
.git
.gitignore
Dockerfile
docker-compose*.yml
docker-compose*.yaml
README.md
*.log
.vscode
.idea
coverage
tmp
out-tsc
# keep source files included so the build can run
&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.amazonaws.com%2Fuploads%2Farticles%2Fxn6g9riqr4mk66yi7k7l.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.amazonaws.com%2Fuploads%2Farticles%2Fxn6g9riqr4mk66yi7k7l.png" alt=" " width="781" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Path: "ecommerce-backoffice\nginx.conf"&lt;br&gt;
&lt;strong&gt;nginx.conf&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;server {
    listen 80;
    server_name localhost;

    # Serve the Angular app
    location / {
        root /usr/share/nginx/html;
        try_files $uri $uri/ /index.html;
        add_header Cache-Control "no-cache, no-store, must-revalidate";
    }

    # Proxy API requests to your backend
    location /api/ {
        proxy_pass https://host.docker.internal:7104;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;

        # Disable SSL verification (for self-signed certs in local dev)
        proxy_ssl_verify off;
        proxy_ssl_session_reuse on;
    }
}
&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.amazonaws.com%2Fuploads%2Farticles%2Fn4np4en69ddhw2sdvbn6.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.amazonaws.com%2Fuploads%2Farticles%2Fn4np4en69ddhw2sdvbn6.png" alt=" " width="800" height="674"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Path: "ecommerce-backoffice\src\environments\environment.prod.ts"&lt;br&gt;
&lt;strong&gt;environment.prod.ts&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;export const environment = {
  production: true,
  // API requests are proxied through nginx to the backend
    baseUrl: 'http://localhost:7104/api'
};
&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.amazonaws.com%2Fuploads%2Farticles%2Fmhcltyv26e0mwu1ajus1.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.amazonaws.com%2Fuploads%2Farticles%2Fmhcltyv26e0mwu1ajus1.png" alt=" " width="800" height="253"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Its time to build Angular Image&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker build -t sakai-ng:latest .&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;docker images
docker run --rm -p 80:80 sakai-ng:latest
&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.amazonaws.com%2Fuploads%2Farticles%2Ff42rxg39xgjfhd65387d.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.amazonaws.com%2Fuploads%2Farticles%2Ff42rxg39xgjfhd65387d.png" alt=" " width="800" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final Step Lets Write docker-compose.yml&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;networks:
  ecommerce-net:
    external: true

services:
  postgres:
    image: postgres:15
    container_name: postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: ECommerceDb
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: Admin123!
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - ecommerce-net

  rabbitmq:
    image: rabbitmq:3-management
    container_name: rabbitmq
    restart: unless-stopped
    ports:
      - "5672:5672"
      - "15672:15672"
    networks:
      - ecommerce-net

  redis:
    image: redis:7
    container_name: ecommerce-redis
    restart: unless-stopped
    ports:
      - "6379:6379"
    networks:
      - ecommerce-net

  ecommerce-api:
    image: ecommerce-api
    container_name: ecommerce-api-container
    restart: unless-stopped
    ports:
      - "7104:8080"
    environment:
      ASPNETCORE_ENVIRONMENT: Development
      ConnectionStrings__PostgreSQL: Host=postgres;Port=5432;Database=ECommerceDb;Username=postgres;Password=Admin123!
      Redis__Host: redis
      RabbitMQ__Host: rabbitmq
    depends_on:
      - postgres
      - rabbitmq
      - redis
    networks:
      - ecommerce-net

  sakai-ng:
    image: sakai-ng:latest
    container_name: sakai-ng
    restart: unless-stopped
    ports:
      - "80:80"
    depends_on:
      - ecommerce-api
    networks:
      - ecommerce-net

volumes:
  postgres_data:
&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.amazonaws.com%2Fuploads%2Farticles%2Frqtg1w00z1wumfpi0n9r.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.amazonaws.com%2Fuploads%2Farticles%2Frqtg1w00z1wumfpi0n9r.png" alt=" " width="800" height="144"&gt;&lt;/a&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fmq5i0033f9w9fkr5a3q5.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.amazonaws.com%2Fuploads%2Farticles%2Fmq5i0033f9w9fkr5a3q5.png" alt=" " width="800" height="209"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lets Test from UI&lt;/strong&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2F9ebncb14s039zmyz124l.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.amazonaws.com%2Fuploads%2Farticles%2F9ebncb14s039zmyz124l.png" alt=" " width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next Lecture Preview&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Lecture 11 : Continuous Integration with Jenkins&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Creating CI/CD pipelines in Jenkins for automated build, test, and deployment workflows.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>docker</category>
      <category>containers</category>
    </item>
    <item>
      <title>🧱 Lecture 9B : Product Management (Angular)</title>
      <dc:creator>Farrukh Rehman</dc:creator>
      <pubDate>Fri, 05 Dec 2025 05:54:53 +0000</pubDate>
      <link>https://dev.to/farrukh_rehman/lecture-9b-product-management-angular-2e2g</link>
      <guid>https://dev.to/farrukh_rehman/lecture-9b-product-management-angular-2e2g</guid>
      <description>&lt;p&gt;Series: From Code to Cloud: Building a Production-Ready .NET Application&lt;br&gt;
By: Farrukh Rehman - Senior .NET Full Stack Developer / Team Lead&lt;br&gt;
LinkedIn: &lt;a href="https://linkedin.com/in/farrukh-rehman" rel="noopener noreferrer"&gt;https://linkedin.com/in/farrukh-rehman&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/farrukh1212cs" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source Code Backend : &lt;a href="https://github.com/farrukh1212cs/ECommerce-Backend.git" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs/ECommerce-Backend.git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source Code Frontend : &lt;a href="https://github.com/farrukh1212cs/ECommerce-Frontend.git" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs/ECommerce-Frontend.git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🎯 Introduction&lt;br&gt;
This module focuses on building complete product management functionality, including full CRUD operations and seamless integration between the frontend interface and backend APIs. It ensures that users can create, update, view, and delete products efficiently while maintaining a smooth and responsive experience across the system.&lt;/p&gt;

&lt;p&gt;This system will allow your administrators to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manage products&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Communicate with the backend APIs you’ve already built&lt;/p&gt;

&lt;p&gt;This lecture covers everything in one place:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;- Auth Guard&lt;/li&gt;
&lt;li&gt;- Full CRUD page for Product&lt;/li&gt;
&lt;li&gt;- Product Service&lt;/li&gt;
&lt;li&gt;- Product Route&lt;/li&gt;
&lt;li&gt;- Update App Routes&lt;/li&gt;
&lt;li&gt;- Update Menu&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;1. - Auth Guard&lt;/strong&gt;&lt;br&gt;
Path : "src/app/services/authguard/auth-guard.service.ts"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;auth-guard.service.ts&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;import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { AuthApiService } from '../api/auth.service';

@Injectable({
  providedIn: 'root'
})
export class AuthGuardService {
  constructor(
    private authService: AuthApiService,
    private router: Router
  ) {}

  canActivate(): boolean {
    const token = this.authService.getToken();
    const refreshToken = this.authService.getRefreshToken();

    // If no token and no refresh token, redirect to login
    if (!token &amp;amp;&amp;amp; !refreshToken) {
      this.router.navigate(['/auth/login']);
      return false;
    }

    // If no token but refresh token exists, try to refresh
    if (!token &amp;amp;&amp;amp; refreshToken) {
      this.attemptTokenRefresh(refreshToken);
      return false; // Wait for refresh attempt
    }

    // If token exists, user is authenticated
    if (token) {
      return true;
    }

    return false;
  }

  private attemptTokenRefresh(refreshToken: string): void {
    this.authService.refresh({ token: '', refreshToken }).subscribe({
      next: () =&amp;gt; {
        // Token refreshed successfully, reload current route
        this.router.navigate([this.router.url]);
      },
      error: () =&amp;gt; {
        // Refresh failed, redirect to login
        this.authService.logout();
        this.router.navigate(['/auth/login']);
      }
    });
  }
}

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. - UI Pages using PrimeNG&lt;/strong&gt;&lt;br&gt;
Create 3 Files for Product&lt;br&gt;
Path : "src/app/features/products/product/product.html"&lt;br&gt;
       "src/app/features/products/product/product.scss" (blank file)&lt;br&gt;
       "src/app/features/products/product/product.ts"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;product.html&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;p-toolbar styleClass="mb-6"&amp;gt;
    &amp;lt;ng-template #start&amp;gt;
        &amp;lt;p-button label="New" icon="pi pi-plus" severity="secondary" class="mr-2" (onClick)="openNew()" /&amp;gt;
        &amp;lt;p-button severity="secondary" label="Delete" icon="pi pi-trash" outlined (onClick)="deleteSelectedProducts()" [disabled]="!selectedProducts || !selectedProducts.length" /&amp;gt;
    &amp;lt;/ng-template&amp;gt;

    &amp;lt;ng-template #end&amp;gt;
        &amp;lt;p-button label="Export" icon="pi pi-upload" severity="secondary" (onClick)="exportCSV()" /&amp;gt;
    &amp;lt;/ng-template&amp;gt;
&amp;lt;/p-toolbar&amp;gt;

&amp;lt;p-table
    #dt
    [value]="products()"
    [rows]="10"
    [columns]="cols"
    [paginator]="true"
    [globalFilterFields]="['name', 'country.name', 'representative.name', 'status']"
    [tableStyle]="{ 'min-width': '75rem' }"
    [(selection)]="selectedProducts"
    [rowHover]="true"
    dataKey="id"
    currentPageReportTemplate="Showing {first} to {last} of {totalRecords} products"
    [showCurrentPageReport]="true"
    [rowsPerPageOptions]="[10, 20, 30]"
&amp;gt;
    &amp;lt;ng-template #caption&amp;gt;
        &amp;lt;div class="flex items-center justify-between"&amp;gt;
            &amp;lt;h5 class="m-0"&amp;gt;Manage Products&amp;lt;/h5&amp;gt;
            &amp;lt;p-iconfield&amp;gt;
                &amp;lt;p-inputicon styleClass="pi pi-search" /&amp;gt;
                &amp;lt;input pInputText type="text" (input)="onGlobalFilter(dt, $event)" placeholder="Search..." /&amp;gt;
            &amp;lt;/p-iconfield&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/ng-template&amp;gt;
    &amp;lt;ng-template #header&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;th style="width: 3rem"&amp;gt;
                &amp;lt;p-tableHeaderCheckbox /&amp;gt;
            &amp;lt;/th&amp;gt;
            &amp;lt;th style="min-width: 8rem"&amp;gt;ID&amp;lt;/th&amp;gt;
            &amp;lt;th pSortableColumn="name" style="min-width:16rem"&amp;gt;
                Name
                &amp;lt;p-sortIcon field="name" /&amp;gt;
            &amp;lt;/th&amp;gt;
            &amp;lt;th pSortableColumn="description" style="min-width:16rem"&amp;gt;
                Description
                &amp;lt;p-sortIcon field="description" /&amp;gt;
            &amp;lt;/th&amp;gt;
            &amp;lt;th pSortableColumn="price" style="min-width: 8rem"&amp;gt;
                Price
                &amp;lt;p-sortIcon field="price" /&amp;gt;
            &amp;lt;/th&amp;gt;
            &amp;lt;th pSortableColumn="stock" style="min-width: 8rem"&amp;gt;
                Stock
                &amp;lt;p-sortIcon field="stock" /&amp;gt;
            &amp;lt;/th&amp;gt;
            &amp;lt;th style="min-width: 12rem"&amp;gt;&amp;lt;/th&amp;gt;
        &amp;lt;/tr&amp;gt;
    &amp;lt;/ng-template&amp;gt;
    &amp;lt;ng-template #body let-product&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;td style="width: 3rem"&amp;gt;
                &amp;lt;p-tableCheckbox [value]="product" /&amp;gt;
            &amp;lt;/td&amp;gt;
            &amp;lt;td style="min-width: 8rem"&amp;gt;{{ product.id }}&amp;lt;/td&amp;gt;
            &amp;lt;td style="min-width: 16rem"&amp;gt;{{ product.name }}&amp;lt;/td&amp;gt;
            &amp;lt;td style="min-width: 16rem"&amp;gt;{{ product.description }}&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;{{ product.price | currency: 'USD' }}&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;{{ product.stock }}&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;
                &amp;lt;p-button icon="pi pi-pencil" class="mr-2" [rounded]="true" [outlined]="true" (click)="editProduct(product)" /&amp;gt;
                &amp;lt;p-button icon="pi pi-trash" severity="danger" [rounded]="true" [outlined]="true" (click)="deleteProduct(product)" /&amp;gt;
            &amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
    &amp;lt;/ng-template&amp;gt;
&amp;lt;/p-table&amp;gt;

&amp;lt;p-dialog [(visible)]="productDialog" [style]="{ width: '450px' }" header="Product Details" [modal]="true"&amp;gt;
    &amp;lt;ng-template #content&amp;gt;
        &amp;lt;div class="flex flex-col gap-6"&amp;gt;
            &amp;lt;div&amp;gt;
                &amp;lt;label for="name" class="block font-bold mb-3"&amp;gt;Name&amp;lt;/label&amp;gt;
                &amp;lt;input type="text" pInputText id="name" [(ngModel)]="product.name" required autofocus fluid /&amp;gt;
                &amp;lt;small class="text-red-500" *ngIf="submitted &amp;amp;&amp;amp; !product.name"&amp;gt;Name is required.&amp;lt;/small&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div&amp;gt;
                &amp;lt;label for="description" class="block font-bold mb-3"&amp;gt;Description&amp;lt;/label&amp;gt;
                &amp;lt;textarea id="description" pTextarea [(ngModel)]="product.description" required rows="3" cols="20" fluid&amp;gt;&amp;lt;/textarea&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class="grid grid-cols-12 gap-4"&amp;gt;
                &amp;lt;div class="col-span-6"&amp;gt;
                    &amp;lt;label for="price" class="block font-bold mb-3"&amp;gt;Price&amp;lt;/label&amp;gt;
                    &amp;lt;p-inputnumber id="price" [(ngModel)]="product.price" mode="currency" currency="USD" locale="en-US" fluid /&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div class="col-span-6"&amp;gt;
                    &amp;lt;label for="stock" class="block font-bold mb-3"&amp;gt;Stock&amp;lt;/label&amp;gt;
                    &amp;lt;p-inputnumber id="stock" [(ngModel)]="product.stock" fluid /&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/ng-template&amp;gt;

    &amp;lt;ng-template #footer&amp;gt;
        &amp;lt;p-button label="Cancel" icon="pi pi-times" text (click)="hideDialog()" /&amp;gt;
        &amp;lt;p-button label="Save" icon="pi pi-check" (click)="saveProduct()" /&amp;gt;
    &amp;lt;/ng-template&amp;gt;
&amp;lt;/p-dialog&amp;gt;

&amp;lt;p-confirmdialog [style]="{ width: '450px' }" /&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;product.scss&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;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;product.ts&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;import { Component, OnInit, signal, ViewChild } from '@angular/core';
import { ConfirmationService, MessageService } from 'primeng/api';
import { Table, TableModule } from 'primeng/table';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { ButtonModule } from 'primeng/button';
import { RippleModule } from 'primeng/ripple';
import { ToastModule } from 'primeng/toast';
import { ToolbarModule } from 'primeng/toolbar';
import { RatingModule } from 'primeng/rating';
import { InputTextModule } from 'primeng/inputtext';
import { TextareaModule } from 'primeng/textarea';
import { SelectModule } from 'primeng/select';
import { RadioButtonModule } from 'primeng/radiobutton';
import { InputNumberModule } from 'primeng/inputnumber';
import { DialogModule } from 'primeng/dialog';
import { TagModule } from 'primeng/tag';
import { InputIconModule } from 'primeng/inputicon';
import { IconFieldModule } from 'primeng/iconfield';
import { ConfirmDialogModule } from 'primeng/confirmdialog';
import { ProductDto, ProductApiService } from '../../../services/api/product.service';

interface Column {
    field: string;
    header: string;
    customExportHeader?: string;
}

interface ExportColumn {
    title: string;
    dataKey: string;
}

@Component({
  selector: 'app-product',
  standalone: true,
  imports: [
    CommonModule,
    TableModule,
    FormsModule,
    ButtonModule,
    RippleModule,
    ToastModule,
    ToolbarModule,
    RatingModule,
    InputTextModule,
    TextareaModule,
    SelectModule,
    RadioButtonModule,
    InputNumberModule,
    DialogModule,
    TagModule,
    InputIconModule,
    IconFieldModule,
    ConfirmDialogModule
  ],
  templateUrl: './product.html',
  styleUrl: './product.scss',
    providers: [MessageService, ConfirmationService]
})
export class Product implements OnInit {
    productDialog: boolean = false;
    products = signal&amp;lt;ProductDto[]&amp;gt;([]);
    product!: ProductDto;
    selectedProducts!: ProductDto[] | null;
    submitted: boolean = false;
    @ViewChild('dt') dt!: Table;
    exportColumns!: ExportColumn[];
    cols!: Column[];

    constructor(
        private productApi: ProductApiService,
        private messageService: MessageService,
        private confirmationService: ConfirmationService
    ) {}

    exportCSV() {
        this.dt.exportCSV();
    }

    ngOnInit() {
        this.loadProducts();
        this.cols = [
            { field: 'id', header: 'ID' },
            { field: 'name', header: 'Name' },
            { field: 'description', header: 'Description' },
            { field: 'price', header: 'Price' },
            { field: 'stock', header: 'Stock' }
        ];
        this.exportColumns = this.cols.map((col) =&amp;gt; ({ title: col.header, dataKey: col.field }));
    }

    loadProducts() {
        this.productApi.list().subscribe((data) =&amp;gt; {
            this.products.set(data);
        });
    }

    onGlobalFilter(table: Table, event: Event) {
        table.filterGlobal((event.target as HTMLInputElement).value, 'contains');
    }

    openNew() {
        // Do not set `id` (omit it) so backend can accept/create without GUID parsing errors
        this.product = { name: '', description: '', price: 0, stock: 0 } as ProductDto;
        this.submitted = false;
        this.productDialog = true;
    }

    editProduct(product: ProductDto) {
        this.product = { ...product };
        this.productDialog = true;
    }

    deleteSelectedProducts() {
        this.confirmationService.confirm({
            message: 'Are you sure you want to delete the selected products?',
            header: 'Confirm',
            icon: 'pi pi-exclamation-triangle',
            accept: () =&amp;gt; {
                if (this.selectedProducts) {
                    this.selectedProducts.forEach(product =&amp;gt; {
                        if (product.id) {
                            this.productApi.delete(product.id).subscribe(() =&amp;gt; {
                                this.loadProducts();
                            });
                        }
                    });
                    this.selectedProducts = null;
                    this.messageService.add({
                        severity: 'success',
                        summary: 'Successful',
                        detail: 'Products Deleted',
                        life: 3000
                    });
                }
            }
        });
    }

    hideDialog() {
        this.productDialog = false;
        this.submitted = false;
    }

    deleteProduct(product: ProductDto) {
        this.confirmationService.confirm({
            message: 'Are you sure you want to delete ' + product.name + '?',
            header: 'Confirm',
            icon: 'pi pi-exclamation-triangle',
            accept: () =&amp;gt; {
                if (product.id) {
                    this.productApi.delete(product.id).subscribe(() =&amp;gt; {
                        this.loadProducts();
                        this.messageService.add({
                            severity: 'success',
                            summary: 'Successful',
                            detail: 'Product Deleted',
                            life: 3000
                        });
                    });
                }
            }
        });
    }

    findIndexById(id: string): number {
        let index = -1;
        for (let i = 0; i &amp;lt; this.products().length; i++) {
            if (this.products()[i].id === id) {
                index = i;
                break;
            }
        }

        return index;
    }

    createId(): string {
        let id = '';
        var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
        for (var i = 0; i &amp;lt; 5; i++) {
            id += chars.charAt(Math.floor(Math.random() * chars.length));
        }
        return id;
    }

    getSeverity(status: string) {
        switch (status) {
            case 'INSTOCK':
                return 'success';
            case 'LOWSTOCK':
                return 'warn';
            case 'OUTOFSTOCK':
                return 'danger';
            default:
                return 'info';
        }
    }

    saveProduct() {
        this.submitted = true;
        if (this.product.name?.trim()) {
            if (this.product.id) {
                this.productApi.update(this.product.id, this.product).subscribe(() =&amp;gt; {
                    this.loadProducts();
                    this.messageService.add({
                        severity: 'success',
                        summary: 'Successful',
                        detail: 'Product Updated',
                        life: 3000
                    });
                });
            } else {
                this.productApi.create(this.product).subscribe((created) =&amp;gt; {
                    this.loadProducts();
                    this.messageService.add({
                        severity: 'success',
                        summary: 'Successful',
                        detail: 'Product Created',
                        life: 3000
                    });
                });
            }
            this.productDialog = false;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. - Product Services&lt;/strong&gt;&lt;br&gt;
Create Product Service&lt;br&gt;
Path : "/src/app/services/api/product.service.ts"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;product.service.ts&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;import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
// Try to read API base url from environment; fallback to '/api' when not set
import { environment } from '../../../environments/environment';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

// Match backend ProductDto
export interface ProductDto {
  id?: string;
  name: string;
  description: string;
  price: number;
  stock: number;
}

interface ApiListResponse&amp;lt;T&amp;gt; {
  fromCache: boolean;
  data: T;
}

@Injectable({
  providedIn: 'root'
})
export class ProductApiService {
  private baseUrl = environment.baseUrl + '/products';

  constructor(private http: HttpClient) {}

  // Returns product[] (unwrapped from { fromCache, data })
  list(): Observable&amp;lt;ProductDto[]&amp;gt; {
    return this.http
      .get&amp;lt;ApiListResponse&amp;lt;ProductDto[]&amp;gt;&amp;gt;(this.baseUrl)
      .pipe(map((res) =&amp;gt; res.data));
  }

  // Returns single product (unwrapped)
  get(id: string): Observable&amp;lt;ProductDto&amp;gt; {
    return this.http
      .get&amp;lt;ApiListResponse&amp;lt;ProductDto&amp;gt;&amp;gt;(`${this.baseUrl}/${id}`)
      .pipe(map((res) =&amp;gt; res.data));
  }

  // Create returns created product (controller returns dto in body)
  create(product: ProductDto): Observable&amp;lt;ProductDto&amp;gt; {
    return this.http.post&amp;lt;ProductDto&amp;gt;(this.baseUrl, product);
  }

  // Update returns no content; use void
  update(id: string, product: ProductDto): Observable&amp;lt;void&amp;gt; {
    return this.http.put&amp;lt;void&amp;gt;(`${this.baseUrl}/${id}`, product);
  }

  delete(id: string): Observable&amp;lt;void&amp;gt; {
    return this.http.delete&amp;lt;void&amp;gt;(`${this.baseUrl}/${id}`);
  }
}

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. - Product Route&lt;/strong&gt;&lt;br&gt;
Create New File for Product Ruting &lt;br&gt;
Path : "src/app/features/products/products.routes.ts"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Routes } from '@angular/router';
import { Product } from './product/product';

export default [
    { path: '', component: Product }
] as Routes;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. - Update App Routes&lt;/strong&gt;&lt;br&gt;
Path : "src/app/app.routes.ts"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Routes } from '@angular/router';
import { AppLayout } from './app/layout/component/app.layout';
import { Dashboard } from './app/pages/dashboard/dashboard';
import { Documentation } from './app/pages/documentation/documentation';
import { Landing } from './app/pages/landing/landing';
import { Notfound } from './app/pages/notfound/notfound';
import { AuthGuardService } from './app/services/authguard/auth-guard.service';

export const appRoutes: Routes = [
    {
        path: '',
        redirectTo: () =&amp;gt; {
            const token = localStorage.getItem('auth_token');
            return token ? '/dashboard' : '/auth/login';
        },
        pathMatch: 'full'
    },
    {
        path: 'dashboard',
        component: AppLayout,
        canActivate: [AuthGuardService],
        children: [
            { path: '', component: Dashboard }
        ]
    },
    {
        path: 'products',
        component: AppLayout,
        canActivate: [AuthGuardService],
        children: [
            { path: '', loadChildren: () =&amp;gt; import('./app/features/products/products.routes') }
        ]
    },
    {
        path: 'uikit',
        component: AppLayout,
        canActivate: [AuthGuardService],
        children: [
            { path: '', loadChildren: () =&amp;gt; import('./app/pages/uikit/uikit.routes') }
        ]
    },
    {
        path: 'pages',
        component: AppLayout,
        canActivate: [AuthGuardService],
        children: [
            { path: '', loadChildren: () =&amp;gt; import('./app/pages/pages.routes') }
        ]
    },
    {
        path: 'documentation',
        component: AppLayout,
        canActivate: [AuthGuardService],
        children: [
            { path: '', component: Documentation }
        ]
    },
    { path: 'landing', component: Landing },
    { path: 'notfound', component: Notfound },
    { path: 'auth', loadChildren: () =&amp;gt; import('./app/features/auth/auth.routes') },
    { path: '**', component: Notfound }
];
&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.amazonaws.com%2Fuploads%2Farticles%2Fg93hwh75kaxg34wxwt9r.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.amazonaws.com%2Fuploads%2Farticles%2Fg93hwh75kaxg34wxwt9r.png" alt=" " width="800" height="671"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. - Update App Menu&lt;/strong&gt;&lt;br&gt;
Path : "src/app/layout/component/app.menu.ts"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { MenuItem } from 'primeng/api';
import { AppMenuitem } from './app.menuitem';

@Component({
    selector: 'app-menu',
    standalone: true,
    imports: [CommonModule, AppMenuitem, RouterModule],
    template: `&amp;lt;ul class="layout-menu"&amp;gt;
        &amp;lt;ng-container *ngFor="let item of model; let i = index"&amp;gt;
            &amp;lt;li app-menuitem *ngIf="!item.separator" [item]="item" [index]="i" [root]="true"&amp;gt;&amp;lt;/li&amp;gt;
            &amp;lt;li *ngIf="item.separator" class="menu-separator"&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;/ng-container&amp;gt;
    &amp;lt;/ul&amp;gt; `
})
export class AppMenu {
    model: MenuItem[] = [];

    ngOnInit() {
        this.model = [
            {
                label: 'Home',
                items: [{ label: 'Dashboard', icon: 'pi pi-fw pi-home', routerLink: ['/dashboard'] }]
            },
            {
                label: 'Products',
                items: [{ label: 'Products', icon: 'pi pi-fw pi-home', routerLink: ['/products'] }]
            },
            {
                label: 'UI Components',
                items: [
                    { label: 'Form Layout', icon: 'pi pi-fw pi-id-card', routerLink: ['/uikit/formlayout'] },
                    { label: 'Input', icon: 'pi pi-fw pi-check-square', routerLink: ['/uikit/input'] },
                    { label: 'Button', icon: 'pi pi-fw pi-mobile', class: 'rotated-icon', routerLink: ['/uikit/button'] },
                    { label: 'Table', icon: 'pi pi-fw pi-table', routerLink: ['/uikit/table'] },
                    { label: 'List', icon: 'pi pi-fw pi-list', routerLink: ['/uikit/list'] },
                    { label: 'Tree', icon: 'pi pi-fw pi-share-alt', routerLink: ['/uikit/tree'] },
                    { label: 'Panel', icon: 'pi pi-fw pi-tablet', routerLink: ['/uikit/panel'] },
                    { label: 'Overlay', icon: 'pi pi-fw pi-clone', routerLink: ['/uikit/overlay'] },
                    { label: 'Media', icon: 'pi pi-fw pi-image', routerLink: ['/uikit/media'] },
                    { label: 'Menu', icon: 'pi pi-fw pi-bars', routerLink: ['/uikit/menu'] },
                    { label: 'Message', icon: 'pi pi-fw pi-comment', routerLink: ['/uikit/message'] },
                    { label: 'File', icon: 'pi pi-fw pi-file', routerLink: ['/uikit/file'] },
                    { label: 'Chart', icon: 'pi pi-fw pi-chart-bar', routerLink: ['/uikit/charts'] },
                    { label: 'Timeline', icon: 'pi pi-fw pi-calendar', routerLink: ['/uikit/timeline'] },
                    { label: 'Misc', icon: 'pi pi-fw pi-circle', routerLink: ['/uikit/misc'] }
                ]
            },
            {
                label: 'Pages',
                icon: 'pi pi-fw pi-briefcase',
                routerLink: ['/pages'],
                items: [
                    {
                        label: 'Landing',
                        icon: 'pi pi-fw pi-globe',
                        routerLink: ['/landing']
                    },
                    {
                        label: 'Auth',
                        icon: 'pi pi-fw pi-user',
                        items: [
                            {
                                label: 'Login',
                                icon: 'pi pi-fw pi-sign-in',
                                routerLink: ['/auth/login']
                            },
                            {
                                label: 'Error',
                                icon: 'pi pi-fw pi-times-circle',
                                routerLink: ['/auth/error']
                            },
                            {
                                label: 'Access Denied',
                                icon: 'pi pi-fw pi-lock',
                                routerLink: ['/auth/access']
                            }
                        ]
                    },
                    {
                        label: 'Crud',
                        icon: 'pi pi-fw pi-pencil',
                        routerLink: ['/pages/crud']
                    },
                    {
                        label: 'Not Found',
                        icon: 'pi pi-fw pi-exclamation-circle',
                        routerLink: ['/pages/notfound']
                    },
                    {
                        label: 'Empty',
                        icon: 'pi pi-fw pi-circle-off',
                        routerLink: ['/pages/empty']
                    }
                ]
            },
            {
                label: 'Hierarchy',
                items: [
                    {
                        label: 'Submenu 1',
                        icon: 'pi pi-fw pi-bookmark',
                        items: [
                            {
                                label: 'Submenu 1.1',
                                icon: 'pi pi-fw pi-bookmark',
                                items: [
                                    { label: 'Submenu 1.1.1', icon: 'pi pi-fw pi-bookmark' },
                                    { label: 'Submenu 1.1.2', icon: 'pi pi-fw pi-bookmark' },
                                    { label: 'Submenu 1.1.3', icon: 'pi pi-fw pi-bookmark' }
                                ]
                            },
                            {
                                label: 'Submenu 1.2',
                                icon: 'pi pi-fw pi-bookmark',
                                items: [{ label: 'Submenu 1.2.1', icon: 'pi pi-fw pi-bookmark' }]
                            }
                        ]
                    },
                    {
                        label: 'Submenu 2',
                        icon: 'pi pi-fw pi-bookmark',
                        items: [
                            {
                                label: 'Submenu 2.1',
                                icon: 'pi pi-fw pi-bookmark',
                                items: [
                                    { label: 'Submenu 2.1.1', icon: 'pi pi-fw pi-bookmark' },
                                    { label: 'Submenu 2.1.2', icon: 'pi pi-fw pi-bookmark' }
                                ]
                            },
                            {
                                label: 'Submenu 2.2',
                                icon: 'pi pi-fw pi-bookmark',
                                items: [{ label: 'Submenu 2.2.1', icon: 'pi pi-fw pi-bookmark' }]
                            }
                        ]
                    }
                ]
            },
            {
                label: 'Get Started',
                items: [
                    {
                        label: 'Documentation',
                        icon: 'pi pi-fw pi-book',
                        routerLink: ['/documentation']
                    },
                    {
                        label: 'View Source',
                        icon: 'pi pi-fw pi-github',
                        url: 'https://github.com/primefaces/sakai-ng',
                        target: '_blank'
                    }
                ]
            }
        ];
    }
}

&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.amazonaws.com%2Fuploads%2Farticles%2Fk95x4im3r1535x0p3mjo.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.amazonaws.com%2Fuploads%2Farticles%2Fk95x4im3r1535x0p3mjo.png" alt=" " width="800" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next Lecture Preview&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Lecture 10 : Dockerizing the Full Stack Application&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Writing Dockerfiles for .NET and Angular/React, setting up docker-compose for local orchestration.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>frontend</category>
      <category>tutorial</category>
      <category>typescript</category>
    </item>
    <item>
      <title>🧱 Lesson 9A - Login and Authentication (Angular)</title>
      <dc:creator>Farrukh Rehman</dc:creator>
      <pubDate>Thu, 27 Nov 2025 09:19:05 +0000</pubDate>
      <link>https://dev.to/farrukh_rehman/lesson-9a-login-and-authentication-angular-549m</link>
      <guid>https://dev.to/farrukh_rehman/lesson-9a-login-and-authentication-angular-549m</guid>
      <description>&lt;p&gt;Series: From Code to Cloud: Building a Production-Ready .NET Application&lt;br&gt;
By: Farrukh Rehman - Senior .NET Full Stack Developer / Team Lead&lt;br&gt;
LinkedIn: &lt;a href="https://linkedin.com/in/farrukh-rehman" rel="noopener noreferrer"&gt;https://linkedin.com/in/farrukh-rehman&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/farrukh1212cs" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source Code Backend : &lt;a href="https://github.com/farrukh1212cs/ECommerce-Backend.git" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs/ECommerce-Backend.git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source Code Frontend : &lt;a href="https://github.com/farrukh1212cs/ECommerce-Frontend.git" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs/ECommerce-Frontend.git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🎯 Introduction&lt;/p&gt;

&lt;p&gt;In this lecture, we develop a full Backoffice Admin Panel using Angular 20 and we will use &lt;a href="https://github.com/primefaces/sakai-ng" rel="noopener noreferrer"&gt;PrimeNg Free Template sakai-ng&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This system will allow your administrators to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manage products&lt;/li&gt;
&lt;li&gt;Manage customers&lt;/li&gt;
&lt;li&gt;View orders&lt;/li&gt;
&lt;li&gt;Update settings&lt;/li&gt;
&lt;li&gt;Handle security &amp;amp; authorization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Communicate with the backend APIs you’ve already built&lt;/p&gt;

&lt;p&gt;We will also integrate authentication using JWT, and create a clean, scalable Angular architecture.&lt;/p&gt;

&lt;p&gt;This lecture covers everything in one place:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;- Angular environment setup&lt;/li&gt;
&lt;li&gt;- Folder structure&lt;/li&gt;
&lt;li&gt;- Services + Models&lt;/li&gt;
&lt;li&gt;- Authentication + Interceptor&lt;/li&gt;
&lt;li&gt;- Move Login Component Features&lt;/li&gt;
&lt;li&gt;- Update App Routes&lt;/li&gt;
&lt;li&gt;- Update App Config&lt;/li&gt;
&lt;li&gt;- Testing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;1. - Angular environment setup We will Use Free Version of PrimeNG (sakai-ng)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clone Project&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;git clone https://github.com/primefaces/sakai-ng&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create New environment files&lt;/strong&gt;&lt;br&gt;
Path: "/src/environments/environment.ts"  and "/src/environments/environment.prod.ts"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const environment = {
  production: false,
  // Set your API base URL here, e.g. 'https://api.example.com'
    baseUrl: 'https://localhost:7104/api'
};

&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.amazonaws.com%2Fuploads%2Farticles%2Fujzlfp9eiwu6zo1xxcgq.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.amazonaws.com%2Fuploads%2Farticles%2Fujzlfp9eiwu6zo1xxcgq.png" alt=" " width="635" height="217"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. - Folder structure&lt;/strong&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fg12qxw5wgt8xc1bguuwf.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.amazonaws.com%2Fuploads%2Farticles%2Fg12qxw5wgt8xc1bguuwf.png" alt=" " width="297" height="686"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. - Services + Models&lt;/strong&gt;&lt;br&gt;
Create Auth Service&lt;br&gt;
Path : "/src/app/services/api/auth.service.ts"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;auth.service.ts&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;import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { environment } from '../../../environments/environment';

export interface LoginRequest {
  email: string;
  password: string;
}

export interface RegisterRequest {
  email: string;
  password: string;
  firstName?: string;
  lastName?: string;
}

export interface RefreshRequest {
  token: string;
  refreshToken: string;
}

export interface AuthResponse {
  token: string;
  refreshToken: string;
}

@Injectable({
  providedIn: 'root'
})
export class AuthApiService {
    private baseUrl = environment.baseUrl + '/auth';



  constructor(private http: HttpClient) {}

  login(request: LoginRequest): Observable&amp;lt;AuthResponse&amp;gt; {
    return this.http.post&amp;lt;AuthResponse&amp;gt;(`${this.baseUrl}/login`, request).pipe(
      tap((response) =&amp;gt; {
        if (response.token &amp;amp;&amp;amp; response.refreshToken) {
          this.saveTokens(response.token, response.refreshToken);
        }
      })
    );
  }

  register(request: RegisterRequest): Observable&amp;lt;AuthResponse&amp;gt; {
    return this.http.post&amp;lt;AuthResponse&amp;gt;(`${this.baseUrl}/register`, request).pipe(
      tap((response) =&amp;gt; {
        if (response.token &amp;amp;&amp;amp; response.refreshToken) {
          this.saveTokens(response.token, response.refreshToken);
        }
      })
    );
  }

  refresh(request: RefreshRequest): Observable&amp;lt;AuthResponse&amp;gt; {
    return this.http.post&amp;lt;AuthResponse&amp;gt;(`${this.baseUrl}/refresh`, request).pipe(
      tap((response) =&amp;gt; {
        if (response.token &amp;amp;&amp;amp; response.refreshToken) {
          this.saveTokens(response.token, response.refreshToken);
        }
      })
    );
  }

  private saveTokens(token: string, refreshToken: string): void {
    localStorage.setItem('auth_token', token);
    localStorage.setItem('refresh_token', refreshToken);
  }

  getToken(): string | null {
    return localStorage.getItem('auth_token');
  }

  getRefreshToken(): string | null {
    return localStorage.getItem('refresh_token');
  }

  logout(): void {
    localStorage.removeItem('auth_token');
    localStorage.removeItem('refresh_token');
  }

  isAuthenticated(): boolean {
    return !!this.getToken();
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. - Authentication + Interceptor&lt;/strong&gt;&lt;br&gt;
Create Interceptor&lt;br&gt;
Path: "/src/app/core/interceptors/auth.interceptor.ts"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { AuthApiService } from '../../app/services/api/auth.service';
import { Router } from '@angular/router';
import { catchError } from 'rxjs/operators';
import { throwError } from 'rxjs';

export const authInterceptor: HttpInterceptorFn = (req, next) =&amp;gt; {
    const authService = inject(AuthApiService);
    const router = inject(Router);
    const token = authService.getToken();

    // Skip interceptor logic for auth endpoints (login, register, refresh)
    if (req.url.includes('/auth/login') || req.url.includes('/auth/register') || req.url.includes('/auth/refresh')) {
        return next(req).pipe(
            catchError((error) =&amp;gt; {
                return throwError(() =&amp;gt; error);
            })
        );
    }

    // If no token and trying to access protected routes, redirect to login
    if (!token) {
        router.navigate(['/auth/login']);
        return throwError(() =&amp;gt; new Error('No authentication token found'));
    }

    // Clone request and add Authorization header with Bearer token
    req = req.clone({
        setHeaders: {
            Authorization: `Bearer ${token}`
        }
    });

    return next(req).pipe(
        catchError((error) =&amp;gt; {
            // If 401 Unauthorized, clear localStorage and redirect to login
            if (error.status === 401) {
                authService.logout();
                router.navigate(['/auth/login']);
                return throwError(() =&amp;gt; new Error('Session expired. Please login again.'));
            }
            return throwError(() =&amp;gt; error);
        })
    );
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. - Move Login Component Features&lt;/strong&gt;&lt;br&gt;
Path: "/src/app/features/auth/login/login.html"&lt;br&gt;
      "/src/app/features/auth/login/login.scss" will be Blank file&lt;br&gt;
      "/src/app/features/auth/login/login.ts"&lt;br&gt;
      "/src/app/features/auth/auth.routes.ts"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;login.html&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;p-toast /&amp;gt;
&amp;lt;app-floating-configurator /&amp;gt;
&amp;lt;div class="bg-surface-50 dark:bg-surface-950 flex items-center justify-center min-h-screen min-w-screen overflow-hidden"&amp;gt;
    &amp;lt;div class="flex flex-col items-center justify-center"&amp;gt;
        &amp;lt;div style="border-radius: 56px; padding: 0.3rem; background: linear-gradient(180deg, var(--primary-color) 10%, rgba(33, 150, 243, 0) 30%)"&amp;gt;
            &amp;lt;div class="w-full bg-surface-0 dark:bg-surface-900 py-20 px-8 sm:px-20" style="border-radius: 53px"&amp;gt;
                &amp;lt;div class="text-center mb-8"&amp;gt;
                    &amp;lt;svg viewBox="0 0 54 40" fill="none" xmlns="http://www.w3.org/2000/svg" class="mb-8 w-16 shrink-0 mx-auto"&amp;gt;
                        &amp;lt;path
                            fill-rule="evenodd"
                            clip-rule="evenodd"
                            d="M17.1637 19.2467C17.1566 19.4033 17.1529 19.561 17.1529 19.7194C17.1529 25.3503 21.7203 29.915 27.3546 29.915C32.9887 29.915 37.5561 25.3503 37.5561 19.7194C37.5561 19.5572 37.5524 19.3959 37.5449 19.2355C38.5617 19.0801 39.5759 18.9013 40.5867 18.6994L40.6926 18.6782C40.7191 19.0218 40.7326 19.369 40.7326 19.7194C40.7326 27.1036 34.743 33.0896 27.3546 33.0896C19.966 33.0896 13.9765 27.1036 13.9765 19.7194C13.9765 19.374 13.9896 19.0316 14.0154 18.6927L14.0486 18.6994C15.0837 18.9062 16.1223 19.0886 17.1637 19.2467ZM33.3284 11.4538C31.6493 10.2396 29.5855 9.52381 27.3546 9.52381C25.1195 9.52381 23.0524 10.2421 21.3717 11.4603C20.0078 11.3232 18.6475 11.1387 17.2933 10.907C19.7453 8.11308 23.3438 6.34921 27.3546 6.34921C31.36 6.34921 34.9543 8.10844 37.4061 10.896C36.0521 11.1292 34.692 11.3152 33.3284 11.4538ZM43.826 18.0518C43.881 18.6003 43.9091 19.1566 43.9091 19.7194C43.9091 28.8568 36.4973 36.2642 27.3546 36.2642C18.2117 36.2642 10.8 28.8568 10.8 19.7194C10.8 19.1615 10.8276 18.61 10.8816 18.0663L7.75383 17.4411C7.66775 18.1886 7.62354 18.9488 7.62354 19.7194C7.62354 30.6102 16.4574 39.4388 27.3546 39.4388C38.2517 39.4388 47.0855 30.6102 47.0855 19.7194C47.0855 18.9439 47.0407 18.1789 46.9536 17.4267L43.826 18.0518ZM44.2613 9.54743L40.9084 10.2176C37.9134 5.95821 32.9593 3.1746 27.3546 3.1746C21.7442 3.1746 16.7856 5.96385 13.7915 10.2305L10.4399 9.56057C13.892 3.83178 20.1756 0 27.3546 0C34.5281 0 40.8075 3.82591 44.2613 9.54743Z"
                            fill="var(--primary-color)"
                        /&amp;gt;
                        &amp;lt;mask id="mask0_1413_1551" style="mask-type: alpha" maskUnits="userSpaceOnUse" x="0" y="8" width="54" height="11"&amp;gt;
                            &amp;lt;path d="M27 18.3652C10.5114 19.1944 0 8.88892 0 8.88892C0 8.88892 16.5176 14.5866 27 14.5866C37.4824 14.5866 54 8.88892 54 8.88892C54 8.88892 43.4886 17.5361 27 18.3652Z" fill="var(--primary-color)" /&amp;gt;
                        &amp;lt;/mask&amp;gt;
                        &amp;lt;g mask="url(#mask0_1413_1551)"&amp;gt;
                            &amp;lt;path
                                d="M-4.673e-05 8.88887L3.73084 -1.91434L-8.00806 17.0473L-4.673e-05 8.88887ZM27 18.3652L26.4253 6.95109L27 18.3652ZM54 8.88887L61.2673 17.7127L50.2691 -1.91434L54 8.88887ZM-4.673e-05 8.88887C-8.00806 17.0473 -8.00469 17.0505 -8.00132 17.0538C-8.00018 17.055 -7.99675 17.0583 -7.9944 17.0607C-7.98963 17.0653 -7.98474 17.0701 -7.97966 17.075C-7.96949 17.0849 -7.95863 17.0955 -7.94707 17.1066C-7.92401 17.129 -7.89809 17.1539 -7.86944 17.1812C-7.8122 17.236 -7.74377 17.3005 -7.66436 17.3743C-7.50567 17.5218 -7.30269 17.7063 -7.05645 17.9221C-6.56467 18.3532 -5.89662 18.9125 -5.06089 19.5534C-3.39603 20.83 -1.02575 22.4605 1.98012 24.0457C7.97874 27.2091 16.7723 30.3226 27.5746 29.7793L26.4253 6.95109C20.7391 7.23699 16.0326 5.61231 12.6534 3.83024C10.9703 2.94267 9.68222 2.04866 8.86091 1.41888C8.45356 1.10653 8.17155 0.867278 8.0241 0.738027C7.95072 0.673671 7.91178 0.637576 7.90841 0.634492C7.90682 0.63298 7.91419 0.639805 7.93071 0.65557C7.93897 0.663455 7.94952 0.673589 7.96235 0.686039C7.96883 0.692262 7.97582 0.699075 7.98338 0.706471C7.98719 0.710167 7.99113 0.714014 7.99526 0.718014C7.99729 0.720008 8.00047 0.723119 8.00148 0.724116C8.00466 0.727265 8.00796 0.730446 -4.673e-05 8.88887ZM27.5746 29.7793C37.6904 29.2706 45.9416 26.3684 51.6602 23.6054C54.5296 22.2191 56.8064 20.8465 58.4186 19.7784C59.2265 19.2431 59.873 18.7805 60.3494 18.4257C60.5878 18.2482 60.7841 18.0971 60.9374 17.977C61.014 17.9169 61.0799 17.8645 61.1349 17.8203C61.1624 17.7981 61.1872 17.7781 61.2093 17.7602C61.2203 17.7512 61.2307 17.7427 61.2403 17.7348C61.2452 17.7308 61.2499 17.727 61.2544 17.7233C61.2566 17.7215 61.2598 17.7188 61.261 17.7179C61.2642 17.7153 61.2673 17.7127 54 8.88887C46.7326 0.0650536 46.7357 0.0625219 46.7387 0.0600241C46.7397 0.0592345 46.7427 0.0567658 46.7446 0.0551857C46.7485 0.0520238 46.7521 0.0489887 46.7557 0.0460799C46.7628 0.0402623 46.7694 0.0349487 46.7753 0.0301318C46.7871 0.0204986 46.7966 0.0128495 46.8037 0.00712562C46.818 -0.00431848 46.8228 -0.00808311 46.8184 -0.00463784C46.8096 0.00228345 46.764 0.0378652 46.6828 0.0983779C46.5199 0.219675 46.2165 0.439161 45.7812 0.727519C44.9072 1.30663 43.5257 2.14765 41.7061 3.02677C38.0469 4.79468 32.7981 6.63058 26.4253 6.95109L27.5746 29.7793ZM54 8.88887C50.2691 -1.91433 50.27 -1.91467 50.271 -1.91498C50.2712 -1.91506 50.272 -1.91535 50.2724 -1.9155C50.2733 -1.91581 50.274 -1.91602 50.2743 -1.91616C50.2752 -1.91643 50.275 -1.91636 50.2738 -1.91595C50.2714 -1.91515 50.2652 -1.91302 50.2552 -1.9096C50.2351 -1.90276 50.1999 -1.89078 50.1503 -1.874C50.0509 -1.84043 49.8938 -1.78773 49.6844 -1.71863C49.2652 -1.58031 48.6387 -1.377 47.8481 -1.13035C46.2609 -0.635237 44.0427 0.0249875 41.5325 0.6823C36.215 2.07471 30.6736 3.15796 27 3.15796V26.0151C33.8087 26.0151 41.7672 24.2495 47.3292 22.7931C50.2586 22.026 52.825 21.2618 54.6625 20.6886C55.5842 20.4011 56.33 20.1593 56.8551 19.986C57.1178 19.8993 57.3258 19.8296 57.4735 19.7797C57.5474 19.7548 57.6062 19.7348 57.6493 19.72C57.6709 19.7127 57.6885 19.7066 57.7021 19.7019C57.7089 19.6996 57.7147 19.6976 57.7195 19.696C57.7219 19.6952 57.7241 19.6944 57.726 19.6938C57.7269 19.6934 57.7281 19.693 57.7286 19.6929C57.7298 19.6924 57.7309 19.692 54 8.88887ZM27 3.15796C23.3263 3.15796 17.7849 2.07471 12.4674 0.6823C9.95717 0.0249875 7.73904 -0.635237 6.15184 -1.13035C5.36118 -1.377 4.73467 -1.58031 4.3155 -1.71863C4.10609 -1.78773 3.94899 -1.84043 3.84961 -1.874C3.79994 -1.89078 3.76474 -1.90276 3.74471 -1.9096C3.73469 -1.91302 3.72848 -1.91515 3.72613 -1.91595C3.72496 -1.91636 3.72476 -1.91643 3.72554 -1.91616C3.72593 -1.91602 3.72657 -1.91581 3.72745 -1.9155C3.72789 -1.91535 3.72874 -1.91506 3.72896 -1.91498C3.72987 -1.91467 3.73084 -1.91433 -4.673e-05 8.88887C-3.73093 19.692 -3.72983 19.6924 -3.72868 19.6929C-3.72821 19.693 -3.72698 19.6934 -3.72603 19.6938C-3.72415 19.6944 -3.72201 19.6952 -3.71961 19.696C-3.71482 19.6976 -3.70901 19.6996 -3.7022 19.7019C-3.68858 19.7066 -3.67095 19.7127 -3.6494 19.72C-3.60629 19.7348 -3.54745 19.7548 -3.47359 19.7797C-3.32589 19.8296 -3.11788 19.8993 -2.85516 19.986C-2.33008 20.1593 -1.58425 20.4011 -0.662589 20.6886C1.17485 21.2618 3.74125 22.026 6.67073 22.7931C12.2327 24.2495 20.1913 26.0151 27 26.0151V3.15796Z"
                                fill="var(--primary-color)"
                            /&amp;gt;
                        &amp;lt;/g&amp;gt;
                    &amp;lt;/svg&amp;gt;
                    &amp;lt;div class="text-surface-900 dark:text-surface-0 text-3xl font-medium mb-4"&amp;gt;Welcome to PrimeLand!&amp;lt;/div&amp;gt;
                    &amp;lt;span class="text-muted-color font-medium"&amp;gt;Sign in to continue&amp;lt;/span&amp;gt;
                &amp;lt;/div&amp;gt;

                &amp;lt;div&amp;gt;
                    &amp;lt;label for="email1" class="block text-surface-900 dark:text-surface-0 text-xl font-medium mb-2"&amp;gt;Email&amp;lt;/label&amp;gt;
                    &amp;lt;input pInputText id="email1" type="text" placeholder="Email address" class="w-full md:w-120 mb-8" [(ngModel)]="email" /&amp;gt;

                    &amp;lt;label for="password1" class="block text-surface-900 dark:text-surface-0 font-medium text-xl mb-2"&amp;gt;Password&amp;lt;/label&amp;gt;
                    &amp;lt;p-password id="password1" [(ngModel)]="password" placeholder="Password" [toggleMask]="true" styleClass="mb-4" [fluid]="true" [feedback]="false"&amp;gt;&amp;lt;/p-password&amp;gt;

                    &amp;lt;div class="flex items-center justify-between mt-2 mb-8 gap-8"&amp;gt;
                        &amp;lt;div class="flex items-center"&amp;gt;
                            &amp;lt;p-checkbox [(ngModel)]="checked" id="rememberme1" binary class="mr-2"&amp;gt;&amp;lt;/p-checkbox&amp;gt;
                            &amp;lt;label for="rememberme1"&amp;gt;Remember me&amp;lt;/label&amp;gt;
                        &amp;lt;/div&amp;gt;
                        &amp;lt;span class="font-medium no-underline ml-2 text-right cursor-pointer text-primary"&amp;gt;Forgot password?&amp;lt;/span&amp;gt;
                    &amp;lt;/div&amp;gt;
                    &amp;lt;p-button label="Sign In" styleClass="w-full" (onClick)="handleLogin()" [loading]="isLoading"&amp;gt;&amp;lt;/p-button&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;login.ts&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;import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { Router, RouterModule } from '@angular/router';
import { ButtonModule } from 'primeng/button';
import { CheckboxModule } from 'primeng/checkbox';
import { InputTextModule } from 'primeng/inputtext';
import { PasswordModule } from 'primeng/password';
import { RippleModule } from 'primeng/ripple';
import { AppFloatingConfigurator } from '../../../layout/component/app.floatingconfigurator';
import { AuthApiService } from '../../../services/api/auth.service';
import { MessageService } from 'primeng/api';
import { ToastModule } from 'primeng/toast';
import { CommonModule } from '@angular/common';

@Component({
    selector: 'app-login',
    standalone: true,
    imports: [
        CommonModule,
        ButtonModule,
        CheckboxModule,
        InputTextModule,
        PasswordModule,
        FormsModule,
        RouterModule,
        RippleModule,
        AppFloatingConfigurator,
        ToastModule
    ],
    providers: [MessageService],
    templateUrl: './login.html',
    styleUrl: './login.scss'
})
export class Login {
    email: string = '';
    password: string = '';
    checked: boolean = false;
    isLoading: boolean = false;

    constructor(
        private authApi: AuthApiService,
        private messageService: MessageService,
        private router: Router
    ) {}

    handleLogin() {
        if (!this.email || !this.password) {
            this.messageService.add({
                severity: 'warn',
                summary: 'Validation Error',
                detail: 'Email and password are required.',
                life: 3000
            });
            return;
        }

        this.isLoading = true;
        this.authApi.login({ email: this.email, password: this.password }).subscribe({
            next: (response: any) =&amp;gt; {
                this.isLoading = false;
                this.messageService.add({
                    severity: 'success',
                    summary: 'Success',
                    detail: 'Login successful!',
                    life: 2000
                });
                // Redirect to dashboard after short delay
                setTimeout(() =&amp;gt; {
                    this.router.navigate(['/dashboard']);
                }, 500);
            },
            error: (err: any) =&amp;gt; {
                this.isLoading = false;
                const errorMsg = err.error?.errors?.[0] || 'Login failed. Please try again.';
                this.messageService.add({
                    severity: 'error',
                    summary: 'Login Failed',
                    detail: errorMsg,
                    life: 3000
                });
            }
        });
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;auth.routes.ts&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;import { Routes } from '@angular/router';
import { Login } from './login/login';

export default [
    { path: '', redirectTo: 'login', pathMatch: 'full' },
    { path: 'login', component: Login }
] as Routes;

&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.amazonaws.com%2Fuploads%2Farticles%2F8z72cpvfuj62r4oasc9v.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.amazonaws.com%2Fuploads%2Farticles%2F8z72cpvfuj62r4oasc9v.png" alt=" " width="348" height="162"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. - Update App Routes&lt;/strong&gt;&lt;br&gt;
Path : "src/app/app.routes.ts"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Routes } from '@angular/router';
import { AppLayout } from './app/layout/component/app.layout';
import { Dashboard } from './app/pages/dashboard/dashboard';
import { Documentation } from './app/pages/documentation/documentation';
import { Landing } from './app/pages/landing/landing';
import { Notfound } from './app/pages/notfound/notfound';

export const appRoutes: Routes = [
    { 
        path: '', 
        redirectTo: () =&amp;gt; {
            const token = localStorage.getItem('auth_token');
            return token ? '/dashboard' : '/auth/login';
        }, 
        pathMatch: 'full' 
    },
    {
        path: 'dashboard',
        component: AppLayout,
        children: [
            { path: '', component: Dashboard }
        ]
    },
    {
        path: 'uikit',
        component: AppLayout,
        children: [
            { path: '', loadChildren: () =&amp;gt; import('./app/pages/uikit/uikit.routes') }
        ]
    },
    {
        path: 'pages',
        component: AppLayout,
        children: [
            { path: '', loadChildren: () =&amp;gt; import('./app/pages/pages.routes') }
        ]
    },
    {
        path: 'documentation',
        component: AppLayout,
        children: [
            { path: '', component: Documentation }
        ]
    },
    { path: 'landing', component: Landing },
    { path: 'notfound', component: Notfound },
    { path: 'auth', loadChildren: () =&amp;gt; import('./app/features/auth/auth.routes') },
    { path: '**', component: Notfound }
];

&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.amazonaws.com%2Fuploads%2Farticles%2Fkfpcz00snxfth1s8xx8a.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.amazonaws.com%2Fuploads%2Farticles%2Fkfpcz00snxfth1s8xx8a.png" alt=" " width="800" height="657"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. - Update App Config&lt;/strong&gt;&lt;br&gt;
Path: "src/app/app.config.ts"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { provideHttpClient, withFetch, withInterceptors } from '@angular/common/http';
import { ApplicationConfig } from '@angular/core';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { provideRouter, withEnabledBlockingInitialNavigation, withInMemoryScrolling } from '@angular/router';
import Aura from '@primeuix/themes/aura';
import { providePrimeNG } from 'primeng/config';
import { appRoutes } from './app.routes';
import { authInterceptor } from '@/core/interceptors/auth.interceptor';



export const appConfig: ApplicationConfig = {
    providers: [
        provideRouter(appRoutes, withInMemoryScrolling({ anchorScrolling: 'enabled', scrollPositionRestoration: 'enabled' }), withEnabledBlockingInitialNavigation()),
        provideHttpClient(withFetch(), withInterceptors([authInterceptor])),
        provideAnimationsAsync(),
        providePrimeNG({ theme: { preset: Aura, options: { darkModeSelector: '.app-dark' } } })
    ]
};
&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.amazonaws.com%2Fuploads%2Farticles%2F9danl1a39oqc7yxzgoug.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.amazonaws.com%2Fuploads%2Farticles%2F9danl1a39oqc7yxzgoug.png" alt=" " width="800" height="232"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. - Lets Test&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Install npm packages&lt;br&gt;
&lt;code&gt;npm i&lt;/code&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fnqcj2vo6r43u0myubfwi.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.amazonaws.com%2Fuploads%2Farticles%2Fnqcj2vo6r43u0myubfwi.png" alt=" " width="659" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After Installation Lets run both Application backend and angular&lt;br&gt;
&lt;code&gt;ng s -o&lt;/code&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fo0m0jn02dfutoxaeoiss.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.amazonaws.com%2Fuploads%2Farticles%2Fo0m0jn02dfutoxaeoiss.png" alt=" " width="763" height="480"&gt;&lt;/a&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2F715fhhlsi1kdwexv70nn.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.amazonaws.com%2Fuploads%2Farticles%2F715fhhlsi1kdwexv70nn.png" alt=" " width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on Login&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fb2w2c93lkd6oovr3egr5.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.amazonaws.com%2Fuploads%2Farticles%2Fb2w2c93lkd6oovr3egr5.png" alt=" " width="800" height="91"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Login Sucessfully, redirected to Dashboard and toke in local storage&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fpmq5bv3b7uekxg8k44kx.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.amazonaws.com%2Fuploads%2Farticles%2Fpmq5bv3b7uekxg8k44kx.png" alt=" " width="800" height="273"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next Lecture Preview&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Lecture 9B : Product Management&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Implementing product CRUD operations and connecting the frontend UI with backend APIs.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>architecture</category>
      <category>security</category>
      <category>angular</category>
    </item>
    <item>
      <title>🧱 Lesson 8 - Authentication &amp; Authorization in .NET</title>
      <dc:creator>Farrukh Rehman</dc:creator>
      <pubDate>Tue, 18 Nov 2025 09:31:30 +0000</pubDate>
      <link>https://dev.to/farrukh_rehman/lesson-8-authentication-authorization-in-net-3m84</link>
      <guid>https://dev.to/farrukh_rehman/lesson-8-authentication-authorization-in-net-3m84</guid>
      <description>&lt;p&gt;Series: From Code to Cloud: Building a Production-Ready .NET Application&lt;br&gt;
By: Farrukh Rehman - Senior .NET Full Stack Developer / Team Lead&lt;br&gt;
LinkedIn: &lt;a href="https://linkedin.com/in/farrukh-rehman" rel="noopener noreferrer"&gt;https://linkedin.com/in/farrukh-rehman&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/farrukh1212cs" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source Code Backend : &lt;a href="https://github.com/farrukh1212cs/ECommerce-Backend.git" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs/ECommerce-Backend.git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source Code Frontend : &lt;a href="https://github.com/farrukh1212cs/ECommerce-Frontend.git" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs/ECommerce-Frontend.git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🎯 Introduction&lt;br&gt;
add secure user authentication (JWT), role-based authorization. We'll use patterns that fit your Clean Architecture: interfaces in Application, implementations in Infrastructure, DI wiring in DependencyInjection, and usage in the API.&lt;/p&gt;

&lt;p&gt;🔐 What you'll learn (brief)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JWT-based auth (register / login / issue token)&lt;/li&gt;
&lt;li&gt;Role-based auth and policy-based authorization&lt;/li&gt;
&lt;li&gt;Seeding admin/user roles&lt;/li&gt;
&lt;li&gt;Protecting controllers/endpoints with [Authorize]&lt;/li&gt;
&lt;li&gt;Refresh token strategy (overview + simple approach)&lt;/li&gt;
&lt;li&gt;Optional: integrate Azure AD or IdentityServer (high-level steps)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;📦 Packages to install&lt;/p&gt;

&lt;p&gt;(choose versions compatible with .NET 8; run from solution root)&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 ECommerce.Infrastructure package Microsoft.AspNetCore.Authentication.JwtBearer --version 8.0.21
dotnet add ECommerce.Infrastructure package Microsoft.AspNetCore.Identity.EntityFrameworkCore --version 8.0.21
dotnet add ECommerce.Domain package Microsoft.AspNetCore.Identity.EntityFrameworkCore --version 8.0.21
dotnet add ECommerce.Infrastructure package Microsoft.IdentityModel.Tokens
dotnet add ECommerce.Application package System.IdentityModel.Tokens.Jwt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🧱 High-level architecture&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Identity stores (users/roles) live in your DB (via EF stores).&lt;/li&gt;
&lt;li&gt;IAuthService in Application defines operations (Register, Login, Validate).&lt;/li&gt;
&lt;li&gt;AuthService in Infrastructure implements token generation and uses UserManager/RoleManager.&lt;/li&gt;
&lt;li&gt;Program.cs / DependencyInjection wires Identity + JwtBearer authentication + role seeding.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🔧 appsettings.json (JWT config example)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;"JwtSettings": {&lt;br&gt;
  "Issuer": "ECommerceApi",&lt;br&gt;
  "Audience": "ECommerceClients",&lt;br&gt;
  "Secret": "3A6DA077-8EBC-4DA9-94DF-2C246564E749",&lt;br&gt;
  "ExpiresInMinutes": 60,&lt;br&gt;
  "RefreshTokenExpiresInDays": 30&lt;br&gt;
}&lt;/code&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fubrr57hvxy8t4jdppcxf.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.amazonaws.com%2Fuploads%2Farticles%2Fubrr57hvxy8t4jdppcxf.png" alt=" " width="624" height="192"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Keep Secret in secure store (Key Vault / environment variables) in production.&lt;/p&gt;

&lt;p&gt;✅ Step 1 — Create Application User Entity&lt;br&gt;
Path: ECommerce.Domain/Entities/ApplicationUser.cs&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.Identity;

namespace ECommerce.Domain.Entities;

public class ApplicationUser : IdentityUser&amp;lt;Guid&amp;gt;
{
    // Add extra properties if needed (FirstName, LastName)
    public string? FirstName { get; set; }
    public string? LastName { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ Step 2 — Add Identity entities &amp;amp; DbContext&lt;/p&gt;

&lt;p&gt;we already have AppDbContext derived from DbContext, extend it to use Identity:&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fcw5h06leve3yjb8q90xc.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.amazonaws.com%2Fuploads%2Farticles%2Fcw5h06leve3yjb8q90xc.png" alt=" " width="800" height="116"&gt;&lt;/a&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 ECommerce.Domain.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

namespace ECommerce.Infrastructure.Data;

public abstract class AppDbContext : IdentityDbContext&amp;lt;ApplicationUser, IdentityRole&amp;lt;Guid&amp;gt;, Guid&amp;gt;
{
    public AppDbContext(DbContextOptions&amp;lt;AppDbContext&amp;gt; options) : base(options) { }

    // DbSets
    public DbSet&amp;lt;Product&amp;gt; Products { get; set; } = null!;
    public DbSet&amp;lt;Customer&amp;gt; Customers { get; set; } = null!;
    public DbSet&amp;lt;Order&amp;gt; Orders { get; set; } = null!;
    public DbSet&amp;lt;OrderItem&amp;gt; OrderItems { get; set; } = null!;
    public DbSet&amp;lt;ApplicationUser&amp;gt; ApplicationUser { get; set; } = null!;

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        // Product
        modelBuilder.Entity&amp;lt;Product&amp;gt;(b =&amp;gt;
        {
            b.ToTable("Products");
            b.HasKey(p =&amp;gt; p.Id);
            b.Property(p =&amp;gt; p.Name).IsRequired().HasMaxLength(200);
            b.Property(p =&amp;gt; p.Price).HasColumnType("decimal(18,2)");
        });

        // Customer
        modelBuilder.Entity&amp;lt;Customer&amp;gt;(b =&amp;gt;
        {
            b.ToTable("Customers");
            b.HasKey(c =&amp;gt; c.Id);
            b.Property(c =&amp;gt; c.FirstName).IsRequired().HasMaxLength(200);
            b.Property(c =&amp;gt; c.Email).IsRequired().HasMaxLength(256);
            b.HasMany(c =&amp;gt; c.Orders)
             .WithOne(o =&amp;gt; o.Customer)
             .HasForeignKey(o =&amp;gt; o.CustomerId)
             .OnDelete(DeleteBehavior.Cascade);
        });

        // Order
        modelBuilder.Entity&amp;lt;Order&amp;gt;(b =&amp;gt;
        {
            b.ToTable("Orders");
            b.HasKey(o =&amp;gt; o.Id);
            b.Property(o =&amp;gt; o.OrderDate).IsRequired();
            b.Property(o =&amp;gt; o.TotalAmount).HasColumnType("decimal(18,2)");
            b.HasMany(o =&amp;gt; o.Items)
             .WithOne(oi =&amp;gt; oi.Order)
             .HasForeignKey(oi =&amp;gt; oi.OrderId)
             .OnDelete(DeleteBehavior.Cascade);
        });

        // OrderItem
        modelBuilder.Entity&amp;lt;OrderItem&amp;gt;(b =&amp;gt;
        {
            b.ToTable("OrderItems");
            b.HasKey(oi =&amp;gt; oi.Id);
            b.Property(oi =&amp;gt; oi.ProductName).IsRequired().HasMaxLength(200);
            b.Property(oi =&amp;gt; oi.UnitPrice).HasColumnType("decimal(18,2)");
            b.Property(oi =&amp;gt; oi.Quantity).IsRequired();
        });

        // If you want to seed or add indexes later, do it here.
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ Step 3 — Define IAuthService (Application layer)&lt;br&gt;
Path: ECommerce.Application/Services/Interfaces/IAuthService.cs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace ECommerce.Application.Services.Interfaces;

public interface IAuthService
{
    Task&amp;lt;AuthenticationResult&amp;gt; RegisterAsync(RegisterRequest request);
    Task&amp;lt;AuthenticationResult&amp;gt; LoginAsync(LoginRequest request);
    Task&amp;lt;AuthenticationResult&amp;gt; RefreshTokenAsync(string token, string refreshToken);
}

public record RegisterRequest(string Email, string Password, string? FirstName = null, string? LastName = null);
public record LoginRequest(string Email, string Password);
public record AuthenticationResult(bool Success, string? Token, string? RefreshToken, IEnumerable&amp;lt;string&amp;gt;? Errors);
public record RefreshRequest(string Token, string RefreshToken);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ Step 4 — Implement AuthService (Infrastructure)&lt;br&gt;
Path: ECommerce.Application/Services/Implementations/AuthService.cs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using ECommerce.Application.Services.Interfaces;
using ECommerce.Domain.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace ECommerce.Application.Services.Implementations;

public class AuthService : IAuthService
{
    private readonly UserManager&amp;lt;ApplicationUser&amp;gt; _userManager;
    private readonly IConfiguration _config;
    private readonly RoleManager&amp;lt;IdentityRole&amp;lt;Guid&amp;gt;&amp;gt; _roleManager;

    public AuthService(UserManager&amp;lt;ApplicationUser&amp;gt; userManager, RoleManager&amp;lt;IdentityRole&amp;lt;Guid&amp;gt;&amp;gt; roleManager, IConfiguration config)
    {
        _userManager = userManager;
        _roleManager = roleManager;
        _config = config;
    }

    public async Task&amp;lt;AuthenticationResult&amp;gt; RegisterAsync(RegisterRequest request)
    {
        var existing = await _userManager.FindByEmailAsync(request.Email);
        if (existing != null)
            return new AuthenticationResult(false, null, null, new[] { "User already exists" });

        var user = new ApplicationUser { Email = request.Email, UserName = request.Email, FirstName = request.FirstName, LastName = request.LastName };
        var result = await _userManager.CreateAsync(user, request.Password);
        if (!result.Succeeded)
            return new AuthenticationResult(false, null, null, result.Errors.Select(e =&amp;gt; e.Description));

        // Optionally add default role
        await _userManager.AddToRoleAsync(user, "User");

        // generate tokens
        var token = await GenerateJwtToken(user);
        var refreshToken = GenerateRefreshToken(); // implement secure random token and store it

        // persist refresh token with user (e.g., in DB)
        // ...

        return new AuthenticationResult(true, token, refreshToken, null);
    }

    public async Task&amp;lt;AuthenticationResult&amp;gt; LoginAsync(LoginRequest request)
    {
        var user = await _userManager.FindByEmailAsync(request.Email);
        if (user == null) return new AuthenticationResult(false, null, null, new[] { "Invalid credentials" });

        if (!await _userManager.CheckPasswordAsync(user, request.Password))
            return new AuthenticationResult(false, null, null, new[] { "Invalid credentials" });

        var token = await GenerateJwtToken(user);
        var refreshToken = GenerateRefreshToken();
        // store refresh token...

        return new AuthenticationResult(true, token, refreshToken, null);
    }

    public Task&amp;lt;AuthenticationResult&amp;gt; RefreshTokenAsync(string token, string refreshToken)
    {
        // validate existing refresh token stored in DB, expiration, rotation, etc.
        throw new NotImplementedException();
    }

    private async Task&amp;lt;string&amp;gt; GenerateJwtToken(ApplicationUser user)
    {
        var jwt = _config.GetSection("JwtSettings");
        var secret = jwt["Secret"]!;
        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var claims = new List&amp;lt;Claim&amp;gt;
        {
            new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
            new Claim(JwtRegisteredClaimNames.Email, user.Email!),
            new Claim(ClaimTypes.Name, user.UserName!)
        };

        var userRoles = await _userManager.GetRolesAsync(user);
        claims.AddRange(userRoles.Select(r =&amp;gt; new Claim(ClaimTypes.Role, r)));

        var token = new JwtSecurityToken(
            issuer: jwt["Issuer"],
            audience: jwt["Audience"],
            claims: claims,
            expires: DateTime.UtcNow.AddMinutes(double.Parse(jwt["ExpiresInMinutes"] ?? "60")),
            signingCredentials: creds);

        return new JwtSecurityTokenHandler().WriteToken(token);
    }

    private static string GenerateRefreshToken()
    {
        return Convert.ToBase64String(RandomNumberGenerator.GetBytes(64));
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ Step 5 — Wire up Identity, JwtBearer and DI&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Flthe51onrqh39y3titv6.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.amazonaws.com%2Fuploads%2Farticles%2Flthe51onrqh39y3titv6.png" alt=" " width="800" height="515"&gt;&lt;/a&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 ECommerce.API.BackgroundServices;
using ECommerce.Application.Services.Interfaces;
using ECommerce.Infrastructure.Caching;
using ECommerce.Infrastructure.Data;
using ECommerce.Infrastructure.Email;
using ECommerce.Infrastructure.Messaging;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using StackExchange.Redis;
using System.Text;



namespace ECommerce.Infrastructure;

public static class DependencyInjection
{
    public static IServiceCollection AddInfrastructure(
        this IServiceCollection services,
        IConfiguration configuration)
    {

        var provider = configuration["DatabaseProvider"] ?? "MySQL";

        if (string.Equals(provider, "SqlServer", StringComparison.OrdinalIgnoreCase))
        {
            var conn = configuration.GetConnectionString("SqlServer");
            services.AddDbContext&amp;lt;AppDbContext, SqlServerDbContext&amp;gt;(options =&amp;gt;
                options.UseSqlServer(conn));
        }
        else if (string.Equals(provider, "MySQL", StringComparison.OrdinalIgnoreCase))
        {
            var conn = configuration.GetConnectionString("MySQL");
            services.AddDbContext&amp;lt;AppDbContext, MySqlDbContext&amp;gt;(options =&amp;gt;
                options.UseMySql(conn, ServerVersion.AutoDetect(conn)));
        }
        else if (string.Equals(provider, "PostgreSQL", StringComparison.OrdinalIgnoreCase))
        {

            var conn = configuration.GetConnectionString("PostgreSQL");
            services.AddDbContext&amp;lt;AppDbContext, PostgresDbContext&amp;gt;(options =&amp;gt;
                options.UseNpgsql(conn));
        }
        else
        {
            throw new InvalidOperationException($"Unsupported provider: {provider}");
        }

        // ✅ Add Identity
        services.AddIdentity&amp;lt;IdentityUser, IdentityRole&amp;gt;()
            .AddEntityFrameworkStores&amp;lt;AppDbContext&amp;gt;()
            .AddDefaultTokenProviders();

        // ✅ JWT Authentication setup
        var jwtSettings = configuration.GetSection("JwtSettings");
        var key = Encoding.UTF8.GetBytes(jwtSettings["Secret"]);

        services.AddAuthentication(options =&amp;gt;
        {
            options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
           .AddJwtBearer(options =&amp;gt;
           {
               options.RequireHttpsMetadata = false;
               options.SaveToken = true;
               options.TokenValidationParameters = new TokenValidationParameters
               {
                   ValidateIssuer = true,
                   ValidateAudience = true,
                   ValidateLifetime = true,
                   ValidateIssuerSigningKey = true,
                   ValidIssuer = jwtSettings["Issuer"],
                   ValidAudience = jwtSettings["Audience"],
                   IssuerSigningKey = new SymmetricSecurityKey(key)
               };
           });

        // ✅ Authorization
        services.AddAuthorization(options =&amp;gt;
        {
            options.AddPolicy("AdminOnly", policy =&amp;gt; policy.RequireRole("Admin"));
            options.AddPolicy("CustomerOnly", policy =&amp;gt; policy.RequireRole("Customer"));
        });

        // ✅ Redis cache setup
        var redisConnection = configuration.GetConnectionString("Redis");
        if (!string.IsNullOrEmpty(redisConnection))
        {
            services.AddSingleton&amp;lt;IConnectionMultiplexer&amp;gt;(sp =&amp;gt;
                ConnectionMultiplexer.Connect(redisConnection));

            services.AddSingleton&amp;lt;ICacheService, RedisCacheService&amp;gt;();
        }

        // RabbitMQ setup
        services.AddSingleton&amp;lt;IMessageQueueService, RabbitMQService&amp;gt;();
        services.AddScoped&amp;lt;IEmailSenderService, EmailSenderService&amp;gt;();


        return services;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ Step 6 — Update Program.cs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app.UseAuthentication();
app.UseAuthorization();
&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.amazonaws.com%2Fuploads%2Farticles%2F7h4p32ebso9pbrr7tkw8.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.amazonaws.com%2Fuploads%2Farticles%2F7h4p32ebso9pbrr7tkw8.png" alt=" " width="492" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✅ Step 7 — Seed roles and an admin user&lt;br&gt;
Path: ECommerce.Infrastructure/Identity/IdentitySeeder.cs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using ECommerce.Domain.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Threading.Tasks;

namespace ECommerce.Infrastructure.Identity
{
    public static class IdentitySeeder
    {
        public static async Task SeedRolesAndAdminAsync(IServiceProvider serviceProvider)
        {
            // Use the correct types matching your Identity setup
            var roleManager = serviceProvider.GetRequiredService&amp;lt;RoleManager&amp;lt;IdentityRole&amp;lt;Guid&amp;gt;&amp;gt;&amp;gt;();
            var userManager = serviceProvider.GetRequiredService&amp;lt;UserManager&amp;lt;ApplicationUser&amp;gt;&amp;gt;();

            string[] roles = { "Admin", "Customer" };

            // Ensure roles exist
            foreach (var role in roles)
            {
                if (!await roleManager.RoleExistsAsync(role))
                {
                    await roleManager.CreateAsync(new IdentityRole&amp;lt;Guid&amp;gt;(role));
                }
            }

            // Create default admin user if missing
            var adminEmail = "admin@ecommerce.com";
            var adminUser = await userManager.FindByEmailAsync(adminEmail);
            if (adminUser == null)
            {
                adminUser = new ApplicationUser
                {
                    UserName = adminEmail,
                    Email = adminEmail,
                    EmailConfirmed = true
                };

                var result = await userManager.CreateAsync(adminUser, "Admin@123");

                if (result.Succeeded)
                {
                    await userManager.AddToRoleAsync(adminUser, "Admin");
                }
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ Step 8 — Add the Above Seeting in Program.cs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    await IdentitySeeder.SeedRolesAndAdminAsync(services);
}
&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.amazonaws.com%2Fuploads%2Farticles%2Fhw1369422wan0nga5dwg.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.amazonaws.com%2Fuploads%2Farticles%2Fhw1369422wan0nga5dwg.png" alt=" " width="752" height="608"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✅ Step 9 — Lets Create Migrations for All DBs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet ef migrations add IdentityMySQL -p ECommerce.Infrastructure -s ECommerce.API --context MySqlDbContext --output-dir "Migrations/MySQL"

dotnet ef migrations add IdentitySqlServer -p ECommerce.Infrastructure -s ECommerce.API --context SqlServerDbContext --output-dir "Migrations/SqlServer"

dotnet ef migrations add IdentityPostgreSQL -p ECommerce.Infrastructure -s ECommerce.API --context PostgresDbContext --output-dir "Migrations/PostgreSQL"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ Step 10 — Update DB&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet ef database update -p ECommerce.Infrastructure -s ECommerce.API --context MySqlDbContext 
dotnet ef database update -p ECommerce.Infrastructure -s ECommerce.API --context SqlServerDbContext 
dotnet ef database update -p ECommerce.Infrastructure -s ECommerce.API --context PostgresDbContext 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ Step 10 — Let Run and Seed Data&lt;/p&gt;

&lt;p&gt;Seedind Done&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fynufhtjogh6cymenhoj8.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.amazonaws.com%2Fuploads%2Farticles%2Fynufhtjogh6cymenhoj8.png" alt=" " width="800" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Verify Data in DB&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fgpgqfbmq1nsxob0xbs7l.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.amazonaws.com%2Fuploads%2Farticles%2Fgpgqfbmq1nsxob0xbs7l.png" alt=" " width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✅ Step 11 — Register IAuthService in DependencyInjection.cs&lt;/p&gt;

&lt;p&gt;&lt;code&gt;services.AddScoped&amp;lt;IAuthService, AuthService&amp;gt;();&lt;/code&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fqtyi35l7pi99enst6e0y.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.amazonaws.com%2Fuploads%2Farticles%2Fqtyi35l7pi99enst6e0y.png" alt=" " width="522" height="146"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✅ Step 12 — Create Auth Controller&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using ECommerce.Application.Services.Interfaces;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;


namespace ECommerce.API.Controllers;

[Authorize]
[Route("api/[controller]")]
[ApiController]
public class AuthController : ControllerBase
{
    private readonly IAuthService _auth;
    public AuthController(IAuthService auth) =&amp;gt; _auth = auth;

    [HttpPost("register")]
    public async Task&amp;lt;IActionResult&amp;gt; Register(Application.Services.Interfaces.RegisterRequest req)
    {
        var res = await _auth.RegisterAsync(req);
        if (!res.Success) return BadRequest(res.Errors);
        return Ok(new { token = res.Token, refreshToken = res.RefreshToken });
    }
    [AllowAnonymous]
    [HttpPost("login")]
    public async Task&amp;lt;IActionResult&amp;gt; Login(Application.Services.Interfaces.LoginRequest req)
    {
        var res = await _auth.LoginAsync(req);
        if (!res.Success) return Unauthorized(res.Errors);
        return Ok(new { token = res.Token, refreshToken = res.RefreshToken });
    }

    [HttpPost("refresh")]
    public async Task&amp;lt;IActionResult&amp;gt; Refresh(Application.Services.Interfaces.RefreshRequest req)
    {
        var res = await _auth.RefreshTokenAsync(req.Token, req.RefreshToken);
        if (!res.Success) return Unauthorized(res.Errors);
        return Ok(new { token = res.Token, refreshToken = res.RefreshToken });
    }
}

&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.amazonaws.com%2Fuploads%2Farticles%2Fhp7mhxg61couhyk8wxnu.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.amazonaws.com%2Fuploads%2Farticles%2Fhp7mhxg61couhyk8wxnu.png" alt=" " width="800" height="714"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✅ Step 13 — Lets Work on CORS for Angular&lt;/p&gt;

&lt;p&gt;Update Program file, Add Core Configurations after swagger Step 1&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// ------------------------------------------------------
// CORS
// ------------------------------------------------------
var allowedOrigins = builder.Configuration.GetSection("Cors:AllowedOrigins").Get&amp;lt;string[]&amp;gt;();

builder.Services.AddCors(options =&amp;gt;
{
    options.AddPolicy("AllowFrontend", policy =&amp;gt;
    {
        policy.WithOrigins(allowedOrigins!)
              .AllowAnyHeader()
              .AllowAnyMethod()
              .AllowCredentials();
    });
});

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

&lt;/div&gt;



&lt;p&gt;Step 2&lt;br&gt;
Use Cors after Redirection&lt;/p&gt;

&lt;p&gt;&lt;code&gt;app.UseCors("AllowFrontend");&lt;/code&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fdo16c2r0kqc1xh6jncgn.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.amazonaws.com%2Fuploads%2Farticles%2Fdo16c2r0kqc1xh6jncgn.png" alt=" " width="800" height="675"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✅ Step 14 — Update appsettings.json&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Cors": {
  "AllowedOrigins": [
    "http://localhost:4200",
    "https://localhost:4200"
  ]
}
&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.amazonaws.com%2Fuploads%2Farticles%2Fxe99dctjs757frubkpii.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.amazonaws.com%2Fuploads%2Farticles%2Fxe99dctjs757frubkpii.png" alt=" " width="800" height="631"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✅ Step 15 — Configure Auth Settings in Swagger&lt;br&gt;
Update in Programfile, Replace &lt;strong&gt;builder.Services.AddSwaggerGen()&lt;/strong&gt; with below code&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.AddSwaggerGen(c =&amp;gt;
{
    c.SwaggerDoc("v1", new() { Title = "ECommerce API", Version = "v1" });

    // Add JWT Authentication to Swagger
    c.AddSecurityDefinition("Bearer", new Microsoft.OpenApi.Models.OpenApiSecurityScheme
    {
        Name = "Authorization",
        Type = Microsoft.OpenApi.Models.SecuritySchemeType.Http,
        Scheme = "Bearer",
        BearerFormat = "JWT",
        In = Microsoft.OpenApi.Models.ParameterLocation.Header,
        Description = "Enter JWT token: Bearer {your token}"
    });

    c.AddSecurityRequirement(new Microsoft.OpenApi.Models.OpenApiSecurityRequirement
    {
        {
            new Microsoft.OpenApi.Models.OpenApiSecurityScheme
            {
                Reference = new Microsoft.OpenApi.Models.OpenApiReference
                {
                    Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme,
                    Id = "Bearer"
                }
            },
            new string[] {}
        }
    });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ Step 16 — Lets Auth Test Application&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fztfcw1294aukgahajqqj.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.amazonaws.com%2Fuploads%2Farticles%2Fztfcw1294aukgahajqqj.png" alt=" " width="800" height="185"&gt;&lt;/a&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fydcr0ygumupndu5xxrvp.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.amazonaws.com%2Fuploads%2Farticles%2Fydcr0ygumupndu5xxrvp.png" alt=" " width="" height=""&gt;&lt;/a&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2F700aiqv4riatr7k1jwnn.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.amazonaws.com%2Fuploads%2Farticles%2F700aiqv4riatr7k1jwnn.png" alt=" " width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✅ Step 17 — Lets Product Controller&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fn4iq23uu1akz2luaguw7.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.amazonaws.com%2Fuploads%2Farticles%2Fn4iq23uu1akz2luaguw7.png" alt=" " width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add Token&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fo2pmanmr1hl6caa9b19m.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.amazonaws.com%2Fuploads%2Farticles%2Fo2pmanmr1hl6caa9b19m.png" alt=" " width="744" height="361"&gt;&lt;/a&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fic6nyv3c05pqopeuu4gm.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.amazonaws.com%2Fuploads%2Farticles%2Fic6nyv3c05pqopeuu4gm.png" alt=" " width="678" height="322"&gt;&lt;/a&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2F3chlcvdni5lpgrzce7jb.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.amazonaws.com%2Fuploads%2Farticles%2F3chlcvdni5lpgrzce7jb.png" alt=" " width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next Lecture Preview&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Lecture 9A : Login Frontend Integration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Creating login pages, implementing HTTP interceptors, error handling, and integrating frontend with backend authentication APIs.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>security</category>
      <category>dotnet</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>🧱 Lesson 7  - Message Queues with RabbitMQ</title>
      <dc:creator>Farrukh Rehman</dc:creator>
      <pubDate>Mon, 10 Nov 2025 15:15:52 +0000</pubDate>
      <link>https://dev.to/farrukh_rehman/lesson-7-message-queues-with-rabbitmq-1lno</link>
      <guid>https://dev.to/farrukh_rehman/lesson-7-message-queues-with-rabbitmq-1lno</guid>
      <description>&lt;p&gt;Series: From Code to Cloud: Building a Production-Ready .NET Application&lt;br&gt;
By: Farrukh Rehman - Senior .NET Full Stack Developer / Team Lead&lt;br&gt;
LinkedIn: &lt;a href="https://linkedin.com/in/farrukh-rehman" rel="noopener noreferrer"&gt;https://linkedin.com/in/farrukh-rehman&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/farrukh1212cs" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source Code Backend : &lt;a href="https://github.com/farrukh1212cs/ECommerce-Backend.git" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs/ECommerce-Backend.git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source Code Frontend : &lt;a href="https://github.com/farrukh1212cs/ECommerce-Frontend.git" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs/ECommerce-Frontend.git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🎯 Introduction&lt;/p&gt;

&lt;p&gt;In a modern microservices or modular system, asynchronous processing is key to maintaining performance and scalability.&lt;br&gt;
Imagine your application needs to send an email every time a user registers or a new product is added.&lt;br&gt;
If you perform this action synchronously inside the request pipeline, your API will slow down and block other requests.&lt;/p&gt;

&lt;p&gt;That’s where RabbitMQ — a message broker — comes in.&lt;br&gt;
It allows you to publish messages (like “Send this email”) into a queue, which can later be consumed by a background service that actually sends the email.&lt;/p&gt;

&lt;p&gt;This decouples your API logic from long-running or slow tasks.&lt;/p&gt;

&lt;p&gt;🐇 Step 1 — Pull RabbitMQ Docker Image&lt;/p&gt;

&lt;p&gt;Run the following command to pull and start RabbitMQ (with management dashboard):&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker run -d --hostname rabbitmq-host --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;✅ Ports Explanation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;5672 → for app communication&lt;/li&gt;
&lt;li&gt;15672 → for web dashboard (management UI)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then open your browser and visit:&lt;br&gt;
👉 &lt;a href="http://localhost:15672" rel="noopener noreferrer"&gt;http://localhost:15672&lt;/a&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2F5iqiznikfapm1z79szf3.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.amazonaws.com%2Fuploads%2Farticles%2F5iqiznikfapm1z79szf3.png" alt=" " width="800" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Default credentials:&lt;br&gt;
Username: guest | Password: guest&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Ffgjz2c5jssxavhm0hg2x.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.amazonaws.com%2Fuploads%2Farticles%2Ffgjz2c5jssxavhm0hg2x.png" alt=" " width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⚙️ Step 2 — Install NuGet Package&lt;/p&gt;

&lt;p&gt;In the Infrastructure project, install the official RabbitMQ client library:&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 ECommerce.Infrastructure package RabbitMQ.Client --version 6.8.1
dotnet add ECommerce.Infrastructure package Microsoft.Extensions.Configuration.Binder
dotnet add ECommerce.Infrastructure package Microsoft.Extensions.Hosting --version 8.0.1

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

&lt;/div&gt;



&lt;p&gt;🧩 Step 3 — Create Message Queue Interface&lt;br&gt;
Path: ECommerce.Application/Services/Interfaces/IMessageQueueService.cs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace ECommerce.Application.Services.Interfaces;

public interface IMessageQueueService
{
    Task PublishAsync&amp;lt;T&amp;gt;(string queueName, T message);
    void Subscribe&amp;lt;T&amp;gt;(string queueName, Func&amp;lt;T, Task&amp;gt; handler);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This defines a simple contract for publishing and consuming messages.&lt;/p&gt;

&lt;p&gt;⚙️ Step 4 — Implement RabbitMQ Service&lt;br&gt;
Path: ECommerce.Infrastructure/Messaging/RabbitMQService.cs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using ECommerce.Application.Services.Interfaces;
using Microsoft.Extensions.Configuration;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System.Text;
using System.Text.Json;

namespace ECommerce.Infrastructure.Messaging;

public class RabbitMQService : IMessageQueueService, IDisposable
{
    private readonly IConnection _connection;
    private readonly RabbitMQ.Client.IModel _channel;

    public RabbitMQService(IConfiguration configuration)
    {
        var factory = new ConnectionFactory
        {
            HostName = configuration.GetValue&amp;lt;string&amp;gt;("RabbitMQ:Host") ?? "localhost",
            UserName = configuration.GetValue&amp;lt;string&amp;gt;("RabbitMQ:Username") ?? "guest",
            Password = configuration.GetValue&amp;lt;string&amp;gt;("RabbitMQ:Password") ?? "guest",
            Port = configuration.GetValue&amp;lt;int?&amp;gt;("RabbitMQ:Port") ?? 5672
        };

        _connection = factory.CreateConnection();
        _channel = _connection.CreateModel();
    }

    public Task PublishAsync&amp;lt;T&amp;gt;(string queueName, T message)
    {
        _channel.QueueDeclare(queueName, durable: true, exclusive: false, autoDelete: false);

        var body = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(message));
        _channel.BasicPublish("", queueName, null, body);

        Console.WriteLine($"📤 Published message to queue '{queueName}'");
        return Task.CompletedTask;
    }

    public void Subscribe&amp;lt;T&amp;gt;(string queueName, Func&amp;lt;T, Task&amp;gt; handler)
    {
        _channel.QueueDeclare(queueName, durable: true, exclusive: false, autoDelete: false);

        var consumer = new EventingBasicConsumer(_channel);
        consumer.Received += async (sender, ea) =&amp;gt;
        {
            var body = ea.Body.ToArray();
            var message = JsonSerializer.Deserialize&amp;lt;T&amp;gt;(Encoding.UTF8.GetString(body));
            if (message != null)
                await handler(message);
        };

        _channel.BasicConsume(queueName, autoAck: true, consumer: consumer);
        Console.WriteLine($"📥 Subscribed to queue '{queueName}'");
    }

    public void Dispose()
    {
        _channel?.Dispose();
        _connection?.Dispose();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🧱 Step 5 — Add Configuration in appsettings.json (we will use ethereal.email for testing)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;"RabbitMQ": {&lt;br&gt;
  "Host": "localhost",&lt;br&gt;
  "Port": 5672,&lt;br&gt;
  "Username": "guest",&lt;br&gt;
  "Password": "guest"&lt;br&gt;
},&lt;br&gt;
  "SmtpSettings": {&lt;br&gt;
    "Host": "smtp.ethereal.email",&lt;br&gt;
    "Port": 587,&lt;br&gt;
    "Username": "alize.hilpert67@ethereal.email",&lt;br&gt;
    "Password": "E4GnEsxZGbrc1XcS7q",&lt;br&gt;
    "EnableSsl": true,&lt;br&gt;
    "From": "no-reply@ecommerce.com"&lt;br&gt;
  }&lt;/code&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fepa0whviy9tr9lyp1nxt.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.amazonaws.com%2Fuploads%2Farticles%2Fepa0whviy9tr9lyp1nxt.png" alt=" " width="727" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;📧 Step 6 — Email Notification DTO&lt;br&gt;
Path: ECommerce.Application/DTOs/EmailNotificationDto.cs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace ECommerce.Application.DTOs;

public class EmailNotificationDto
{
    public string To { get; set; } = string.Empty;
    public string Subject { get; set; } = string.Empty;
    public string Body { get; set; } = string.Empty;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🧱 Step 7 — Add Interface for Email Sender&lt;br&gt;
Path: ECommerce.Application/Services/Interfaces/IEmailSenderService.cs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using ECommerce.Application.DTOs;

namespace ECommerce.Application.Services.Interfaces;

public interface IEmailSenderService
{
    Task SendEmailAsync(EmailNotificationDto email);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;📬 Step 8 — Email Sender Service&lt;br&gt;
Path: ECommerce.Infrastructure/Email/EmailSenderService.cs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using ECommerce.Application.DTOs;
using ECommerce.Application.Services.Interfaces;
using Microsoft.Extensions.Configuration;
using System.Net;
using System.Net.Mail;

namespace ECommerce.Infrastructure.Email;

public class EmailSenderService : IEmailSenderService
{
    private readonly IConfiguration _config;

    public EmailSenderService(IConfiguration config)
    {
        _config = config;
    }

    public async Task SendEmailAsync(EmailNotificationDto email)
    {
        var smtp = _config.GetSection("SmtpSettings");

        using var client = new SmtpClient(smtp["Host"], int.Parse(smtp["Port"] ?? "587"))
        {
            Credentials = new NetworkCredential(smtp["Username"], smtp["Password"]),
            EnableSsl = bool.Parse(smtp["EnableSsl"] ?? "true")
        };

        var message = new MailMessage
        {
            From = new MailAddress(smtp["From"] ?? smtp["Username"]),
            Subject = email.Subject,
            Body = email.Body,
            IsBodyHtml = true
        };
        message.To.Add(email.To);

        await client.SendMailAsync(message);
        Console.WriteLine($"📨 Email sent to {email.To}");
    }
}

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

&lt;/div&gt;



&lt;p&gt;🧩 Step 9 — Background Worker to Consume Email Queue&lt;br&gt;
Path: ECommerce.API/BackgroundServices/EmailNotificationConsumer.cs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using ECommerce.Application.DTOs;
using ECommerce.Application.Services.Interfaces;

namespace ECommerce.API.BackgroundServices;

public class EmailNotificationConsumer : BackgroundService
{
    private readonly IMessageQueueService _queue;
    private readonly IEmailSenderService _emailSender;

    public EmailNotificationConsumer(IMessageQueueService queue, IEmailSenderService emailSender)
    {
        _queue = queue;
        _emailSender = emailSender;
    }

    protected override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _queue.Subscribe&amp;lt;EmailNotificationDto&amp;gt;("email_notifications", async email =&amp;gt;
        {
            Console.WriteLine($"📧 Sending email to: {email.To}");
            await _emailSender.SendEmailAsync(email);
        });

        return Task.CompletedTask;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🧩 Step 10 — Register Everything DependencyInjection.cs&lt;br&gt;
Path: ECommerce.Infrastructure/DependencyInjection.cs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using ECommerce.API.BackgroundServices;
using ECommerce.Application.Services.Interfaces;
using ECommerce.Infrastructure.Caching;
using ECommerce.Infrastructure.Data;
using ECommerce.Infrastructure.Email;
using ECommerce.Infrastructure.Messaging;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using StackExchange.Redis;



namespace ECommerce.Infrastructure;

public static class DependencyInjection
{
    public static IServiceCollection AddInfrastructure(
        this IServiceCollection services,
        IConfiguration configuration)
    {
        var provider = configuration["DatabaseProvider"] ?? "MySQL";

        if (string.Equals(provider, "SqlServer", StringComparison.OrdinalIgnoreCase))
        {
            var conn = configuration.GetConnectionString("SqlServer");
            services.AddDbContext&amp;lt;AppDbContext, SqlServerDbContext&amp;gt;(options =&amp;gt;
                options.UseSqlServer(conn));
        }
        else if (string.Equals(provider, "MySQL", StringComparison.OrdinalIgnoreCase))
        {
            var conn = configuration.GetConnectionString("MySQL");
            services.AddDbContext&amp;lt;AppDbContext, MySqlDbContext&amp;gt;(options =&amp;gt;
                options.UseMySql(conn, ServerVersion.AutoDetect(conn)));
        }
        else if (string.Equals(provider, "PostgreSQL", StringComparison.OrdinalIgnoreCase))
        {

            var conn = configuration.GetConnectionString("PostgreSQL");
            services.AddDbContext&amp;lt;AppDbContext, PostgresDbContext&amp;gt;(options =&amp;gt;
                options.UseNpgsql(conn));
        }
        else
        {
            throw new InvalidOperationException($"Unsupported provider: {provider}");
        }

        // ✅ Redis cache setup
        var redisConnection = configuration.GetConnectionString("Redis");
        if (!string.IsNullOrEmpty(redisConnection))
        {
            services.AddSingleton&amp;lt;IConnectionMultiplexer&amp;gt;(sp =&amp;gt;
                ConnectionMultiplexer.Connect(redisConnection));

            services.AddSingleton&amp;lt;ICacheService, RedisCacheService&amp;gt;();
        }

        // RabbitMQ setup
        services.AddSingleton&amp;lt;IMessageQueueService, RabbitMQService&amp;gt;();
        services.AddScoped&amp;lt;IEmailSenderService, EmailSenderService&amp;gt;();

        return services;
    }
}

&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.amazonaws.com%2Fuploads%2Farticles%2Fltjmsntcb7y5fipg10ud.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.amazonaws.com%2Fuploads%2Farticles%2Fltjmsntcb7y5fipg10ud.png" alt=" " width="523" height="114"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🧩 Step 11 — Update Program.cs&lt;br&gt;
Path: ECommerce.API/Program.cs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// add infrastructure (DbContext + provider selection)
builder.Services.AddInfrastructure(builder.Configuration);

builder.Services.AddHostedService&amp;lt;EmailNotificationConsumer&amp;gt;();
&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.amazonaws.com%2Fuploads%2Farticles%2Fb6iqyh36dsdpxj72gzxb.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.amazonaws.com%2Fuploads%2Farticles%2Fb6iqyh36dsdpxj72gzxb.png" alt=" " width="538" height="94"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🧩 Step 12 — Lets Test&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try Using Swagger&lt;/strong&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fs0xab1mq17dnx30w2i73.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.amazonaws.com%2Fuploads%2Farticles%2Fs0xab1mq17dnx30w2i73.png" alt=" " width="800" height="354"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend Code&lt;/strong&gt;&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2Fe1e8jmkhiv7kzv3tn3u3.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.amazonaws.com%2Fuploads%2Farticles%2Fe1e8jmkhiv7kzv3tn3u3.png" alt=" " width="800" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RabbitMQ&lt;/strong&gt;&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2F7nkudf6p4ufdcfplj5y9.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.amazonaws.com%2Fuploads%2Farticles%2F7nkudf6p4ufdcfplj5y9.png" alt=" " width="800" height="389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consumer&lt;/strong&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Foy5v7ou7r6jr2vwglsam.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.amazonaws.com%2Fuploads%2Farticles%2Foy5v7ou7r6jr2vwglsam.png" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RabbitMQ Service&lt;/strong&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fcu2sgazeb45z7jp2q346.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.amazonaws.com%2Fuploads%2Farticles%2Fcu2sgazeb45z7jp2q346.png" alt=" " width="800" height="186"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Email&lt;/strong&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fqguuj864fy92z5iuxeqc.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.amazonaws.com%2Fuploads%2Farticles%2Fqguuj864fy92z5iuxeqc.png" alt=" " width="800" height="220"&gt;&lt;/a&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Flv1mdmfzz6u4r8rj2ugg.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.amazonaws.com%2Fuploads%2Farticles%2Flv1mdmfzz6u4r8rj2ugg.png" alt=" " width="800" height="311"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next Lecture Preview&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Lecture 8 : Authentication &amp;amp; Authorization in .NET&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Implementing JWT-based authentication, role-based authorization, and optional Azure AD/IdentityServer integration.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>microservices</category>
      <category>tutorial</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>🧱 Lesson 6  - Redis Caching for Performance Optimization</title>
      <dc:creator>Farrukh Rehman</dc:creator>
      <pubDate>Wed, 05 Nov 2025 13:44:19 +0000</pubDate>
      <link>https://dev.to/farrukh_rehman/lesson-6-redis-caching-for-performance-optimization-5d12</link>
      <guid>https://dev.to/farrukh_rehman/lesson-6-redis-caching-for-performance-optimization-5d12</guid>
      <description>&lt;p&gt;Series: From Code to Cloud: Building a Production-Ready .NET Application&lt;br&gt;
By: Farrukh Rehman - Senior .NET Full Stack Developer / Team Lead&lt;br&gt;
LinkedIn: &lt;a href="https://linkedin.com/in/farrukh-rehman" rel="noopener noreferrer"&gt;https://linkedin.com/in/farrukh-rehman&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/farrukh1212cs" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source Code Backend : &lt;a href="https://github.com/farrukh1212cs/ECommerce-Backend.git" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs/ECommerce-Backend.git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source Code Frontend : &lt;a href="https://github.com/farrukh1212cs/ECommerce-Frontend.git" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs/ECommerce-Frontend.git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🎯 &lt;strong&gt;Introduction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In modern e-commerce applications, performance is critical — users expect fast responses, low latency, and instant data access.&lt;br&gt;
One of the most effective ways to achieve this is by introducing caching.&lt;/p&gt;

&lt;p&gt;In this lesson, we’ll integrate Redis, an in-memory data store, to cache frequently accessed data (like products, categories, and user sessions) and reduce database load.&lt;/p&gt;

&lt;p&gt;Redis helps you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Decrease response times ⚡&lt;/li&gt;
&lt;li&gt;Reduce the number of database calls 📉&lt;/li&gt;
&lt;li&gt;Handle high traffic efficiently 🚀&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end of this lesson, your e-commerce system will have a working Redis integration ready for caching services.&lt;/p&gt;

&lt;p&gt;🧩 &lt;strong&gt;Step 1: Pull Redis Docker Image&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let’s begin by setting up Redis locally using Docker.&lt;br&gt;
Run the following command in your terminal:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker pull redis:latest&lt;/code&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2F472mi1j6iqajni3tvxtf.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.amazonaws.com%2Fuploads%2Farticles%2F472mi1j6iqajni3tvxtf.png" alt=" " width="800" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the image is downloaded, start the Redis container:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker run --name ecommerce-redis -p 6379:6379 -d redis&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can verify that Redis is running using:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker ps&lt;/code&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fxke4utrm8owg90883l5s.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.amazonaws.com%2Fuploads%2Farticles%2Fxke4utrm8owg90883l5s.png" alt=" " width="800" height="140"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⚙️ &lt;strong&gt;Step 2: Install Redis Package&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In your ECommerce.Infrastructure project, install the official Redis client for .NET — StackExchange.Redis.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dotnet add ECommerce.Infrastructure package StackExchange.Redis&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;🧠 &lt;strong&gt;Step 3: Configure Redis in appsettings.json&lt;/strong&gt;&lt;br&gt;
Add your Redis connection string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; "DatabaseProvider": "PostgreSQL",
 "ConnectionStrings": {
   "PostgreSQL": "Host=localhost;Port=5432;Database=ECommerceDb;Username=postgres;Password=Admin123!",
   "MySQL": "Server=localhost;Port=3306;Database=ECommerceDb;User=root;Password=Admin123!;",
   "SqlServer": "Server=localhost,1433;Database=ECommerceDb;User Id=sa;Password=Admin123!;TrustServerCertificate=True;",
   "Redis": "localhost:6379"
 }
&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.amazonaws.com%2Fuploads%2Farticles%2F869b9j36jci3mnlxk7q8.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.amazonaws.com%2Fuploads%2Farticles%2F869b9j36jci3mnlxk7q8.png" alt=" " width="800" height="264"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🧩 &lt;strong&gt;Step 4 : Create Interface ICacheService.cs&lt;/strong&gt;&lt;br&gt;
Path : ECommerce.Application/Services/Interfaces/ICacheService.cs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace ECommerce.Application.Services.Interfaces;

public interface ICacheService
{
    Task SetAsync&amp;lt;T&amp;gt;(string key, T value, TimeSpan? expiry = null);
    Task&amp;lt;T?&amp;gt; GetAsync&amp;lt;T&amp;gt;(string key);
    Task RemoveAsync(string key);
}

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

&lt;/div&gt;



&lt;p&gt;✅ Purpose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Defines a simple contract for caching.&lt;/li&gt;
&lt;li&gt;Keeps Redis implementation hidden behind the interface.&lt;/li&gt;
&lt;li&gt;Allows you to switch to another caching provider (e.g., MemoryCache, DistributedCache) in the future.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚙️ &lt;strong&gt;Step 5 : Implement the Interface in RedisCacheService.cs&lt;/strong&gt;&lt;br&gt;
Path : ECommerce.Infrastructure/Caching/RedisCacheService.cs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using ECommerce.Application.Services.Interfaces;
using StackExchange.Redis;
using System.Text.Json;

namespace ECommerce.Infrastructure.Caching;

public class RedisCacheService : ICacheService
{
    private readonly IDatabase _database;

    public RedisCacheService(IConnectionMultiplexer redis)
    {
        _database = redis.GetDatabase();
    }

    public async Task SetAsync&amp;lt;T&amp;gt;(string key, T value, TimeSpan? expiry = null)
    {
        var json = JsonSerializer.Serialize(value);
        await _database.StringSetAsync(key, json, expiry);
    }

    public async Task&amp;lt;T?&amp;gt; GetAsync&amp;lt;T&amp;gt;(string key)
    {
        var value = await _database.StringGetAsync(key);
        return value.IsNullOrEmpty ? default : JsonSerializer.Deserialize&amp;lt;T&amp;gt;(value!);
    }

    public async Task RemoveAsync(string key)
    {
        await _database.KeyDeleteAsync(key);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ &lt;strong&gt;Step 6 : Updated DependencyInjection.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 ECommerce.Application.Services.Interfaces;
using ECommerce.Infrastructure.Caching;
using ECommerce.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using StackExchange.Redis;

namespace ECommerce.Infrastructure;

public static class DependencyInjection
{
    public static IServiceCollection AddInfrastructure(
        this IServiceCollection services,
        IConfiguration configuration)
    {
        var provider = configuration["DatabaseProvider"] ?? "MySQL";

        if (string.Equals(provider, "SqlServer", StringComparison.OrdinalIgnoreCase))
        {
            var conn = configuration.GetConnectionString("SqlServer");
            services.AddDbContext&amp;lt;AppDbContext, SqlServerDbContext&amp;gt;(options =&amp;gt;
                options.UseSqlServer(conn));
        }
        else if (string.Equals(provider, "MySQL", StringComparison.OrdinalIgnoreCase))
        {
            var conn = configuration.GetConnectionString("MySQL");
            services.AddDbContext&amp;lt;AppDbContext, MySqlDbContext&amp;gt;(options =&amp;gt;
                options.UseMySql(conn, ServerVersion.AutoDetect(conn)));
        }
        else if (string.Equals(provider, "PostgreSQL", StringComparison.OrdinalIgnoreCase))
        {

            var conn = configuration.GetConnectionString("PostgreSQL");
            services.AddDbContext&amp;lt;AppDbContext, PostgresDbContext&amp;gt;(options =&amp;gt;
                options.UseNpgsql(conn));
        }
        else
        {
            throw new InvalidOperationException($"Unsupported provider: {provider}");
        }

        // ✅ Redis cache setup
        var redisConnection = configuration.GetConnectionString("Redis");
        if (!string.IsNullOrEmpty(redisConnection))
        {
            services.AddSingleton&amp;lt;IConnectionMultiplexer&amp;gt;(sp =&amp;gt;
                ConnectionMultiplexer.Connect(redisConnection));

            services.AddSingleton&amp;lt;ICacheService, RedisCacheService&amp;gt;();
        }

        return services;
    }
}

&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.amazonaws.com%2Fuploads%2Farticles%2F67n3l8behyrj0pv8k2fy.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.amazonaws.com%2Fuploads%2Farticles%2F67n3l8behyrj0pv8k2fy.png" alt=" " width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Step 7 : Updated ProductsController with Redis Cache Support&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 ECommerce.Application.DTOs;
using ECommerce.Application.Services.Interfaces;
using Microsoft.AspNetCore.Mvc;

namespace ECommerce.API.Controllers;

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly IProductService _productService;
    private readonly ICacheService _cacheService;

    public ProductsController(IProductService productService, ICacheService cacheService)
    {
        _productService = productService;
        _cacheService = cacheService;
    }

    [HttpGet]
    public async Task&amp;lt;ActionResult&amp;lt;IEnumerable&amp;lt;ProductDto&amp;gt;&amp;gt;&amp;gt; GetAll()
    {
        const string cacheKey = "products:all";

        // Try get from cache
        var cachedProducts = await _cacheService.GetAsync&amp;lt;IEnumerable&amp;lt;ProductDto&amp;gt;&amp;gt;(cacheKey);
        if (cachedProducts != null)
        {
            return Ok(new
            {
                fromCache = true,
                data = cachedProducts
            });
        }

        // Get from DB if not cached
        var products = await _productService.GetAllAsync();

        // Cache for 60 minutes
        await _cacheService.SetAsync(cacheKey, products, TimeSpan.FromMinutes(60));

        return Ok(new
        {
            fromCache = false,
            data = products
        });
    }

    [HttpGet("{id}")]
    public async Task&amp;lt;ActionResult&amp;lt;ProductDto&amp;gt;&amp;gt; GetById(Guid id)
    {
        var cacheKey = $"product:{id}";

        // Check cache
        var cachedProduct = await _cacheService.GetAsync&amp;lt;ProductDto&amp;gt;(cacheKey);
        if (cachedProduct != null)
        {
            return Ok(new
            {
                fromCache = true,
                data = cachedProduct
            });
        }

        // Fetch from service
        var product = await _productService.GetByIdAsync(id);
        if (product == null)
            return NotFound();

        // Cache for 60 minutes
        await _cacheService.SetAsync(cacheKey, product, TimeSpan.FromMinutes(60));

        return Ok(new
        {
            fromCache = false,
            data = product
        });
    }

    [HttpPost]
    public async Task&amp;lt;ActionResult&amp;gt; Create(ProductDto dto)
    {
        await _productService.AddAsync(dto);

        // Invalidate cache
        await _cacheService.RemoveAsync("products:all");

        return CreatedAtAction(nameof(GetById), new { id = dto.Id }, dto);
    }

    [HttpPut("{id}")]
    public async Task&amp;lt;ActionResult&amp;gt; Update(Guid id, ProductDto dto)
    {
        if (id != dto.Id)
            return BadRequest("Mismatched product ID.");

        await _productService.UpdateAsync(dto);

        // Invalidate caches
        await _cacheService.RemoveAsync("products:all");
        await _cacheService.RemoveAsync($"product:{id}");

        return NoContent();
    }

    [HttpDelete("{id}")]
    public async Task&amp;lt;ActionResult&amp;gt; Delete(Guid id)
    {
        await _productService.DeleteAsync(id);

        // Invalidate caches
        await _cacheService.RemoveAsync("products:all");
        await _cacheService.RemoveAsync($"product:{id}");

        return NoContent();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ Key Notes&lt;/p&gt;

&lt;p&gt;Caching scope:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GetAll → caches list of products (products:all)&lt;/li&gt;
&lt;li&gt;GetById → caches single product (product:{id})&lt;/li&gt;
&lt;li&gt;Invalidation logic:&lt;/li&gt;
&lt;li&gt;When a product is created, updated, or deleted, relevant keys are removed.&lt;/li&gt;
&lt;li&gt;Cache TTL (Time to Live):&lt;/li&gt;
&lt;li&gt;Currently set to 60 minutes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;✅ &lt;strong&gt;Step 8 : Final Testing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test on GetAll&lt;/strong&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fcicv58ae37mlwnxr3x7i.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.amazonaws.com%2Fuploads%2Farticles%2Fcicv58ae37mlwnxr3x7i.png" alt=" " width="800" height="433"&gt;&lt;/a&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2F4nxzm8q1gy0540zk1317.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.amazonaws.com%2Fuploads%2Farticles%2F4nxzm8q1gy0540zk1317.png" alt=" " width="628" height="277"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Get By Id&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Frp6peh4v206suip26eif.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.amazonaws.com%2Fuploads%2Farticles%2Frp6peh4v206suip26eif.png" alt=" " width="800" height="326"&gt;&lt;/a&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fng4j4hq9aoznuguyf3bg.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.amazonaws.com%2Fuploads%2Farticles%2Fng4j4hq9aoznuguyf3bg.png" alt=" " width="800" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next Lecture Preview&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Lecture 7 : Message Queues with RabbitMQ&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Implementing event-driven communication, background jobs, and asynchronous processing.&lt;/p&gt;

</description>
      <category>redis</category>
      <category>cache</category>
      <category>csharp</category>
      <category>aspnetcore</category>
    </item>
    <item>
      <title>🧱 Lesson 5  - Working with PostgreSQL (Multi-Database Setup)</title>
      <dc:creator>Farrukh Rehman</dc:creator>
      <pubDate>Mon, 03 Nov 2025 04:17:22 +0000</pubDate>
      <link>https://dev.to/farrukh_rehman/lesson-5-working-with-postgresql-multi-database-setup-3ngg</link>
      <guid>https://dev.to/farrukh_rehman/lesson-5-working-with-postgresql-multi-database-setup-3ngg</guid>
      <description>&lt;p&gt;Series: From Code to Cloud: Building a Production-Ready .NET Application&lt;br&gt;
By: Farrukh Rehman - Senior .NET Full Stack Developer / Team Lead&lt;br&gt;
LinkedIn: &lt;a href="https://linkedin.com/in/farrukh-rehman" rel="noopener noreferrer"&gt;https://linkedin.com/in/farrukh-rehman&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/farrukh1212cs" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source Code Backend : &lt;a href="https://github.com/farrukh1212cs/ECommerce-Backend.git" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs/ECommerce-Backend.git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source Code Frontend : &lt;a href="https://github.com/farrukh1212cs/ECommerce-Frontend.git" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs/ECommerce-Frontend.git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;PostgreSQL is a powerful open-source database widely used in enterprise and cloud systems.&lt;br&gt;
In multi-tenant or SaaS environments, it’s common to support multiple database providers (e.g., MySQL, SQL Server, PostgreSQL) based on customer or deployment needs.&lt;/p&gt;

&lt;p&gt;Our goal today:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🐳 Pull and run PostgreSQL via Docker&lt;/li&gt;
&lt;li&gt;⚙️ Add PostgreSQL support in EF Core&lt;/li&gt;
&lt;li&gt;🏗️ Create PostgresDbContext and design-time factory&lt;/li&gt;
&lt;li&gt;🧱 Run migrations and verify setup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🐳 &lt;strong&gt;Step 1 — Pull PostgreSQL Docker Image and Run Container&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let’s start by running PostgreSQL locally inside Docker.&lt;br&gt;
&lt;code&gt;docker pull postgres&lt;/code&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fsbwn6kgnghzt2zqian8i.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.amazonaws.com%2Fuploads%2Farticles%2Fsbwn6kgnghzt2zqian8i.png" alt=" " width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run container&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;docker run -d --name postgres -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=Admin123! -p 5432:5432 postgres:latest&lt;/code&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fvgj4ca2qxyy69pgerprr.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.amazonaws.com%2Fuploads%2Farticles%2Fvgj4ca2qxyy69pgerprr.png" alt=" " width="800" height="105"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⚙️ &lt;strong&gt;Step 2 — Update appsettings.json&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add a new connection string for PostgreSQL in your ECommerce.API project.&lt;br&gt;
&lt;code&gt;"PostgreSQL":"Host=localhost;Port=5432;Database=ECommerceDb;Username=postgres;Password=Admin123!",&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Now your application can dynamically choose the provider by changing:&lt;/strong&gt;&lt;br&gt;
"DatabaseProvider": "PostgreSQL"&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fs80ojpro5388cjc1e2ez.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.amazonaws.com%2Fuploads%2Farticles%2Fs80ojpro5388cjc1e2ez.png" alt=" " width="800" height="123"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🧱 &lt;strong&gt;Step 3 — Create PostgreSQL DbContext&lt;/strong&gt;&lt;br&gt;
Path : ECommerce.Infrastructure/Data/PostgresDbContext.cs&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.EntityFrameworkCore;

namespace ECommerce.Infrastructure.Data;

public class PostgresDbContext : AppDbContext
{
    public PostgresDbContext(DbContextOptions&amp;lt;PostgresDbContext&amp;gt; options)
  : base(options)
    {
    }
}
&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.amazonaws.com%2Fuploads%2Farticles%2F5v8k1sic7pkxm1qzi48q.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.amazonaws.com%2Fuploads%2Farticles%2F5v8k1sic7pkxm1qzi48q.png" alt=" " width="800" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install Package&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dotnet add ECommerce.Infrastructure package Npgsql.EntityFrameworkCore.PostgreSQL --version 8.0.11&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;🧩 &lt;strong&gt;Step 5 — Add Design-Time Factory&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;EF Core CLI needs this factory for dotnet ef migrations when it can’t build the host automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create PostgresDbContextFactory&lt;/strong&gt;&lt;br&gt;
Path : ECommerce.Infrastructure/Data/PostgresDbContextFactory.cs&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.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;

namespace ECommerce.Infrastructure.Data;

public class PostgresDbContextFactory : IDesignTimeDbContextFactory&amp;lt;PostgresDbContext&amp;gt;
{
    public PostgresDbContext CreateDbContext(string[] args)
    {
        var basePath = Path.Combine(Directory.GetCurrentDirectory(), "../ECommerce.API");
        var configuration = new ConfigurationBuilder()
            .SetBasePath(basePath)
            .AddJsonFile("appsettings.json", optional: false)
            .AddJsonFile("appsettings.Development.json", optional: true)
            .Build();

        var connectionString = configuration.GetConnectionString("PostgreSQL");

        var optionsBuilder = new DbContextOptionsBuilder&amp;lt;PostgresDbContext&amp;gt;();
        optionsBuilder.UseNpgsql(connectionString);

        return new PostgresDbContext(optionsBuilder.Options);
    }
}

&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.amazonaws.com%2Fuploads%2Farticles%2Fjp2lqc6qa5owtivrmhqj.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.amazonaws.com%2Fuploads%2Farticles%2Fjp2lqc6qa5owtivrmhqj.png" alt=" " width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update Dependency Injection&lt;/strong&gt;&lt;br&gt;
Path : ECommerce.Infrastructure/DependencyInjection.cs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using ECommerce.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace ECommerce.Infrastructure;

public static class DependencyInjection
{
    public static IServiceCollection AddInfrastructure(
        this IServiceCollection services,
        IConfiguration configuration)
    {
        var provider = configuration["DatabaseProvider"] ?? "MySQL";

        if (string.Equals(provider, "SqlServer", StringComparison.OrdinalIgnoreCase))
        {
            var conn = configuration.GetConnectionString("SqlServer");
            services.AddDbContext&amp;lt;AppDbContext, SqlServerDbContext&amp;gt;(options =&amp;gt;
                options.UseSqlServer(conn));
        }
        else if (string.Equals(provider, "MySQL", StringComparison.OrdinalIgnoreCase))
        {
            var conn = configuration.GetConnectionString("MySQL");
            services.AddDbContext&amp;lt;AppDbContext, MySqlDbContext&amp;gt;(options =&amp;gt;
                options.UseMySql(conn, ServerVersion.AutoDetect(conn)));
        }
        else if (string.Equals(provider, "PostgreSQL", StringComparison.OrdinalIgnoreCase))
        {

            var conn = configuration.GetConnectionString("PostgreSQL");
            services.AddDbContext&amp;lt;AppDbContext, PostgresDbContext&amp;gt;(options =&amp;gt;
                options.UseNpgsql(conn));
        }
        else
        {
            throw new InvalidOperationException($"Unsupported provider: {provider}");
        }

        return services;
    }
}

&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.amazonaws.com%2Fuploads%2Farticles%2Frjqllagdljxev8taf8pl.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.amazonaws.com%2Fuploads%2Farticles%2Frjqllagdljxev8taf8pl.png" alt=" " width="800" height="604"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🧱 &lt;strong&gt;Step 6 — Create Migrations&lt;/strong&gt;&lt;br&gt;
Now you can generate a separate migration folder for PostgreSQL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet ef migrations add InitPostgres -p ECommerce.Infrastructure -s ECommerce.API --context PostgresDbContext --output-dir "Migrations/PostgreSQL"
dotnet ef database update -p ECommerce.Infrastructure -s ECommerce.API --context PostgresDbContext  
&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.amazonaws.com%2Fuploads%2Farticles%2F1sflqm62osotgtv5e9h7.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.amazonaws.com%2Fuploads%2Farticles%2F1sflqm62osotgtv5e9h7.png" alt=" " width="800" height="77"&gt;&lt;/a&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fug4a17q97umqymfahqtl.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.amazonaws.com%2Fuploads%2Farticles%2Fug4a17q97umqymfahqtl.png" alt=" " width="800" height="86"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Total Files Changed&lt;/strong&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2F3pxyr1ktvfd40vra8r9n.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.amazonaws.com%2Fuploads%2Farticles%2F3pxyr1ktvfd40vra8r9n.png" alt=" " width="800" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Let's Connect using PGAdmin&lt;/strong&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fwo037j8lqnlqc0cus0hm.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.amazonaws.com%2Fuploads%2Farticles%2Fwo037j8lqnlqc0cus0hm.png" alt=" " width="800" height="284"&gt;&lt;/a&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fzrsbkk7db4omlr0ki69t.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.amazonaws.com%2Fuploads%2Farticles%2Fzrsbkk7db4omlr0ki69t.png" alt=" " width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final Test and Verify&lt;/strong&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fankf5lbfyoa7b1g3eslm.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.amazonaws.com%2Fuploads%2Farticles%2Fankf5lbfyoa7b1g3eslm.png" alt=" " width="800" height="391"&gt;&lt;/a&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fjsj5czy2mz90pdmmnv7e.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.amazonaws.com%2Fuploads%2Farticles%2Fjsj5czy2mz90pdmmnv7e.png" alt=" " width="800" height="862"&gt;&lt;/a&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fi3t6q2o6lad5zyj68rdh.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.amazonaws.com%2Fuploads%2Farticles%2Fi3t6q2o6lad5zyj68rdh.png" alt=" " width="800" height="311"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next Lecture Preview&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Lecture 6 : Redis Caching for Performance Optimization&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Installing and configuring Redis; caching responses, queries, and improving API performance.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>postgressql</category>
      <category>csharp</category>
      <category>entityframework</category>
    </item>
    <item>
      <title>🧱 Lesson 4  - Adding SQL Server Support (Multi-Database Setup)</title>
      <dc:creator>Farrukh Rehman</dc:creator>
      <pubDate>Tue, 28 Oct 2025 17:51:54 +0000</pubDate>
      <link>https://dev.to/farrukh_rehman/lesson-4-adding-sql-server-support-multi-database-setup-3i5h</link>
      <guid>https://dev.to/farrukh_rehman/lesson-4-adding-sql-server-support-multi-database-setup-3i5h</guid>
      <description>&lt;p&gt;Series: From Code to Cloud: Building a Production-Ready .NET Application&lt;br&gt;
By: Farrukh Rehman - Senior .NET Full Stack Developer / Team Lead&lt;br&gt;
LinkedIn: &lt;a href="https://linkedin.com/in/farrukh-rehman" rel="noopener noreferrer"&gt;https://linkedin.com/in/farrukh-rehman&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/farrukh1212cs" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source Code Backend : &lt;a href="https://github.com/farrukh1212cs/ECommerce-Backend.git" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs/ECommerce-Backend.git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source Code Frontend : &lt;a href="https://github.com/farrukh1212cs/ECommerce-Frontend.git" rel="noopener noreferrer"&gt;https://github.com/farrukh1212cs/ECommerce-Frontend.git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In Lecture 3 we added MySQL (Docker + EF Core) and wired it into our Clean Architecture solution.&lt;/p&gt;

&lt;p&gt;In Lecture 4 we’ll add SQL Server as a second provider and make the app switchable at runtime by configuration. You’ll learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how to pull and run SQL Server in Docker,&lt;/li&gt;
&lt;li&gt;how to configure appsettings.json to hold both providers,&lt;/li&gt;
&lt;li&gt;how to implement a single DependencyInjection extension that picks the provider at startup, and&lt;/li&gt;
&lt;li&gt;how to run EF Core migrations for SQL Server (and switch back to MySQL later).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pull &amp;amp; run SQL Server from Docker Hub&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;docker pull mcr.microsoft.com/mssql/server:2022-latest&lt;br&gt;
&lt;/code&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fpcm940kjiukh1njkg22u.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.amazonaws.com%2Fuploads%2Farticles%2Fpcm940kjiukh1njkg22u.png" alt=" " width="800" height="223"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run the container (works in PowerShell / Bash)&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;docker run -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=Admin123!"  -p 1433:1433 --name ecommerce-sqlserver -d mcr.microsoft.com/mssql/server:2022-latest&lt;/code&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fgaazh4b13sqt2ejubcz2.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.amazonaws.com%2Fuploads%2Farticles%2Fgaazh4b13sqt2ejubcz2.png" alt=" " width="800" height="49"&gt;&lt;/a&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2F3l4ecl6s4iv2e3q99ovz.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.amazonaws.com%2Fuploads%2Farticles%2F3l4ecl6s4iv2e3q99ovz.png" alt=" " width="800" height="99"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Default admin user: sa&lt;/li&gt;
&lt;li&gt;Use a strong password (example above): Admin123!&lt;/li&gt;
&lt;li&gt;Maps container port 1433 → host 1433&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Lets Connect Server using Azure Data Studio&lt;/strong&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Ftbjkieqtgn0gj8zuutka.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.amazonaws.com%2Fuploads%2Farticles%2Ftbjkieqtgn0gj8zuutka.png" alt=" " width="623" height="982"&gt;&lt;/a&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Frpefsqazd78eks9dh4pu.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.amazonaws.com%2Fuploads%2Farticles%2Frpefsqazd78eks9dh4pu.png" alt=" " width="327" height="207"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add / update connection strings in appsettings.json&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;{
  "DatabaseProvider": "SqlServer", // "MySql" or "SqlServer"
  "ConnectionStrings": {
    "MySQL": "Server=localhost;Port=3306;Database=ECommerceDb;User=root;Password=Admin123!;",
    "SqlServer": "Server=localhost,1433;Database=ECommerceDb;User Id=sa;Password=Admin123!;TrustServerCertificate=True;"

  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}
&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.amazonaws.com%2Fuploads%2Farticles%2Fibwdczw1pda1ecm7e7h1.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.amazonaws.com%2Fuploads%2Farticles%2Fibwdczw1pda1ecm7e7h1.png" alt=" " width="800" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Now Install SQL Server Package&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;dotnet add ECommerce.Infrastructure package Microsoft.EntityFrameworkCore.SqlServer --version 8.0.21&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create New MySqlDbContext&lt;/strong&gt;&lt;br&gt;
Path : "ECommerce.Infrastructure/Data/MySqlDbContext.cs"&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.EntityFrameworkCore;

namespace ECommerce.Infrastructure.Data;

public class MySqlDbContext : AppDbContext
{
    public MySqlDbContext(DbContextOptions&amp;lt;MySqlDbContext&amp;gt; options)
        : base(options)
    {
    }
}

&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.amazonaws.com%2Fuploads%2Farticles%2F5w2c1iqqox211pqre2fv.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.amazonaws.com%2Fuploads%2Farticles%2F5w2c1iqqox211pqre2fv.png" alt=" " width="800" height="335"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create New DBContext for SQL Server&lt;/strong&gt;&lt;br&gt;
Path : ECommerce.Infrastructure/Data/SqlServerDbContext.cs&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.EntityFrameworkCore;

namespace ECommerce.Infrastructure.Data;

public class SqlServerDbContext : AppDbContext
{
    public SqlServerDbContext(DbContextOptions&amp;lt;SqlServerDbContext&amp;gt; options)
        : base(options)
    {
    }
}
&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.amazonaws.com%2Fuploads%2Farticles%2Fbrh40opsqomydg0wqe16.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.amazonaws.com%2Fuploads%2Farticles%2Fbrh40opsqomydg0wqe16.png" alt=" " width="800" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RENAME and UPDATE DesignTimeDbContextFactory&lt;/strong&gt;&lt;br&gt;
Path : ECommerce.Infrastructure/Data/DesignTimeDbContextFactory.cs&lt;br&gt;
TO ECommerce.Infrastructure/Data/MySqlDbContextFactory.cs&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.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;

namespace ECommerce.Infrastructure.Data;

public class MySqlDbContextFactory : IDesignTimeDbContextFactory&amp;lt;MySqlDbContext&amp;gt;
{
    public MySqlDbContext CreateDbContext(string[] args)
    {
        // Locate the API project's appsettings.json
        var basePath = Path.Combine(Directory.GetCurrentDirectory(), "../ECommerce.API");
        var configuration = new ConfigurationBuilder()
            .SetBasePath(basePath)
            .AddJsonFile("appsettings.json", optional: false)
            .AddJsonFile("appsettings.Development.json", optional: true)
            .Build();

        var connectionString = configuration.GetConnectionString("MySQL")
            ?? throw new InvalidOperationException("MySQL connection string not found in appsettings.json");

        var optionsBuilder = new DbContextOptionsBuilder&amp;lt;MySqlDbContext&amp;gt;();
        // Use a fixed version instead of ServerVersion.AutoDetect to avoid connection attempts during design-time
        optionsBuilder.UseMySql(
            connectionString,
            new MySqlServerVersion(new Version(8, 0, 36))
        );

        return new MySqlDbContext(optionsBuilder.Options);
    }
}

&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.amazonaws.com%2Fuploads%2Farticles%2Fmmjjs1r7zd6l5mfh72vc.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.amazonaws.com%2Fuploads%2Farticles%2Fmmjjs1r7zd6l5mfh72vc.png" alt=" " width="800" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create Data Factory for SQL Server&lt;/strong&gt;&lt;br&gt;
Path : ECommerce.Infrastructure/Data/SqlServerDbContextFactory.cs&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.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;

namespace ECommerce.Infrastructure.Data;

public class SqlServerDbContextFactory : IDesignTimeDbContextFactory&amp;lt;SqlServerDbContext&amp;gt;
{
    public SqlServerDbContext CreateDbContext(string[] args)
    {
        var basePath = Path.Combine(Directory.GetCurrentDirectory(), "../ECommerce.API");
        var configuration = new ConfigurationBuilder()
            .SetBasePath(basePath)
            .AddJsonFile("appsettings.json", optional: false)
            .AddJsonFile("appsettings.Development.json", optional: true)
            .Build();

        var connectionString = configuration.GetConnectionString("SqlServer")
            ?? throw new InvalidOperationException("SqlServer connection string not found in appsettings.json");

        var optionsBuilder = new DbContextOptionsBuilder&amp;lt;SqlServerDbContext&amp;gt;();
        optionsBuilder.UseSqlServer(connectionString);

        return new SqlServerDbContext(optionsBuilder.Options);
    }
}

&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.amazonaws.com%2Fuploads%2Farticles%2F7bvepuhn99faq1tg7mgj.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.amazonaws.com%2Fuploads%2Farticles%2F7bvepuhn99faq1tg7mgj.png" alt=" " width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Implement multi-provider DependencyInjection (Infrastructure)&lt;/strong&gt;&lt;br&gt;
Create ECommerce.Infrastructure/DependencyInjection.cs. This extension reads DatabaseProvider and registers ApplicationDbContext (or ECommerceDbContext) with the appropriate provider.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using ECommerce.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace ECommerce.Infrastructure;

public static class DependencyInjection
{
    public static IServiceCollection AddInfrastructure(
        this IServiceCollection services,
        IConfiguration configuration)
    {
        var provider = configuration["DatabaseProvider"] ?? "MySQL";

        if (string.Equals(provider, "SqlServer", StringComparison.OrdinalIgnoreCase))
        {
            var conn = configuration.GetConnectionString("SqlServer");
            services.AddDbContext&amp;lt;AppDbContext, SqlServerDbContext&amp;gt;(options =&amp;gt;
                options.UseSqlServer(conn));
        }
        else if (string.Equals(provider, "MySQL", StringComparison.OrdinalIgnoreCase))
        {
            var conn = configuration.GetConnectionString("MySQL");
            services.AddDbContext&amp;lt;AppDbContext, MySqlDbContext&amp;gt;(options =&amp;gt;
                options.UseMySql(conn, ServerVersion.AutoDetect(conn)));
        }
        else
        {
            throw new InvalidOperationException($"Unsupported provider: {provider}");
        }

        return services;
    }
}
&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.amazonaws.com%2Fuploads%2Farticles%2Fox2gqq212gvfofsjy3pd.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.amazonaws.com%2Fuploads%2Farticles%2Fox2gqq212gvfofsjy3pd.png" alt=" " width="800" height="548"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update Program file&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 ECommerce.Application.Services.Implementations;
using ECommerce.Application.Services.Interfaces;
using ECommerce.Domain.Repositories;
using ECommerce.Infrastructure.Data;
using ECommerce.Infrastructure.Repositories;
using Microsoft.EntityFrameworkCore;
using ECommerce.Infrastructure;

var builder = WebApplication.CreateBuilder(args);

// ------------------------------------------------------
// Add Controllers
// ------------------------------------------------------
builder.Services.AddControllers();

// add infrastructure (DbContext + provider selection)
builder.Services.AddInfrastructure(builder.Configuration);

// ------------------------------------------------------
//  Repository Registrations
// ------------------------------------------------------
builder.Services.AddScoped&amp;lt;IProductRepository, ProductRepository&amp;gt;();
builder.Services.AddScoped&amp;lt;ICustomerRepository, CustomerRepository&amp;gt;();
builder.Services.AddScoped&amp;lt;IOrderRepository, OrderRepository&amp;gt;();
builder.Services.AddScoped&amp;lt;IOrderItemRepository, OrderItemRepository&amp;gt;();


// ------------------------------------------------------
//  Service Registrations (Application Layer)
// ------------------------------------------------------
builder.Services.AddScoped&amp;lt;IProductService, ProductService&amp;gt;();
builder.Services.AddScoped&amp;lt;ICustomerService, CustomerService&amp;gt;();
builder.Services.AddScoped&amp;lt;IOrderService, OrderService&amp;gt;();
builder.Services.AddScoped&amp;lt;IOrderItemService, OrderItemService&amp;gt;();


// ------------------------------------------------------
//  Swagger
// ------------------------------------------------------
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();


var app = builder.Build();

// ------------------------------------------------------
// Middleware Pipeline
// ------------------------------------------------------
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

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

&lt;/div&gt;



&lt;p&gt;*&lt;em&gt;Now Move Old (MYSQL) migrations to MySQL folder *&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir Migrations\MySQL
move Migrations\*.cs Migrations\MySQL\
&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.amazonaws.com%2Fuploads%2Farticles%2Frwq6lfqrkv46zailvx62.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.amazonaws.com%2Fuploads%2Farticles%2Frwq6lfqrkv46zailvx62.png" alt=" " width="800" height="118"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lets Create Migrations for SQL Server&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dotnet ef migrations add InitSqlServer -p ECommerce.Infrastructure -s ECommerce.API --context SqlServerDbContext --output-dir "Migrations/SqlServer"&lt;/code&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fyy15qd6twldzsjffodbq.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.amazonaws.com%2Fuploads%2Farticles%2Fyy15qd6twldzsjffodbq.png" alt=" " width="800" height="159"&gt;&lt;/a&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fshpg2nonr4elst5h7iha.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.amazonaws.com%2Fuploads%2Farticles%2Fshpg2nonr4elst5h7iha.png" alt=" " width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lets Test with SQL Server using swagger&lt;/strong&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fyc40ugg0zgn16hyy3fkc.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.amazonaws.com%2Fuploads%2Farticles%2Fyc40ugg0zgn16hyy3fkc.png" alt=" " width="800" height="391"&gt;&lt;/a&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fq4u6oqmtfe95s54tv6l2.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.amazonaws.com%2Fuploads%2Farticles%2Fq4u6oqmtfe95s54tv6l2.png" alt=" " width="800" height="181"&gt;&lt;/a&gt;&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fwoq3brjvx55oy9sef8a3.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.amazonaws.com%2Fuploads%2Farticles%2Fwoq3brjvx55oy9sef8a3.png" alt=" " width="800" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next Lecture Preview&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Lecture 5 : Working with PostgreSQL&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Configuring Entity Framework Core for multiple providers and supporting PostgresSQL alongside MySQL and SQL Server&lt;/p&gt;

</description>
      <category>database</category>
      <category>sqlserver</category>
      <category>docker</category>
      <category>dotnet</category>
    </item>
  </channel>
</rss>
