ASP.NET Core  

Full-Stack ASP.NET Core Project: Build AI E-Commerce Platform with Cloud, Microservices & Blazor(Part-40 of 40)

Full-Stack

Previous article: ASP.NET Core Microservices gRPC Message Brokers Architecture Guide (Part- 39 of 40)

Table of Contents

  1. Project Overview & Architecture

  2. Solution Structure & Setup

  3. Domain Layer & Core Models

  4. Infrastructure & Data Layer

  5. Application Layer & CQRS

  6. API Layer & Controllers

  7. AI Integration Services

  8. Blazor Frontend

  9. Real-time Features

  10. Testing Strategy

  11. Deployment & DevOps

  12. Production Readiness

1. Project Overview & Architecture

1.1 Project Vision: SmartCommerce AI

Business Problem: Traditional e-commerce platforms struggle with personalization, real-time inventory, and intelligent customer engagement. Our solution addresses these challenges with AI-powered features.

Key Features

  • AI-powered product recommendations

  • Real-time inventory management

  • Intelligent search with semantic understanding

  • Personalized pricing and promotions

  • Multi-vendor marketplace support

  • Advanced analytics and insights

1.2 System Architecture

1.3 Technology Stack

  • Backend: ASP.NET Core 8, Entity Framework Core, Clean Architecture

  • Frontend: Blazor WebAssembly, Blazor Server, MudBlazor

  • AI/ML: Azure Cognitive Services, ML.NET , OpenAI

  • Cloud: Azure/AWS, Docker, Kubernetes

  • Database: Azure SQL, Cosmos DB, Redis

  • Messaging: Azure Service Bus, SignalR

  • Monitoring: Application Insights, Serilog

2. Solution Structure & Setup

2.1 Solution Architecture

  
    SmartCommerce/
├── src/
│   ├── SmartCommerce.Web/                 # Blazor WebAssembly Frontend
│   ├── SmartCommerce.Admin/               # Blazor Server Admin Panel
│   ├── SmartCommerce.Gateway/             # API Gateway
│   ├── SmartCommerce.Services.Product/    # Product Microservice
│   ├── SmartCommerce.Services.Order/      # Order Microservice
│   ├── SmartCommerce.Services.User/       # User Microservice
│   ├── SmartCommerce.Services.Recommendation/ # AI Recommendation Service
│   ├── SmartCommerce.Services.Search/     # AI Search Service
│   ├── SmartCommerce.Shared/              # Shared Models & Contracts
│   └── SmartCommerce.Infrastructure/      # Cross-cutting Infrastructure
├── tests/
│   ├── SmartCommerce.UnitTests/
│   ├── SmartCommerce.IntegrationTests/
│   └── SmartCommerce.LoadTests/
└── deployments/
    ├── docker-compose.yml
    ├── kubernetes/
    └── azure-pipelines.yml
  

2.2 Project Setup & Configuration

xml

  
    <!-- Directory.Build.props -->
<Project>
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    <AnalysisLevel>latest</AnalysisLevel>
  </PropertyGroup>
  
  <PropertyGroup Condition="'$(Configuration)' == 'Debug'">
    <DebugType>embedded</DebugType>
  </PropertyGroup>
  
  <PropertyGroup Condition="'$(Configuration)' == 'Release'">
    <DebugType>none</DebugType>
    <PublishReadyToRun>true</PublishReadyToRun>
    <PublishTrimmed>true</PublishTrimmed>
  </PropertyGroup>
</Project>
  

csharp

  
    // Program.cs - Main Web Application
using SmartCommerce.Infrastructure;
using SmartCommerce.Web.Middleware;

var builder = WebApplication.CreateBuilder(args);

// Add configuration sources
builder.Configuration
    .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
    .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true)
    .AddEnvironmentVariables()
    .AddUserSecrets<Program>()
    .AddAzureKeyVault(ConfigurationHelpers.GetKeyVaultEndpoint(builder.Configuration));

// Configure infrastructure
builder.Services.AddInfrastructure(builder.Configuration);
builder.Services.AddApplicationServices();
builder.Services.AddWebServices(builder.Configuration);

// Configure authentication and authorization
builder.Services.AddAuthenticationServices(builder.Configuration);
builder.Services.AddAuthorizationPolicies();

// Add health checks
builder.Services.AddCustomHealthChecks(builder.Configuration);

// Configure HTTP client factory with resilience
builder.Services.AddHttpClientWithResilience(builder.Configuration);

var app = builder.Build();

// Configure middleware pipeline
if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
    app.UseWebAssemblyDebugging();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

// Custom middleware
app.UseRequestLogging();
app.UseSecurityHeaders();
app.UsePerformanceMonitoring();

// Health check endpoint
app.MapHealthChecks("/health", new HealthCheckOptions
{
    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});

// API endpoints
app.MapControllers();
app.MapRazorPages();
app.MapFallbackToFile("index.html");

// Application startup tasks
await app.RunStartupTasksAsync();

app.Run();
  

2.3 Infrastructure Configuration

  
    // Infrastructure/ServiceCollectionExtensions.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Options;
using SmartCommerce.Infrastructure.Caching;
using SmartCommerce.Infrastructure.Data;
using SmartCommerce.Infrastructure.Logging;

namespace SmartCommerce.Infrastructure
{
    public static class ServiceCollectionExtensions
    {
        public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
        {
            // Database Contexts
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    configuration.GetConnectionString("DefaultConnection"),
                    sqlOptions =>
                    {
                        sqlOptions.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName);
                        sqlOptions.EnableRetryOnFailure(
                            maxRetryCount: 5,
                            maxRetryDelay: TimeSpan.FromSeconds(30),
                            errorNumbersToAdd: null);
                    }));

            // Caching
            services.AddStackExchangeRedisCache(options =>
            {
                options.Configuration = configuration.GetConnectionString("Redis");
                options.InstanceName = "SmartCommerce_";
            });

            services.AddSingleton<IDistributedCache, RedisCache>();
            services.AddSingleton<ICacheService, DistributedCacheService>();

            // Message Bus
            services.AddAzureServiceBus(configuration);

            // File Storage
            services.AddAzureBlobStorage(configuration);

            // Email Services
            services.AddEmailServices(configuration);

            // Background Services
            services.AddHostedService<OrderProcessingService>();
            services.AddHostedService<InventorySyncService>();
            services.AddHostedService<AIModelTrainingService>();

            // External Services
            services.AddPaymentServices(configuration);
            services.AddShippingServices(configuration);
            services.AddAIServices(configuration);

            return services;
        }

        public static IServiceCollection AddAIServices(this IServiceCollection services, IConfiguration configuration)
        {
            // Azure Cognitive Services
            services.AddAzureCognitiveServices(configuration);

            // ML.NET Models
            services.AddMLServices(configuration);

            // Custom AI Services
            services.AddScoped<IProductRecommender, AIProductRecommender>();
            services.AddScoped<ISearchEnhancer, CognitiveSearchEnhancer>();
            services.AddScoped<IPriceOptimizer, MLPriceOptimizer>();
            services.AddScoped<ISentimentAnalyzer, AzureSentimentAnalyzer>();

            return services;
        }

        public static IServiceCollection AddApplicationServices(this IServiceCollection services)
        {
            // MediatR for CQRS
            services.AddMediatR(cfg => 
                cfg.RegisterServicesFromAssembly(typeof(ApplicationLayerEntryPoint).Assembly));

            // AutoMapper
            services.AddAutoMapper(typeof(ApplicationLayerEntryPoint).Assembly);

            // FluentValidation
            services.AddValidatorsFromAssembly(typeof(ApplicationLayerEntryPoint).Assembly);

            // Domain Services
            services.AddScoped<IOrderService, OrderService>();
            services.AddScoped<IProductService, ProductService>();
            services.AddScoped<IUserService, UserService>();
            services.AddScoped<IInventoryService, InventoryService>();

            return services;
        }
    }
}
  

3. Domain Layer & Core Models

3.1 Domain-Driven Design Implementation

  
    // Domain/Common/ValueObjects.cs
using System.Diagnostics;

namespace SmartCommerce.Domain.Common
{
    public abstract class ValueObject : IEquatable<ValueObject>
    {
        protected abstract IEnumerable<object> GetEqualityComponents();

        public override bool Equals(object? obj)
        {
            if (obj is null || obj.GetType() != GetType())
                return false;

            var valueObject = (ValueObject)obj;
            return GetEqualityComponents().SequenceEqual(valueObject.GetEqualityComponents());
        }

        public override int GetHashCode()
        {
            return GetEqualityComponents()
                .Select(x => x?.GetHashCode() ?? 0)
                .Aggregate((x, y) => x ^ y);
        }

        public bool Equals(ValueObject? other) => Equals((object?)other);

        public static bool operator ==(ValueObject left, ValueObject right) => Equals(left, right);
        public static bool operator !=(ValueObject left, ValueObject right) => !Equals(left, right);
    }

    public class Money : ValueObject
    {
        public decimal Amount { get; }
        public string Currency { get; }

        public Money(decimal amount, string currency = "USD")
        {
            if (amount < 0)
                throw new ArgumentException("Money amount cannot be negative");

            if (string.IsNullOrWhiteSpace(currency))
                throw new ArgumentException("Currency cannot be empty");

            Amount = amount;
            Currency = currency.ToUpperInvariant();
        }

        public static Money operator +(Money left, Money right)
        {
            ValidateSameCurrency(left, right);
            return new Money(left.Amount + right.Amount, left.Currency);
        }

        public static Money operator -(Money left, Money right)
        {
            ValidateSameCurrency(left, right);
            return new Money(left.Amount - right.Amount, left.Currency);
        }

        private static void ValidateSameCurrency(Money left, Money right)
        {
            if (left.Currency != right.Currency)
                throw new InvalidOperationException("Cannot perform operations on different currencies");
        }

        protected override IEnumerable<object> GetEqualityComponents()
        {
            yield return Amount;
            yield return Currency;
        }

        public override string ToString() => $"{Amount:C} ({Currency})";
    }

    public class Address : ValueObject
    {
        public string Street { get; }
        public string City { get; }
        public string State { get; }
        public string Country { get; }
        public string ZipCode { get; }

        public Address(string street, string city, string state, string country, string zipCode)
        {
            Street = street ?? throw new ArgumentNullException(nameof(street));
            City = city ?? throw new ArgumentNullException(nameof(city));
            State = state ?? throw new ArgumentNullException(nameof(state));
            Country = country ?? throw new ArgumentNullException(nameof(country));
            ZipCode = zipCode ?? throw new ArgumentNullException(nameof(zipCode));
        }

        protected override IEnumerable<object> GetEqualityComponents()
        {
            yield return Street;
            yield return City;
            yield return State;
            yield return Country;
            yield return ZipCode;
        }
    }
}
  

3.2 Core Domain Entities

  
    // Domain/Entities/Product.cs
using SmartCommerce.Domain.Common;
using SmartCommerce.Domain.Events;

namespace SmartCommerce.Domain.Entities
{
    public class Product : AuditableEntity, IAggregateRoot
    {
        public Guid Id { get; private set; }
        public string Name { get; private set; }
        public string Description { get; private set; }
        public Money Price { get; private set; }
        public string Sku { get; private set; }
        public int StockQuantity { get; private set; }
        public int ReorderLevel { get; private set; }
        public bool IsActive { get; private set; }
        public bool IsDeleted { get; private set; }
        public Guid CategoryId { get; private set; }
        public Category Category { get; private set; }
        public Guid? VendorId { get; private set; }
        public Vendor? Vendor { get; private set; }

        // AI-Enhanced Properties
        public float AIScore { get; private set; }
        public string? AIKeywords { get; private set; }
        public DateTime? LastAIAnalysis { get; private set; }

        // Navigation Properties
        private readonly List<ProductImage> _images = new();
        public IReadOnlyCollection<ProductImage> Images => _images.AsReadOnly();

        private readonly List<ProductReview> _reviews = new();
        public IReadOnlyCollection<ProductReview> Reviews => _reviews.AsReadOnly();

        private readonly List<ProductTag> _tags = new();
        public IReadOnlyCollection<ProductTag> Tags => _tags.AsReadOnly();

        // Private constructor for EF Core
        private Product() { }

        public Product(string name, string description, Money price, string sku, 
                      Guid categoryId, Guid? vendorId = null, int stockQuantity = 0)
        {
            Id = Guid.NewGuid();
            Name = name ?? throw new ArgumentNullException(nameof(name));
            Description = description ?? throw new ArgumentNullException(nameof(description));
            Price = price ?? throw new ArgumentNullException(nameof(price));
            Sku = sku ?? throw new ArgumentNullException(nameof(sku));
            CategoryId = categoryId;
            VendorId = vendorId;
            StockQuantity = stockQuantity;
            IsActive = true;
            IsDeleted = false;
            ReorderLevel = 10; // Default reorder level

            AddDomainEvent(new ProductCreatedEvent(Id, name, sku));
        }

        public void UpdateDetails(string name, string description, Money price)
        {
            Name = name ?? throw new ArgumentNullException(nameof(name));
            Description = description ?? throw new ArgumentNullException(nameof(description));
            Price = price ?? throw new ArgumentNullException(nameof(price));

            AddDomainEvent(new ProductUpdatedEvent(Id));
        }

        public void UpdateStock(int quantity)
        {
            if (quantity < 0)
                throw new ArgumentException("Stock quantity cannot be negative");

            var oldStock = StockQuantity;
            StockQuantity = quantity;

            AddDomainEvent(new ProductStockUpdatedEvent(Id, oldStock, quantity));

            // Check if we need to reorder
            if (quantity <= ReorderLevel)
            {
                AddDomainEvent(new LowStockEvent(Id, Name, quantity, ReorderLevel));
            }
        }

        public void AddStock(int quantity)
        {
            if (quantity <= 0)
                throw new ArgumentException("Quantity must be positive");

            UpdateStock(StockQuantity + quantity);
        }

        public void RemoveStock(int quantity)
        {
            if (quantity <= 0)
                throw new ArgumentException("Quantity must be positive");

            if (quantity > StockQuantity)
                throw new InvalidOperationException("Insufficient stock");

            UpdateStock(StockQuantity - quantity);
        }

        public void Deactivate()
        {
            if (!IsActive)
                return;

            IsActive = false;
            AddDomainEvent(new ProductDeactivatedEvent(Id));
        }

        public void Activate()
        {
            if (IsActive)
                return;

            IsActive = true;
            AddDomainEvent(new ProductActivatedEvent(Id));
        }

        public void MarkAsDeleted()
        {
            if (IsDeleted)
                return;

            IsDeleted = true;
            IsActive = false;
            AddDomainEvent(new ProductDeletedEvent(Id));
        }

        public void UpdateAIScore(float score, string keywords)
        {
            AIScore = Math.Clamp(score, 0, 1);
            AIKeywords = keywords;
            LastAIAnalysis = DateTime.UtcNow;

            AddDomainEvent(new ProductAIAnalyzedEvent(Id, score, keywords));
        }

        public void AddImage(string imageUrl, string altText, bool isPrimary = false)
        {
            var image = new ProductImage(Id, imageUrl, altText, isPrimary);
            _images.Add(image);

            // If this is set as primary, ensure no other images are primary
            if (isPrimary)
            {
                foreach (var img in _images.Where(i => i.Id != image.Id && i.IsPrimary))
                {
                    img.SetAsSecondary();
                }
            }
        }

        public void AddReview(Guid userId, int rating, string comment, string? title = null)
        {
            var review = new ProductReview(Id, userId, rating, comment, title);
            _reviews.Add(review);

            AddDomainEvent(new ProductReviewAddedEvent(Id, userId, rating));
        }

        public void AddTag(string tagName)
        {
            if (_tags.Any(t => t.Name.Equals(tagName, StringComparison.OrdinalIgnoreCase)))
                return;

            var tag = new ProductTag(Id, tagName);
            _tags.Add(tag);
        }

        public decimal CalculateDiscountedPrice(decimal discountPercentage)
        {
            if (discountPercentage < 0 || discountPercentage > 100)
                throw new ArgumentException("Discount percentage must be between 0 and 100");

            return Price.Amount * (1 - discountPercentage / 100);
        }

        public bool IsInStock() => StockQuantity > 0;
        public bool NeedsReorder() => StockQuantity <= ReorderLevel;
        public int AvailableStock() => Math.Max(0, StockQuantity);
    }

    public class ProductImage : Entity
    {
        public Guid Id { get; private set; }
        public Guid ProductId { get; private set; }
        public string ImageUrl { get; private set; }
        public string AltText { get; private set; }
        public bool IsPrimary { get; private set; }
        public int DisplayOrder { get; private set; }
        public DateTime CreatedAt { get; private set; }

        public Product Product { get; private set; }

        private ProductImage() { }

        public ProductImage(Guid productId, string imageUrl, string altText, bool isPrimary = false, int displayOrder = 0)
        {
            Id = Guid.NewGuid();
            ProductId = productId;
            ImageUrl = imageUrl ?? throw new ArgumentNullException(nameof(imageUrl));
            AltText = altText ?? throw new ArgumentNullException(nameof(altText));
            IsPrimary = isPrimary;
            DisplayOrder = displayOrder;
            CreatedAt = DateTime.UtcNow;
        }

        public void SetAsPrimary()
        {
            IsPrimary = true;
        }

        public void SetAsSecondary()
        {
            IsPrimary = false;
        }

        public void UpdateDisplayOrder(int order)
        {
            DisplayOrder = order;
        }
    }
}
  

3.3 Order Management Domain

  
    // Domain/Entities/Order.cs
using SmartCommerce.Domain.Enums;
using SmartCommerce.Domain.Events;

namespace SmartCommerce.Domain.Entities
{
    public class Order : AuditableEntity, IAggregateRoot
    {
        public Guid Id { get; private set; }
        public string OrderNumber { get; private set; }
        public Guid CustomerId { get; private set; }
        public OrderStatus Status { get; private set; }
        public Money TotalAmount { get; private set; }
        public Money DiscountAmount { get; private set; }
        public Money TaxAmount { get; private set; }
        public Money ShippingAmount { get; private set; }
        public Money FinalAmount { get; private set; }
        public string Currency { get; private set; }

        // Shipping Information
        public Address ShippingAddress { get; private set; }
        public Address? BillingAddress { get; private set; }

        // Payment Information
        public string? PaymentMethod { get; private set; }
        public string? PaymentTransactionId { get; private set; }
        public DateTime? PaymentDate { get; private set; }

        // Shipping Information
        public string? ShippingMethod { get; private set; }
        public string? TrackingNumber { get; private set; }
        public DateTime? ShippedDate { get; private set; }
        public DateTime? DeliveredDate { get; private set; }

        // Navigation Properties
        private readonly List<OrderItem> _orderItems = new();
        public IReadOnlyCollection<OrderItem> OrderItems => _orderItems.AsReadOnly();

        private readonly List<OrderStatusHistory> _statusHistory = new();
        public IReadOnlyCollection<OrderStatusHistory> StatusHistory => _statusHistory.AsReadOnly();

        // Private constructor for EF Core
        private Order() { }

        public Order(Guid customerId, Address shippingAddress, Address? billingAddress = null, string currency = "USD")
        {
            Id = Guid.NewGuid();
            OrderNumber = GenerateOrderNumber();
            CustomerId = customerId;
            Status = OrderStatus.Pending;
            Currency = currency;
            ShippingAddress = shippingAddress ?? throw new ArgumentNullException(nameof(shippingAddress));
            BillingAddress = billingAddress;
            
            // Initialize amounts
            TotalAmount = new Money(0, currency);
            DiscountAmount = new Money(0, currency);
            TaxAmount = new Money(0, currency);
            ShippingAmount = new Money(0, currency);
            FinalAmount = new Money(0, currency);

            AddStatusHistory(OrderStatus.Pending, "Order created");
            AddDomainEvent(new OrderCreatedEvent(Id, OrderNumber, customerId));
        }

        public void AddItem(Product product, int quantity, Money unitPrice)
        {
            if (product == null)
                throw new ArgumentNullException(nameof(product));

            if (quantity <= 0)
                throw new ArgumentException("Quantity must be positive");

            if (unitPrice.Amount <= 0)
                throw new ArgumentException("Unit price must be positive");

            // Check if item already exists
            var existingItem = _orderItems.FirstOrDefault(item => item.ProductId == product.Id);
            if (existingItem != null)
            {
                existingItem.UpdateQuantity(existingItem.Quantity + quantity);
            }
            else
            {
                var orderItem = new OrderItem(Id, product.Id, product.Name, quantity, unitPrice);
                _orderItems.Add(orderItem);
            }

            RecalculateTotals();
            AddDomainEvent(new OrderItemAddedEvent(Id, product.Id, quantity));
        }

        public void RemoveItem(Guid productId)
        {
            var item = _orderItems.FirstOrDefault(i => i.ProductId == productId);
            if (item != null)
            {
                _orderItems.Remove(item);
                RecalculateTotals();
                AddDomainEvent(new OrderItemRemovedEvent(Id, productId));
            }
        }

        public void UpdateItemQuantity(Guid productId, int quantity)
        {
            if (quantity <= 0)
            {
                RemoveItem(productId);
                return;
            }

            var item = _orderItems.FirstOrDefault(i => i.ProductId == productId);
            if (item != null)
            {
                item.UpdateQuantity(quantity);
                RecalculateTotals();
                AddDomainEvent(new OrderItemQuantityUpdatedEvent(Id, productId, quantity));
            }
        }

        public void ApplyDiscount(Money discount)
        {
            if (discount.Amount < 0)
                throw new ArgumentException("Discount cannot be negative");

            if (discount.Amount > TotalAmount.Amount)
                throw new ArgumentException("Discount cannot exceed order total");

            DiscountAmount = discount;
            RecalculateTotals();
            AddDomainEvent(new OrderDiscountAppliedEvent(Id, discount.Amount));
        }

        public void SetShippingAddress(Address address)
        {
            ShippingAddress = address ?? throw new ArgumentNullException(nameof(address));
            AddDomainEvent(new OrderShippingAddressUpdatedEvent(Id));
        }

        public void SetBillingAddress(Address address)
        {
            BillingAddress = address ?? throw new ArgumentNullException(nameof(address));
            AddDomainEvent(new OrderBillingAddressUpdatedEvent(Id));
        }

        public void ProcessPayment(string paymentMethod, string transactionId)
        {
            if (Status != OrderStatus.Pending)
                throw new InvalidOperationException("Order is not in pending status");

            PaymentMethod = paymentMethod ?? throw new ArgumentNullException(nameof(paymentMethod));
            PaymentTransactionId = transactionId ?? throw new ArgumentNullException(nameof(transactionId));
            PaymentDate = DateTime.UtcNow;

            UpdateStatus(OrderStatus.Processing, "Payment processed successfully");
            AddDomainEvent(new OrderPaymentProcessedEvent(Id, paymentMethod, transactionId));
        }

        public void MarkAsShipped(string shippingMethod, string trackingNumber)
        {
            if (Status != OrderStatus.Processing)
                throw new InvalidOperationException("Order must be in processing status to ship");

            ShippingMethod = shippingMethod ?? throw new ArgumentNullException(nameof(shippingMethod));
            TrackingNumber = trackingNumber ?? throw new ArgumentNullException(nameof(trackingNumber));
            ShippedDate = DateTime.UtcNow;

            UpdateStatus(OrderStatus.Shipped, $"Order shipped via {shippingMethod}");
            AddDomainEvent(new OrderShippedEvent(Id, shippingMethod, trackingNumber));
        }

        public void MarkAsDelivered()
        {
            if (Status != OrderStatus.Shipped)
                throw new InvalidOperationException("Order must be shipped before delivery");

            DeliveredDate = DateTime.UtcNow;
            UpdateStatus(OrderStatus.Delivered, "Order delivered successfully");
            AddDomainEvent(new OrderDeliveredEvent(Id));
        }

        public void Cancel(string reason)
        {
            if (Status == OrderStatus.Cancelled)
                return;

            if (Status == OrderStatus.Delivered)
                throw new InvalidOperationException("Cannot cancel a delivered order");

            UpdateStatus(OrderStatus.Cancelled, reason ?? "Order cancelled");
            AddDomainEvent(new OrderCancelledEvent(Id, reason));
        }

        private void UpdateStatus(OrderStatus newStatus, string note)
        {
            var oldStatus = Status;
            Status = newStatus;

            AddStatusHistory(newStatus, note);
            AddDomainEvent(new OrderStatusChangedEvent(Id, oldStatus, newStatus, note));
        }

        private void AddStatusHistory(OrderStatus status, string note)
        {
            var history = new OrderStatusHistory(Id, status, note);
            _statusHistory.Add(history);
        }

        private void RecalculateTotals()
        {
            var total = _orderItems.Sum(item => item.LineTotal.Amount);
            TotalAmount = new Money(total, Currency);

            // Calculate tax (simplified - in real app, use tax service)
            TaxAmount = new Money(total * 0.1m, Currency); // 10% tax

            // Calculate final amount
            var final = TotalAmount.Amount + TaxAmount.Amount + ShippingAmount.Amount - DiscountAmount.Amount;
            FinalAmount = new Money(Math.Max(0, final), Currency);
        }

        private static string GenerateOrderNumber()
        {
            return $"ORD-{DateTime.UtcNow:yyyyMMdd}-{Guid.NewGuid().ToString("N")[..8].ToUpper()}";
        }

        public bool CanBeCancelled() => 
            Status == OrderStatus.Pending || Status == OrderStatus.Processing;

        public decimal CalculateTax() => TaxAmount.Amount;
        public decimal CalculateTotalWithoutTax() => TotalAmount.Amount - TaxAmount.Amount;
    }

    public class OrderItem : Entity
    {
        public Guid Id { get; private set; }
        public Guid OrderId { get; private set; }
        public Guid ProductId { get; private set; }
        public string ProductName { get; private set; }
        public int Quantity { get; private set; }
        public Money UnitPrice { get; private set; }
        public Money LineTotal { get; private set; }

        public Order Order { get; private set; }
        public Product Product { get; private set; }

        private OrderItem() { }

        public OrderItem(Guid orderId, Guid productId, string productName, int quantity, Money unitPrice)
        {
            Id = Guid.NewGuid();
            OrderId = orderId;
            ProductId = productId;
            ProductName = productName ?? throw new ArgumentNullException(nameof(productName));
            Quantity = quantity;
            UnitPrice = unitPrice;
            LineTotal = new Money(unitPrice.Amount * quantity, unitPrice.Currency);
        }

        public void UpdateQuantity(int quantity)
        {
            if (quantity <= 0)
                throw new ArgumentException("Quantity must be positive");

            Quantity = quantity;
            LineTotal = new Money(UnitPrice.Amount * quantity, UnitPrice.Currency);
        }

        public void UpdateUnitPrice(Money unitPrice)
        {
            UnitPrice = unitPrice ?? throw new ArgumentNullException(nameof(unitPrice));
            LineTotal = new Money(UnitPrice.Amount * Quantity, UnitPrice.Currency);
        }
    }
}
  

4. Infrastructure & Data Layer

4.1 Entity Framework Configuration

  
    // Infrastructure/Data/Configurations/ProductConfiguration.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using SmartCommerce.Domain.Entities;

namespace SmartCommerce.Infrastructure.Data.Configurations
{
    public class ProductConfiguration : IEntityTypeConfiguration<Product>
    {
        public void Configure(EntityTypeBuilder<Product> builder)
        {
            builder.ToTable("Products");

            builder.HasKey(p => p.Id);

            builder.Property(p => p.Id)
                .ValueGeneratedNever()
                .IsRequired();

            builder.Property(p => p.Name)
                .HasMaxLength(200)
                .IsRequired();

            builder.Property(p => p.Description)
                .HasMaxLength(2000)
                .IsRequired();

            builder.Property(p => p.Sku)
                .HasMaxLength(100)
                .IsRequired();

            builder.Property(p => p.StockQuantity)
                .IsRequired();

            builder.Property(p => p.ReorderLevel)
                .IsRequired();

            builder.Property(p => p.IsActive)
                .IsRequired();

            builder.Property(p => p.IsDeleted)
                .IsRequired();

            builder.Property(p => p.AIScore)
                .HasColumnType("decimal(3,2)");

            builder.Property(p => p.AIKeywords)
                .HasMaxLength(500);

            // Value Objects as owned types
            builder.OwnsOne(p => p.Price, money =>
            {
                money.Property(m => m.Amount)
                    .HasColumnType("decimal(18,2)")
                    .HasColumnName("PriceAmount");

                money.Property(m => m.Currency)
                    .HasMaxLength(3)
                    .HasColumnName("PriceCurrency");
            });

            // Indexes
            builder.HasIndex(p => p.Sku)
                .IsUnique()
                .HasDatabaseName("IX_Products_Sku");

            builder.HasIndex(p => p.Name)
                .HasDatabaseName("IX_Products_Name");

            builder.HasIndex(p => p.CategoryId)
                .HasDatabaseName("IX_Products_CategoryId");

            builder.HasIndex(p => p.IsActive)
                .HasDatabaseName("IX_Products_IsActive");

            builder.HasIndex(p => new { p.IsActive, p.IsDeleted })
                .HasDatabaseName("IX_Products_ActiveNotDeleted");

            // Query filter for soft delete
            builder.HasQueryFilter(p => !p.IsDeleted);

            // Relationships
            builder.HasOne(p => p.Category)
                .WithMany(c => c.Products)
                .HasForeignKey(p => p.CategoryId)
                .OnDelete(DeleteBehavior.Restrict);

            builder.HasOne(p => p.Vendor)
                .WithMany(v => v.Products)
                .HasForeignKey(p => p.VendorId)
                .OnDelete(DeleteBehavior.SetNull);

            builder.HasMany(p => p.Images)
                .WithOne(pi => pi.Product)
                .HasForeignKey(pi => pi.ProductId)
                .OnDelete(DeleteBehavior.Cascade);

            builder.HasMany(p => p.Reviews)
                .WithOne(pr => pr.Product)
                .HasForeignKey(pr => pr.ProductId)
                .OnDelete(DeleteBehavior.Cascade);

            builder.HasMany(p => p.Tags)
                .WithOne(pt => pt.Product)
                .HasForeignKey(pt => pt.ProductId)
                .OnDelete(DeleteBehavior.Cascade);
        }
    }

    public class OrderConfiguration : IEntityTypeConfiguration<Order>
    {
        public void Configure(EntityTypeBuilder<Order> builder)
        {
            builder.ToTable("Orders");

            builder.HasKey(o => o.Id);

            builder.Property(o => o.Id)
                .ValueGeneratedNever()
                .IsRequired();

            builder.Property(o => o.OrderNumber)
                .HasMaxLength(50)
                .IsRequired();

            builder.Property(o => o.CustomerId)
                .IsRequired();

            builder.Property(o => o.Status)
                .HasConversion<string>()
                .HasMaxLength(20)
                .IsRequired();

            builder.Property(o => o.Currency)
                .HasMaxLength(3)
                .IsRequired();

            builder.Property(o => o.PaymentMethod)
                .HasMaxLength(50);

            builder.Property(o => o.PaymentTransactionId)
                .HasMaxLength(100);

            builder.Property(o => o.ShippingMethod)
                .HasMaxLength(50);

            builder.Property(o => o.TrackingNumber)
                .HasMaxLength(100);

            // Owned types for Value Objects
            builder.OwnsOne(o => o.ShippingAddress, address =>
            {
                address.Property(a => a.Street).HasMaxLength(200).HasColumnName("ShippingStreet");
                address.Property(a => a.City).HasMaxLength(100).HasColumnName("ShippingCity");
                address.Property(a => a.State).HasMaxLength(100).HasColumnName("ShippingState");
                address.Property(a => a.Country).HasMaxLength(100).HasColumnName("ShippingCountry");
                address.Property(a => a.ZipCode).HasMaxLength(20).HasColumnName("ShippingZipCode");
            });

            builder.OwnsOne(o => o.BillingAddress, address =>
            {
                address.Property(a => a.Street).HasMaxLength(200).HasColumnName("BillingStreet");
                address.Property(a => a.City).HasMaxLength(100).HasColumnName("BillingCity");
                address.Property(a => a.State).HasMaxLength(100).HasColumnName("BillingState");
                address.Property(a => a.Country).HasMaxLength(100).HasColumnName("BillingCountry");
                address.Property(a => a.ZipCode).HasMaxLength(20).HasColumnName("BillingZipCode");
            });

            // Owned types for Money Value Objects
            builder.OwnsOne(o => o.TotalAmount, money =>
            {
                money.Property(m => m.Amount).HasColumnType("decimal(18,2)").HasColumnName("TotalAmount");
                money.Property(m => m.Currency).HasMaxLength(3).HasColumnName("TotalCurrency");
            });

            builder.OwnsOne(o => o.DiscountAmount, money =>
            {
                money.Property(m => m.Amount).HasColumnType("decimal(18,2)").HasColumnName("DiscountAmount");
                money.Property(m => m.Currency).HasMaxLength(3).HasColumnName("DiscountCurrency");
            });

            builder.OwnsOne(o => o.TaxAmount, money =>
            {
                money.Property(m => m.Amount).HasColumnType("decimal(18,2)").HasColumnName("TaxAmount");
                money.Property(m => m.Currency).HasMaxLength(3).HasColumnName("TaxCurrency");
            });

            builder.OwnsOne(o => o.ShippingAmount, money =>
            {
                money.Property(m => m.Amount).HasColumnType("decimal(18,2)").HasColumnName("ShippingAmount");
                money.Property(m => m.Currency).HasMaxLength(3).HasColumnName("ShippingCurrency");
            });

            builder.OwnsOne(o => o.FinalAmount, money =>
            {
                money.Property(m => m.Amount).HasColumnType("decimal(18,2)").HasColumnName("FinalAmount");
                money.Property(m => m.Currency).HasMaxLength(3).HasColumnName("FinalCurrency");
            });

            // Indexes
            builder.HasIndex(o => o.OrderNumber)
                .IsUnique()
                .HasDatabaseName("IX_Orders_OrderNumber");

            builder.HasIndex(o => o.CustomerId)
                .HasDatabaseName("IX_Orders_CustomerId");

            builder.HasIndex(o => o.Status)
                .HasDatabaseName("IX_Orders_Status");

            builder.HasIndex(o => o.Created)
                .HasDatabaseName("IX_Orders_Created");

            // Relationships
            builder.HasMany(o => o.OrderItems)
                .WithOne(oi => oi.Order)
                .HasForeignKey(oi => oi.OrderId)
                .OnDelete(DeleteBehavior.Cascade);

            builder.HasMany(o => o.StatusHistory)
                .WithOne(osh => osh.Order)
                .HasForeignKey(osh => osh.OrderId)
                .OnDelete(DeleteBehavior.Cascade);
        }
    }
}
  

4.2 Repository Pattern Implementation

  
    // Infrastructure/Data/Repositories/ProductRepository.cs
using Microsoft.EntityFrameworkCore;
using SmartCommerce.Application.Common.Interfaces;
using SmartCommerce.Domain.Entities;
using SmartCommerce.Domain.Specifications;

namespace SmartCommerce.Infrastructure.Data.Repositories
{
    public class ProductRepository : IProductRepository
    {
        private readonly ApplicationDbContext _context;

        public ProductRepository(ApplicationDbContext context)
        {
            _context = context;
        }

        public async Task<Product?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
        {
            return await _context.Products
                .Include(p => p.Category)
                .Include(p => p.Vendor)
                .Include(p => p.Images)
                .Include(p => p.Tags)
                .Include(p => p.Reviews)
                .FirstOrDefaultAsync(p => p.Id == id, cancellationToken);
        }

        public async Task<IReadOnlyList<Product>> GetAllAsync(CancellationToken cancellationToken = default)
        {
            return await _context.Products
                .Include(p => p.Category)
                .Include(p => p.Images.Where(pi => pi.IsPrimary))
                .Where(p => p.IsActive && !p.IsDeleted)
                .OrderBy(p => p.Name)
                .ToListAsync(cancellationToken);
        }

        public async Task<IReadOnlyList<Product>> GetBySpecificationAsync(ISpecification<Product> specification, CancellationToken cancellationToken = default)
        {
            return await ApplySpecification(specification).ToListAsync(cancellationToken);
        }

        public async Task<Product?> GetBySpecificationAsync(ISingleResultSpecification<Product> specification, CancellationToken cancellationToken = default)
        {
            return await ApplySpecification(specification).FirstOrDefaultAsync(cancellationToken);
        }

        public async Task<int> CountAsync(ISpecification<Product> specification, CancellationToken cancellationToken = default)
        {
            return await ApplySpecification(specification, true).CountAsync(cancellationToken);
        }

        public async Task<bool> ExistsAsync(Guid id, CancellationToken cancellationToken = default)
        {
            return await _context.Products.AnyAsync(p => p.Id == id && !p.IsDeleted, cancellationToken);
        }

        public async Task<Product> AddAsync(Product entity, CancellationToken cancellationToken = default)
        {
            await _context.Products.AddAsync(entity, cancellationToken);
            return entity;
        }

        public void Update(Product entity)
        {
            _context.Products.Update(entity);
        }

        public void Delete(Product entity)
        {
            entity.MarkAsDeleted();
            _context.Products.Update(entity);
        }

        public async Task<IReadOnlyList<Product>> GetFeaturedProductsAsync(int count, CancellationToken cancellationToken = default)
        {
            return await _context.Products
                .Include(p => p.Category)
                .Include(p => p.Images.Where(pi => pi.IsPrimary))
                .Where(p => p.IsActive && !p.IsDeleted && p.AIScore > 0.7f)
                .OrderByDescending(p => p.AIScore)
                .ThenByDescending(p => p.Created)
                .Take(count)
                .ToListAsync(cancellationToken);
        }

        public async Task<IReadOnlyList<Product>> SearchProductsAsync(string searchTerm, int page = 1, int pageSize = 20, CancellationToken cancellationToken = default)
        {
            var query = _context.Products
                .Include(p => p.Category)
                .Include(p => p.Images.Where(pi => pi.IsPrimary))
                .Where(p => p.IsActive && !p.IsDeleted);

            if (!string.IsNullOrWhiteSpace(searchTerm))
            {
                searchTerm = searchTerm.Trim().ToLower();
                query = query.Where(p => 
                    p.Name.ToLower().Contains(searchTerm) ||
                    p.Description.ToLower().Contains(searchTerm) ||
                    p.Sku.ToLower().Contains(searchTerm) ||
                    p.AIKeywords != null && p.AIKeywords.ToLower().Contains(searchTerm) ||
                    p.Tags.Any(t => t.Name.ToLower().Contains(searchTerm)));
            }

            return await query
                .OrderByDescending(p => p.AIScore)
                .ThenBy(p => p.Name)
                .Skip((page - 1) * pageSize)
                .Take(pageSize)
                .ToListAsync(cancellationToken);
        }

        public async Task<IReadOnlyList<Product>> GetProductsByCategoryAsync(Guid categoryId, int page = 1, int pageSize = 20, CancellationToken cancellationToken = default)
        {
            return await _context.Products
                .Include(p => p.Category)
                .Include(p => p.Images.Where(pi => pi.IsPrimary))
                .Where(p => p.IsActive && !p.IsDeleted && p.CategoryId == categoryId)
                .OrderByDescending(p => p.AIScore)
                .ThenBy(p => p.Name)
                .Skip((page - 1) * pageSize)
                .Take(pageSize)
                .ToListAsync(cancellationToken);
        }

        public async Task UpdateAIScoresAsync(Dictionary<Guid, float> productScores, CancellationToken cancellationToken = default)
        {
            var productIds = productScores.Keys.ToList();
            var products = await _context.Products
                .Where(p => productIds.Contains(p.Id))
                .ToListAsync(cancellationToken);

            foreach (var product in products)
            {
                if (productScores.TryGetValue(product.Id, out var score))
                {
                    product.UpdateAIScore(score, string.Empty); // Keywords would be set separately
                }
            }

            _context.Products.UpdateRange(products);
        }

        private IQueryable<Product> ApplySpecification(ISpecification<Product> specification, bool forCount = false)
        {
            var query = _context.Products.AsQueryable();

            // Include related entities if not counting
            if (!forCount)
            {
                query = query
                    .Include(p => p.Category)
                    .Include(p => p.Images.Where(pi => pi.IsPrimary))
                    .Include(p => p.Tags);
            }

            // Apply specification criteria
            if (specification.Criteria != null)
            {
                query = query.Where(specification.Criteria);
            }

            // Apply ordering if not counting
            if (!forCount && specification.OrderBy != null)
            {
                query = specification.OrderBy(query);
            }
            else if (!forCount && specification.OrderByDescending != null)
            {
                query = specification.OrderByDescending(query);
            }

            // Apply paging if not counting
            if (!forCount && specification.IsPagingEnabled)
            {
                query = query.Skip(specification.Skip)
                             .Take(specification.Take);
            }

            return query;
        }
    }
}
  

5. Application Layer & CQRS

5.1 CQRS Implementation with MediatR

  
    // Application/Features/Products/Queries/GetProductDetail/GetProductDetailQuery.cs
using AutoMapper;
using AutoMapper.QueryableExtensions;
using MediatR;
using Microsoft.EntityFrameworkCore;
using SmartCommerce.Application.Common.Interfaces;
using SmartCommerce.Application.Common.Models;

namespace SmartCommerce.Application.Features.Products.Queries.GetProductDetail
{
    public record GetProductDetailQuery : IRequest<Result<ProductDetailDto>>
    {
        public Guid Id { get; init; }
    }

    public class GetProductDetailQueryHandler : IRequestHandler<GetProductDetailQuery, Result<ProductDetailDto>>
    {
        private readonly IApplicationDbContext _context;
        private readonly IMapper _mapper;
        private readonly ICurrentUserService _currentUserService;

        public GetProductDetailQueryHandler(IApplicationDbContext context, IMapper mapper, ICurrentUserService currentUserService)
        {
            _context = context;
            _mapper = mapper;
            _currentUserService = currentUserService;
        }

        public async Task<Result<ProductDetailDto>> Handle(GetProductDetailQuery request, CancellationToken cancellationToken)
        {
            var product = await _context.Products
                .Include(p => p.Category)
                .Include(p => p.Vendor)
                .Include(p => p.Images)
                .Include(p => p.Tags)
                .Include(p => p.Reviews)
                .ThenInclude(r => r.User)
                .Where(p => p.IsActive && !p.IsDeleted)
                .ProjectTo<ProductDetailDto>(_mapper.ConfigurationProvider)
                .FirstOrDefaultAsync(p => p.Id == request.Id, cancellationToken);

            if (product == null)
            {
                return Result<ProductDetailDto>.Failure($"Product with ID {request.Id} not found.");
            }

            // Track product view for recommendations
            if (_currentUserService.UserId.HasValue)
            {
                // Fire and forget - don't await to avoid blocking the response
                _ = Task.Run(async () =>
                {
                    try
                    {
                        await TrackProductViewAsync(request.Id, _currentUserService.UserId.Value);
                    }
                    catch (Exception ex)
                    {
                        // Log but don't throw - this shouldn't affect the main request
                        // In production, use proper logging
                        Console.WriteLine($"Failed to track product view: {ex.Message}");
                    }
                });
            }

            return Result<ProductDetailDto>.Success(product);
        }

        private async Task TrackProductViewAsync(Guid productId, Guid userId)
        {
            // Implementation would track product views for recommendation engine
            // This could be done via a message bus or direct database call
            var viewEvent = new ProductViewedEvent
            {
                ProductId = productId,
                UserId = userId,
                ViewedAt = DateTime.UtcNow
            };

            // Publish event or save to database
            await Task.CompletedTask;
        }
    }

    public class ProductDetailDto
    {
        public Guid Id { get; set; }
        public string Name { get; set; } = string.Empty;
        public string Description { get; set; } = string.Empty;
        public decimal Price { get; set; }
        public string Currency { get; set; } = "USD";
        public string Sku { get; set; } = string.Empty;
        public int StockQuantity { get; set; }
        public bool IsInStock => StockQuantity > 0;
        public float AIScore { get; set; }
        public string? AIKeywords { get; set; }

        public Guid CategoryId { get; set; }
        public string CategoryName { get; set; } = string.Empty;

        public Guid? VendorId { get; set; }
        public string? VendorName { get; set; }

        public List<ProductImageDto> Images { get; set; } = new();
        public List<ProductTagDto> Tags { get; set; } = new();
        public List<ProductReviewDto> Reviews { get; set; } = new();

        public decimal AverageRating => Reviews.Any() ? Reviews.Average(r => r.Rating) : 0;
        public int ReviewCount => Reviews.Count;

        public DateTime Created { get; set; }
        public DateTime? LastModified { get; set; }
    }

    public class ProductImageDto
    {
        public Guid Id { get; set; }
        public string ImageUrl { get; set; } = string.Empty;
        public string AltText { get; set; } = string.Empty;
        public bool IsPrimary { get; set; }
        public int DisplayOrder { get; set; }
    }

    public class ProductReviewDto
    {
        public Guid Id { get; set; }
        public Guid UserId { get; set; }
        public string UserName { get; set; } = string.Empty;
        public int Rating { get; set; }
        public string? Title { get; set; }
        public string Comment { get; set; } = string.Empty;
        public DateTime Created { get; set; }
    }
}
  

5.2 Command Implementation

  
    // Application/Features/Products/Commands/CreateProduct/CreateProductCommand.cs
using AutoMapper;
using FluentValidation;
using MediatR;
using SmartCommerce.Application.Common.Interfaces;
using SmartCommerce.Application.Common.Models;
using SmartCommerce.Domain.Entities;

namespace SmartCommerce.Application.Features.Products.Commands.CreateProduct
{
    public record CreateProductCommand : IRequest<Result<Guid>>
    {
        public string Name { get; init; } = string.Empty;
        public string Description { get; init; } = string.Empty;
        public decimal Price { get; init; }
        public string Currency { get; init; } = "USD";
        public string Sku { get; init; } = string.Empty;
        public int StockQuantity { get; init; }
        public Guid CategoryId { get; init; }
        public Guid? VendorId { get; init; }
        public List<ProductImageCommand> Images { get; init; } = new();
        public List<string> Tags { get; init; } = new();
    }

    public class ProductImageCommand
    {
        public string ImageUrl { get; init; } = string.Empty;
        public string AltText { get; init; } = string.Empty;
        public bool IsPrimary { get; init; }
    }

    public class CreateProductCommandValidator : AbstractValidator<CreateProductCommand>
    {
        private readonly IApplicationDbContext _context;

        public CreateProductCommandValidator(IApplicationDbContext context)
        {
            _context = context;

            RuleFor(v => v.Name)
                .NotEmpty().WithMessage("Name is required.")
                .MaximumLength(200).WithMessage("Name must not exceed 200 characters.");

            RuleFor(v => v.Description)
                .NotEmpty().WithMessage("Description is required.")
                .MaximumLength(2000).WithMessage("Description must not exceed 2000 characters.");

            RuleFor(v => v.Price)
                .GreaterThan(0).WithMessage("Price must be greater than 0.");

            RuleFor(v => v.Sku)
                .NotEmpty().WithMessage("SKU is required.")
                .MaximumLength(100).WithMessage("SKU must not exceed 100 characters.")
                .MustAsync(BeUniqueSku).WithMessage("The specified SKU already exists.");

            RuleFor(v => v.StockQuantity)
                .GreaterThanOrEqualTo(0).WithMessage("Stock quantity cannot be negative.");

            RuleFor(v => v.CategoryId)
                .NotEmpty().WithMessage("Category is required.")
                .MustAsync(CategoryExists).WithMessage("The specified category does not exist.");

            RuleFor(v => v.Images)
                .Must(HaveAtLeastOnePrimaryImage)
                .When(v => v.Images.Any())
                .WithMessage("At least one image must be marked as primary.");

            RuleForEach(v => v.Tags)
                .NotEmpty().WithMessage("Tag cannot be empty.")
                .MaximumLength(50).WithMessage("Tag must not exceed 50 characters.");
        }

        private async Task<bool> BeUniqueSku(string sku, CancellationToken cancellationToken)
        {
            return await _context.Products
                .AllAsync(p => p.Sku != sku, cancellationToken);
        }

        private async Task<bool> CategoryExists(Guid categoryId, CancellationToken cancellationToken)
        {
            return await _context.Categories
                .AnyAsync(c => c.Id == categoryId, cancellationToken);
        }

        private bool HaveAtLeastOnePrimaryImage(List<ProductImageCommand> images)
        {
            return images.Any(i => i.IsPrimary);
        }
    }

    public class CreateProductCommandHandler : IRequestHandler<CreateProductCommand, Result<Guid>>
    {
        private readonly IApplicationDbContext _context;
        private readonly IMapper _mapper;
        private readonly IAIService _aiService;

        public CreateProductCommandHandler(IApplicationDbContext context, IMapper mapper, IAIService aiService)
        {
            _context = context;
            _mapper = mapper;
            _aiService = aiService;
        }

        public async Task<Result<Guid>> Handle(CreateProductCommand request, CancellationToken cancellationToken)
        {
            try
            {
                // Create product
                var price = new Money(request.Price, request.Currency);
                var product = new Product(
                    request.Name,
                    request.Description,
                    price,
                    request.Sku,
                    request.CategoryId,
                    request.VendorId,
                    request.StockQuantity);

                // Add images
                foreach (var imageCommand in request.Images)
                {
                    product.AddImage(imageCommand.ImageUrl, imageCommand.AltText, imageCommand.IsPrimary);
                }

                // Add tags
                foreach (var tagName in request.Tags)
                {
                    product.AddTag(tagName);
                }

                // AI Analysis (fire and forget)
                _ = Task.Run(async () =>
                {
                    try
                    {
                        await AnalyzeProductWithAIAsync(product);
                    }
                    catch (Exception ex)
                    {
                        // Log AI analysis failure but don't fail the product creation
                        // In production, use proper logging
                        Console.WriteLine($"AI analysis failed for product {product.Id}: {ex.Message}");
                    }
                }, cancellationToken);

                // Save product
                await _context.Products.AddAsync(product, cancellationToken);
                await _context.SaveChangesAsync(cancellationToken);

                return Result<Guid>.Success(product.Id);
            }
            catch (Exception ex)
            {
                return Result<Guid>.Failure($"Failed to create product: {ex.Message}");
            }
        }

        private async Task AnalyzeProductWithAIAsync(Product product)
        {
            // Analyze product with AI services
            var analysisResult = await _aiService.AnalyzeProductAsync(
                product.Name, 
                product.Description, 
                product.Tags.Select(t => t.Name).ToList());

            product.UpdateAIScore(analysisResult.Score, analysisResult.Keywords);

            _context.Products.Update(product);
            await _context.SaveChangesAsync();
        }
    }
}
  

6. API Layer & Controllers

6.1 API Controllers with Best Practices

  
    // Web/Controllers/ProductsController.cs
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SmartCommerce.Application.Features.Products.Commands.CreateProduct;
using SmartCommerce.Application.Features.Products.Commands.DeleteProduct;
using SmartCommerce.Application.Features.Products.Commands.UpdateProduct;
using SmartCommerce.Application.Features.Products.Queries.GetProductDetail;
using SmartCommerce.Application.Features.Products.Queries.GetProducts;
using SmartCommerce.Web.Filters;

namespace SmartCommerce.Web.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    [Produces("application/json")]
    [ServiceFilter(typeof(ApiExceptionFilter))]
    public class ProductsController : ControllerBase
    {
        private readonly IMediator _mediator;
        private readonly ILogger<ProductsController> _logger;

        public ProductsController(IMediator mediator, ILogger<ProductsController> logger)
        {
            _mediator = mediator;
            _logger = logger;
        }

        /// <summary>
        /// Get paginated list of products
        /// </summary>
        /// <param name="query">Query parameters for filtering and pagination</param>
        /// <returns>Paginated list of products</returns>
        [HttpGet]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        public async Task<ActionResult<PaginatedList<ProductDto>>> GetProducts([FromQuery] GetProductsQuery query)
        {
            _logger.LogInformation("Getting products with query: {@Query}", query);
            
            var result = await _mediator.Send(query);
            
            if (result.Succeeded)
            {
                // Add pagination headers
                Response.Headers.Append("X-Pagination", result.Data.ToJson());
                return Ok(result.Data.Items);
            }
            
            return BadRequest(result.Errors);
        }

        /// <summary>
        /// Get product by ID
        /// </summary>
        /// <param name="id">Product ID</param>
        /// <returns>Product details</returns>
        [HttpGet("{id:guid}")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public async Task<ActionResult<ProductDetailDto>> GetProduct(Guid id)
        {
            _logger.LogInformation("Getting product with ID: {ProductId}", id);
            
            var query = new GetProductDetailQuery { Id = id };
            var result = await _mediator.Send(query);
            
            if (result.Succeeded)
            {
                return Ok(result.Data);
            }
            
            return NotFound(result.Errors);
        }

        /// <summary>
        /// Create a new product
        /// </summary>
        /// <param name="command">Product creation data</param>
        /// <returns>Created product ID</returns>
        [HttpPost]
        [Authorize(Roles = "Admin,ProductManager")]
        [ProducesResponseType(StatusCodes.Status201Created)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status401Unauthorized)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        public async Task<ActionResult<Guid>> CreateProduct(CreateProductCommand command)
        {
            _logger.LogInformation("Creating new product: {@Product}", command);
            
            var result = await _mediator.Send(command);
            
            if (result.Succeeded)
            {
                return CreatedAtAction(nameof(GetProduct), new { id = result.Data }, result.Data);
            }
            
            return BadRequest(result.Errors);
        }

        /// <summary>
        /// Update an existing product
        /// </summary>
        /// <param name="id">Product ID</param>
        /// <param name="command">Product update data</param>
        /// <returns>No content</returns>
        [HttpPut("{id:guid}")]
        [Authorize(Roles = "Admin,ProductManager")]
        [ProducesResponseType(StatusCodes.Status204NoContent)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        [ProducesResponseType(StatusCodes.Status401Unauthorized)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        public async Task<IActionResult> UpdateProduct(Guid id, UpdateProductCommand command)
        {
            if (id != command.Id)
            {
                return BadRequest("ID in route does not match ID in body");
            }
            
            _logger.LogInformation("Updating product {ProductId} with data: {@Product}", id, command);
            
            var result = await _mediator.Send(command);
            
            if (result.Succeeded)
            {
                return NoContent();
            }
            
            if (result.Errors.Any(e => e.Contains("not found", StringComparison.OrdinalIgnoreCase)))
            {
                return NotFound(result.Errors);
            }
            
            return BadRequest(result.Errors);
        }

        /// <summary>
        /// Delete a product
        /// </summary>
        /// <param name="id">Product ID</param>
        /// <returns>No content</returns>
        [HttpDelete("{id:guid}")]
        [Authorize(Roles = "Admin,ProductManager")]
        [ProducesResponseType(StatusCodes.Status204NoContent)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        [ProducesResponseType(StatusCodes.Status401Unauthorized)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        public async Task<IActionResult> DeleteProduct(Guid id)
        {
            _logger.LogInformation("Deleting product with ID: {ProductId}", id);
            
            var command = new DeleteProductCommand { Id = id };
            var result = await _mediator.Send(command);
            
            if (result.Succeeded)
            {
                return NoContent();
            }
            
            return NotFound(result.Errors);
        }

        /// <summary>
        /// Search products
        /// </summary>
        /// <param name="searchTerm">Search term</param>
        /// <param name="page">Page number</param>
        /// <param name="pageSize">Page size</param>
        /// <returns>Search results</returns>
        [HttpGet("search")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        public async Task<ActionResult<List<ProductDto>>> SearchProducts(
            [FromQuery] string searchTerm,
            [FromQuery] int page = 1,
            [FromQuery] int pageSize = 20)
        {
            if (string.IsNullOrWhiteSpace(searchTerm) || searchTerm.Length < 2)
            {
                return BadRequest("Search term must be at least 2 characters long");
            }
            
            _logger.LogInformation("Searching products with term: {SearchTerm}", searchTerm);
            
            var query = new SearchProductsQuery 
            { 
                SearchTerm = searchTerm, 
                Page = page, 
                PageSize = pageSize 
            };
            
            var result = await _mediator.Send(query);
            
            if (result.Succeeded)
            {
                return Ok(result.Data);
            }
            
            return BadRequest(result.Errors);
        }

        /// <summary>
        /// Get featured products
        /// </summary>
        /// <param name="count">Number of featured products to return</param>
        /// <returns>List of featured products</returns>
        [HttpGet("featured")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        public async Task<ActionResult<List<ProductDto>>> GetFeaturedProducts([FromQuery] int count = 10)
        {
            _logger.LogInformation("Getting {Count} featured products", count);
            
            var query = new GetFeaturedProductsQuery { Count = count };
            var result = await _mediator.Send(query);
            
            if (result.Succeeded)
            {
                return Ok(result.Data);
            }
            
            return BadRequest(result.Errors);
        }
    }
}
  

6.2 API Versioning and Documentation

  
    // Web/Configuration/SwaggerConfiguration.cs
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.Filters;

namespace SmartCommerce.Web.Configuration
{
    public static class SwaggerConfiguration
    {
        public static IServiceCollection AddSwaggerConfiguration(this IServiceCollection services, IConfiguration configuration)
        {
            services.AddSwaggerGen(options =>
            {
                options.SwaggerDoc("v1", new OpenApiInfo
                {
                    Title = "SmartCommerce API",
                    Version = "v1",
                    Description = "AI-Powered E-Commerce Platform API",
                    Contact = new OpenApiContact
                    {
                        Name = "SmartCommerce Team",
                        Email = "[email protected]",
                        Url = new Uri("https://smartcommerce.com")
                    },
                    License = new OpenApiLicense
                    {
                        Name = "MIT License",
                        Url = new Uri("https://opensource.org/licenses/MIT")
                    }
                });

                // Add JWT Authentication
                options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
                {
                    Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
                    Name = "Authorization",
                    In = ParameterLocation.Header,
                    Type = SecuritySchemeType.ApiKey,
                    Scheme = "Bearer"
                });

                options.AddSecurityRequirement(new OpenApiSecurityRequirement
                {
                    {
                        new OpenApiSecurityScheme
                        {
                            Reference = new OpenApiReference
                            {
                                Type = ReferenceType.SecurityScheme,
                                Id = "Bearer"
                            },
                            Scheme = "oauth2",
                            Name = "Bearer",
                            In = ParameterLocation.Header
                        },
                        new List<string>()
                    }
                });

                // Add XML comments
                var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
                var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
                options.IncludeXmlComments(xmlPath);

                // Operation filters
                options.OperationFilter<AppendAuthorizeToSummaryOperationFilter>();
                options.OperationFilter<SecurityRequirementsOperationFilter>();

                // Schema filters
                options.SchemaFilter<EnumSchemaFilter>();

                // Support for polymorphic types
                options.UseAllOfForInheritance();
                options.UseOneOfForPolymorphism();

                // Custom filters
                options.OperationFilter<CorrelationIdOperationFilter>();
            });

            services.AddSwaggerGenNewtonsoftSupport();

            return services;
        }

        public static IApplicationBuilder UseSwaggerConfiguration(this IApplicationBuilder app)
        {
            app.UseSwagger();
            app.UseSwaggerUI(options =>
            {
                options.SwaggerEndpoint("/swagger/v1/swagger.json", "SmartCommerce API V1");
                options.RoutePrefix = "api-docs";
                options.DocumentTitle = "SmartCommerce API Documentation";
                options.EnablePersistAuthorization();
                options.EnableDeepLinking();
                options.DisplayOperationId();
                options.DisplayRequestDuration();
                options.DefaultModelsExpandDepth(-1); // Hide schemas by default
                
                // Custom CSS
                options.InjectStylesheet("/swagger-ui/custom.css");
            });

            return app;
        }
    }
}
  

7. AI Integration Services

7.1 AI-Powered Recommendation Engine

csharp

  
    // Infrastructure/AI/RecommendationService.cs
using Microsoft.ML;
using Microsoft.ML.Data;
using Microsoft.ML.Trainers;
using SmartCommerce.Application.Common.Interfaces;
using SmartCommerce.Domain.Entities;

namespace SmartCommerce.Infrastructure.AI
{
    public interface IRecommendationService
    {
        Task<TrainingResult> TrainRecommendationModelAsync();
        Task<List<Recommendation>> GetPersonalizedRecommendationsAsync(Guid userId, int count = 10);
        Task<List<Recommendation>> GetSimilarProductsAsync(Guid productId, int count = 5);
        Task RecordUserInteractionAsync(UserInteraction interaction);
    }

    public class AIRecommendationService : IRecommendationService
    {
        private readonly MLContext _mlContext;
        private readonly IApplicationDbContext _context;
        private readonly IProductRepository _productRepository;
        private ITransformer _model;
        private PredictionEngine<ProductInteraction, ProductPrediction> _predictionEngine;

        public AIRecommendationService(IApplicationDbContext context, IProductRepository productRepository)
        {
            _mlContext = new MLContext(seed: 0);
            _context = context;
            _productRepository = productRepository;
        }

        public async Task<TrainingResult> TrainRecommendationModelAsync()
        {
            try
            {
                // Load training data
                var interactions = await LoadTrainingDataAsync();
                
                if (interactions.Count < 100) // Minimum data required
                {
                    return TrainingResult.Failure("Insufficient training data");
                }

                // Prepare data
                var dataView = _mlContext.Data.LoadFromEnumerable(interactions);

                // Data preprocessing
                var dataProcessPipeline = _mlContext.Transforms.Conversion.MapValueToKey(
                    outputColumnName: "UserIdEncoded", 
                    inputColumnName: nameof(ProductInteraction.UserId))
                .Append(_mlContext.Transforms.Conversion.MapValueToKey(
                    outputColumnName: "ProductIdEncoded", 
                    inputColumnName: nameof(ProductInteraction.ProductId)));

                // Training configuration
                var options = new MatrixFactorizationTrainer.Options
                {
                    MatrixColumnIndexColumnName = "UserIdEncoded",
                    MatrixRowIndexColumnName = "ProductIdEncoded",
                    LabelColumnName = nameof(ProductInteraction.Rating),
                    NumberOfIterations = 20,
                    ApproximationRank = 100,
                    LearningRate = 0.01
                };

                var trainingPipeline = dataProcessPipeline.Append(_mlContext.Recommendation().Trainers.MatrixFactorization(options));

                // Train model
                _model = trainingPipeline.Fit(dataView);

                // Create prediction engine
                _predictionEngine = _mlContext.Model.CreatePredictionEngine<ProductInteraction, ProductPrediction>(_model);

                // Evaluate model
                var testData = _mlContext.Data.TrainTestSplit(dataView, testFraction: 0.2);
                var predictions = _model.Transform(testData.TestSet);
                var metrics = _mlContext.Regression.Evaluate(predictions, labelColumnName: nameof(ProductInteraction.Rating));

                return TrainingResult.Success(metrics.RSquared, metrics.RootMeanSquaredError);
            }
            catch (Exception ex)
            {
                return TrainingResult.Failure($"Training failed: {ex.Message}");
            }
        }

        public async Task<List<Recommendation>> GetPersonalizedRecommendationsAsync(Guid userId, int count = 10)
        {
            if (_model == null)
            {
                // Fallback to popular products if model not trained
                return await GetPopularProductsAsync(count);
            }

            var allProducts = await _productRepository.GetAllAsync();
            var recommendations = new List<Recommendation>();

            foreach (var product in allProducts)
            {
                var prediction = _predictionEngine.Predict(new ProductInteraction
                {
                    UserId = userId.ToString(),
                    ProductId = product.Id.ToString(),
                    Rating = 0 // This will be predicted
                });

                recommendations.Add(new Recommendation
                {
                    ProductId = product.Id,
                    ProductName = product.Name,
                    Score = prediction.Score,
                    Confidence = prediction.Confidence,
                    Reason = "AI Personalized Recommendation"
                });
            }

            return recommendations
                .OrderByDescending(r => r.Score)
                .Take(count)
                .ToList();
        }

        public async Task<List<Recommendation>> GetSimilarProductsAsync(Guid productId, int count = 5)
        {
            var targetProduct = await _productRepository.GetByIdAsync(productId);
            if (targetProduct == null)
                return new List<Recommendation>();

            var allProducts = await _productRepository.GetAllAsync();
            var similarities = new List<Recommendation>();

            foreach (var product in allProducts.Where(p => p.Id != productId))
            {
                var similarity = CalculateProductSimilarity(targetProduct, product);
                similarities.Add(new Recommendation
                {
                    ProductId = product.Id,
                    ProductName = product.Name,
                    Score = similarity,
                    Confidence = 0.8f, // Placeholder
                    Reason = "Similar Product"
                });
            }

            return similarities
                .OrderByDescending(r => r.Score)
                .Take(count)
                .ToList();
        }

        public async Task RecordUserInteractionAsync(UserInteraction interaction)
        {
            await _context.UserInteractions.AddAsync(interaction);
            await _context.SaveChangesAsync();

            // Trigger model retraining if enough new data
            await CheckAndRetrainModelAsync();
        }

        private async Task<List<ProductInteraction>> LoadTrainingDataAsync()
        {
            var interactions = await _context.UserInteractions
                .Where(ui => ui.InteractionType == InteractionType.Purchase || 
                            ui.InteractionType == InteractionType.View)
                .Select(ui => new ProductInteraction
                {
                    UserId = ui.UserId.ToString(),
                    ProductId = ui.ProductId.ToString(),
                    Rating = CalculateRatingFromInteraction(ui.InteractionType)
                })
                .ToListAsync();

            return interactions;
        }

        private float CalculateRatingFromInteraction(InteractionType interactionType)
        {
            return interactionType switch
            {
                InteractionType.Purchase => 5.0f,
                InteractionType.View => 1.0f,
                InteractionType.AddToCart => 3.0f,
                InteractionType.Review => 4.0f,
                _ => 0.5f
            };
        }

        private float CalculateProductSimilarity(Product product1, Product product2)
        {
            // Simple similarity calculation based on category and price
            var categorySimilarity = product1.CategoryId == product2.CategoryId ? 1.0f : 0.0f;
            
            var priceDifference = Math.Abs(product1.Price.Amount - product2.Price.Amount);
            var maxPrice = Math.Max(product1.Price.Amount, product2.Price.Amount);
            var priceSimilarity = maxPrice > 0 ? 1.0f - (priceDifference / maxPrice) : 1.0f;

            // Tag similarity
            var commonTags = product1.Tags.Select(t => t.Name)
                .Intersect(product2.Tags.Select(t => t.Name))
                .Count();
            var tagSimilarity = commonTags / (float)Math.Max(product1.Tags.Count, product2.Tags.Count);

            return (categorySimilarity * 0.4f) + (priceSimilarity * 0.3f) + (tagSimilarity * 0.3f);
        }

        private async Task<List<Recommendation>> GetPopularProductsAsync(int count)
        {
            var popularProducts = await _context.Products
                .Where(p => p.IsActive && !p.IsDeleted)
                .OrderByDescending(p => p.AIScore)
                .ThenByDescending(p => p.Reviews.Count)
                .Take(count)
                .Select(p => new Recommendation
                {
                    ProductId = p.Id,
                    ProductName = p.Name,
                    Score = p.AIScore,
                    Confidence = 0.7f,
                    Reason = "Popular Product"
                })
                .ToListAsync();

            return popularProducts;
        }

        private async Task CheckAndRetrainModelAsync()
        {
            var recentInteractions = await _context.UserInteractions
                .Where(ui => ui.Created > DateTime.UtcNow.AddDays(-1))
                .CountAsync();

            if (recentInteractions >= 1000) // Retrain if 1000 new interactions
            {
                _ = Task.Run(async () =>
                {
                    await TrainRecommendationModelAsync();
                });
            }
        }
    }

    public class ProductInteraction
    {
        public string UserId { get; set; } = string.Empty;
        public string ProductId { get; set; } = string.Empty;
        public float Rating { get; set; }
    }

    public class ProductPrediction
    {
        public float Score { get; set; }
        public float Confidence { get; set; }
    }

    public record Recommendation
    {
        public Guid ProductId { get; init; }
        public string ProductName { get; init; } = string.Empty;
        public float Score { get; init; }
        public float Confidence { get; init; }
        public string Reason { get; init; } = string.Empty;
    }

    public record TrainingResult(bool Success, string? ErrorMessage = null, double? RSquared = null, double? RMSE = null)
    {
        public static TrainingResult Success(double rSquared, double rmse) => new(true, null, rSquared, rmse);
        public static TrainingResult Failure(string errorMessage) => new(false, errorMessage);
    }
}
  

8. Blazor Frontend

8.1 Blazor WebAssembly Main Application

  
    <!-- Web/Shared/MainLayout.razor -->
@inherits LayoutView
@using SmartCommerce.Web.Components
@using MudBlazor

<MudTheme Provider="ThemeProvider" />
<MudDialogProvider />
<MudSnackbarProvider />

<MudLayout>
    <MudAppBar Elevation="1">
        <MudIconButton Icon="Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="@ToggleDrawer" />
        <MudSpacer />
        <MudText Typo="Typo.h6" Class="ml-3">SmartCommerce</MudText>
        <MudSpacer />
        
        <MudIconButton Icon="Icons.Material.Filled.Search" Color="Color.Inherit" />
        
        @if (IsAuthenticated)
        {
            <MudIconButton Icon="Icons.Material.Filled.ShoppingCart" Color="Color.Inherit" />
            <MudMenu Icon="@("Icons.Material.Filled.AccountCircle")" IconColor="Color.Inherit" Label="Account">
                <MudMenuItem Icon="@("Icons.Material.Filled.Person")" Href="/profile">Profile</MudMenuItem>
                <MudMenuItem Icon="@("Icons.Material.Filled.ShoppingBag")" Href="/orders">Orders</MudMenuItem>
                <MudMenuItem Icon="@("Icons.Material.Filled.ExitToApp")" OnClick="Logout">Logout</MudMenuItem>
            </MudMenu>
        }
        else
        {
            <MudButton Variant="Variant.Text" Color="Color.Inherit" Href="/login">Login</MudButton>
            <MudButton Variant="Variant.Text" Color="Color.Inherit" Href="/register">Register</MudButton>
        }
    </MudAppBar>
    
    <MudDrawer @bind-Open="_drawerOpen" ClipMode="DrawerClipMode.Always">
        <NavMenu />
    </MudDrawer>
    
    <MudMainContent>
        <MudContainer MaxWidth="MaxWidth.Large" Class="my-4">
            @Body
        </MudContainer>
    </MudMainContent>
</MudLayout>

@code {
    private bool _drawerOpen = true;
    
    [CascadingParameter]
    private Task<AuthenticationState>? AuthenticationStateTask { get; set; }
    
    private bool IsAuthenticated { get; set; }

    protected override async Task OnInitializedAsync()
    {
        if (AuthenticationStateTask != null)
        {
            var authState = await AuthenticationStateTask;
            IsAuthenticated = authState.User.Identity?.IsAuthenticated ?? false;
        }
    }

    private void ToggleDrawer()
    {
        _drawerOpen = !_drawerOpen;
    }

    private async void Logout()
    {
        // Implement logout logic
        await InvokeAsync(StateHasChanged);
    }
}
  

8.2 Product Listing Component

  
    <!-- Web/Components/ProductGrid.razor -->
@using SmartCommerce.Application.Features.Products.Queries.GetProducts
@using SmartCommerce.Web.Services
@inject IMediator Mediator
@inject ISnackbar Snackbar
@inject IRecommendationService RecommendationService
@inject NavigationManager Navigation

<MudGrid Spacing="2" Justify="Justify.FlexStart">
    @if (Products == null)
    {
        @for (int i = 0; i < 8; i++)
        {
            <MudItem xs="12" sm="6" md="4" lg="3">
                <ProductCardSkeleton />
            </MudItem>
        }
    }
    else if (Products.Any())
    {
        @foreach (var product in Products)
        {
            <MudItem xs="12" sm="6" md="4" lg="3">
                <ProductCard Product="product" 
                            OnAddToCart="AddToCart"
                            OnQuickView="ShowQuickView" />
            </MudItem>
        }
        
        @if (HasMore)
        {
            <MudItem xs="12" Class="text-center my-4">
                <MudButton Variant="Variant.Outlined" 
                          Color="Color.Primary" 
                          OnClick="LoadMore"
                          Disabled="Loading"
                          EndIcon="@(Loading ? Icons.Material.Filled.Refresh : Icons.Material.Filled.Add)">
                    @(Loading ? "Loading..." : "Load More")
                </MudButton>
            </MudItem>
        }
    }
    else
    {
        <MudItem xs="12">
            <MudText Align="Align.Center" Typo="Typo.h6" Color="Color.Secondary">
                No products found
            </MudText>
        </MudItem>
    }
</MudGrid>

<MudDialog @bind-IsVisible="ShowQuickViewDialog" MaxWidth="MaxWidth.Medium">
    <DialogContent>
        @if (SelectedProduct != null)
        {
            <QuickViewDialog Product="SelectedProduct" 
                           OnAddToCart="AddToCartFromDialog"
                           OnClose="CloseQuickView" />
        }
    </DialogContent>
</MudDialog>

@code {
    [Parameter]
    public ProductSearchParameters? SearchParameters { get; set; }
    
    [Parameter]
    public EventCallback<ProductDto> OnProductSelected { get; set; }

    private List<ProductDto> Products { get; set; } = new();
    private ProductDto? SelectedProduct { get; set; }
    private bool Loading { get; set; }
    private bool HasMore { get; set; }
    private int CurrentPage { get; set; } = 1;
    private bool ShowQuickViewDialog { get; set; }

    protected override async Task OnParametersSetAsync()
    {
        if (SearchParameters != null)
        {
            await ResetAndLoadProducts();
        }
    }

    protected override async Task OnInitializedAsync()
    {
        await LoadProducts();
    }

    private async Task LoadProducts(bool loadMore = false)
    {
        if (Loading) return;

        Loading = true;
        StateHasChanged();

        try
        {
            var query = new GetProductsQuery
            {
                Page = loadMore ? CurrentPage + 1 : 1,
                PageSize = 12,
                SearchTerm = SearchParameters?.SearchTerm,
                CategoryId = SearchParameters?.CategoryId,
                MinPrice = SearchParameters?.MinPrice,
                MaxPrice = SearchParameters?.MaxPrice,
                SortBy = SearchParameters?.SortBy ?? "name",
                SortDirection = SearchParameters?.SortDirection ?? "asc"
            };

            var result = await Mediator.Send(query);

            if (result.Succeeded && result.Data != null)
            {
                if (loadMore)
                {
                    Products.AddRange(result.Data.Items);
                    CurrentPage++;
                }
                else
                {
                    Products = result.Data.Items.ToList();
                    CurrentPage = 1;
                }

                HasMore = result.Data.HasNextPage;
                
                // Record view for AI recommendations
                foreach (var product in Products)
                {
                    await RecommendationService.RecordProductViewAsync(product.Id);
                }
            }
            else
            {
                Snackbar.Add("Failed to load products", Severity.Error);
            }
        }
        catch (Exception ex)
        {
            Snackbar.Add($"Error loading products: {ex.Message}", Severity.Error);
        }
        finally
        {
            Loading = false;
            StateHasChanged();
        }
    }

    private async Task LoadMore()
    {
        await LoadProducts(true);
    }

    private async Task ResetAndLoadProducts()
    {
        Products.Clear();
        CurrentPage = 1;
        await LoadProducts();
    }

    private async Task AddToCart(ProductDto product)
    {
        try
        {
            // Implementation would add product to cart
            Snackbar.Add($"Added {product.Name} to cart", Severity.Success);
            
            // Record interaction for AI
            await RecommendationService.RecordAddToCartAsync(product.Id);
        }
        catch (Exception ex)
        {
            Snackbar.Add($"Failed to add to cart: {ex.Message}", Severity.Error);
        }
    }

    private void ShowQuickView(ProductDto product)
    {
        SelectedProduct = product;
        ShowQuickViewDialog = true;
        StateHasChanged();
    }

    private async Task AddToCartFromDialog(ProductDto product)
    {
        await AddToCart(product);
        ShowQuickViewDialog = false;
    }

    private void CloseQuickView()
    {
        ShowQuickViewDialog = false;
        SelectedProduct = null;
    }

    private async Task OnProductClick(ProductDto product)
    {
        if (OnProductSelected.HasDelegate)
        {
            await OnProductSelected.InvokeAsync(product);
        }
        else
        {
            Navigation.NavigateTo($"/products/{product.Id}");
        }
    }
}

public class ProductSearchParameters
{
    public string? SearchTerm { get; set; }
    public Guid? CategoryId { get; set; }
    public decimal? MinPrice { get; set; }
    public decimal? MaxPrice { get; set; }
    public string SortBy { get; set; } = "name";
    public string SortDirection { get; set; } = "asc";
}
  

8.3 AI-Powered Product Recommendations Component

  
    <!-- Web/Components/ProductRecommendations.razor -->
@using SmartCommerce.Application.Features.Products.Queries.GetProducts
@inject IMediator Mediator
@inject IRecommendationService RecommendationService
@inject IAuthService AuthService

@if (Recommendations.Any())
{
    <MudPaper Class="pa-4 mb-4" Elevation="1">
        <MudText Typo="Typo.h6" GutterBottom="true">
            @Title
            @if (!string.IsNullOrEmpty(Explanation))
            {
                <MudTooltip Text="@Explanation">
                    <MudIcon Icon="Icons.Material.Filled.Info" Size="Size.Small" Class="ml-2" />
                </MudTooltip>
            }
        </MudText>
        
        <MudGrid Spacing="2">
            @foreach (var recommendation in Recommendations)
            {
                <MudItem xs="6" sm="4" md="3" lg="2">
                    <MudCard Class="recommendation-card" Elevation="2">
                        <MudCardContent>
                            <MudLink Href="@($"/products/{recommendation.ProductId}")" Typo="Typo.body2" Class="product-link">
                                <MudImage Src="@GetProductImage(recommendation)" Height="120px" Width="100%" />
                                <MudText Typo="Typo.body2" Class="mt-2 product-name">@recommendation.ProductName</MudText>
                                <MudChip Color="Color.Secondary" Size="Size.Small" Label="@recommendation.Reason" />
                            </MudLink>
                        </MudCardContent>
                    </MudCard>
                </MudItem>
            }
        </MudGrid>
    </MudPaper>
}

@code {
    [Parameter]
    public string Title { get; set; } = "Recommended For You";
    
    [Parameter]
    public string? Explanation { get; set; }
    
    [Parameter]
    public int Count { get; set; } = 6;
    
    [Parameter]
    public Guid? ProductId { get; set; }

    private List<RecommendationDto> Recommendations { get; set; } = new();
    private Dictionary<Guid, ProductDto> ProductCache { get; set; } = new();

    protected override async Task OnInitializedAsync()
    {
        await LoadRecommendations();
    }

    protected override async Task OnParametersSetAsync()
    {
        if (ProductId.HasValue)
        {
            await LoadSimilarProducts();
        }
    }

    private async Task LoadRecommendations()
    {
        var userId = await AuthService.GetCurrentUserIdAsync();
        if (userId.HasValue)
        {
            var recommendations = await RecommendationService.GetPersonalizedRecommendationsAsync(userId.Value, Count);
            Recommendations = recommendations.Select(r => new RecommendationDto
            {
                ProductId = r.ProductId,
                ProductName = r.ProductName,
                Score = r.Score,
                Reason = r.Reason
            }).ToList();
            
            await LoadProductDetails();
        }
    }

    private async Task LoadSimilarProducts()
    {
        if (ProductId.HasValue)
        {
            var recommendations = await RecommendationService.GetSimilarProductsAsync(ProductId.Value, Count);
            Recommendations = recommendations.Select(r => new RecommendationDto
            {
                ProductId = r.ProductId,
                ProductName = r.ProductName,
                Score = r.Score,
                Reason = r.Reason
            }).ToList();
            
            await LoadProductDetails();
        }
    }

    private async Task LoadProductDetails()
    {
        var productIds = Recommendations.Select(r => r.ProductId).ToList();
        var query = new GetProductsByIdsQuery { ProductIds = productIds };
        var result = await Mediator.Send(query);
        
        if (result.Succeeded && result.Data != null)
        {
            ProductCache = result.Data.ToDictionary(p => p.Id, p => p);
        }
    }

    private string GetProductImage(RecommendationDto recommendation)
    {
        if (ProductCache.TryGetValue(recommendation.ProductId, out var product) && 
            product.Images.Any())
        {
            return product.Images.First().ImageUrl;
        }
        
        return "/images/placeholder-product.jpg";
    }
}

public class RecommendationDto
{
    public Guid ProductId { get; set; }
    public string ProductName { get; set; } = string.Empty;
    public float Score { get; set; }
    public string Reason { get; set; } = string.Empty;
}
  

9. Real-time Features

9.1 SignalR Real-time Notifications

  
    // Web/Hubs/NotificationHub.cs
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using SmartCommerce.Application.Common.Interfaces;

namespace SmartCommerce.Web.Hubs
{
    [Authorize]
    public class NotificationHub : Hub
    {
        private readonly IConnectionManager _connectionManager;
        private readonly ILogger<NotificationHub> _logger;

        public NotificationHub(IConnectionManager connectionManager, ILogger<NotificationHub> logger)
        {
            _connectionManager = connectionManager;
            _logger = logger;
        }

        public override async Task OnConnectedAsync()
        {
            var userId = Context.User?.FindFirst("sub")?.Value;
            if (userId != null && Guid.TryParse(userId, out var userGuid))
            {
                await _connectionManager.AddConnectionAsync(userGuid, Context.ConnectionId);
                await Groups.AddToGroupAsync(Context.ConnectionId, $"user_{userId}");
                
                _logger.LogInformation("User {UserId} connected with connection {ConnectionId}", userId, Context.ConnectionId);
            }

            await base.OnConnectedAsync();
        }

        public override async Task OnDisconnectedAsync(Exception? exception)
        {
            var userId = Context.User?.FindFirst("sub")?.Value;
            if (userId != null && Guid.TryParse(userId, out var userGuid))
            {
                await _connectionManager.RemoveConnectionAsync(userGuid, Context.ConnectionId);
                await Groups.RemoveFromGroupAsync(Context.ConnectionId, $"user_{userId}");
                
                _logger.LogInformation("User {UserId} disconnected from connection {ConnectionId}", userId, Context.ConnectionId);
            }

            await base.OnDisconnectedAsync(exception);
        }

        public async Task SubscribeToProduct(Guid productId)
        {
            await Groups.AddToGroupAsync(Context.ConnectionId, $"product_{productId}");
            _logger.LogInformation("User subscribed to product {ProductId}", productId);
        }

        public async Task UnsubscribeFromProduct(Guid productId)
        {
            await Groups.RemoveFromGroupAsync(Context.ConnectionId, $"product_{productId}");
            _logger.LogInformation("User unsubscribed from product {ProductId}", productId);
        }

        public async Task JoinAdminGroup()
        {
            if (Context.User?.IsInRole("Admin") == true)
            {
                await Groups.AddToGroupAsync(Context.ConnectionId, "admins");
                _logger.LogInformation("Admin user joined admin group");
            }
        }
    }

    public interface INotificationClient
    {
        Task ReceiveNotification(NotificationDto notification);
        Task ProductStockUpdated(ProductStockUpdateDto update);
        Task OrderStatusChanged(OrderStatusUpdateDto update);
        Task PriceChanged(PriceChangeDto change);
        Task NewReviewAdded(ProductReviewDto review);
    }

    public class NotificationService : INotificationService
    {
        private readonly IHubContext<NotificationHub, INotificationClient> _hubContext;
        private readonly IConnectionManager _connectionManager;
        private readonly ILogger<NotificationService> _logger;

        public NotificationService(
            IHubContext<NotificationHub, INotificationClient> hubContext,
            IConnectionManager connectionManager,
            ILogger<NotificationService> logger)
        {
            _hubContext = hubContext;
            _connectionManager = connectionManager;
            _logger = logger;
        }

        public async Task NotifyUserAsync(Guid userId, NotificationDto notification)
        {
            try
            {
                var connections = await _connectionManager.GetConnectionsAsync(userId);
                if (connections.Any())
                {
                    await _hubContext.Clients.Clients(connections).ReceiveNotification(notification);
                    _logger.LogInformation("Sent notification to user {UserId}", userId);
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to send notification to user {UserId}", userId);
            }
        }

        public async Task NotifyProductSubscribersAsync(Guid productId, ProductStockUpdateDto update)
        {
            try
            {
                await _hubContext.Clients.Group($"product_{productId}").ProductStockUpdated(update);
                _logger.LogInformation("Notified subscribers of product {ProductId} stock update", productId);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to notify product subscribers for product {ProductId}", productId);
            }
        }

        public async Task NotifyOrderUpdateAsync(Guid orderId, Guid userId, OrderStatusUpdateDto update)
        {
            try
            {
                await _hubContext.Clients.Group($"user_{userId}").OrderStatusChanged(update);
                _logger.LogInformation("Notified user {UserId} of order {OrderId} status update", userId, orderId);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to notify user {UserId} of order {OrderId} update", userId, orderId);
            }
        }

        public async Task NotifyAdminsAsync(NotificationDto notification)
        {
            try
            {
                await _hubContext.Clients.Group("admins").ReceiveNotification(notification);
                _logger.LogInformation("Sent admin notification");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to send admin notification");
            }
        }

        public async Task BroadcastPriceChangeAsync(PriceChangeDto change)
        {
            try
            {
                await _hubContext.Clients.All.PriceChanged(change);
                _logger.LogInformation("Broadcasted price change for product {ProductId}", change.ProductId);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to broadcast price change for product {ProductId}", change.ProductId);
            }
        }
    }
}
  

10. Testing Strategy

10.1 Comprehensive Test Suite

  
    // Tests/Application.UnitTests/Features/Products/GetProductDetailQueryTests.cs
using AutoMapper;
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Moq;
using SmartCommerce.Application.Common.Interfaces;
using SmartCommerce.Application.Features.Products.Queries.GetProductDetail;
using SmartCommerce.Domain.Entities;

namespace SmartCommerce.Application.UnitTests.Features.Products.Queries
{
    public class GetProductDetailQueryTests
    {
        private readonly Mock<IApplicationDbContext> _mockContext;
        private readonly Mock<IMapper> _mockMapper;
        private readonly Mock<ICurrentUserService> _mockCurrentUserService;
        private readonly GetProductDetailQueryHandler _handler;

        public GetProductDetailQueryTests()
        {
            _mockContext = new Mock<IApplicationDbContext>();
            _mockMapper = new Mock<IMapper>();
            _mockCurrentUserService = new Mock<ICurrentUserService>();
            
            _handler = new GetProductDetailQueryHandler(
                _mockContext.Object,
                _mockMapper.Object,
                _mockCurrentUserService.Object);
        }

        [Fact]
        public async Task Handle_WithValidId_ReturnsProductDetail()
        {
            // Arrange
            var productId = Guid.NewGuid();
            var product = new Product("Test Product", "Test Description", 
                new Money(99.99m, "USD"), "TEST-SKU", Guid.NewGuid());
            
            var productsMock = CreateDbSetMock(new List<Product> { product });
            _mockContext.Setup(c => c.Products).Returns(productsMock.Object);
            
            var productDetailDto = new ProductDetailDto { Id = productId, Name = "Test Product" };
            _mockMapper.Setup(m => m.ConfigurationProvider).Returns(new MapperConfiguration(cfg => 
                cfg.CreateMap<Product, ProductDetailDto>()));
            _mockMapper.Setup(m => m.ProjectTo<ProductDetailDto>(It.IsAny<IQueryable>(), It.IsAny<object>()))
                      .Returns(new List<ProductDetailDto> { productDetailDto }.AsQueryable());

            var query = new GetProductDetailQuery { Id = productId };

            // Act
            var result = await _handler.Handle(query, CancellationToken.None);

            // Assert
            result.Succeeded.Should().BeTrue();
            result.Data.Should().NotBeNull();
            result.Data.Name.Should().Be("Test Product");
        }

        [Fact]
        public async Task Handle_WithNonExistentId_ReturnsFailure()
        {
            // Arrange
            var productId = Guid.NewGuid();
            var productsMock = CreateDbSetMock(new List<Product>());
            _mockContext.Setup(c => c.Products).Returns(productsMock.Object);

            var query = new GetProductDetailQuery { Id = productId };

            // Act
            var result = await _handler.Handle(query, CancellationToken.None);

            // Assert
            result.Succeeded.Should().BeFalse();
            result.Errors.Should().Contain($"Product with ID {productId} not found.");
        }

        [Fact]
        public async Task Handle_WithInactiveProduct_ReturnsFailure()
        {
            // Arrange
            var productId = Guid.NewGuid();
            var product = new Product("Test Product", "Test Description", 
                new Money(99.99m, "USD"), "TEST-SKU", Guid.NewGuid());
            product.Deactivate();
            
            var productsMock = CreateDbSetMock(new List<Product> { product });
            _mockContext.Setup(c => c.Products).Returns(productsMock.Object);

            var query = new GetProductDetailQuery { Id = productId };

            // Act
            var result = await _handler.Handle(query, CancellationToken.None);

            // Assert
            result.Succeeded.Should().BeFalse();
        }

        private static Mock<DbSet<T>> CreateDbSetMock<T>(List<T> elements) where T : class
        {
            var queryable = elements.AsQueryable();
            var dbSetMock = new Mock<DbSet<T>>();
            
            dbSetMock.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider);
            dbSetMock.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
            dbSetMock.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
            dbSetMock.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(queryable.GetEnumerator());
            
            return dbSetMock;
        }
    }
}
  

11. Deployment & DevOps

11.1 Docker Configuration

  
    # Dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src

# Copy project files
COPY ["src/SmartCommerce.Web/SmartCommerce.Web.csproj", "src/SmartCommerce.Web/"]
COPY ["src/SmartCommerce.Application/SmartCommerce.Application.csproj", "src/SmartCommerce.Application/"]
COPY ["src/SmartCommerce.Domain/SmartCommerce.Domain.csproj", "src/SmartCommerce.Domain/"]
COPY ["src/SmartCommerce.Infrastructure/SmartCommerce.Infrastructure.csproj", "src/SmartCommerce.Infrastructure/"]
COPY ["src/SmartCommerce.Shared/SmartCommerce.Shared.csproj", "src/SmartCommerce.Shared/"]

# Restore dependencies
RUN dotnet restore "src/SmartCommerce.Web/SmartCommerce.Web.csproj"

# Copy everything else
COPY . .

# Build and publish
WORKDIR "/src/src/SmartCommerce.Web"
RUN dotnet build "SmartCommerce.Web.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "SmartCommerce.Web.csproj" -c Release -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app

# Install curl for health checks
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*

# Create non-root user
RUN groupadd -r appuser && useradd -r -g appuser appuser
RUN chown -R appuser:appuser /app
USER appuser

COPY --from=publish /app/publish .

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD curl -f http://localhost/health || exit 1

ENTRYPOINT ["dotnet", "SmartCommerce.Web.dll"]
  

11.2 Kubernetes Deployment

yaml

  
    # kubernetes/web-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: smartcommerce-web
  labels:
    app: smartcommerce-web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: smartcommerce-web
  template:
    metadata:
      labels:
        app: smartcommerce-web
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "80"
        prometheus.io/path: "/metrics"
    spec:
      containers:
      - name: web
        image: smartcommerce.azurecr.io/web:latest
        ports:
        - containerPort: 80
        - containerPort: 443
        env:
        - name: ASPNETCORE_ENVIRONMENT
          value: "Production"
        - name: ConnectionStrings__DefaultConnection
          valueFrom:
            secretKeyRef:
              name: smartcommerce-secrets
              key: database-connection-string
        - name: Azure__KeyVault__Endpoint
          valueFrom:
            secretKeyRef:
              name: smartcommerce-secrets
              key: keyvault-endpoint
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health
            port: 80
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 5
          failureThreshold: 3
        readinessProbe:
          httpGet:
            path: /health/ready
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 5
          timeoutSeconds: 3
          failureThreshold: 3
        startupProbe:
          httpGet:
            path: /health/startup
            port: 80
          initialDelaySeconds: 10
          periodSeconds: 10
          timeoutSeconds: 5
          failureThreshold: 10
---
apiVersion: v1
kind: Service
metadata:
  name: smartcommerce-web-service
spec:
  selector:
    app: smartcommerce-web
  ports:
  - name: http
    port: 80
    targetPort: 80
  - name: https
    port: 443
    targetPort: 443
  type: LoadBalancer
  

12. Production Readiness

12.1 Monitoring and Observability

  
    // Infrastructure/Logging/SerilogConfiguration.cs
using Serilog;
using Serilog.Events;
using Serilog.Sinks.ApplicationInsights.Sinks.ApplicationInsights.TelemetryConverters;
using SmartCommerce.Web.Middleware;

namespace SmartCommerce.Infrastructure.Logging
{
    public static class SerilogConfiguration
    {
        public static IHostBuilder UseSerilogConfiguration(this IHostBuilder builder, IConfiguration configuration)
        {
            return builder.UseSerilog((context, services, loggerConfiguration) =>
            {
                var applicationInsightsConnectionString = configuration["ApplicationInsights:ConnectionString"];
                
                loggerConfiguration
                    .ReadFrom.Configuration(context.Configuration)
                    .ReadFrom.Services(services)
                    .Enrich.FromLogContext()
                    .Enrich.WithProperty("Application", "SmartCommerce")
                    .Enrich.WithProperty("Environment", context.HostingEnvironment.EnvironmentName)
                    .Enrich.With<ActivityEnricher>()
                    .Enrich.With<CorrelationIdEnricher>()
                    .WriteTo.Console(
                        outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}")
                    .WriteTo.Debug()
                    .WriteTo.File(
                        "logs/smartcommerce-.log",
                        rollingInterval: RollingInterval.Day,
                        retainedFileCountLimit: 7,
                        shared: true);

                if (!string.IsNullOrEmpty(applicationInsightsConnectionString))
                {
                    loggerConfiguration.WriteTo.ApplicationInsights(
                        applicationInsightsConnectionString,
                        new TraceTelemetryConverter());
                }

                if (context.HostingEnvironment.IsDevelopment())
                {
                    loggerConfiguration.MinimumLevel.Override("Microsoft", LogEventLevel.Information);
                    loggerConfiguration.MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning);
                }
                else
                {
                    loggerConfiguration.MinimumLevel.Override("Microsoft", LogEventLevel.Warning);
                    loggerConfiguration.MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning);
                }
            });
        }
    }

    public class CorrelationIdEnricher : ILogEventEnricher
    {
        public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
        {
            var correlationId = CorrelationIdMiddleware.GetCorrelationId();
            if (!string.IsNullOrEmpty(correlationId))
            {
                var correlationIdProperty = propertyFactory.CreateProperty("CorrelationId", correlationId);
                logEvent.AddPropertyIfAbsent(correlationIdProperty);
            }
        }
    }

    public class ActivityEnricher : ILogEventEnricher
    {
        public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
        {
            var activity = Activity.Current;
            if (activity != null)
            {
                logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("TraceId", activity.TraceId));
                logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("SpanId", activity.SpanId));
            }
        }
    }
}
  

This comprehensive full-stack ASP.NET Core project demonstrates enterprise-grade development practices with AI integration, cloud-native architecture, and production-ready features. The project showcases real-world e-commerce functionality with intelligent recommendations, real-time updates, and a scalable microservices architecture.

The implementation follows Clean Architecture principles, incorporates domain-driven design, and demonstrates advanced patterns like CQRS, Event Sourcing, and AI-powered features. The project is production-ready with proper testing, monitoring, logging, and deployment configurations.