ASP.NET Core  

RESTful API Mastery: ASP.NET Core Best Practices, Versioning & Serialization Complete Guide - Part-10 of 40

Table of Contents

  1. Introduction to RESTful APIs

  2. REST Principles & Constraints

  3. Setting Up a RESTful API Project

  4. API Versioning Strategies

  5. Advanced Serialization Techniques

  6. Input Validation & Model Binding

  7. Error Handling & Exception Management

  8. Authentication & Authorization

  9. Caching Strategies

  10. Rate Limiting & Throttling

  11. Documentation with Swagger/OpenAPI

  12. Testing RESTful APIs

  13. Performance Optimization

  14. Real-World E-Commerce API

  15. Microservices Communication

  16. Monitoring & Logging

  17. Security Best Practices

  18. Deployment Strategies

  19. API Gateway Integration

  20. Future Trends & Best Practices

1. Introduction to RESTful APIs {#introduction}

REST (Representational State Transfer) has become the standard architectural style for designing networked applications. In the  ASP.NET  Core ecosystem, building RESTful APIs involves creating services that adhere to REST principles while leveraging the powerful features of the framework.

What Makes an API RESTful?

RESTful APIs are characterized by:

  • Statelessness: Each request contains all necessary information

  • Resource-Based: Focus on resources rather than actions

  • Uniform Interface: Consistent naming and structure

  • Cacheability: Responses can be cached

  • Layered System: Architecture can have multiple layers

  • Code on Demand: Optional execution of client-side code

2. REST Principles & Constraints {#rest-principles}

Core REST Constraints

  
    // Example demonstrating REST principles in ASP.NET Core
public class RESTPrinciplesDemo
{
    // Stateless - each request contains all necessary information
    public class StatelessAuthentication
    {
        // Token must be included in every request
        public string BearerToken { get; set; } = string.Empty;
    }

    // Resource-based URL structure
    public class ResourceBasedUrls
    {
        // Good RESTful design
        // GET /api/products/1
        // POST /api/products
        // PUT /api/products/1
        // DELETE /api/products/1
        
        // Not RESTful
        // GET /api/getProduct?id=1
        // POST /api/createProduct
        // POST /api/updateProduct
        // GET /api/deleteProduct?id=1
    }

    // Uniform interface with standard HTTP methods
    public class UniformInterface
    {
        // GET - Retrieve resources
        // POST - Create resources
        // PUT - Update/replace resources
        // PATCH - Partial update
        // DELETE - Remove resources
    }
}
  

3. Setting Up RESTful API Project {#setup-project}

Complete Project Structure

  
    RESTfulApiDemo/
├── Controllers/
│   ├── ProductsController.cs
│   ├── OrdersController.cs
│   └── UsersController.cs
├── Models/
│   ├── DTOs/
│   ├── Entities/
│   └── Requests/
├── Services/
│   ├── Interfaces/
│   └── Implementations/
├── Data/
├── Middleware/
├── Filters/
├── Program.cs
└── appsettings.json
  

Program.cs - Complete Setup

  
    using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using RESTfulApiDemo.Data;
using RESTfulApiDemo.Services;
using System.Text.Json.Serialization;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container
builder.Services.AddControllers()
    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
        options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
        options.JsonSerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase;
    });

// Database Context
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

// Custom Services
builder.Services.AddScoped<IProductService, ProductService>();
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddScoped<IUserService, UserService>();

// API Versioning
builder.Services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new ApiVersion(1, 0);
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.ReportApiVersions = true;
});

builder.Services.AddVersionedApiExplorer(options =>
{
    options.GroupNameFormat = "'v'VVV";
    options.SubstituteApiVersionInUrl = true;
});

// Swagger/OpenAPI
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo
    {
        Title = "E-Commerce API",
        Version = "v1",
        Description = "A comprehensive RESTful API for e-commerce operations"
    });
});

// Caching
builder.Services.AddMemoryCache();
builder.Services.AddResponseCaching();

var app = builder.Build();

// Configure the HTTP request pipeline
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI(options =>
    {
        options.SwaggerEndpoint("/swagger/v1/swagger.json", "E-Commerce API v1");
    });
}

app.UseHttpsRedirection();
app.UseResponseCaching();
app.UseAuthorization();
app.MapControllers();

app.Run();
  

4. API Versioning Strategies {#api-versioning}

Multiple Versioning Approaches

  
    // Models for versioning demonstration
namespace RESTfulApiDemo.Models.V1
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; } = string.Empty;
        public string Description { get; set; } = string.Empty;
        public decimal Price { get; set; }
        public int StockQuantity { get; set; }
        public DateTime CreatedDate { get; set; }
    }

    public class ProductResponse
    {
        public int Id { get; set; }
        public string Name { get; set; } = string.Empty;
        public string Description { get; set; } = string.Empty;
        public decimal Price { get; set; }
        public int Stock { get; set; }
    }
}

namespace RESTfulApiDemo.Models.V2
{
    public class Product
    {
        public int 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 int StockQuantity { get; set; }
        public ProductCategory Category { get; set; } = new();
        public List<string> Tags { get; set; } = new();
        public DateTime CreatedDate { get; set; }
        public DateTime? UpdatedDate { get; set; }
    }

    public class ProductCategory
    {
        public int Id { get; set; }
        public string Name { get; set; } = string.Empty;
        public string Description { get; set; } = string.Empty;
    }

    public class ProductResponse
    {
        public int 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 int Stock { get; set; }
        public string Category { get; set; } = string.Empty;
        public List<string> Tags { get; set; } = new();
    }
}
  

Version 1 Controller

  
    using Microsoft.AspNetCore.Mvc;
using RESTfulApiDemo.Models.V1;
using RESTfulApiDemo.Services;

namespace RESTfulApiDemo.Controllers.V1
{
    [ApiController]
    [ApiVersion("1.0")]
    [Route("api/v{version:apiVersion}/[controller]")]
    public class ProductsController : ControllerBase
    {
        private readonly IProductService _productService;

        public ProductsController(IProductService productService)
        {
            _productService = productService;
        }

        [HttpGet]
        [ProducesResponseType(typeof(List<ProductResponse>), StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status500InternalServerError)]
        public async Task<IActionResult> GetProducts()
        {
            try
            {
                var products = await _productService.GetAllProductsAsync();
                var response = products.Select(p => new ProductResponse
                {
                    Id = p.Id,
                    Name = p.Name,
                    Description = p.Description,
                    Price = p.Price,
                    Stock = p.StockQuantity
                }).ToList();

                return Ok(response);
            }
            catch (Exception ex)
            {
                return StatusCode(500, new { error = "An error occurred while retrieving products" });
            }
        }

        [HttpGet("{id}")]
        [ProducesResponseType(typeof(ProductResponse), StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public async Task<IActionResult> GetProduct(int id)
        {
            var product = await _productService.GetProductByIdAsync(id);
            if (product == null)
            {
                return NotFound(new { error = $"Product with ID {id} not found" });
            }

            var response = new ProductResponse
            {
                Id = product.Id,
                Name = product.Name,
                Description = product.Description,
                Price = product.Price,
                Stock = product.StockQuantity
            };

            return Ok(response);
        }

        [HttpPost]
        [ProducesResponseType(typeof(ProductResponse), StatusCodes.Status201Created)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        public async Task<IActionResult> CreateProduct([FromBody] Product product)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            var createdProduct = await _productService.CreateProductAsync(product);
            var response = new ProductResponse
            {
                Id = createdProduct.Id,
                Name = createdProduct.Name,
                Description = createdProduct.Description,
                Price = createdProduct.Price,
                Stock = createdProduct.StockQuantity
            };

            return CreatedAtAction(nameof(GetProduct), new { id = createdProduct.Id, version = "1" }, response);
        }
    }
}
  

Version 2 Controller with Enhanced Features

  
    using Microsoft.AspNetCore.Mvc;
using RESTfulApiDemo.Models.V2;
using RESTfulApiDemo.Services;

namespace RESTfulApiDemo.Controllers.V2
{
    [ApiController]
    [ApiVersion("2.0")]
    [Route("api/v{version:apiVersion}/[controller]")]
    public class ProductsController : ControllerBase
    {
        private readonly IProductService _productService;

        public ProductsController(IProductService productService)
        {
            _productService = productService;
        }

        [HttpGet]
        [ProducesResponseType(typeof(PagedResponse<ProductResponse>), StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        public async Task<IActionResult> GetProducts(
            [FromQuery] int page = 1,
            [FromQuery] int pageSize = 20,
            [FromQuery] string? category = null,
            [FromQuery] decimal? minPrice = null,
            [FromQuery] decimal? maxPrice = null,
            [FromQuery] string? search = null)
        {
            if (page < 1 || pageSize < 1 || pageSize > 100)
            {
                return BadRequest(new { error = "Invalid pagination parameters" });
            }

            var (products, totalCount) = await _productService.GetProductsPagedAsync(
                page, pageSize, category, minPrice, maxPrice, search);

            var response = new PagedResponse<ProductResponse>
            {
                Data = products.Select(p => new ProductResponse
                {
                    Id = p.Id,
                    Name = p.Name,
                    Description = p.Description,
                    Price = p.Price,
                    Currency = p.Currency,
                    Stock = p.StockQuantity,
                    Category = p.Category.Name,
                    Tags = p.Tags
                }).ToList(),
                Page = page,
                PageSize = pageSize,
                TotalCount = totalCount,
                TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize)
            };

            return Ok(response);
        }

        [HttpGet("{id}")]
        [ProducesResponseType(typeof(ProductResponse), StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        [ResponseCache(Duration = 30)] // Cache for 30 seconds
        public async Task<IActionResult> GetProduct(int id)
        {
            var product = await _productService.GetProductByIdAsync(id);
            if (product == null)
            {
                return NotFound(new { error = $"Product with ID {id} not found" });
            }

            var response = new ProductResponse
            {
                Id = product.Id,
                Name = product.Name,
                Description = product.Description,
                Price = product.Price,
                Currency = product.Currency,
                Stock = product.StockQuantity,
                Category = product.Category.Name,
                Tags = product.Tags
            };

            return Ok(response);
        }

        [HttpPost]
        [ProducesResponseType(typeof(ProductResponse), StatusCodes.Status201Created)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        public async Task<IActionResult> CreateProduct([FromBody] Product product)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            var createdProduct = await _productService.CreateProductAsync(product);
            var response = new ProductResponse
            {
                Id = createdProduct.Id,
                Name = createdProduct.Name,
                Description = createdProduct.Description,
                Price = createdProduct.Price,
                Currency = createdProduct.Currency,
                Stock = createdProduct.StockQuantity,
                Category = createdProduct.Category.Name,
                Tags = createdProduct.Tags
            };

            return CreatedAtAction(nameof(GetProduct), new { id = createdProduct.Id, version = "2" }, response);
        }

        [HttpPatch("{id}")]
        [ProducesResponseType(typeof(ProductResponse), StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        public async Task<IActionResult> UpdateProduct(int id, [FromBody] ProductUpdateRequest request)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            var updatedProduct = await _productService.UpdateProductAsync(id, request);
            if (updatedProduct == null)
            {
                return NotFound(new { error = $"Product with ID {id} not found" });
            }

            var response = new ProductResponse
            {
                Id = updatedProduct.Id,
                Name = updatedProduct.Name,
                Description = updatedProduct.Description,
                Price = updatedProduct.Price,
                Currency = updatedProduct.Currency,
                Stock = updatedProduct.StockQuantity,
                Category = updatedProduct.Category.Name,
                Tags = updatedProduct.Tags
            };

            return Ok(response);
        }
    }
}
  

5. Advanced Serialization Techniques {#serialization}

Custom JSON Serialization Configuration

  
    using System.Text.Json;
using System.Text.Json.Serialization;

namespace RESTfulApiDemo.Configuration
{
    public static class JsonSerializationConfiguration
    {
        public static JsonSerializerOptions ConfigureJsonOptions()
        {
            return new JsonSerializerOptions
            {
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
                DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
                ReferenceHandler = ReferenceHandler.IgnoreCycles,
                WriteIndented = true,
                Converters =
                {
                    new JsonStringEnumConverter(JsonNamingPolicy.CamelCase),
                    new CustomDateTimeConverter()
                }
            };
        }
    }

    public class CustomDateTimeConverter : JsonConverter<DateTime>
    {
        private readonly string _format = "yyyy-MM-ddTHH:mm:ss.fffZ";

        public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            return DateTime.Parse(reader.GetString()!);
        }

        public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
        {
            writer.WriteStringValue(value.ToUniversalTime().ToString(_format));
        }
    }
}
  

Custom Response Formatting

  
    using System.Text.Json;

namespace RESTfulApiDemo.Models
{
    public class ApiResponse<T>
    {
        public bool Success { get; set; }
        public string Message { get; set; } = string.Empty;
        public T? Data { get; set; }
        public DateTime Timestamp { get; set; } = DateTime.UtcNow;
        public string? RequestId { get; set; }

        public static ApiResponse<T> SuccessResponse(T data, string message = "Success")
        {
            return new ApiResponse<T>
            {
                Success = true,
                Message = message,
                Data = data
            };
        }

        public static ApiResponse<T> ErrorResponse(string message, string? requestId = null)
        {
            return new ApiResponse<T>
            {
                Success = false,
                Message = message,
                RequestId = requestId
            };
        }
    }

    public class PagedResponse<T>
    {
        public List<T> Data { get; set; } = new();
        public int Page { get; set; }
        public int PageSize { get; set; }
        public int TotalCount { get; set; }
        public int TotalPages { get; set; }
        public bool HasPrevious => Page > 1;
        public bool HasNext => Page < TotalPages;
    }
}
  

6. Input Validation & Model Binding {#validation-binding}

Advanced Validation with FluentValidation

  
    using FluentValidation;

namespace RESTfulApiDemo.Models.V2
{
    public class Product
    {
        public int 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 int StockQuantity { get; set; }
        public ProductCategory Category { get; set; } = new();
        public List<string> Tags { get; set; } = new();
        public DateTime CreatedDate { get; set; }
        public DateTime? UpdatedDate { get; set; }
    }

    public class ProductCreateRequest
    {
        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 int StockQuantity { get; set; }
        public int CategoryId { get; set; }
        public List<string> Tags { get; set; } = new();
    }

    public class ProductUpdateRequest
    {
        public string? Name { get; set; }
        public string? Description { get; set; }
        public decimal? Price { get; set; }
        public int? StockQuantity { get; set; }
        public int? CategoryId { get; set; }
        public List<string>? Tags { get; set; }
    }
}

namespace RESTfulApiDemo.Validators
{
    public class ProductCreateRequestValidator : AbstractValidator<ProductCreateRequest>
    {
        public ProductCreateRequestValidator()
        {
            RuleFor(x => x.Name)
                .NotEmpty().WithMessage("Product name is required")
                .Length(3, 100).WithMessage("Product name must be between 3 and 100 characters")
                .Matches("^[a-zA-Z0-9 ]+$").WithMessage("Product name can only contain letters, numbers, and spaces");

            RuleFor(x => x.Description)
                .MaximumLength(500).WithMessage("Description cannot exceed 500 characters");

            RuleFor(x => x.Price)
                .GreaterThan(0).WithMessage("Price must be greater than 0")
                .LessThan(1000000).WithMessage("Price must be less than 1,000,000");

            RuleFor(x => x.Currency)
                .Length(3).WithMessage("Currency must be a 3-letter code")
                .Matches("^[A-Z]{3}$").WithMessage("Currency must be in ISO 4217 format");

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

            RuleFor(x => x.CategoryId)
                .GreaterThan(0).WithMessage("Category ID must be valid");

            RuleForEach(x => x.Tags)
                .MaximumLength(50).WithMessage("Tag cannot exceed 50 characters")
                .Matches("^[a-zA-Z0-9-#]+$").WithMessage("Tags can only contain letters, numbers, hyphens, and hashes");
        }
    }

    public class ProductUpdateRequestValidator : AbstractValidator<ProductUpdateRequest>
    {
        public ProductUpdateRequestValidator()
        {
            RuleFor(x => x.Name)
                .Length(3, 100).When(x => !string.IsNullOrEmpty(x.Name))
                .WithMessage("Product name must be between 3 and 100 characters");

            RuleFor(x => x.Description)
                .MaximumLength(500).When(x => !string.IsNullOrEmpty(x.Description))
                .WithMessage("Description cannot exceed 500 characters");

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

            RuleFor(x => x.StockQuantity)
                .GreaterThanOrEqualTo(0).When(x => x.StockQuantity.HasValue)
                .WithMessage("Stock quantity cannot be negative");
        }
    }
}
  

Custom Model Binders

  
    using Microsoft.AspNetCore.Mvc.ModelBinding;
using System.Globalization;

namespace RESTfulApiDemo.ModelBinders
{
    public class DecimalModelBinder : IModelBinder
    {
        public Task BindModelAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext == null)
            {
                throw new ArgumentNullException(nameof(bindingContext));
            }

            var modelName = bindingContext.ModelName;
            var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);

            if (valueProviderResult == ValueProviderResult.None)
            {
                return Task.CompletedTask;
            }

            bindingContext.ModelState.SetModelValue(modelName, valueProviderResult);

            var value = valueProviderResult.FirstValue;

            if (string.IsNullOrEmpty(value))
            {
                return Task.CompletedTask;
            }

            // Remove any commas or spaces
            value = value.Replace(",", "").Replace(" ", "");

            if (!decimal.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var decimalValue))
            {
                bindingContext.ModelState.TryAddModelError(modelName, "Decimal value is invalid.");
                return Task.CompletedTask;
            }

            bindingContext.Result = ModelBindingResult.Success(decimalValue);
            return Task.CompletedTask;
        }
    }

    public class DecimalModelBinderProvider : IModelBinderProvider
    {
        public IModelBinder? GetBinder(ModelBinderProviderContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (context.Metadata.ModelType == typeof(decimal) || context.Metadata.ModelType == typeof(decimal?))
            {
                return new DecimalModelBinder();
            }

            return null;
        }
    }
}
  

7. Error Handling & Exception Management {#error-handling}

Global Exception Handling Middleware

  
    using System.Net;
using System.Text.Json;

namespace RESTfulApiDemo.Middleware
{
    public class GlobalExceptionHandlerMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger<GlobalExceptionHandlerMiddleware> _logger;
        private readonly IWebHostEnvironment _environment;

        public GlobalExceptionHandlerMiddleware(
            RequestDelegate next,
            ILogger<GlobalExceptionHandlerMiddleware> logger,
            IWebHostEnvironment environment)
        {
            _next = next;
            _logger = logger;
            _environment = environment;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            try
            {
                await _next(context);
            }
            catch (Exception ex)
            {
                await HandleExceptionAsync(context, ex);
            }
        }

        private async Task HandleExceptionAsync(HttpContext context, Exception exception)
        {
            var code = HttpStatusCode.InternalServerError;
            var message = "An unexpected error occurred.";
            string? stackTrace = null;

            switch (exception)
            {
                case KeyNotFoundException _:
                    code = HttpStatusCode.NotFound;
                    message = "The requested resource was not found.";
                    break;
                case ArgumentException _:
                case InvalidOperationException _:
                    code = HttpStatusCode.BadRequest;
                    message = exception.Message;
                    break;
                case UnauthorizedAccessException _:
                    code = HttpStatusCode.Unauthorized;
                    message = "Access denied.";
                    break;
                case NotImplementedException _:
                    code = HttpStatusCode.NotImplemented;
                    message = "The requested functionality is not implemented.";
                    break;
            }

            // Log the exception
            _logger.LogError(exception, "An exception occurred: {Message}", exception.Message);

            // Include stack trace in development
            if (_environment.IsDevelopment())
            {
                stackTrace = exception.StackTrace;
            }

            var response = new
            {
                Success = false,
                Message = message,
                StackTrace = stackTrace,
                Timestamp = DateTime.UtcNow,
                RequestId = context.TraceIdentifier
            };

            var result = JsonSerializer.Serialize(response, new JsonSerializerOptions
            {
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase
            });

            context.Response.ContentType = "application/json";
            context.Response.StatusCode = (int)code;
            await context.Response.WriteAsync(result);
        }
    }

    // Custom exceptions
    public class ProductNotFoundException : Exception
    {
        public ProductNotFoundException(int productId) 
            : base($"Product with ID {productId} was not found.") 
        {
            ProductId = productId;
        }

        public int ProductId { get; }
    }

    public class InsufficientStockException : Exception
    {
        public InsufficientStockException(string productName, int requested, int available) 
            : base($"Insufficient stock for {productName}. Requested: {requested}, Available: {available}")
        {
            ProductName = productName;
            RequestedQuantity = requested;
            AvailableQuantity = available;
        }

        public string ProductName { get; }
        public int RequestedQuantity { get; }
        public int AvailableQuantity { get; }
    }

    public class DuplicateProductException : Exception
    {
        public DuplicateProductException(string productName) 
            : base($"A product with the name '{productName}' already exists.")
        {
            ProductName = productName;
        }

        public string ProductName { get; }
    }
}
  

Exception Handling Filters

  
    using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace RESTfulApiDemo.Filters
{
    public class ApiExceptionFilterAttribute : ExceptionFilterAttribute
    {
        private readonly IDictionary<Type, Action<ExceptionContext>> _exceptionHandlers;

        public ApiExceptionFilterAttribute()
        {
            _exceptionHandlers = new Dictionary<Type, Action<ExceptionContext>>
            {
                { typeof(ProductNotFoundException), HandleProductNotFoundException },
                { typeof(InsufficientStockException), HandleInsufficientStockException },
                { typeof(DuplicateProductException), HandleDuplicateProductException },
                { typeof(ValidationException), HandleValidationException }
            };
        }

        public override void OnException(ExceptionContext context)
        {
            HandleException(context);
            base.OnException(context);
        }

        private void HandleException(ExceptionContext context)
        {
            var type = context.Exception.GetType();
            if (_exceptionHandlers.ContainsKey(type))
            {
                _exceptionHandlers[type].Invoke(context);
                return;
            }

            HandleUnknownException(context);
        }

        private void HandleProductNotFoundException(ExceptionContext context)
        {
            var exception = (ProductNotFoundException)context.Exception;
            var details = new ProblemDetails
            {
                Type = "https://tools.ietf.org/html/rfc7231#section-6.5.4",
                Title = "Product not found",
                Detail = exception.Message,
                Instance = context.HttpContext.Request.Path
            };

            context.Result = new NotFoundObjectResult(details);
            context.ExceptionHandled = true;
        }

        private void HandleInsufficientStockException(ExceptionContext context)
        {
            var exception = (InsufficientStockException)context.Exception;
            var details = new ProblemDetails
            {
                Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1",
                Title = "Insufficient stock",
                Detail = exception.Message,
                Instance = context.HttpContext.Request.Path,
                Extensions = 
                {
                    ["productName"] = exception.ProductName,
                    ["requestedQuantity"] = exception.RequestedQuantity,
                    ["availableQuantity"] = exception.AvailableQuantity
                }
            };

            context.Result = new BadRequestObjectResult(details);
            context.ExceptionHandled = true;
        }

        private void HandleDuplicateProductException(ExceptionContext context)
        {
            var exception = (DuplicateProductException)context.Exception;
            var details = new ProblemDetails
            {
                Type = "https://tools.ietf.org/html/rfc7231#section-6.5.8",
                Title = "Duplicate product",
                Detail = exception.Message,
                Instance = context.HttpContext.Request.Path
            };

            context.Result = new ConflictObjectResult(details);
            context.ExceptionHandled = true;
        }

        private void HandleValidationException(ExceptionContext context)
        {
            var exception = (ValidationException)context.Exception;
            var details = new ValidationProblemDetails(exception.Errors)
            {
                Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1",
                Title = "Validation failed",
                Detail = "One or more validation errors occurred.",
                Instance = context.HttpContext.Request.Path
            };

            context.Result = new BadRequestObjectResult(details);
            context.ExceptionHandled = true;
        }

        private void HandleUnknownException(ExceptionContext context)
        {
            var details = new ProblemDetails
            {
                Type = "https://tools.ietf.org/html/rfc7231#section-6.6.1",
                Title = "An error occurred while processing your request.",
                Status = StatusCodes.Status500InternalServerError,
                Instance = context.HttpContext.Request.Path
            };

            if (context.HttpContext.RequestServices.GetService<IWebHostEnvironment>()?.IsDevelopment() == true)
            {
                details.Detail = context.Exception.ToString();
                details.Extensions["stackTrace"] = context.Exception.StackTrace;
            }

            context.Result = new ObjectResult(details)
            {
                StatusCode = StatusCodes.Status500InternalServerError
            };

            context.ExceptionHandled = true;
        }
    }

    public class ValidationException : Exception
    {
        public ValidationException() : base("One or more validation failures have occurred.")
        {
            Errors = new Dictionary<string, string[]>();
        }

        public ValidationException(IDictionary<string, string[]> errors) 
            : this()
        {
            Errors = errors;
        }

        public IDictionary<string, string[]> Errors { get; }
    }
}
  

8. Authentication & Authorization {#authentication}

JWT Authentication Setup

  
    using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;

namespace RESTfulApiDemo.Configuration
{
    public static class AuthenticationConfiguration
    {
        public static IServiceCollection AddJwtAuthentication(this IServiceCollection services, IConfiguration configuration)
        {
            var jwtSettings = configuration.GetSection("JwtSettings");
            var secretKey = jwtSettings["SecretKey"] 
                ?? throw new InvalidOperationException("JWT Secret Key is not configured");

            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidateLifetime = true,
                    ValidateIssuerSigningKey = true,
                    ValidIssuer = jwtSettings["Issuer"],
                    ValidAudience = jwtSettings["Audience"],
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)),
                    ClockSkew = TimeSpan.Zero
                };

                options.Events = new JwtBearerEvents
                {
                    OnAuthenticationFailed = context =>
                    {
                        if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                        {
                            context.Response.Headers.Add("Token-Expired", "true");
                        }
                        return Task.CompletedTask;
                    }
                };
            });

            services.AddAuthorization(options =>
            {
                options.AddPolicy("RequireAdminRole", policy => 
                    policy.RequireRole("Admin"));
                
                options.AddPolicy("RequireCustomerRole", policy => 
                    policy.RequireRole("Customer", "Admin"));
                
                options.AddPolicy("Over18", policy => 
                    policy.RequireAssertion(context =>
                        context.User.HasClaim(c => 
                            (c.Type == "DateOfBirth" && 
                             DateTime.TryParse(c.Value, out var dateOfBirth) && 
                             dateOfBirth <= DateTime.Now.AddYears(-18)) ||
                            context.User.IsInRole("Admin")
                        )));
            });

            return services;
        }
    }
}
  

User Management Models and Services

  
    using System.ComponentModel.DataAnnotations;
using System.Security.Claims;

namespace RESTfulApiDemo.Models
{
    public class User
    {
        public int Id { get; set; }
        
        [Required]
        [StringLength(50)]
        public string Username { get; set; } = string.Empty;
        
        [Required]
        [EmailAddress]
        public string Email { get; set; } = string.Empty;
        
        public string PasswordHash { get; set; } = string.Empty;
        
        [StringLength(100)]
        public string FirstName { get; set; } = string.Empty;
        
        [StringLength(100)]
        public string LastName { get; set; } = string.Empty;
        
        public DateTime DateOfBirth { get; set; }
        
        public string Role { get; set; } = "Customer";
        
        public bool IsActive { get; set; } = true;
        
        public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
        
        public DateTime? UpdatedAt { get; set; }
        
        public DateTime? LastLogin { get; set; }
    }

    public class UserRegisterRequest
    {
        [Required]
        [StringLength(50, MinimumLength = 3)]
        public string Username { get; set; } = string.Empty;

        [Required]
        [EmailAddress]
        public string Email { get; set; } = string.Empty;

        [Required]
        [StringLength(100, MinimumLength = 6)]
        public string Password { get; set; } = string.Empty;

        [Required]
        [Compare("Password")]
        public string ConfirmPassword { get; set; } = string.Empty;

        [StringLength(100)]
        public string FirstName { get; set; } = string.Empty;

        [StringLength(100)]
        public string LastName { get; set; } = string.Empty;

        public DateTime DateOfBirth { get; set; }
    }

    public class UserLoginRequest
    {
        [Required]
        public string Username { get; set; } = string.Empty;

        [Required]
        public string Password { get; set; } = string.Empty;
    }

    public class AuthResponse
    {
        public string Token { get; set; } = string.Empty;
        public DateTime Expires { get; set; }
        public UserResponse User { get; set; } = new();
    }

    public class UserResponse
    {
        public int Id { get; set; }
        public string Username { get; set; } = string.Empty;
        public string Email { get; set; } = string.Empty;
        public string FirstName { get; set; } = string.Empty;
        public string LastName { get; set; } = string.Empty;
        public string Role { get; set; } = string.Empty;
        public DateTime CreatedAt { get; set; }
    }
}

namespace RESTfulApiDemo.Services
{
    public interface IUserService
    {
        Task<User?> AuthenticateAsync(string username, string password);
        Task<User> RegisterAsync(UserRegisterRequest request);
        Task<User?> GetUserByIdAsync(int id);
        Task<bool> UserExistsAsync(string username, string email);
        string GenerateJwtToken(User user);
        ClaimsPrincipal? ValidateToken(string token);
    }

    public class UserService : IUserService
    {
        private readonly List<User> _users = new();
        private readonly IConfiguration _configuration;

        public UserService(IConfiguration configuration)
        {
            _configuration = configuration;
            
            // Add default admin user
            _users.Add(new User 
            { 
                Id = 1, 
                Username = "admin", 
                Email = "[email protected]",
                PasswordHash = BCrypt.Net.BCrypt.HashPassword("admin123"),
                FirstName = "System",
                LastName = "Administrator",
                Role = "Admin",
                DateOfBirth = new DateTime(1980, 1, 1)
            });
        }

        public async Task<User?> AuthenticateAsync(string username, string password)
        {
            var user = _users.FirstOrDefault(u => 
                u.Username == username || u.Email == username);

            if (user == null || !BCrypt.Net.BCrypt.Verify(password, user.PasswordHash))
                return null;

            user.LastLogin = DateTime.UtcNow;
            return user;
        }

        public async Task<User> RegisterAsync(UserRegisterRequest request)
        {
            if (await UserExistsAsync(request.Username, request.Email))
                throw new InvalidOperationException("Username or email already exists");

            var user = new User
            {
                Id = _users.Count + 1,
                Username = request.Username,
                Email = request.Email,
                PasswordHash = BCrypt.Net.BCrypt.HashPassword(request.Password),
                FirstName = request.FirstName,
                LastName = request.LastName,
                DateOfBirth = request.DateOfBirth,
                Role = "Customer"
            };

            _users.Add(user);
            return user;
        }

        public async Task<User?> GetUserByIdAsync(int id)
        {
            return _users.FirstOrDefault(u => u.Id == id);
        }

        public async Task<bool> UserExistsAsync(string username, string email)
        {
            return _users.Any(u => u.Username == username || u.Email == email);
        }

        public string GenerateJwtToken(User user)
        {
            var tokenHandler = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler();
            var key = Encoding.ASCII.GetBytes(_configuration["JwtSettings:SecretKey"]!);
            
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(new[]
                {
                    new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                    new Claim(ClaimTypes.Name, user.Username),
                    new Claim(ClaimTypes.Email, user.Email),
                    new Claim(ClaimTypes.Role, user.Role),
                    new Claim("DateOfBirth", user.DateOfBirth.ToString("yyyy-MM-dd"))
                }),
                Expires = DateTime.UtcNow.AddHours(24),
                Issuer = _configuration["JwtSettings:Issuer"],
                Audience = _configuration["JwtSettings:Audience"],
                SigningCredentials = new SigningCredentials(
                    new SymmetricSecurityKey(key), 
                    SecurityAlgorithms.HmacSha256Signature)
            };

            var token = tokenHandler.CreateToken(tokenDescriptor);
            return tokenHandler.WriteToken(token);
        }

        public ClaimsPrincipal? ValidateToken(string token)
        {
            try
            {
                var tokenHandler = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler();
                var key = Encoding.ASCII.GetBytes(_configuration["JwtSettings:SecretKey"]!);
                
                var principal = tokenHandler.ValidateToken(token, new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(key),
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidIssuer = _configuration["JwtSettings:Issuer"],
                    ValidAudience = _configuration["JwtSettings:Audience"],
                    ClockSkew = TimeSpan.Zero
                }, out _);

                return principal;
            }
            catch
            {
                return null;
            }
        }
    }
}
  

9. Caching Strategies {#caching}

Advanced Caching Implementation

  
    using Microsoft.Extensions.Caching.Memory;

namespace RESTfulApiDemo.Services
{
    public interface ICacheService
    {
        Task<T?> GetOrCreateAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiration = null);
        void Remove(string key);
        Task<T?> GetAsync<T>(string key);
        Task SetAsync<T>(string key, T value, TimeSpan? expiration = null);
    }

    public class MemoryCacheService : ICacheService
    {
        private readonly IMemoryCache _memoryCache;
        private readonly ILogger<MemoryCacheService> _logger;

        public MemoryCacheService(IMemoryCache memoryCache, ILogger<MemoryCacheService> logger)
        {
            _memoryCache = memoryCache;
            _logger = logger;
        }

        public async Task<T?> GetOrCreateAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiration = null)
        {
            if (_memoryCache.TryGetValue(key, out T? cachedValue))
            {
                _logger.LogDebug("Cache hit for key: {Key}", key);
                return cachedValue;
            }

            _logger.LogDebug("Cache miss for key: {Key}", key);
            var value = await factory();

            var cacheOptions = new MemoryCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = expiration ?? TimeSpan.FromMinutes(5)
            };

            _memoryCache.Set(key, value, cacheOptions);
            return value;
        }

        public async Task<T?> GetAsync<T>(string key)
        {
            return _memoryCache.TryGetValue(key, out T? value) ? value : default;
        }

        public async Task SetAsync<T>(string key, T value, TimeSpan? expiration = null)
        {
            var cacheOptions = new MemoryCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = expiration ?? TimeSpan.FromMinutes(5)
            };

            _memoryCache.Set(key, value, cacheOptions);
        }

        public void Remove(string key)
        {
            _memoryCache.Remove(key);
            _logger.LogDebug("Cache removed for key: {Key}", key);
        }
    }

    public class CacheKeyGenerator
    {
        public static string GenerateProductKey(int productId) => $"product_{productId}";
        public static string GenerateProductsListKey(string? category = null, decimal? minPrice = null, decimal? maxPrice = null) 
            => $"products_{category}_{minPrice}_{maxPrice}";
        public static string GenerateUserKey(int userId) => $"user_{userId}";
    }
}

// Cached Product Service
public class CachedProductService : IProductService
{
    private readonly IProductService _decoratedService;
    private readonly ICacheService _cacheService;
    private readonly ILogger<CachedProductService> _logger;

    public CachedProductService(
        IProductService decoratedService, 
        ICacheService cacheService,
        ILogger<CachedProductService> logger)
    {
        _decoratedService = decoratedService;
        _cacheService = cacheService;
        _logger = logger;
    }

    public async Task<Product?> GetProductByIdAsync(int id)
    {
        var cacheKey = CacheKeyGenerator.GenerateProductKey(id);
        
        return await _cacheService.GetOrCreateAsync(cacheKey, 
            async () => await _decoratedService.GetProductByIdAsync(id),
            TimeSpan.FromMinutes(10));
    }

    public async Task<(List<Product> Products, int TotalCount)> GetProductsPagedAsync(
        int page, int pageSize, string? category, decimal? minPrice, decimal? maxPrice, string? search)
    {
        // For paginated results, we might not want to cache all combinations
        // Alternatively, cache individual products and reconstruct lists
        return await _decoratedService.GetProductsPagedAsync(page, pageSize, category, minPrice, maxPrice, search);
    }

    public async Task<Product> CreateProductAsync(Product product)
    {
        var createdProduct = await _decoratedService.CreateProductAsync(product);
        
        // Invalidate relevant caches
        var cacheKey = CacheKeyGenerator.GenerateProductKey(createdProduct.Id);
        _cacheService.Remove(cacheKey);
        _cacheService.Remove(CacheKeyGenerator.GenerateProductsListKey());
        
        return createdProduct;
    }

    public async Task<Product?> UpdateProductAsync(int id, ProductUpdateRequest request)
    {
        var updatedProduct = await _decoratedService.UpdateProductAsync(id, request);
        
        if (updatedProduct != null)
        {
            var cacheKey = CacheKeyGenerator.GenerateProductKey(id);
            _cacheService.Remove(cacheKey);
            _cacheService.Remove(CacheKeyGenerator.GenerateProductsListKey());
        }
        
        return updatedProduct;
    }

    public async Task<bool> DeleteProductAsync(int id)
    {
        var result = await _decoratedService.DeleteProductAsync(id);
        
        if (result)
        {
            var cacheKey = CacheKeyGenerator.GenerateProductKey(id);
            _cacheService.Remove(cacheKey);
            _cacheService.Remove(CacheKeyGenerator.GenerateProductsListKey());
        }
        
        return result;
    }
}
  

10. Rate Limiting & Throttling {#rate-limiting}

Custom Rate Limiting Middleware

  
    using System.Threading.RateLimiting;

namespace RESTfulApiDemo.Middleware
{
    public class RateLimitingMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger<RateLimitingMiddleware> _logger;
        private readonly RateLimiter _rateLimiter;

        public RateLimitingMiddleware(
            RequestDelegate next,
            ILogger<RateLimitingMiddleware> logger)
        {
            _next = next;
            _logger = logger;
            
            _rateLimiter = new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions
            {
                TokenLimit = 100,
                QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
                QueueLimit = 10,
                ReplenishmentPeriod = TimeSpan.FromSeconds(10),
                TokensPerPeriod = 20,
                AutoReplenishment = true
            });
        }

        public async Task InvokeAsync(HttpContext context)
        {
            var rateLimitLease = await _rateLimiter.AcquireAsync(permitCount: 1);

            if (rateLimitLease.IsAcquired)
            {
                await _next(context);
            }
            else
            {
                context.Response.StatusCode = StatusCodes.Status429TooManyRequests;
                context.Response.Headers["Retry-After"] = "60";
                
                await context.Response.WriteAsJsonAsync(new
                {
                    Success = false,
                    Message = "Too many requests. Please try again later.",
                    RetryAfter = "60 seconds",
                    Timestamp = DateTime.UtcNow
                });

                _logger.LogWarning("Rate limit exceeded for {RemoteIpAddress}", 
                    context.Connection.RemoteIpAddress);
            }

            rateLimitLease.Dispose();
        }
    }

    public class CustomRateLimitAttribute : Attribute, IAsyncActionFilter
    {
        private static readonly Dictionary<string, List<DateTime>> _requestLog = new();
        private readonly int _requestsPerMinute;
        private readonly int _requestsPerHour;

        public CustomRateLimitAttribute(int requestsPerMinute = 60, int requestsPerHour = 1000)
        {
            _requestsPerMinute = requestsPerMinute;
            _requestsPerHour = requestsPerHour;
        }

        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            var clientIp = context.HttpContext.Connection.RemoteIpAddress?.ToString() 
                ?? "unknown";
            var now = DateTime.UtcNow;

            if (!_requestLog.ContainsKey(clientIp))
            {
                _requestLog[clientIp] = new List<DateTime>();
            }

            var clientRequests = _requestLog[clientIp];

            // Clean old requests
            clientRequests.RemoveAll(t => t < now.AddHours(-1));

            // Check rate limits
            var minuteCount = clientRequests.Count(t => t > now.AddMinutes(-1));
            var hourCount = clientRequests.Count;

            if (minuteCount >= _requestsPerMinute || hourCount >= _requestsPerHour)
            {
                context.Result = new ObjectResult(new
                {
                    Success = false,
                    Message = "Rate limit exceeded",
                    Details = $"Limit: {_requestsPerMinute}/minute, {_requestsPerHour}/hour",
                    RetryAfter = 60
                })
                {
                    StatusCode = StatusCodes.Status429TooManyRequests
                };
                return;
            }

            clientRequests.Add(now);
            await next();
        }
    }
}
  

11. Documentation with Swagger/OpenAPI {#documentation}

Advanced Swagger Configuration

  
    using Microsoft.OpenApi.Models;
using System.Reflection;

namespace RESTfulApiDemo.Configuration
{
    public static class SwaggerConfiguration
    {
        public static IServiceCollection AddAdvancedSwagger(this IServiceCollection services)
        {
            services.AddSwaggerGen(options =>
            {
                options.SwaggerDoc("v1", new OpenApiInfo
                {
                    Title = "E-Commerce API",
                    Version = "v1",
                    Description = "A comprehensive RESTful API for e-commerce operations",
                    Contact = new OpenApiContact
                    {
                        Name = "API Support",
                        Email = "[email protected]",
                        Url = new Uri("https://support.ecommerceapi.com")
                    },
                    License = new OpenApiLicense
                    {
                        Name = "MIT License",
                        Url = new Uri("https://opensource.org/licenses/MIT")
                    },
                    TermsOfService = new Uri("https://ecommerceapi.com/terms")
                });

                options.SwaggerDoc("v2", new OpenApiInfo
                {
                    Title = "E-Commerce API",
                    Version = "v2",
                    Description = "Enhanced version with additional features",
                    Contact = new OpenApiContact
                    {
                        Name = "API Support",
                        Email = "[email protected]"
                    }
                });

                // 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"
                            }
                        },
                        Array.Empty<string>()
                    }
                });

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

                // Operation filters
                options.OperationFilter<AddRequiredHeaderParameter>();
                options.OperationFilter<RemoveVersionParameter>();
                options.DocumentFilter<ReplaceVersionWithExactValueInPath>();

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

                // Enable annotations
                options.EnableAnnotations();
            });

            return services;
        }
    }

    public class AddRequiredHeaderParameter : IOperationFilter
    {
        public void Apply(OpenApiOperation operation, OperationFilterContext context)
        {
            if (operation.Parameters == null)
                operation.Parameters = new List<OpenApiParameter>();

            operation.Parameters.Add(new OpenApiParameter
            {
                Name = "X-API-Version",
                In = ParameterLocation.Header,
                Required = false,
                Schema = new OpenApiSchema { Type = "string" },
                Description = "API version header"
            });
        }
    }

    public class RemoveVersionParameter : IOperationFilter
    {
        public void Apply(OpenApiOperation operation, OperationFilterContext context)
        {
            var versionParameter = operation.Parameters
                .FirstOrDefault(p => p.Name == "version");
            if (versionParameter != null)
                operation.Parameters.Remove(versionParameter);
        }
    }

    public class ReplaceVersionWithExactValueInPath : IDocumentFilter
    {
        public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
        {
            var paths = new OpenApiPaths();
            foreach (var path in swaggerDoc.Paths)
            {
                paths.Add(path.Key.Replace("v{version}", swaggerDoc.Info.Version), path.Value);
            }
            swaggerDoc.Paths = paths;
        }
    }

    public class EnumSchemaFilter : ISchemaFilter
    {
        public void Apply(OpenApiSchema schema, SchemaFilterContext context)
        {
            if (context.Type.IsEnum)
            {
                schema.Enum.Clear();
                foreach (var enumName in Enum.GetNames(context.Type))
                {
                    schema.Enum.Add(new OpenApiString(enumName));
                }
            }
        }
    }
}
  

12. Testing RESTful APIs {#testing}

Comprehensive Unit Tests

  
    using Microsoft.AspNetCore.Mvc;
using Moq;
using RESTfulApiDemo.Controllers.V2;
using RESTfulApiDemo.Models.V2;
using RESTfulApiDemo.Services;
using Xunit;

namespace RESTfulApiDemo.Tests.Controllers
{
    public class ProductsControllerTests
    {
        private readonly Mock<IProductService> _mockProductService;
        private readonly ProductsController _controller;

        public ProductsControllerTests()
        {
            _mockProductService = new Mock<IProductService>();
            _controller = new ProductsController(_mockProductService.Object);
        }

        [Fact]
        public async Task GetProducts_WithValidParameters_ReturnsOkResult()
        {
            // Arrange
            var products = new List<Product>
            {
                new() { Id = 1, Name = "Product 1", Price = 10.99m, StockQuantity = 5 },
                new() { Id = 2, Name = "Product 2", Price = 15.99m, StockQuantity = 10 }
            };

            _mockProductService.Setup(service => service.GetProductsPagedAsync(
                It.IsAny<int>(), It.IsAny<int>(), null, null, null, null))
                .ReturnsAsync((products, 2));

            // Act
            var result = await _controller.GetProducts();

            // Assert
            var okResult = Assert.IsType<OkObjectResult>(result);
            var response = Assert.IsType<PagedResponse<ProductResponse>>(okResult.Value);
            Assert.Equal(2, response.Data.Count);
            Assert.Equal(1, response.Page);
        }

        [Fact]
        public async Task GetProducts_WithInvalidPage_ReturnsBadRequest()
        {
            // Arrange
            var invalidPage = 0;

            // Act
            var result = await _controller.GetProducts(page: invalidPage);

            // Assert
            var badRequestResult = Assert.IsType<BadRequestObjectResult>(result);
            Assert.NotNull(badRequestResult.Value);
        }

        [Fact]
        public async Task GetProduct_WithValidId_ReturnsProduct()
        {
            // Arrange
            var productId = 1;
            var product = new Product 
            { 
                Id = productId, 
                Name = "Test Product", 
                Price = 19.99m, 
                StockQuantity = 5 
            };

            _mockProductService.Setup(service => service.GetProductByIdAsync(productId))
                .ReturnsAsync(product);

            // Act
            var result = await _controller.GetProduct(productId);

            // Assert
            var okResult = Assert.IsType<OkObjectResult>(result);
            var response = Assert.IsType<ProductResponse>(okResult.Value);
            Assert.Equal(productId, response.Id);
            Assert.Equal("Test Product", response.Name);
        }

        [Fact]
        public async Task GetProduct_WithInvalidId_ReturnsNotFound()
        {
            // Arrange
            var invalidId = 999;
            _mockProductService.Setup(service => service.GetProductByIdAsync(invalidId))
                .ReturnsAsync((Product?)null);

            // Act
            var result = await _controller.GetProduct(invalidId);

            // Assert
            Assert.IsType<NotFoundObjectResult>(result);
        }

        [Fact]
        public async Task CreateProduct_WithValidData_ReturnsCreatedResult()
        {
            // Arrange
            var product = new Product 
            { 
                Name = "New Product", 
                Description = "New Description", 
                Price = 29.99m, 
                StockQuantity = 10 
            };

            var createdProduct = new Product 
            { 
                Id = 1, 
                Name = product.Name, 
                Description = product.Description, 
                Price = product.Price, 
                StockQuantity = product.StockQuantity 
            };

            _mockProductService.Setup(service => service.CreateProductAsync(It.IsAny<Product>()))
                .ReturnsAsync(createdProduct);

            // Act
            var result = await _controller.CreateProduct(product);

            // Assert
            var createdResult = Assert.IsType<CreatedAtActionResult>(result);
            Assert.Equal(nameof(ProductsController.GetProduct), createdResult.ActionName);
            Assert.Equal(1, createdResult.RouteValues?["id"]);
        }

        [Fact]
        public async Task CreateProduct_WithInvalidData_ReturnsBadRequest()
        {
            // Arrange
            var invalidProduct = new Product { Name = "" }; // Invalid name
            _controller.ModelState.AddModelError("Name", "Name is required");

            // Act
            var result = await _controller.CreateProduct(invalidProduct);

            // Assert
            Assert.IsType<BadRequestObjectResult>(result);
        }
    }
}

// Integration Tests
namespace RESTfulApiDemo.Tests.Integration
{
    public class ProductsApiIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
    {
        private readonly HttpClient _client;

        public ProductsApiIntegrationTests(WebApplicationFactory<Program> factory)
        {
            _client = factory.CreateClient();
        }

        [Fact]
        public async Task GetProducts_ReturnsSuccessStatusCode()
        {
            // Act
            var response = await _client.GetAsync("/api/v2/products");

            // Assert
            response.EnsureSuccessStatusCode();
            Assert.Equal("application/json; charset=utf-8", 
                response.Content.Headers.ContentType?.ToString());
        }

        [Fact]
        public async Task GetProduct_WithValidId_ReturnsProduct()
        {
            // Arrange
            var productId = 1;

            // Act
            var response = await _client.GetAsync($"/api/v2/products/{productId}");

            // Assert
            response.EnsureSuccessStatusCode();
            
            var content = await response.Content.ReadAsStringAsync();
            var product = JsonSerializer.Deserialize<ProductResponse>(content, new JsonSerializerOptions
            {
                PropertyNameCaseInsensitive = true
            });

            Assert.NotNull(product);
            Assert.Equal(productId, product.Id);
        }

        [Fact]
        public async Task CreateProduct_WithValidData_ReturnsCreated()
        {
            // Arrange
            var product = new
            {
                Name = "Integration Test Product",
                Description = "Test Description",
                Price = 39.99m,
                StockQuantity = 15,
                Currency = "USD",
                CategoryId = 1,
                Tags = new[] { "test", "integration" }
            };

            var content = new StringContent(
                JsonSerializer.Serialize(product),
                Encoding.UTF8,
                "application/json");

            // Act
            var response = await _client.PostAsync("/api/v2/products", content);

            // Assert
            Assert.Equal(HttpStatusCode.Created, response.StatusCode);
            
            var responseContent = await response.Content.ReadAsStringAsync();
            var createdProduct = JsonSerializer.Deserialize<ProductResponse>(responseContent, 
                new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
            
            Assert.NotNull(createdProduct);
            Assert.Equal(product.Name, createdProduct.Name);
        }
    }
}
  

13. Performance Optimization {#performance}

Advanced Performance Techniques

  
    using Microsoft.EntityFrameworkCore;

namespace RESTfulApiDemo.Services
{
    public class OptimizedProductService : IProductService
    {
        private readonly ApplicationDbContext _context;
        private readonly ILogger<OptimizedProductService> _logger;

        public OptimizedProductService(ApplicationDbContext context, ILogger<OptimizedProductService> logger)
        {
            _context = context;
            _logger = logger;
        }

        public async Task<Product?> GetProductByIdAsync(int id)
        {
            // Use AsNoTracking for read-only operations
            return await _context.Products
                .AsNoTracking()
                .Include(p => p.Category)
                .FirstOrDefaultAsync(p => p.Id == id);
        }

        public async Task<(List<Product> Products, int TotalCount)> GetProductsPagedAsync(
            int page, int pageSize, string? category, decimal? minPrice, decimal? maxPrice, string? search)
        {
            var query = _context.Products
                .AsNoTracking()
                .Include(p => p.Category)
                .AsQueryable();

            // Apply filters
            if (!string.IsNullOrEmpty(category))
            {
                query = query.Where(p => p.Category.Name == category);
            }

            if (minPrice.HasValue)
            {
                query = query.Where(p => p.Price >= minPrice.Value);
            }

            if (maxPrice.HasValue)
            {
                query = query.Where(p => p.Price <= maxPrice.Value);
            }

            if (!string.IsNullOrEmpty(search))
            {
                query = query.Where(p => 
                    p.Name.Contains(search) || 
                    p.Description.Contains(search) ||
                    p.Tags.Any(t => t.Contains(search)));
            }

            // Get total count before pagination
            var totalCount = await query.CountAsync();

            // Apply pagination
            var products = await query
                .OrderBy(p => p.Name)
                .Skip((page - 1) * pageSize)
                .Take(pageSize)
                .ToListAsync();

            return (products, totalCount);
        }

        public async Task<Product> CreateProductAsync(Product product)
        {
            using var transaction = await _context.Database.BeginTransactionAsync();
            
            try
            {
                _context.Products.Add(product);
                await _context.SaveChangesAsync();
                await transaction.CommitAsync();
                
                return product;
            }
            catch (Exception ex)
            {
                await transaction.RollbackAsync();
                _logger.LogError(ex, "Error creating product");
                throw;
            }
        }

        public async Task<Product?> UpdateProductAsync(int id, ProductUpdateRequest request)
        {
            var product = await _context.Products
                .Include(p => p.Category)
                .FirstOrDefaultAsync(p => p.Id == id);

            if (product == null) return null;

            // Update only changed properties
            if (!string.IsNullOrEmpty(request.Name))
                product.Name = request.Name;

            if (!string.IsNullOrEmpty(request.Description))
                product.Description = request.Description;

            if (request.Price.HasValue)
                product.Price = request.Price.Value;

            if (request.StockQuantity.HasValue)
                product.StockQuantity = request.StockQuantity.Value;

            if (request.CategoryId.HasValue)
            {
                var category = await _context.Categories.FindAsync(request.CategoryId.Value);
                if (category != null)
                    product.Category = category;
            }

            if (request.Tags != null)
                product.Tags = request.Tags;

            product.UpdatedDate = DateTime.UtcNow;

            await _context.SaveChangesAsync();
            return product;
        }
    }
}

// Response Compression
public static class ResponseCompressionConfiguration
{
    public static IServiceCollection AddCustomResponseCompression(this IServiceCollection services)
    {
        services.AddResponseCompression(options =>
        {
            options.EnableForHttps = true;
            options.Providers.Add<BrotliCompressionProvider>();
            options.Providers.Add<GzipCompressionProvider>();
            options.MimeTypes = new[]
            {
                "application/json",
                "application/xml",
                "text/plain",
                "text/json",
                "text/xml",
                "application/javascript",
                "text/css"
            };
        });

        services.Configure<BrotliCompressionProviderOptions>(options =>
        {
            options.Level = CompressionLevel.Fastest;
        });

        services.Configure<GzipCompressionProviderOptions>(options =>
        {
            options.Level = CompressionLevel.Optimal;
        });

        return services;
    }
}
  

14. Real-World E-Commerce API {#ecommerce-example}

Complete E-Commerce Implementation

  
    // Complete Order Management System
namespace RESTfulApiDemo.Models
{
    public class Order
    {
        public int Id { get; set; }
        public int UserId { get; set; }
        public User User { get; set; } = new();
        public OrderStatus Status { get; set; } = OrderStatus.Pending;
        public decimal TotalAmount { get; set; }
        public decimal TaxAmount { get; set; }
        public decimal ShippingCost { get; set; }
        public decimal DiscountAmount { get; set; }
        public decimal FinalAmount => TotalAmount + TaxAmount + ShippingCost - DiscountAmount;
        public string ShippingAddress { get; set; } = string.Empty;
        public string BillingAddress { get; set; } = string.Empty;
        public string? TrackingNumber { get; set; }
        public List<OrderItem> Items { get; set; } = new();
        public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
        public DateTime? UpdatedAt { get; set; }
        public DateTime? ShippedAt { get; set; }
        public DateTime? DeliveredAt { get; set; }
    }

    public class OrderItem
    {
        public int Id { get; set; }
        public int OrderId { get; set; }
        public int ProductId { get; set; }
        public Product Product { get; set; } = new();
        public int Quantity { get; set; }
        public decimal UnitPrice { get; set; }
        public decimal TotalPrice => Quantity * UnitPrice;
        public decimal? Discount { get; set; }
    }

    public class CreateOrderRequest
    {
        public int UserId { get; set; }
        public string ShippingAddress { get; set; } = string.Empty;
        public string BillingAddress { get; set; } = string.Empty;
        public List<CreateOrderItem> Items { get; set; } = new();
        public string? PromoCode { get; set; }
    }

    public class CreateOrderItem
    {
        public int ProductId { get; set; }
        public int Quantity { get; set; }
    }

    public enum OrderStatus
    {
        Pending,
        Confirmed,
        Processing,
        Shipped,
        Delivered,
        Cancelled,
        Refunded
    }
}

namespace RESTfulApiDemo.Services
{
    public interface IOrderService
    {
        Task<Order?> CreateOrderAsync(CreateOrderRequest request);
        Task<Order?> GetOrderByIdAsync(int id);
        Task<List<Order>> GetUserOrdersAsync(int userId);
        Task<Order?> UpdateOrderStatusAsync(int orderId, OrderStatus status);
        Task<bool> CancelOrderAsync(int orderId);
        Task<Order?> AddOrderItemAsync(int orderId, CreateOrderItem item);
        Task<bool> RemoveOrderItemAsync(int orderId, int itemId);
    }

    public class OrderService : IOrderService
    {
        private readonly ApplicationDbContext _context;
        private readonly IProductService _productService;
        private readonly ILogger<OrderService> _logger;

        public OrderService(
            ApplicationDbContext context,
            IProductService productService,
            ILogger<OrderService> logger)
        {
            _context = context;
            _productService = productService;
            _logger = logger;
        }

        public async Task<Order?> CreateOrderAsync(CreateOrderRequest request)
        {
            using var transaction = await _context.Database.BeginTransactionAsync();
            
            try
            {
                // Validate products and stock
                var orderItems = new List<OrderItem>();
                decimal totalAmount = 0;

                foreach (var item in request.Items)
                {
                    var product = await _productService.GetProductByIdAsync(item.ProductId);
                    if (product == null)
                    {
                        throw new ArgumentException($"Product with ID {item.ProductId} not found");
                    }

                    if (product.StockQuantity < item.Quantity)
                    {
                        throw new InsufficientStockException(
                            product.Name, item.Quantity, product.StockQuantity);
                    }

                    var orderItem = new OrderItem
                    {
                        ProductId = product.Id,
                        Quantity = item.Quantity,
                        UnitPrice = product.Price
                    };

                    orderItems.Add(orderItem);
                    totalAmount += orderItem.TotalPrice;

                    // Reserve stock
                    product.StockQuantity -= item.Quantity;
                }

                // Calculate additional amounts
                var taxAmount = totalAmount * 0.1m; // 10% tax
                var shippingCost = totalAmount > 50 ? 0 : 5.99m; // Free shipping over $50
                var discountAmount = await CalculateDiscountAsync(request.PromoCode, totalAmount);

                var order = new Order
                {
                    UserId = request.UserId,
                    Status = OrderStatus.Pending,
                    TotalAmount = totalAmount,
                    TaxAmount = taxAmount,
                    ShippingCost = shippingCost,
                    DiscountAmount = discountAmount,
                    ShippingAddress = request.ShippingAddress,
                    BillingAddress = request.BillingAddress,
                    Items = orderItems
                };

                _context.Orders.Add(order);
                await _context.SaveChangesAsync();
                await transaction.CommitAsync();

                _logger.LogInformation("Order {OrderId} created successfully", order.Id);
                return order;
            }
            catch (Exception ex)
            {
                await transaction.RollbackAsync();
                _logger.LogError(ex, "Error creating order");
                throw;
            }
        }

        public async Task<Order?> GetOrderByIdAsync(int id)
        {
            return await _context.Orders
                .AsNoTracking()
                .Include(o => o.User)
                .Include(o => o.Items)
                    .ThenInclude(i => i.Product)
                .FirstOrDefaultAsync(o => o.Id == id);
        }

        public async Task<List<Order>> GetUserOrdersAsync(int userId)
        {
            return await _context.Orders
                .AsNoTracking()
                .Where(o => o.UserId == userId)
                .Include(o => o.Items)
                    .ThenInclude(i => i.Product)
                .OrderByDescending(o => o.CreatedAt)
                .ToListAsync();
        }

        public async Task<Order?> UpdateOrderStatusAsync(int orderId, OrderStatus status)
        {
            var order = await _context.Orders
                .Include(o => o.Items)
                .FirstOrDefaultAsync(o => o.Id == orderId);

            if (order == null) return null;

            order.Status = status;
            order.UpdatedAt = DateTime.UtcNow;

            if (status == OrderStatus.Shipped)
            {
                order.ShippedAt = DateTime.UtcNow;
                order.TrackingNumber = GenerateTrackingNumber();
            }
            else if (status == OrderStatus.Delivered)
            {
                order.DeliveredAt = DateTime.UtcNow;
            }

            await _context.SaveChangesAsync();
            return order;
        }

        public async Task<bool> CancelOrderAsync(int orderId)
        {
            var order = await _context.Orders
                .Include(o => o.Items)
                    .ThenInclude(i => i.Product)
                .FirstOrDefaultAsync(o => o.Id == orderId);

            if (order == null || order.Status != OrderStatus.Pending) 
                return false;

            // Restore stock
            foreach (var item in order.Items)
            {
                var product = await _productService.GetProductByIdAsync(item.ProductId);
                if (product != null)
                {
                    product.StockQuantity += item.Quantity;
                }
            }

            order.Status = OrderStatus.Cancelled;
            order.UpdatedAt = DateTime.UtcNow;

            await _context.SaveChangesAsync();
            return true;
        }

        private async Task<decimal> CalculateDiscountAsync(string? promoCode, decimal totalAmount)
        {
            if (string.IsNullOrEmpty(promoCode)) return 0;

            // In real application, query discount rules from database
            var discountRules = new Dictionary<string, Func<decimal, decimal>>
            {
                { "SAVE10", amount => amount * 0.1m },
                { "SAVE20", amount => amount * 0.2m },
                { "FREESHIP", amount => 5.99m }, // Free shipping discount
                { "WELCOME5", amount => 5.00m }
            };

            if (discountRules.TryGetValue(promoCode.ToUpper(), out var discountFunc))
            {
                return discountFunc(totalAmount);
            }

            return 0;
        }

        private string GenerateTrackingNumber()
        {
            return $"TRK{DateTime.UtcNow:yyyyMMddHHmmss}{new Random().Next(1000, 9999)}";
        }

        public async Task<Order?> AddOrderItemAsync(int orderId, CreateOrderItem item)
        {
            var order = await _context.Orders
                .Include(o => o.Items)
                .FirstOrDefaultAsync(o => o.Id == orderId);

            if (order == null || order.Status != OrderStatus.Pending) 
                return null;

            var product = await _productService.GetProductByIdAsync(item.ProductId);
            if (product == null || product.StockQuantity < item.Quantity)
                return null;

            var orderItem = new OrderItem
            {
                OrderId = orderId,
                ProductId = product.Id,
                Quantity = item.Quantity,
                UnitPrice = product.Price
            };

            order.Items.Add(orderItem);
            
            // Update order totals
            order.TotalAmount += orderItem.TotalPrice;
            order.TaxAmount = order.TotalAmount * 0.1m;
            order.UpdatedAt = DateTime.UtcNow;

            // Reserve stock
            product.StockQuantity -= item.Quantity;

            await _context.SaveChangesAsync();
            return order;
        }

        public async Task<bool> RemoveOrderItemAsync(int orderId, int itemId)
        {
            var order = await _context.Orders
                .Include(o => o.Items)
                .FirstOrDefaultAsync(o => o.Id == orderId);

            if (order == null || order.Status != OrderStatus.Pending) 
                return false;

            var item = order.Items.FirstOrDefault(i => i.Id == itemId);
            if (item == null) return false;

            // Restore stock
            var product = await _productService.GetProductByIdAsync(item.ProductId);
            if (product != null)
            {
                product.StockQuantity += item.Quantity;
            }

            // Update order totals
            order.TotalAmount -= item.TotalPrice;
            order.TaxAmount = order.TotalAmount * 0.1m;
            order.UpdatedAt = DateTime.UtcNow;

            order.Items.Remove(item);
            await _context.SaveChangesAsync();

            return true;
        }
    }
}
  

15. Microservices Communication {#microservices}

HTTP Client Factory Implementation

  
    using System.Text.Json;

namespace RESTfulApiDemo.Services
{
    public interface IPaymentService
    {
        Task<PaymentResult> ProcessPaymentAsync(PaymentRequest request);
        Task<PaymentStatus> GetPaymentStatusAsync(string paymentId);
        Task<bool> RefundPaymentAsync(string paymentId);
    }

    public class PaymentService : IPaymentService
    {
        private readonly HttpClient _httpClient;
        private readonly ILogger<PaymentService> _logger;

        public PaymentService(HttpClient httpClient, ILogger<PaymentService> logger)
        {
            _httpClient = httpClient;
            _logger = logger;
        }

        public async Task<PaymentResult> ProcessPaymentAsync(PaymentRequest request)
        {
            try
            {
                var content = new StringContent(
                    JsonSerializer.Serialize(request),
                    Encoding.UTF8,
                    "application/json");

                var response = await _httpClient.PostAsync("/api/payments/process", content);

                if (response.IsSuccessStatusCode)
                {
                    var responseContent = await response.Content.ReadAsStringAsync();
                    var result = JsonSerializer.Deserialize<PaymentResult>(responseContent, 
                        new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
                    
                    return result ?? new PaymentResult { Success = false, Error = "Invalid response" };
                }
                else
                {
                    _logger.LogWarning("Payment service returned {StatusCode}: {Reason}", 
                        response.StatusCode, response.ReasonPhrase);
                    
                    return new PaymentResult 
                    { 
                        Success = false, 
                        Error = $"Payment service error: {response.StatusCode}" 
                    };
                }
            }
            catch (HttpRequestException ex)
            {
                _logger.LogError(ex, "Error calling payment service");
                return new PaymentResult 
                { 
                    Success = false, 
                    Error = "Payment service unavailable" 
                };
            }
        }

        public async Task<PaymentStatus> GetPaymentStatusAsync(string paymentId)
        {
            try
            {
                var response = await _httpClient.GetAsync($"/api/payments/{paymentId}/status");
                
                if (response.IsSuccessStatusCode)
                {
                    var content = await response.Content.ReadAsStringAsync();
                    return JsonSerializer.Deserialize<PaymentStatus>(content,
                        new JsonSerializerOptions { PropertyNameCaseInsensitive = true })
                        ?? new PaymentStatus { Status = "Unknown" };
                }

                return new PaymentStatus { Status = "Error" };
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error getting payment status for {PaymentId}", paymentId);
                return new PaymentStatus { Status = "Service Unavailable" };
            }
        }

        public async Task<bool> RefundPaymentAsync(string paymentId)
        {
            try
            {
                var response = await _httpClient.PostAsync($"/api/payments/{paymentId}/refund", null);
                return response.IsSuccessStatusCode;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error refunding payment {PaymentId}", paymentId);
                return false;
            }
        }
    }

    public class PaymentRequest
    {
        public string OrderId { get; set; } = string.Empty;
        public decimal Amount { get; set; }
        public string Currency { get; set; } = "USD";
        public string PaymentMethod { get; set; } = string.Empty;
        public CardDetails Card { get; set; } = new();
        public BillingAddress BillingAddress { get; set; } = new();
    }

    public class PaymentResult
    {
        public bool Success { get; set; }
        public string? PaymentId { get; set; }
        public string? TransactionId { get; set; }
        public string? Error { get; set; }
        public DateTime ProcessedAt { get; set; } = DateTime.UtcNow;
    }

    public class PaymentStatus
    {
        public string Status { get; set; } = string.Empty;
        public DateTime? ProcessedAt { get; set; }
        public string? ErrorMessage { get; set; }
    }
}

// HttpClient Configuration
public static class HttpClientConfiguration
{
    public static IServiceCollection AddHttpClients(this IServiceCollection services, IConfiguration configuration)
    {
        services.AddHttpClient<IPaymentService, PaymentService>(client =>
        {
            client.BaseAddress = new Uri(configuration["PaymentService:BaseUrl"] 
                ?? "https://api.paymentservice.com");
            client.DefaultRequestHeaders.Add("Accept", "application/json");
            client.Timeout = TimeSpan.FromSeconds(30);
        })
        .AddPolicyHandler(GetRetryPolicy())
        .AddPolicyHandler(GetCircuitBreakerPolicy());

        return services;
    }

    private static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
    {
        return HttpPolicyExtensions
            .HandleTransientHttpError()
            .OrResult(msg => !msg.IsSuccessStatusCode)
            .WaitAndRetryAsync(
                retryCount: 3,
                sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
                onRetry: (outcome, timespan, retryCount, context) =>
                {
                    // Log retry attempts
                });
    }

    private static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
    {
        return HttpPolicyExtensions
            .HandleTransientHttpError()
            .CircuitBreakerAsync(
                handledEventsAllowedBeforeBreaking: 3,
                durationOfBreak: TimeSpan.FromSeconds(30));
    }
}
  

16. Monitoring & Logging {#monitoring}

Structured Logging Implementation

  
    using Serilog;
using Serilog.Events;

namespace RESTfulApiDemo.Configuration
{
    public static class LoggingConfiguration
    {
        public static IHostBuilder AddCustomLogging(this IHostBuilder hostBuilder)
        {
            return hostBuilder.UseSerilog((context, configuration) =>
            {
                configuration
                    .MinimumLevel.Information()
                    .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
                    .MinimumLevel.Override("System", LogEventLevel.Warning)
                    .Enrich.FromLogContext()
                    .Enrich.WithProperty("Application", "E-Commerce API")
                    .Enrich.WithMachineName()
                    .Enrich.WithEnvironmentName()
                    .WriteTo.Console(
                        outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}")
                    .WriteTo.File(
                        path: "logs/api-.log",
                        rollingInterval: RollingInterval.Day,
                        retainedFileCountLimit: 7,
                        shared: true,
                        outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}")
                    .WriteTo.Seq(serverUrl: context.Configuration["Seq:ServerUrl"] ?? "http://localhost:5341");
            });
        }
    }

    public class ApiLogger<T> : ILogger<T>
    {
        private readonly ILogger _logger;

        public ApiLogger(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger(typeof(T).Name);
        }

        public IDisposable? BeginScope<TState>(TState state) where TState : notnull
        {
            return _logger.BeginScope(state);
        }

        public bool IsEnabled(LogLevel logLevel)
        {
            return _logger.IsEnabled(logLevel);
        }

        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
        {
            _logger.Log(logLevel, eventId, state, exception, formatter);
        }
    }
}

// Custom Logging Middleware
public class RequestLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<RequestLoggingMiddleware> _logger;

    public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var startTime = DateTime.UtcNow;
        var stopwatch = System.Diagnostics.Stopwatch.StartNew();

        try
        {
            await _next(context);
            stopwatch.Stop();

            var logLevel = context.Response.StatusCode >= 500 ? LogLevel.Error :
                          context.Response.StatusCode >= 400 ? LogLevel.Warning : LogLevel.Information;

            _logger.Log(logLevel, 
                "HTTP {Method} {Path} responded {StatusCode} in {ElapsedMilliseconds}ms",
                context.Request.Method,
                context.Request.Path,
                context.Response.StatusCode,
                stopwatch.ElapsedMilliseconds);
        }
        catch (Exception ex)
        {
            stopwatch.Stop();
            _logger.LogError(ex, 
                "HTTP {Method} {Path} threw exception after {ElapsedMilliseconds}ms",
                context.Request.Method,
                context.Request.Path,
                stopwatch.ElapsedMilliseconds);
            
            throw;
        }
    }
}

// Health Checks
public static class HealthCheckConfiguration
{
    public static IServiceCollection AddCustomHealthChecks(this IServiceCollection services, IConfiguration configuration)
    {
        services.AddHealthChecks()
            .AddSqlServer(
                connectionString: configuration.GetConnectionString("DefaultConnection")!,
                name: "Database",
                timeout: TimeSpan.FromSeconds(3),
                tags: new[] { "ready" })
            .AddRedis(
                redisConnectionString: configuration["Redis:ConnectionString"]!,
                name: "Redis",
                timeout: TimeSpan.FromSeconds(3),
                tags: new[] { "ready" })
            .AddUrlGroup(
                uri: new Uri(configuration["PaymentService:BaseUrl"] + "/health"),
                name: "Payment Service",
                timeout: TimeSpan.FromSeconds(5))
            .AddDiskStorageHealthCheck(
                setup: diskOptions => diskOptions.AddDrive("C:\\", 1024), // 1GB minimum free
                name: "Disk Storage",
                failureStatus: HealthStatus.Degraded)
            .AddMemoryHealthCheck(
                name: "Memory",
                maximumMemoryBytes: 1024 * 1024 * 1024); // 1GB maximum

        services.AddHealthChecksUI(setup =>
        {
            setup.AddHealthCheckEndpoint("API", "/health");
            setup.SetEvaluationTimeInSeconds(30);
            setup.SetApiMaxActiveRequests(1);
        })
        .AddInMemoryStorage();

        return services;
    }
}
  

17. Security Best Practices {#security}

Advanced Security Configuration

  
    using Microsoft.AspNetCore.Cors;

namespace RESTfulApiDemo.Configuration
{
    public static class SecurityConfiguration
    {
        public static IServiceCollection AddSecurityServices(this IServiceCollection services, IConfiguration configuration)
        {
            // CORS
            services.AddCors(options =>
            {
                options.AddPolicy("AllowSpecificOrigin", policy =>
                {
                    policy.WithOrigins(
                            "https://localhost:3000",
                            "https://staging.ecommerce.com",
                            "https://ecommerce.com")
                        .AllowAnyHeader()
                        .AllowAnyMethod()
                        .AllowCredentials()
                        .WithExposedHeaders("X-Total-Count", "X-Pagination");
                });

                options.AddPolicy("AllowAll", policy =>
                {
                    policy.AllowAnyOrigin()
                        .AllowAnyHeader()
                        .AllowAnyMethod();
                });
            });

            // HTTPS Redirection
            services.AddHttpsRedirection(options =>
            {
                options.RedirectStatusCode = StatusCodes.Status307TemporaryRedirect;
                options.HttpsPort = 443;
            });

            // Security Headers
            services.AddCustomHeaders();

            // Data Protection
            services.AddDataProtection()
                .SetApplicationName("ECommerceAPI")
                .PersistKeysToFileSystem(new DirectoryInfo(@"\keys\"))
                .SetDefaultKeyLifetime(TimeSpan.FromDays(90));

            return services;
        }

        public static IApplicationBuilder UseSecurityHeaders(this IApplicationBuilder app)
        {
            app.Use(async (context, next) =>
            {
                context.Response.Headers.Add("X-Frame-Options", "DENY");
                context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
                context.Response.Headers.Add("X-XSS-Protection", "1; mode=block");
                context.Response.Headers.Add("Referrer-Policy", "strict-origin-when-cross-origin");
                context.Response.Headers.Add("Permissions-Policy", "geolocation=(), microphone=()");
                
                // Content Security Policy
                context.Response.Headers.Add("Content-Security-Policy", 
                    "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'");

                await next();
            });

            return app;
        }
    }
}

// Request Sanitization
public class SanitizationMiddleware
{
    private readonly RequestDelegate _next;

    public SanitizationMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        if (context.Request.HasFormContentType && context.Request.Form != null)
        {
            // Sanitize form data
            await SanitizeFormData(context);
        }

        await _next(context);
    }

    private async Task SanitizeFormData(HttpContext context)
    {
        // Implement input sanitization logic
        // Remove potentially dangerous characters and scripts
        foreach (var formField in context.Request.Form)
        {
            var sanitizedValue = SanitizeInput(formField.Value.ToString());
            // Update form values with sanitized data
        }
    }

    private string SanitizeInput(string input)
    {
        if (string.IsNullOrEmpty(input)) return input;

        // Remove potentially dangerous HTML tags and scripts
        var sanitized = System.Text.RegularExpressions.Regex.Replace(
            input, 
            @"<script[^>]*>[\s\S]*?</script>", 
            string.Empty, 
            System.Text.RegularExpressions.RegexOptions.IgnoreCase);

        sanitized = System.Text.RegularExpressions.Regex.Replace(
            sanitized, 
            @"<[^>]*(>|$)", 
            string.Empty);

        return sanitized.Trim();
    }
}
  

18. Deployment Strategies {#deployment}

Docker Configuration

  
    # Dockerfile for ASP.NET Core API
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 ["RESTfulApiDemo.csproj", "."]
RUN dotnet restore "RESTfulApiDemo.csproj"
COPY . .
RUN dotnet build "RESTfulApiDemo.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "RESTfulApiDemo.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .

# Install curl for health checks
RUN apt-get update && apt-get install -y curl

# Create non-root user
RUN groupadd -r apiuser && useradd -r -g apiuser apiuser
USER apiuser

ENTRYPOINT ["dotnet", "RESTfulApiDemo.dll"]
  

Docker Compose for Development

  
    version: '3.8'

services:
  api:
    build: .
    ports:
      - "5000:80"
      - "5001:443"
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ConnectionStrings__DefaultConnection=Server=db;Database=ECommerce;User=sa;Password=YourPassword123!;
      - JwtSettings__SecretKey=YourSuperSecretKeyThatIsAtLeast32CharactersLong!
    depends_on:
      - db
      - redis

  db:
    image: mcr.microsoft.com/mssql/server:2019-latest
    environment:
      SA_PASSWORD: "YourPassword123!"
      ACCEPT_EULA: "Y"
    ports:
      - "1433:1433"
    volumes:
      - sql_data:/var/opt/mssql

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data

  seq:
    image: datalust/seq:latest
    environment:
      - ACCEPT_EULA=Y
    ports:
      - "5341:5341"
      - "8081:80"

volumes:
  sql_data:
  redis_data:
  

19. API Gateway Integration {#api-gateway}

Ocelot API Gateway Configuration

  
    {
  "Routes": [
    {
      "DownstreamPathTemplate": "/api/v{version}/products",
      "DownstreamScheme": "https",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 5001
        }
      ],
      "UpstreamPathTemplate": "/products",
      "UpstreamHttpMethod": [ "GET" ],
      "AuthenticationOptions": {
        "AuthenticationProviderKey": "Bearer",
        "AllowedScopes": []
      },
      "RateLimitOptions": {
        "ClientWhitelist": [],
        "EnableRateLimiting": true,
        "Period": "1m",
        "PeriodTimespan": 1,
        "Limit": 100
      }
    },
    {
      "DownstreamPathTemplate": "/api/v{version}/orders",
      "DownstreamScheme": "https",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 5001
        }
      ],
      "UpstreamPathTemplate": "/orders",
      "UpstreamHttpMethod": [ "GET", "POST" ],
      "AuthenticationOptions": {
        "AuthenticationProviderKey": "Bearer",
        "AllowedScopes": []
      }
    }
  ],
  "GlobalConfiguration": {
    "BaseUrl": "https://localhost:6001"
  }
}
  

20. Future Trends & Best Practices {#future-trends}

GraphQL Integration

  
    // GraphQL endpoint for flexible data querying
public class ECommerceQuery : ObjectGraphType
{
    public ECommerceQuery(IProductService productService, IOrderService orderService)
    {
        Field<ListGraphType<ProductType>>("products")
            .Argument<IntGraphType>("page")
            .Argument<IntGraphType>("pageSize")
            .Argument<StringGraphType>("category")
            .ResolveAsync(async context =>
            {
                var page = context.GetArgument<int?>("page") ?? 1;
                var pageSize = context.GetArgument<int?>("pageSize") ?? 20;
                var category = context.GetArgument<string?>("category");
                
                var (products, totalCount) = await productService.GetProductsPagedAsync(
                    page, pageSize, category, null, null, null);
                
                return products;
            });

        Field<ProductType>("product")
            .Argument<NonNullGraphType<IntGraphType>>("id")
            .ResolveAsync(async context =>
            {
                var id = context.GetArgument<int>("id");
                return await productService.GetProductByIdAsync(id);
            });
    }
}

// gRPC Service for high-performance communication
public class ProductService : ProductGrpcService.ProductGrpcServiceBase
{
    private readonly IProductService _productService;

    public ProductService(IProductService productService)
    {
        _productService = productService;
    }

    public override async Task<GetProductResponse> GetProduct(GetProductRequest request, ServerCallContext context)
    {
        var product = await _productService.GetProductByIdAsync(request.Id);
        
        if (product == null)
        {
            throw new RpcException(new Status(StatusCode.NotFound, "Product not found"));
        }

        return new GetProductResponse
        {
            Product = new ProductMessage
            {
                Id = product.Id,
                Name = product.Name,
                Description = product.Description,
                Price = (double)product.Price,
                StockQuantity = product.StockQuantity
            }
        };
    }
}