![cloud]()
Previous article: AI Integration Boom: Infusing ASP.NET Core with ML.NET & OpenAI (Part-24 of 40)
Table of Contents
Introduction to Cloud-Native ASP.NET Core
Azure Cloud Services Deep Dive
AWS Cloud Integration Mastery
Global Scaling Strategies
Cloud Storage Solutions
Microservices in Cloud
Production Deployment Patterns
Real-World Case Study
Best Practices & Optimization
1. Introduction to Cloud-Native ASP.NET Core
1.1 What is Cloud-Native Development?
Cloud-native development represents a paradigm shift in how we build, deploy, and scale applications. Unlike traditional approaches, cloud-native applications are designed from the ground up to leverage cloud infrastructure, enabling unprecedented scalability, resilience, and flexibility.
Key Characteristics:
1.2 Why ASP.NET Core for Cloud?
ASP.NET Core has evolved into the perfect framework for cloud-native development:
// Program.cs - Modern ASP.NET Core cloud-ready setup
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Configuration;
using Azure.Identity;
using Amazon.Extensions.NETCore.Setup;
var builder = WebApplication.CreateBuilder(args);
// Cloud-optimized configuration
builder.Configuration
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables() // Critical for cloud deployment
.AddUserSecrets<Program>() // For local development
.AddAzureKeyVault( // For production secrets
new Uri(builder.Configuration["KeyVault:Endpoint"]),
new DefaultAzureCredential());
// Cloud-optimized services
builder.Services.AddControllers();
builder.Services.AddHealthChecks(); // Essential for cloud monitoring
builder.Services.AddApplicationInsightsTelemetry();
// Configure for cloud scalability
builder.Services.Configure<HostOptions>(options =>
{
options.ShutdownTimeout = TimeSpan.FromSeconds(30); // Graceful shutdown
});
var app = builder.Build();
// Cloud-specific middleware pipeline
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error"); // Production error handling
app.UseHsts(); // HTTP Strict Transport Security
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
// Health check endpoint for cloud load balancers
app.MapHealthChecks("/health");
app.MapControllers();
await app.RunAsync();
1.3 Real-World Business Impact
Scenario : E-commerce Platform Scaling for Holiday Season
Imagine an e-commerce platform that normally handles 10,000 daily users but needs to scale to 1,000,000 users during Black Friday sales. Traditional infrastructure would collapse under this load, but cloud-native ASP.NET Core applications can scale dynamically.
// Startup configuration for auto-scaling
public void ConfigureServices(IServiceCollection services)
{
// Configure for horizontal scaling
services.AddDistributedMemoryCache(); // Use Redis in production
// Stateless design for scale-out
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
// Add session with distributed cache support
services.AddSession(options =>
{
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
options.IdleTimeout = TimeSpan.FromMinutes(20);
});
}
2. Azure Cloud Services Deep Dive
2.1 Azure App Service for ASP.NET Core
Azure App Service provides fully managed platform for deploying web applications with built-in scaling, security, and monitoring.
2.1.1 Deployment Configuration
appsettings.json for Azure
{
"Azure": {
"AppService": {
"InstanceId": "{WEBSITE_INSTANCE_ID}",
"SKU": "{WEBSITE_SKU}",
"ResourceGroup": "{WEBSITE_RESOURCE_GROUP}"
}
},
"ConnectionStrings": {
"AzureSQL": "Server=tcp:{server}.database.windows.net;Database={db};User ID={user};Password={password};Trusted_Connection=False;Encrypt=True;"
},
"ApplicationInsights": {
"InstrumentationKey": "{your_instrumentation_key}"
}
}
2.1.2 Program.cs Configuration for Azure
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Azure.Identity;
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
var builtConfig = config.Build();
// Azure App Configuration for centralized configuration management
if (!context.HostingEnvironment.IsDevelopment())
{
config.AddAzureAppConfiguration(options =>
{
options.Connect(builtConfig["ConnectionStrings:AppConfig"])
.ConfigureKeyVault(kv =>
{
kv.SetCredential(new DefaultAzureCredential());
});
});
}
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
// Azure-specific configurations
webBuilder.ConfigureKestrel(serverOptions =>
{
serverOptions.Limits.MaxRequestBodySize = 52428800; // 50MB
serverOptions.Limits.MaxConcurrentConnections = 100;
serverOptions.Limits.MaxConcurrentUpgradedConnections = 100;
});
})
.UseAzureAppServices() // Azure-specific logging and diagnostics
.ConfigureLogging((context, logging) =>
{
logging.AddAzureWebAppDiagnostics();
});
}
2.1.3 Advanced Azure Deployment with Bicep
main.bicep - Infrastructure as Code:
param location string = 'eastus'
param appName string = 'my-aspnet-app'
param skuName string = 'S1'
param skuCapacity int = 2
resource appServicePlan 'Microsoft.Web/serverfarms@2021-03-01' = {
name: 'plan-${appName}'
location: location
sku: {
name: skuName
capacity: skuCapacity
}
properties: {
reserved: true
}
}
resource webApp 'Microsoft.Web/sites@2021-03-01' = {
name: appName
location: location
properties: {
serverFarmId: appServicePlan.id
siteConfig: {
netFrameworkVersion: 'v6.0'
alwaysOn: true
appSettings: [
{
name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
value: appInsights.properties.InstrumentationKey
}
]
}
httpsOnly: true
}
identity: {
type: 'SystemAssigned'
}
}
resource appInsights 'Microsoft.Insights/components@2020-02-02' = {
name: 'ai-${appName}'
location: location
properties: {
Application_Type: 'web'
Flow_Type: 'Bluefield'
}
}
2.2 Azure SQL Database Integration
2.2.1 Entity Framework Core with Azure SQL
// DataContext.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
public class ApplicationDbContext : DbContext
{
private readonly IConfiguration _configuration;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options,
IConfiguration configuration)
: base(options)
{
_configuration = configuration;
}
public DbSet<Product> Products { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<Customer> Customers { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
// Use retry logic for cloud database
optionsBuilder.UseSqlServer(
_configuration.GetConnectionString("AzureSQL"),
options =>
{
options.EnableRetryOnFailure(
maxRetryCount: 5,
maxRetryDelay: TimeSpan.FromSeconds(30),
errorNumbersToAdd: null);
});
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Configure for cloud performance
modelBuilder.Entity<Product>(entity =>
{
entity.HasKey(e => e.Id);
entity.HasIndex(e => e.CategoryId); // Important for query performance
entity.Property(e => e.Price).HasColumnType("decimal(18,2)");
});
// Configure soft delete pattern
modelBuilder.Entity<Customer>(entity =>
{
entity.HasQueryFilter(e => !e.IsDeleted);
});
}
}
// Startup configuration
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>((serviceProvider, options) =>
{
var configuration = serviceProvider.GetRequiredService<IConfiguration>();
options.UseSqlServer(
configuration.GetConnectionString("AzureSQL"),
sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 5,
maxRetryDelay: TimeSpan.FromSeconds(30),
errorNumbersToAdd: null);
});
});
}
2.2.2 Database Resilience with Polly
// DatabaseResilienceService.cs
using Polly;
using Polly.Retry;
using System.Data.SqlClient;
public class DatabaseResilienceService
{
private readonly AsyncRetryPolicy _retryPolicy;
private readonly ILogger<DatabaseResilienceService> _logger;
public DatabaseResilienceService(ILogger<DatabaseResilienceService> logger)
{
_logger = logger;
_retryPolicy = Policy
.Handle<SqlException>(ex => IsTransientError(ex))
.Or<TimeoutException>()
.WaitAndRetryAsync(
retryCount: 3,
sleepDurationProvider: retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
onRetry: (exception, timeSpan, retryCount, context) =>
{
_logger.LogWarning(exception,
$"Database operation failed. Retrying attempt {retryCount} after {timeSpan.TotalSeconds}s.");
});
}
public async Task<T> ExecuteWithResilienceAsync<T>(Func<Task<T>> operation)
{
return await _retryPolicy.ExecuteAsync(operation);
}
private bool IsTransientError(SqlException ex)
{
// SQL Server transient error numbers
int[] transientErrors = { 4060, 40197, 40501, 40613,
49918, 49919, 49920, 11001 };
return transientErrors.Contains(ex.Number);
}
}
// Usage in repository
public class ProductRepository
{
private readonly ApplicationDbContext _context;
private readonly DatabaseResilienceService _resilienceService;
public ProductRepository(ApplicationDbContext context,
DatabaseResilienceService resilienceService)
{
_context = context;
_resilienceService = resilienceService;
}
public async Task<List<Product>> GetProductsByCategoryAsync(int categoryId)
{
return await _resilienceService.ExecuteWithResilienceAsync(async () =>
{
return await _context.Products
.Where(p => p.CategoryId == categoryId && p.IsActive)
.OrderBy(p => p.Name)
.ToListAsync();
});
}
}
2.3 Azure Storage Integration
2.3.1 Blob Storage for File Management
// AzureBlobStorageService.cs
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using Microsoft.Extensions.Configuration;
public interface IFileStorageService
{
Task<string> UploadFileAsync(Stream fileStream, string fileName, string contentType);
Task<Stream> DownloadFileAsync(string fileName);
Task<bool> DeleteFileAsync(string fileName);
Task<List<string>> ListFilesAsync(string prefix = null);
}
public class AzureBlobStorageService : IFileStorageService
{
private readonly BlobServiceClient _blobServiceClient;
private readonly string _containerName;
private readonly ILogger<AzureBlobStorageService> _logger;
public AzureBlobStorageService(IConfiguration configuration,
ILogger<AzureBlobStorageService> logger)
{
var connectionString = configuration.GetConnectionString("AzureStorage");
_blobServiceClient = new BlobServiceClient(connectionString);
_containerName = configuration["AzureStorage:ContainerName"] ?? "default";
_logger = logger;
InitializeContainerAsync().GetAwaiter().GetResult();
}
private async Task InitializeContainerAsync()
{
try
{
var containerClient = _blobServiceClient.GetBlobContainerClient(_containerName);
await containerClient.CreateIfNotExistsAsync(PublicAccessType.None);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to initialize blob container");
throw;
}
}
public async Task<string> UploadFileAsync(Stream fileStream, string fileName, string contentType)
{
try
{
var containerClient = _blobServiceClient.GetBlobContainerClient(_containerName);
var blobClient = containerClient.GetBlobClient(fileName);
await blobClient.UploadAsync(fileStream, new BlobHttpHeaders
{
ContentType = contentType
});
return blobClient.Uri.ToString();
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to upload file: {fileName}");
throw;
}
}
public async Task<Stream> DownloadFileAsync(string fileName)
{
try
{
var containerClient = _blobServiceClient.GetBlobContainerClient(_containerName);
var blobClient = containerClient.GetBlobClient(fileName);
var response = await blobClient.DownloadAsync();
return response.Value.Content;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to download file: {fileName}");
throw;
}
}
public async Task<bool> DeleteFileAsync(string fileName)
{
try
{
var containerClient = _blobServiceClient.GetBlobContainerClient(_containerName);
var blobClient = containerClient.GetBlobClient(fileName);
return await blobClient.DeleteIfExistsAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to delete file: {fileName}");
throw;
}
}
public async Task<List<string>> ListFilesAsync(string prefix = null)
{
var fileNames = new List<string>();
var containerClient = _blobServiceClient.GetBlobContainerClient(_containerName);
await foreach (var blobItem in containerClient.GetBlobsAsync(prefix: prefix))
{
fileNames.Add(blobItem.Name);
}
return fileNames;
}
}
// Controller usage
[ApiController]
[Route("api/[controller]")]
public class FilesController : ControllerBase
{
private readonly IFileStorageService _fileStorageService;
private readonly ILogger<FilesController> _logger;
public FilesController(IFileStorageService fileStorageService,
ILogger<FilesController> logger)
{
_fileStorageService = fileStorageService;
_logger = logger;
}
[HttpPost("upload")]
public async Task<IActionResult> UploadFile(IFormFile file)
{
if (file == null || file.Length == 0)
return BadRequest("No file uploaded");
try
{
using var stream = file.OpenReadStream();
var fileUrl = await _fileStorageService.UploadFileAsync(
stream,
Guid.NewGuid().ToString() + Path.GetExtension(file.FileName),
file.ContentType);
return Ok(new { FileUrl = fileUrl });
}
catch (Exception ex)
{
_logger.LogError(ex, "File upload failed");
return StatusCode(500, "File upload failed");
}
}
[HttpGet("download/{fileName}")]
public async Task<IActionResult> DownloadFile(string fileName)
{
try
{
var stream = await _fileStorageService.DownloadFileAsync(fileName);
return File(stream, "application/octet-stream", fileName);
}
catch (Exception ex)
{
_logger.LogError(ex, $"File download failed: {fileName}");
return NotFound();
}
}
}
2.3.2 Table Storage for NoSQL Data
// AzureTableStorageService.cs
using Azure.Data.Tables;
using Microsoft.Extensions.Configuration;
public class AzureTableStorageService<T> where T : class, ITableEntity, new()
{
private readonly TableServiceClient _tableServiceClient;
private readonly TableClient _tableClient;
private readonly string _tableName;
private readonly ILogger<AzureTableStorageService<T>> _logger;
public AzureTableStorageService(IConfiguration configuration,
ILogger<AzureTableStorageService<T>> logger)
{
var connectionString = configuration.GetConnectionString("AzureStorage");
_tableServiceClient = new TableServiceClient(connectionString);
_tableName = typeof(T).Name.ToLower() + "s";
_tableClient = _tableServiceClient.GetTableClient(_tableName);
_logger = logger;
InitializeTableAsync().GetAwaiter().GetResult();
}
private async Task InitializeTableAsync()
{
try
{
await _tableClient.CreateIfNotExistsAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to initialize table: {_tableName}");
throw;
}
}
public async Task<T> GetEntityAsync(string partitionKey, string rowKey)
{
try
{
var response = await _tableClient.GetEntityAsync<T>(partitionKey, rowKey);
return response.Value;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to get entity: {partitionKey}/{rowKey}");
return null;
}
}
public async Task UpsertEntityAsync(T entity)
{
try
{
await _tableClient.UpsertEntityAsync(entity);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to upsert entity");
throw;
}
}
public async Task DeleteEntityAsync(string partitionKey, string rowKey)
{
try
{
await _tableClient.DeleteEntityAsync(partitionKey, rowKey);
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to delete entity: {partitionKey}/{rowKey}");
throw;
}
}
public async Task<List<T>> QueryEntitiesAsync(string filter = null)
{
var entities = new List<T>();
try
{
var query = _tableClient.QueryAsync<T>(filter);
await foreach (var entity in query)
{
entities.Add(entity);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to query entities");
throw;
}
return entities;
}
}
// Example entity
public class UserSession : ITableEntity
{
public string PartitionKey { get; set; } // UserId
public string RowKey { get; set; } // SessionId
public DateTimeOffset? Timestamp { get; set; }
public Azure.ETag ETag { get; set; }
public string UserName { get; set; }
public DateTime LoginTime { get; set; }
public DateTime LastActivity { get; set; }
public string IpAddress { get; set; }
}
3. AWS Cloud Integration Mastery
3.1 AWS Elastic Beanstalk for ASP.NET Core
AWS Elastic Beanstalk provides an easy-to-use service for deploying and scaling web applications.
3.1.1 AWS Configuration and Deployment
aws-beanstalk.config
option_settings:
aws:elasticbeanstalk:application:environment:
ASPNETCORE_ENVIRONMENT: Production
AWS_ACCESS_KEY_ID: your-access-key
AWS_SECRET_ACCESS_KEY: your-secret-key
aws:elasticbeanstalk:container:dotnet:app:
AppPath: /var/app/current
aws:elasticbeanstalk:cloudwatch:logs:
StreamLogs: true
RetentionInDays: 30
aws:elasticbeanstalk:healthreporting:system:
SystemType: enhanced
packages:
yum:
nginx: []
dotnet8: []
files:
"/etc/nginx/conf.d/proxy.conf":
content: |
client_max_body_size 50M;
3.1.2 Program.cs for AWS
using Amazon;
using Amazon.Extensions.NETCore.Setup;
using Amazon.Runtime;
using Amazon.S3;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
config.AddEnvironmentVariables();
// AWS Systems Manager Parameter Store for configuration
if (context.HostingEnvironment.IsProduction())
{
var awsOptions = config.Build().GetAWSOptions();
config.AddSystemsManager("/my-app/production", awsOptions,
TimeSpan.FromMinutes(5));
}
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
// AWS-specific configurations
webBuilder.ConfigureKestrel(serverOptions =>
{
serverOptions.Limits.MaxRequestBodySize = 52428800; // 50MB
});
})
.ConfigureLogging((context, logging) =>
{
logging.AddAWSProvider(); // CloudWatch logging
if (context.HostingEnvironment.IsDevelopment())
{
logging.AddConsole();
logging.AddDebug();
}
})
.UseDefaultServiceProvider((context, options) =>
{
options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
options.ValidateOnBuild = true;
});
}
// Startup configuration for AWS
public class Startup
{
public Startup(IConfiguration configuration, IWebHostEnvironment environment)
{
Configuration = configuration;
Environment = environment;
}
public IConfiguration Configuration { get; }
public IWebHostEnvironment Environment { get; }
public void ConfigureServices(IServiceCollection services)
{
// AWS service configuration
services.AddDefaultAWSOptions(Configuration.GetAWSOptions());
services.AddAWSService<IAmazonS3>();
services.AddControllersWithViews();
// Health checks for AWS load balancer
services.AddHealthChecks()
.AddS3HealthCheck("s3-health-check")
.AddDynamoDBHealthCheck("dynamodb-health-check");
// AWS Cognito for authentication
services.AddCognitoIdentity();
// AWS X-Ray for distributed tracing
services.AddXRay("my-aspnet-app");
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseXRay("my-aspnet-app");
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
// Health check endpoint for AWS load balancer
app.UseEndpoints(endpoints =>
{
endpoints.MapHealthChecks("/health");
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
3.2 AWS S3 Integration
3.2.1 S3 File Storage Service
// AWSS3StorageService.cs
using Amazon.S3;
using Amazon.S3.Model;
using Amazon.S3.Transfer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
public interface IAwsFileStorageService
{
Task<string> UploadFileAsync(Stream fileStream, string fileName, string contentType);
Task<Stream> DownloadFileAsync(string fileName);
Task<bool> DeleteFileAsync(string fileName);
Task<List<S3Object>> ListFilesAsync(string prefix = null);
Task<string> GeneratePresignedUrlAsync(string fileName, DateTime expiration);
}
public class AwsS3StorageService : IAwsFileStorageService
{
private readonly IAmazonS3 _s3Client;
private readonly string _bucketName;
private readonly ILogger<AwsS3StorageService> _logger;
public AwsS3StorageService(IAmazonS3 s3Client, IConfiguration configuration,
ILogger<AwsS3StorageService> logger)
{
_s3Client = s3Client;
_bucketName = configuration["AWS:S3:BucketName"] ?? "my-app-bucket";
_logger = logger;
InitializeBucketAsync().GetAwaiter().GetResult();
}
private async Task InitializeBucketAsync()
{
try
{
var bucketExists = await _s3Client.DoesS3BucketExistAsync(_bucketName);
if (!bucketExists)
{
var putBucketRequest = new PutBucketRequest
{
BucketName = _bucketName,
UseClientRegion = true
};
await _s3Client.PutBucketAsync(putBucketRequest);
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to initialize S3 bucket: {_bucketName}");
throw;
}
}
public async Task<string> UploadFileAsync(Stream fileStream, string fileName, string contentType)
{
try
{
var transferUtility = new TransferUtility(_s3Client);
var uploadRequest = new TransferUtilityUploadRequest
{
InputStream = fileStream,
Key = fileName,
BucketName = _bucketName,
ContentType = contentType,
CannedACL = S3CannedACL.Private // Security best practice
};
await transferUtility.UploadAsync(uploadRequest);
return $"https://{_bucketName}.s3.amazonaws.com/{fileName}";
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to upload file to S3: {fileName}");
throw;
}
}
public async Task<Stream> DownloadFileAsync(string fileName)
{
try
{
var request = new GetObjectRequest
{
BucketName = _bucketName,
Key = fileName
};
using var response = await _s3Client.GetObjectAsync(request);
var memoryStream = new MemoryStream();
await response.ResponseStream.CopyToAsync(memoryStream);
memoryStream.Position = 0;
return memoryStream;
}
catch (AmazonS3Exception ex) when (ex.ErrorCode == "NoSuchKey")
{
_logger.LogWarning($"File not found in S3: {fileName}");
return null;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to download file from S3: {fileName}");
throw;
}
}
public async Task<bool> DeleteFileAsync(string fileName)
{
try
{
var deleteRequest = new DeleteObjectRequest
{
BucketName = _bucketName,
Key = fileName
};
await _s3Client.DeleteObjectAsync(deleteRequest);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to delete file from S3: {fileName}");
return false;
}
}
public async Task<List<S3Object>> ListFilesAsync(string prefix = null)
{
try
{
var request = new ListObjectsV2Request
{
BucketName = _bucketName,
Prefix = prefix
};
var response = await _s3Client.ListObjectsV2Async(request);
return response.S3Objects;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to list files from S3");
throw;
}
}
public async Task<string> GeneratePresignedUrlAsync(string fileName, DateTime expiration)
{
try
{
var request = new GetPreSignedUrlRequest
{
BucketName = _bucketName,
Key = fileName,
Expires = expiration,
Verb = HttpVerb.GET
};
return _s3Client.GetPreSignedURL(request);
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to generate presigned URL for: {fileName}");
throw;
}
}
}
// Controller for S3 operations
[ApiController]
[Route("api/[controller]")]
public class AwsFilesController : ControllerBase
{
private readonly IAwsFileStorageService _fileStorageService;
private readonly ILogger<AwsFilesController> _logger;
public AwsFilesController(IAwsFileStorageService fileStorageService,
ILogger<AwsFilesController> logger)
{
_fileStorageService = fileStorageService;
_logger = logger;
}
[HttpPost("upload")]
[RequestSizeLimit(50_000_000)] // 50MB limit
public async Task<IActionResult> UploadFile(IFormFile file)
{
if (file == null || file.Length == 0)
return BadRequest("No file uploaded");
try
{
using var stream = file.OpenReadStream();
var fileUrl = await _fileStorageService.UploadFileAsync(
stream,
$"{Guid.NewGuid()}{Path.GetExtension(file.FileName)}",
file.ContentType);
return Ok(new { FileUrl = fileUrl, FileName = file.FileName });
}
catch (Exception ex)
{
_logger.LogError(ex, "S3 file upload failed");
return StatusCode(500, "File upload failed");
}
}
[HttpGet("download/{fileName}")]
public async Task<IActionResult> DownloadFile(string fileName)
{
try
{
var stream = await _fileStorageService.DownloadFileAsync(fileName);
if (stream == null)
return NotFound();
return File(stream, "application/octet-stream", fileName);
}
catch (Exception ex)
{
_logger.LogError(ex, $"S3 file download failed: {fileName}");
return StatusCode(500, "File download failed");
}
}
[HttpGet("presigned-url/{fileName}")]
public async Task<IActionResult> GetPresignedUrl(string fileName, [FromQuery] int expiresInMinutes = 60)
{
try
{
var expiration = DateTime.UtcNow.AddMinutes(expiresInMinutes);
var presignedUrl = await _fileStorageService.GeneratePresignedUrlAsync(fileName, expiration);
return Ok(new { PresignedUrl = presignedUrl, Expires = expiration });
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to generate presigned URL for: {fileName}");
return StatusCode(500, "Failed to generate presigned URL");
}
}
}
3.3 AWS RDS with SQL Server
3.3.1 Entity Framework Core with AWS RDS
// AWS RDS Configuration
public class AwsRdsDbContext : DbContext
{
private readonly IConfiguration _configuration;
private readonly ILogger<AwsRdsDbContext> _logger;
public AwsRdsDbContext(DbContextOptions<AwsRdsDbContext> options,
IConfiguration configuration,
ILogger<AwsRdsDbContext> logger)
: base(options)
{
_configuration = configuration;
_logger = logger;
}
public DbSet<Order> Orders { get; set; }
public DbSet<OrderItem> OrderItems { get; set; }
public DbSet<Customer> Customers { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
var connectionString = _configuration.GetConnectionString("AwsRds");
optionsBuilder.UseSqlServer(connectionString, options =>
{
options.EnableRetryOnFailure(
maxRetryCount: 5,
maxRetryDelay: TimeSpan.FromSeconds(30),
errorNumbersToAdd: null);
options.CommandTimeout(60); // 60 second command timeout
});
// Enable sensitive data logging only in development
if (_configuration.GetValue<bool>("Logging:EnableSensitiveDataLogging"))
{
optionsBuilder.EnableSensitiveDataLogging();
}
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Configure for RDS performance
modelBuilder.Entity<Order>(entity =>
{
entity.HasKey(e => e.Id);
entity.HasIndex(e => e.CustomerId);
entity.HasIndex(e => e.OrderDate);
entity.HasIndex(e => e.Status);
entity.Property(e => e.TotalAmount)
.HasColumnType("decimal(18,2)");
// Configure relationships
entity.HasOne(e => e.Customer)
.WithMany(c => c.Orders)
.HasForeignKey(e => e.CustomerId)
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity<OrderItem>(entity =>
{
entity.HasKey(e => e.Id);
entity.HasIndex(e => e.OrderId);
entity.HasIndex(e => e.ProductId);
entity.Property(e => e.UnitPrice)
.HasColumnType("decimal(18,2)");
entity.Property(e => e.TotalPrice)
.HasColumnType("decimal(18,2)")
.HasComputedColumnSql("[UnitPrice] * [Quantity]");
});
// Soft delete configuration
modelBuilder.Entity<Customer>(entity =>
{
entity.HasQueryFilter(e => !e.IsDeleted);
});
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
// Audit trail
var entries = ChangeTracker.Entries()
.Where(e => e.Entity is IAuditable &&
(e.State == EntityState.Added || e.State == EntityState.Modified));
foreach (var entityEntry in entries)
{
var auditable = (IAuditable)entityEntry.Entity;
if (entityEntry.State == EntityState.Added)
{
auditable.CreatedAt = DateTime.UtcNow;
auditable.CreatedBy = GetCurrentUserId();
}
else
{
auditable.UpdatedAt = DateTime.UtcNow;
auditable.UpdatedBy = GetCurrentUserId();
}
}
try
{
return await base.SaveChangesAsync(cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error saving changes to database");
throw;
}
}
private string GetCurrentUserId()
{
// Implement your user ID retrieval logic
return "system"; // Placeholder
}
}
// Repository pattern with RDS
public class OrderRepository : IOrderRepository
{
private readonly AwsRdsDbContext _context;
private readonly ILogger<OrderRepository> _logger;
public OrderRepository(AwsRdsDbContext context, ILogger<OrderRepository> logger)
{
_context = context;
_logger = logger;
}
public async Task<Order> GetOrderByIdAsync(int orderId)
{
try
{
return await _context.Orders
.Include(o => o.Customer)
.Include(o => o.OrderItems)
.ThenInclude(oi => oi.Product)
.FirstOrDefaultAsync(o => o.Id == orderId);
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error retrieving order {orderId}");
throw;
}
}
public async Task<List<Order>> GetOrdersByCustomerAsync(int customerId, DateTime? startDate = null, DateTime? endDate = null)
{
try
{
var query = _context.Orders
.Where(o => o.CustomerId == customerId);
if (startDate.HasValue)
query = query.Where(o => o.OrderDate >= startDate.Value);
if (endDate.HasValue)
query = query.Where(o => o.OrderDate <= endDate.Value);
return await query
.OrderByDescending(o => o.OrderDate)
.ToListAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error retrieving orders for customer {customerId}");
throw;
}
}
public async Task<Order> CreateOrderAsync(Order order)
{
try
{
_context.Orders.Add(order);
await _context.SaveChangesAsync();
return order;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating order");
throw;
}
}
public async Task<bool> UpdateOrderStatusAsync(int orderId, OrderStatus status)
{
try
{
var order = await _context.Orders.FindAsync(orderId);
if (order == null)
return false;
order.Status = status;
await _context.SaveChangesAsync();
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error updating order status for {orderId}");
throw;
}
}
}
4. Global Scaling Strategies
4.1 Content Delivery Network (CDN) Integration
4.1.1 Azure CDN with ASP.NET Core
// Azure CDN Service
using Azure.ResourceManager.Cdn;
using Azure.ResourceManager.Cdn.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
public class AzureCdnService
{
private readonly CdnManagementClient _cdnClient;
private readonly string _resourceGroupName;
private readonly string _profileName;
private readonly ILogger<AzureCdnService> _logger;
public AzureCdnService(IConfiguration configuration, ILogger<AzureCdnService> logger)
{
var connectionString = configuration.GetConnectionString("AzureCdn");
_cdnClient = new CdnManagementClient(new Uri(connectionString),
new DefaultAzureCredential());
_resourceGroupName = configuration["Azure:Cdn:ResourceGroup"];
_profileName = configuration["Azure:Cdn:ProfileName"];
_logger = logger;
}
public async Task PurgeCdnCacheAsync(List<string> contentPaths)
{
try
{
var purgeParameters = new PurgeParameters(contentPaths);
await _cdnClient.Endpoints.PurgeContentAsync(
_resourceGroupName,
_profileName,
"my-endpoint",
purgeParameters);
_logger.LogInformation($"CDN cache purged for {contentPaths.Count} paths");
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to purge CDN cache");
throw;
}
}
public async Task LoadCdnContentAsync(List<string> contentPaths)
{
try
{
var loadParameters = new LoadParameters(contentPaths);
await _cdnClient.Endpoints.LoadContentAsync(
_resourceGroupName,
_profileName,
"my-endpoint",
loadParameters);
_logger.LogInformation($"CDN content loaded for {contentPaths.Count} paths");
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to load CDN content");
throw;
}
}
}
// CDN Helper for ASP.NET Core
public static class CdnHelper
{
private static readonly string _cdnUrl;
static CdnHelper()
{
_cdnUrl = "https://my-cdn.azureedge.net";
}
public static string GetCdnUrl(string relativePath)
{
if (string.IsNullOrEmpty(relativePath))
return relativePath;
// Remove leading slash if present
if (relativePath.StartsWith("/"))
relativePath = relativePath.Substring(1);
return $"{_cdnUrl}/{relativePath}";
}
public static string GetImageUrl(string imageName, string size = "original")
{
return $"{_cdnUrl}/images/{size}/{imageName}";
}
public static string GetScriptUrl(string scriptName, bool minified = true)
{
var extension = minified ? ".min.js" : ".js";
return $"{_cdnUrl}/scripts/{scriptName}{extension}";
}
public static string GetStyleUrl(string styleName, bool minified = true)
{
var extension = minified ? ".min.css" : ".css";
return $"{_cdnUrl}/styles/{styleName}{extension}";
}
}
// Tag Helper for CDN URLs
[HtmlTargetElement("cdn-script")]
public class CdnScriptTagHelper : TagHelper
{
[HtmlAttributeName("src")]
public string Source { get; set; }
[HtmlAttributeName("minified")]
public bool Minified { get; set; } = true;
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "script";
output.Attributes.SetAttribute("src", CdnHelper.GetScriptUrl(Source, Minified));
output.Attributes.SetAttribute("crossorigin", "anonymous");
}
}
[HtmlTargetElement("cdn-style")]
public class CdnStyleTagHelper : TagHelper
{
[HtmlAttributeName("href")]
public string Href { get; set; }
[HtmlAttributeName("minified")]
public bool Minified { get; set; } = true;
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "link";
output.Attributes.SetAttribute("rel", "stylesheet");
output.Attributes.SetAttribute("href", CdnHelper.GetStyleUrl(Href, Minified));
output.Attributes.SetAttribute("crossorigin", "anonymous");
}
}
// Usage in Razor views
@*
<cdn-script src="site" minified="true"></cdn-script>
<cdn-style href="bootstrap" minified="true"></cdn-style>
*@
4.1.2 AWS CloudFront Integration
// AWS CloudFront Service
using Amazon.CloudFront;
using Amazon.CloudFront.Model;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
public class AwsCloudFrontService
{
private readonly IAmazonCloudFront _cloudFrontClient;
private readonly string _distributionId;
private readonly ILogger<AwsCloudFrontService> _logger;
public AwsCloudFrontService(IAmazonCloudFront cloudFrontClient,
IConfiguration configuration,
ILogger<AwsCloudFrontService> logger)
{
_cloudFrontClient = cloudFrontClient;
_distributionId = configuration["AWS:CloudFront:DistributionId"];
_logger = logger;
}
public async Task CreateInvalidationAsync(List<string> paths)
{
try
{
var invalidationBatch = new InvalidationBatch
{
CallerReference = DateTime.UtcNow.Ticks.ToString(),
Paths = new Paths
{
Items = paths,
Quantity = paths.Count
}
};
var request = new CreateInvalidationRequest
{
DistributionId = _distributionId,
InvalidationBatch = invalidationBatch
};
await _cloudFrontClient.CreateInvalidationAsync(request);
_logger.LogInformation($"CloudFront invalidation created for {paths.Count} paths");
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to create CloudFront invalidation");
throw;
}
}
public string GetSignedUrl(string resourceUrl, DateTime expiryTime)
{
var url = CloudFrontSigner.GetCannedSignedURL(
CloudFrontSigner.Protocol.https,
"d123.cloudfront.net",
null, // private key file path would be configured
resourceUrl,
"key-pair-id", // from AWS
expiryTime);
return url;
}
}
// CloudFront URL Helper
public static class CloudFrontHelper
{
private static readonly string _cloudFrontDomain = "d123.cloudfront.net";
public static string GetResourceUrl(string resourcePath)
{
return $"https://{_cloudFrontDomain}/{resourcePath.TrimStart('/')}";
}
public static string GetImageUrl(string imageName, string version = null)
{
var url = $"images/{imageName}";
if (!string.IsNullOrEmpty(version))
{
url += $"?v={version}";
}
return GetResourceUrl(url);
}
public static string GetAssetUrl(string assetPath, bool cacheBust = true)
{
var url = $"assets/{assetPath}";
if (cacheBust)
{
var version = Guid.NewGuid().ToString("N").Substring(0, 8);
url += $"?v={version}";
}
return GetResourceUrl(url);
}
}
4.2 Global Database Strategies
4.2.1 Multi-Region Database Setup
// Global Database Context
public class GlobalDbContext : DbContext
{
private readonly IConfiguration _configuration;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger<GlobalDbContext> _logger;
public GlobalDbContext(DbContextOptions<GlobalDbContext> options,
IConfiguration configuration,
IHttpContextAccessor httpContextAccessor,
ILogger<GlobalDbContext> logger)
: base(options)
{
_configuration = configuration;
_httpContextAccessor = httpContextAccessor;
_logger = logger;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
var connectionString = GetRegionalConnectionString();
optionsBuilder.UseSqlServer(connectionString, options =>
{
options.EnableRetryOnFailure(
maxRetryCount: 3,
maxRetryDelay: TimeSpan.FromSeconds(10),
errorNumbersToAdd: null);
});
}
}
private string GetRegionalConnectionString()
{
// Determine user's region from request headers or GeoIP
var userRegion = GetUserRegion();
// Get appropriate connection string for the region
var regionalConfigKey = $"Database:ConnectionStrings:{userRegion}";
var connectionString = _configuration[regionalConfigKey];
if (string.IsNullOrEmpty(connectionString))
{
// Fallback to primary region
connectionString = _configuration["Database:ConnectionStrings:Primary"];
_logger.LogWarning($"Using primary database connection for region: {userRegion}");
}
return connectionString;
}
private string GetUserRegion()
{
var httpContext = _httpContextAccessor.HttpContext;
if (httpContext?.Request == null)
return "Primary"; // Default region
// Check for region header (set by CDN or load balancer)
var regionHeader = httpContext.Request.Headers["X-User-Region"].FirstOrDefault();
if (!string.IsNullOrEmpty(regionHeader))
return regionHeader;
// Fallback: determine region from IP address (simplified)
var userIp = httpContext.Connection.RemoteIpAddress?.ToString();
return DetermineRegionFromIp(userIp);
}
private string DetermineRegionFromIp(string ipAddress)
{
// Simplified region determination
// In production, use GeoIP service
if (string.IsNullOrEmpty(ipAddress))
return "Primary";
// Example logic - replace with actual GeoIP service
return ipAddress.StartsWith("192.168.") ? "Primary" : "Secondary";
}
}
// Database synchronization service
public class DatabaseSynchronizationService
{
private readonly IConfiguration _configuration;
private readonly ILogger<DatabaseSynchronizationService> _logger;
public DatabaseSynchronizationService(IConfiguration configuration,
ILogger<DatabaseSynchronizationService> logger)
{
_configuration = configuration;
_logger = logger;
}
public async Task SynchronizeDataAsync<T>(string sourceRegion, string targetRegion,
DateTime? since = null) where T : class
{
try
{
using var sourceContext = CreateDbContext(sourceRegion);
using var targetContext = CreateDbContext(targetRegion);
var query = sourceContext.Set<T>().AsQueryable();
if (since.HasValue)
{
// Assuming entities have a LastModified property
query = query.Where(e => EF.Property<DateTime>(e, "LastModified") > since.Value);
}
var entities = await query.ToListAsync();
foreach (var entity in entities)
{
var existingEntity = await targetContext.Set<T>().FindAsync(GetKeyValues(entity));
if (existingEntity == null)
{
targetContext.Set<T>().Add(entity);
}
else
{
targetContext.Entry(existingEntity).CurrentValues.SetValues(entity);
}
}
await targetContext.SaveChangesAsync();
_logger.LogInformation($"Synchronized {entities.Count} {typeof(T).Name} entities from {sourceRegion} to {targetRegion}");
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to synchronize data from {sourceRegion} to {targetRegion}");
throw;
}
}
private object[] GetKeyValues<T>(T entity) where T : class
{
// Implementation to get primary key values
// This is a simplified version
var keyProperties = typeof(T).GetProperties()
.Where(p => p.Name == "Id" || p.Name.EndsWith("Id"))
.ToArray();
return keyProperties.Select(p => p.GetValue(entity)).ToArray();
}
private DbContext CreateDbContext(string region)
{
var optionsBuilder = new DbContextOptionsBuilder<GlobalDbContext>();
var connectionString = _configuration[$"Database:ConnectionStrings:{region}"];
optionsBuilder.UseSqlServer(connectionString);
return new GlobalDbContext(optionsBuilder.Options, _configuration, null, _logger);
}
}
5. Cloud Storage Solutions
5.1 Multi-Cloud Storage Abstraction
// Unified Cloud Storage Interface
public interface ICloudStorageService
{
Task<string> UploadFileAsync(Stream fileStream, string fileName, string contentType);
Task<Stream> DownloadFileAsync(string fileName);
Task<bool> DeleteFileAsync(string fileName);
Task<bool> FileExistsAsync(string fileName);
Task<FileMetadata> GetFileMetadataAsync(string fileName);
Task<List<string>> ListFilesAsync(string prefix = null);
Task<string> GenerateDownloadUrlAsync(string fileName, TimeSpan expiryDuration);
}
public class FileMetadata
{
public string FileName { get; set; }
public long Size { get; set; }
public string ContentType { get; set; }
public DateTime LastModified { get; set; }
public string ETag { get; set; }
}
// Multi-Cloud Storage Service
public class MultiCloudStorageService : ICloudStorageService
{
private readonly IAzureBlobStorageService _azureStorage;
private readonly IAwsS3StorageService _awsStorage;
private readonly IConfiguration _configuration;
private readonly ILogger<MultiCloudStorageService> _logger;
public MultiCloudStorageService(IAzureBlobStorageService azureStorage,
IAwsS3StorageService awsStorage,
IConfiguration configuration,
ILogger<MultiCloudStorageService> logger)
{
_azureStorage = azureStorage;
_awsStorage = awsStorage;
_configuration = configuration;
_logger = logger;
}
public async Task<string> UploadFileAsync(Stream fileStream, string fileName, string contentType)
{
var primaryProvider = GetPrimaryStorageProvider();
try
{
string fileUrl;
if (primaryProvider == StorageProvider.Azure)
{
fileUrl = await _azureStorage.UploadFileAsync(fileStream, fileName, contentType);
// Async replication to secondary provider
_ = Task.Run(async () =>
{
try
{
fileStream.Position = 0; // Reset stream position
await _awsStorage.UploadFileAsync(fileStream, fileName, contentType);
_logger.LogInformation($"File replicated to secondary storage: {fileName}");
}
catch (Exception ex)
{
_logger.LogWarning(ex, $"Failed to replicate file to secondary storage: {fileName}");
}
});
}
else
{
fileUrl = await _awsStorage.UploadFileAsync(fileStream, fileName, contentType);
// Async replication to secondary provider
_ = Task.Run(async () =>
{
try
{
fileStream.Position = 0; // Reset stream position
await _azureStorage.UploadFileAsync(fileStream, fileName, contentType);
_logger.LogInformation($"File replicated to secondary storage: {fileName}");
}
catch (Exception ex)
{
_logger.LogWarning(ex, $"Failed to replicate file to secondary storage: {fileName}");
}
});
}
return fileUrl;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to upload file: {fileName} to primary storage");
// Fallback to secondary provider
try
{
fileStream.Position = 0;
var fallbackUrl = await GetSecondaryStorageProvider().UploadFileAsync(fileStream, fileName, contentType);
_logger.LogWarning($"File uploaded to secondary storage as fallback: {fileName}");
return fallbackUrl;
}
catch (Exception fallbackEx)
{
_logger.LogError(fallbackEx, $"Failed to upload file to secondary storage: {fileName}");
throw new StorageException("All storage providers failed", ex);
}
}
}
public async Task<Stream> DownloadFileAsync(string fileName)
{
// Try primary provider first
try
{
return await GetPrimaryStorageProvider().DownloadFileAsync(fileName);
}
catch (Exception primaryEx)
{
_logger.LogWarning(primaryEx, $"Failed to download from primary storage: {fileName}");
// Fallback to secondary provider
try
{
var stream = await GetSecondaryStorageProvider().DownloadFileAsync(fileName);
_logger.LogInformation($"File downloaded from secondary storage: {fileName}");
return stream;
}
catch (Exception secondaryEx)
{
_logger.LogError(secondaryEx, $"Failed to download from secondary storage: {fileName}");
throw new StorageException("All storage providers failed", secondaryEx);
}
}
}
public async Task<bool> DeleteFileAsync(string fileName)
{
var tasks = new List<Task<bool>>
{
_azureStorage.DeleteFileAsync(fileName),
_awsStorage.DeleteFileAsync(fileName)
};
var results = await Task.WhenAll(tasks);
return results.All(r => r);
}
public async Task<bool> FileExistsAsync(string fileName)
{
// Check primary provider first
var exists = await GetPrimaryStorageProvider().FileExistsAsync(fileName);
if (exists)
return true;
// Check secondary provider
return await GetSecondaryStorageProvider().FileExistsAsync(fileName);
}
public async Task<FileMetadata> GetFileMetadataAsync(string fileName)
{
try
{
return await GetPrimaryStorageProvider().GetFileMetadataAsync(fileName);
}
catch (Exception ex)
{
_logger.LogWarning(ex, $"Failed to get metadata from primary storage: {fileName}");
return await GetSecondaryStorageProvider().GetFileMetadataAsync(fileName);
}
}
public async Task<List<string>> ListFilesAsync(string prefix = null)
{
// Merge files from both providers, remove duplicates
var azureFiles = await _azureStorage.ListFilesAsync(prefix);
var awsFiles = await _awsStorage.ListFilesAsync(prefix);
return azureFiles.Union(awsFiles).Distinct().ToList();
}
public async Task<string> GenerateDownloadUrlAsync(string fileName, TimeSpan expiryDuration)
{
try
{
return await GetPrimaryStorageProvider().GenerateDownloadUrlAsync(fileName, expiryDuration);
}
catch (Exception ex)
{
_logger.LogWarning(ex, $"Failed to generate URL from primary storage: {fileName}");
return await GetSecondaryStorageProvider().GenerateDownloadUrlAsync(fileName, expiryDuration);
}
}
private ICloudStorageService GetPrimaryStorageProvider()
{
var primaryProvider = _configuration["Storage:PrimaryProvider"] ?? "Azure";
return primaryProvider.Equals("Azure", StringComparison.OrdinalIgnoreCase)
? _azureStorage
: _awsStorage;
}
private ICloudStorageService GetSecondaryStorageProvider()
{
var primaryProvider = _configuration["Storage:PrimaryProvider"] ?? "Azure";
return primaryProvider.Equals("Azure", StringComparison.OrdinalIgnoreCase)
? _awsStorage
: _azureStorage;
}
}
public enum StorageProvider
{
Azure,
AWS
}
public class StorageException : Exception
{
public StorageException(string message, Exception innerException)
: base(message, innerException)
{
}
}
6. Microservices in Cloud
6.1 Cloud-Native Microservices Architecture
6.1.1 API Gateway Pattern
// Ocelot API Gateway Configuration
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);
config.AddEnvironmentVariables();
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.ConfigureLogging((context, logging) =>
{
logging.AddConsole();
logging.AddDebug();
logging.AddApplicationInsights();
});
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddOcelot()
.AddConsul()
.AddConfigStoredInConsul();
// Add authentication and authorization
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://login.microsoftonline.com/{tenantId}";
options.Audience = "api://{api-client-id}";
});
services.AddAuthorization();
// Add health checks
services.AddHealthChecks();
// Add distributed caching for rate limiting
services.AddDistributedRedisCache(options =>
{
options.Configuration = Configuration.GetConnectionString("Redis");
options.InstanceName = "ApiGateway_";
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
// Health check endpoint
app.UseEndpoints(endpoints =>
{
endpoints.MapHealthChecks("/health");
});
app.UseOcelot().Wait();
}
}
// ocelot.json configuration
{
"Routes": [
{
"DownstreamPathTemplate": "/api/products",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "products-service",
"Port": 443
}
],
"UpstreamPathTemplate": "/products",
"UpstreamHttpMethod": [ "GET" ],
"AuthenticationOptions": {
"AuthenticationProviderKey": "Bearer"
},
"RateLimitOptions": {
"ClientWhitelist": [],
"EnableRateLimiting": true,
"Period": "1s",
"PeriodTimespan": 1,
"Limit": 100
}
},
{
"DownstreamPathTemplate": "/api/orders",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "orders-service",
"Port": 443
}
],
"UpstreamPathTemplate": "/orders",
"UpstreamHttpMethod": [ "GET", "POST", "PUT" ],
"AuthenticationOptions": {
"AuthenticationProviderKey": "Bearer"
}
}
],
"GlobalConfiguration": {
"BaseUrl": "https://api.mycompany.com",
"ServiceDiscoveryProvider": {
"Host": "consul",
"Port": 8500,
"Type": "Consul"
}
}
}
6.1.2 Microservice Communication
// Resilient HTTP Client Factory
public static class HttpClientFactoryExtensions
{
public static void AddResilientHttpClient<TClient, TImplementation>(
this IServiceCollection services,
string clientName,
string baseAddress)
where TClient : class
where TImplementation : class, TClient
{
services.AddHttpClient<TClient, TImplementation>(clientName, client =>
{
client.BaseAddress = new Uri(baseAddress);
client.DefaultRequestHeaders.Add("User-Agent", "MyApp/1.0");
client.Timeout = TimeSpan.FromSeconds(30);
})
.AddPolicyHandler(GetRetryPolicy())
.AddPolicyHandler(GetCircuitBreakerPolicy())
.AddPolicyHandler(GetTimeoutPolicy())
.AddHttpMessageHandler<LoggingHandler>();
}
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) =>
{
var logger = context.GetLogger();
logger?.LogWarning($"Retry {retryCount} after {timespan.TotalSeconds}s for {context.PolicyKey}");
});
}
private static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.CircuitBreakerAsync(
handledEventsAllowedBeforeBreaking: 3,
durationOfBreak: TimeSpan.FromSeconds(30),
onBreak: (outcome, breakDelay, context) =>
{
var logger = context.GetLogger();
logger?.LogWarning($"Circuit breaker opened for {breakDelay.TotalSeconds}s");
},
onReset: (context) =>
{
var logger = context.GetLogger();
logger?.LogInformation("Circuit breaker reset");
});
}
private static IAsyncPolicy<HttpResponseMessage> GetTimeoutPolicy()
{
return Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(15));
}
}
// Logging Handler for HTTP requests
public class LoggingHandler : DelegatingHandler
{
private readonly ILogger<LoggingHandler> _logger;
public LoggingHandler(ILogger<LoggingHandler> logger)
{
_logger = logger;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var stopwatch = Stopwatch.StartNew();
try
{
_logger.LogInformation($"Sending HTTP request: {request.Method} {request.RequestUri}");
var response = await base.SendAsync(request, cancellationToken);
stopwatch.Stop();
_logger.LogInformation($"Received HTTP response: {(int)response.StatusCode} in {stopwatch.ElapsedMilliseconds}ms");
return response;
}
catch (Exception ex)
{
stopwatch.Stop();
_logger.LogError(ex, $"HTTP request failed after {stopwatch.ElapsedMilliseconds}ms");
throw;
}
}
}
// Products Service Client
public interface IProductsServiceClient
{
Task<List<Product>> GetProductsAsync();
Task<Product> GetProductAsync(int id);
Task<Product> CreateProductAsync(Product product);
Task<bool> UpdateProductAsync(Product product);
Task<bool> DeleteProductAsync(int id);
}
public class ProductsServiceClient : IProductsServiceClient
{
private readonly HttpClient _httpClient;
private readonly ILogger<ProductsServiceClient> _logger;
public ProductsServiceClient(HttpClient httpClient, ILogger<ProductsServiceClient> logger)
{
_httpClient = httpClient;
_logger = logger;
}
public async Task<List<Product>> GetProductsAsync()
{
try
{
var response = await _httpClient.GetAsync("/api/products");
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<List<Product>>(content, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to get products");
throw;
}
}
public async Task<Product> GetProductAsync(int id)
{
try
{
var response = await _httpClient.GetAsync($"/api/products/{id}");
if (response.StatusCode == HttpStatusCode.NotFound)
return null;
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<Product>(content, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to get product {id}");
throw;
}
}
public async Task<Product> CreateProductAsync(Product product)
{
try
{
var json = JsonSerializer.Serialize(product);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync("/api/products", content);
response.EnsureSuccessStatusCode();
var responseContent = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<Product>(responseContent, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to create product");
throw;
}
}
public async Task<bool> UpdateProductAsync(Product product)
{
try
{
var json = JsonSerializer.Serialize(product);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PutAsync($"/api/products/{product.Id}", content);
return response.IsSuccessStatusCode;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to update product {product.Id}");
throw;
}
}
public async Task<bool> DeleteProductAsync(int id)
{
try
{
var response = await _httpClient.DeleteAsync($"/api/products/{id}");
return response.IsSuccessStatusCode;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to delete product {id}");
throw;
}
}
}
// Service registration
public void ConfigureServices(IServiceCollection services)
{
services.AddResilientHttpClient<IProductsServiceClient, ProductsServiceClient>(
"ProductsService",
"https://products-service.mycompany.com");
services.AddResilientHttpClient<IOrdersServiceClient, OrdersServiceClient>(
"OrdersService",
"https://orders-service.mycompany.com");
services.AddTransient<LoggingHandler>();
}
7. Production Deployment Patterns
7.1 Docker and Kubernetes Deployment
7.1.1 Dockerfile for ASP.NET Core
# Multi-stage build for optimized production image
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
# Build stage
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
# Copy project files
COPY ["MyApp/MyApp.csproj", "MyApp/"]
COPY ["MyApp.Core/MyApp.Core.csproj", "MyApp.Core/"]
COPY ["MyApp.Infrastructure/MyApp.Infrastructure.csproj", "MyApp.Infrastructure/"]
# Restore dependencies
RUN dotnet restore "MyApp/MyApp.csproj"
COPY . .
# Build application
WORKDIR "/src/MyApp"
RUN dotnet build "MyApp.csproj" -c Release -o /app/build
# Publish stage
FROM build AS publish
RUN dotnet publish "MyApp.csproj" -c Release -o /app/publish /p:UseAppHost=false
# Final stage
FROM base AS final
WORKDIR /app
# Install necessary tools
RUN apt-get update && apt-get install -y \
curl \
&& rm -rf /var/lib/apt/lists/*
# Create non-root user for security
RUN groupadd -r appuser && useradd -r -g appuser appuser
RUN chown -R appuser:appuser /app
USER appuser
COPY --from=publish /app/publish .
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost/health || exit 1
ENTRYPOINT ["dotnet", "MyApp.dll"]
7.1.2 Kubernetes Deployment Configuration
deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deployment
labels:
app: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "80"
prometheus.io/path: "/metrics"
spec:
containers:
- name: myapp
image: myregistry.azurecr.io/myapp:latest
ports:
- containerPort: 80
- containerPort: 443
env:
- name: ASPNETCORE_ENVIRONMENT
value: "Production"
- name: ConnectionStrings__DefaultConnection
valueFrom:
secretKeyRef:
name: myapp-secrets
key: database-connection-string
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health/ready
port: 80
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
startupProbe:
httpGet:
path: /health/startup
port: 80
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 10
---
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
selector:
app: myapp
ports:
- name: http
port: 80
targetPort: 80
- name: https
port: 443
targetPort: 443
type: LoadBalancer
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: myapp-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp-deployment
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
7.1.3 Kubernetes Ingress Configuration
ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
tls:
- hosts:
- myapp.example.com
secretName: myapp-tls-secret
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-service
port:
number: 80
- path: /api
pathType: Prefix
backend:
service:
name: myapp-service
port:
number: 80
7.2 CI/CD Pipeline Configuration
7.2.1 GitHub Actions for Azure
.github/workflows/azure-deploy.yml
name: Build and Deploy to Azure
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
AZURE_WEBAPP_NAME: my-aspnet-app
AZURE_WEBAPP_PACKAGE_PATH: './publish'
DOTNET_VERSION: '8.0.x'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup .NET Core
uses: actions/setup-dotnet@v3
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Restore dependencies
run: dotnet restore
- name: Build with dotnet
run: dotnet build --configuration Release --no-restore
- name: Test with dotnet
run: dotnet test --verbosity normal --logger trx
- name: Publish with dotnet
run: dotnet publish -c Release -o ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}
- name: Upload artifact for deployment job
uses: actions/upload-artifact@v3
with:
name: .net-app
path: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}
deploy:
runs-on: ubuntu-latest
needs: build
environment:
name: 'Production'
url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
steps:
- name: Download artifact from build job
uses: actions/download-artifact@v3
with:
name: .net-app
- name: Deploy to Azure Web App
id: deploy-to-webapp
uses: azure/webapps-deploy@v2
with:
app-name: ${{ env.AZURE_WEBAPP_NAME }}
publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}
package: .
- name: Run smoke tests
run: |
echo "Running smoke tests against ${{ steps.deploy-to-webapp.outputs.webapp-url }}"
curl -f ${{ steps.deploy-to-webapp.outputs.webapp-url }}/health || exit 1
7.2.2 AWS CodePipeline Configuration
buildspec.yml
version: 0.2
phases:
install:
runtime-versions:
dotnet: 8.0
commands:
- echo Installing dependencies...
pre_build:
commands:
- echo Restoring NuGet packages...
- dotnet restore
- echo Running tests...
- dotnet test --logger trx
build:
commands:
- echo Building application...
- dotnet build --configuration Release
- echo Publishing application...
- dotnet publish -c Release -o ./publish
post_build:
commands:
- echo Build completed on `date`
artifacts:
files:
- '**/*'
base-directory: 'publish'
discard-paths: no
cache:
paths:
- '**/*'
8. Real-World Case Study
8.1 E-Commerce Platform Global Scaling
8.1.1 Architecture Overview
Scenario : Global E-Commerce Platform handling 10,000+ transactions per minute during peak seasons.
// Global E-Commerce Architecture Implementation
public class GlobalECommerceService
{
private readonly IProductService _productService;
private readonly IOrderService _orderService;
private readonly IInventoryService _inventoryService;
private readonly IPaymentService _paymentService;
private readonly IShippingService _shippingService;
private readonly ILogger<GlobalECommerceService> _logger;
private readonly ICacheService _cacheService;
private readonly IEventBus _eventBus;
public GlobalECommerceService(
IProductService productService,
IOrderService orderService,
IInventoryService inventoryService,
IPaymentService paymentService,
IShippingService shippingService,
ILogger<GlobalECommerceService> logger,
ICacheService cacheService,
IEventBus eventBus)
{
_productService = productService;
_orderService = orderService;
_inventoryService = inventoryService;
_paymentService = paymentService;
_shippingService = shippingService;
_logger = logger;
_cacheService = cacheService;
_eventBus = eventBus;
}
public async Task<OrderResult> PlaceOrderAsync(OrderRequest request)
{
var stopwatch = Stopwatch.StartNew();
try
{
// Step 1: Validate request
await ValidateOrderRequestAsync(request);
// Step 2: Check product availability with cache
var product = await GetProductWithCacheAsync(request.ProductId);
if (product == null)
throw new ProductNotFoundException(request.ProductId);
// Step 3: Reserve inventory
var inventoryResult = await _inventoryService.ReserveInventoryAsync(
request.ProductId, request.Quantity, request.Region);
if (!inventoryResult.Success)
throw new InventoryUnavailableException(request.ProductId);
// Step 4: Process payment
var paymentResult = await _paymentService.ProcessPaymentAsync(
request.PaymentMethod, request.TotalAmount, request.Currency);
if (!paymentResult.Success)
throw new PaymentFailedException(paymentResult.ErrorMessage);
// Step 5: Create order
var order = await _orderService.CreateOrderAsync(request, inventoryResult.ReservationId);
// Step 6: Publish order created event
await _eventBus.PublishAsync(new OrderCreatedEvent
{
OrderId = order.Id,
CustomerId = request.CustomerId,
TotalAmount = request.TotalAmount,
Currency = request.Currency,
Region = request.Region,
Timestamp = DateTime.UtcNow
});
// Step 7: Update cache and return result
await UpdateProductCacheAsync(product);
stopwatch.Stop();
_logger.LogInformation($"Order {order.Id} placed successfully in {stopwatch.ElapsedMilliseconds}ms");
return new OrderResult
{
Success = true,
OrderId = order.Id,
EstimatedDelivery = await _shippingService.GetEstimatedDeliveryAsync(
request.Region, order.Id)
};
}
catch (Exception ex)
{
stopwatch.Stop();
_logger.LogError(ex, $"Order placement failed after {stopwatch.ElapsedMilliseconds}ms");
// Compensating actions for failed order
await CompensateFailedOrderAsync(request);
throw;
}
}
private async Task<Product> GetProductWithCacheAsync(int productId)
{
var cacheKey = $"product:{productId}";
// Try to get from cache first
var cachedProduct = await _cacheService.GetAsync<Product>(cacheKey);
if (cachedProduct != null)
return cachedProduct;
// If not in cache, get from database
var product = await _productService.GetProductAsync(productId);
if (product != null)
{
// Cache for 5 minutes with sliding expiration
await _cacheService.SetAsync(cacheKey, product, TimeSpan.FromMinutes(5));
}
return product;
}
private async Task UpdateProductCacheAsync(Product product)
{
var cacheKey = $"product:{product.Id}";
await _cacheService.SetAsync(cacheKey, product, TimeSpan.FromMinutes(5));
}
private async Task CompensateFailedOrderAsync(OrderRequest request)
{
// Implement compensating transactions for failed orders
try
{
// Release reserved inventory
await _inventoryService.ReleaseReservationAsync(request.ProductId, request.Quantity);
// Refund payment if already processed
await _paymentService.RefundPaymentAsync(request.PaymentMethod.TransactionId);
_logger.LogInformation($"Compensating actions completed for failed order");
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to execute compensating actions");
// Alert monitoring system about failed compensation
await _eventBus.PublishAsync(new CompensationFailedEvent
{
OrderRequest = request,
Timestamp = DateTime.UtcNow
});
}
}
}
// Event-driven architecture for order processing
public class OrderCreatedEventHandler : IEventHandler<OrderCreatedEvent>
{
private readonly IShippingService _shippingService;
private readonly IEmailService _emailService;
private readonly IAnalyticsService _analyticsService;
private readonly ILogger<OrderCreatedEventHandler> _logger;
public OrderCreatedEventHandler(
IShippingService shippingService,
IEmailService emailService,
IAnalyticsService analyticsService,
ILogger<OrderCreatedEventHandler> logger)
{
_shippingService = shippingService;
_emailService = emailService;
_analyticsService = analyticsService;
_logger = logger;
}
public async Task HandleAsync(OrderCreatedEvent @event)
{
try
{
// Process shipping asynchronously
var shippingTask = _shippingService.ProcessShippingAsync(@event.OrderId, @event.Region);
// Send confirmation email
var emailTask = _emailService.SendOrderConfirmationAsync(@event.OrderId, @event.CustomerId);
// Update analytics
var analyticsTask = _analyticsService.TrackOrderAsync(@event);
// Wait for all tasks to complete
await Task.WhenAll(shippingTask, emailTask, analyticsTask);
_logger.LogInformation($"Order {@event.OrderId} processed successfully");
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to process order {@event.OrderId}");
throw;
}
}
}
9. Best Practices & Optimization
9.1 Performance Optimization
9.1.1 Caching Strategies
// Distributed Cache Service
public interface IDistributedCacheService
{
Task<T> GetAsync<T>(string key);
Task SetAsync<T>(string key, T value, TimeSpan? expiration = null);
Task<bool> RemoveAsync(string key);
Task<bool> ExistsAsync(string key);
Task<TimeSpan?> GetTimeToLiveAsync(string key);
Task<bool> SetExpirationAsync(string key, TimeSpan expiration);
}
public class RedisCacheService : IDistributedCacheService
{
private readonly IConnectionMultiplexer _redis;
private readonly IDatabase _database;
private readonly ILogger<RedisCacheService> _logger;
private readonly JsonSerializerOptions _jsonOptions;
public RedisCacheService(IConnectionMultiplexer redis, ILogger<RedisCacheService> logger)
{
_redis = redis;
_database = redis.GetDatabase();
_logger = logger;
_jsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = false
};
}
public async Task<T> GetAsync<T>(string key)
{
try
{
var cachedValue = await _database.StringGetAsync(key);
if (cachedValue.IsNull)
return default;
return JsonSerializer.Deserialize<T>(cachedValue, _jsonOptions);
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to get cache value for key: {key}");
return default;
}
}
public async Task SetAsync<T>(string key, T value, TimeSpan? expiration = null)
{
try
{
var serializedValue = JsonSerializer.Serialize(value, _jsonOptions);
await _database.StringSetAsync(key, serializedValue, expiration);
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to set cache value for key: {key}");
}
}
public async Task<bool> RemoveAsync(string key)
{
try
{
return await _database.KeyDeleteAsync(key);
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to remove cache key: {key}");
return false;
}
}
public async Task<bool> ExistsAsync(string key)
{
try
{
return await _database.KeyExistsAsync(key);
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to check cache key existence: {key}");
return false;
}
}
public async Task<TimeSpan?> GetTimeToLiveAsync(string key)
{
try
{
return await _database.KeyTimeToLiveAsync(key);
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to get TTL for key: {key}");
return null;
}
}
public async Task<bool> SetExpirationAsync(string key, TimeSpan expiration)
{
try
{
return await _database.KeyExpireAsync(key, expiration);
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to set expiration for key: {key}");
return false;
}
}
}
// Cache-Aside Pattern Implementation
public class CachedProductService : IProductService
{
private readonly IProductService _decoratedService;
private readonly IDistributedCacheService _cacheService;
private readonly ILogger<CachedProductService> _logger;
public CachedProductService(
IProductService decoratedService,
IDistributedCacheService cacheService,
ILogger<CachedProductService> logger)
{
_decoratedService = decoratedService;
_cacheService = cacheService;
_logger = logger;
}
public async Task<Product> GetProductAsync(int productId)
{
var cacheKey = $"product:{productId}";
try
{
// Try to get from cache first
var cachedProduct = await _cacheService.GetAsync<Product>(cacheKey);
if (cachedProduct != null)
{
_logger.LogDebug($"Cache hit for product {productId}");
return cachedProduct;
}
// Cache miss - get from underlying service
_logger.LogDebug($"Cache miss for product {productId}");
var product = await _decoratedService.GetProductAsync(productId);
if (product != null)
{
// Cache the product with appropriate expiration
var expiration = GetCacheExpiration(product);
await _cacheService.SetAsync(cacheKey, product, expiration);
}
return product;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error in cached product service for product {productId}");
// Fallback to underlying service if cache fails
return await _decoratedService.GetProductAsync(productId);
}
}
public async Task<List<Product>> GetProductsByCategoryAsync(int categoryId)
{
var cacheKey = $"products:category:{categoryId}";
try
{
var cachedProducts = await _cacheService.GetAsync<List<Product>>(cacheKey);
if (cachedProducts != null)
{
_logger.LogDebug($"Cache hit for category {categoryId}");
return cachedProducts;
}
_logger.LogDebug($"Cache miss for category {categoryId}");
var products = await _decoratedService.GetProductsByCategoryAsync(categoryId);
if (products?.Any() == true)
{
await _cacheService.SetAsync(cacheKey, products, TimeSpan.FromMinutes(10));
}
return products;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error getting cached products for category {categoryId}");
return await _decoratedService.GetProductsByCategoryAsync(categoryId);
}
}
public async Task<Product> CreateProductAsync(Product product)
{
var result = await _decoratedService.CreateProductAsync(product);
// Invalidate relevant cache entries
await InvalidateProductCacheAsync(product.Id, product.CategoryId);
return result;
}
public async Task<bool> UpdateProductAsync(Product product)
{
var result = await _decoratedService.UpdateProductAsync(product);
if (result)
{
// Invalidate relevant cache entries
await InvalidateProductCacheAsync(product.Id, product.CategoryId);
}
return result;
}
public async Task<bool> DeleteProductAsync(int productId)
{
// Get product before deletion to know category for cache invalidation
var product = await _decoratedService.GetProductAsync(productId);
var result = await _decoratedService.DeleteProductAsync(productId);
if (result && product != null)
{
await InvalidateProductCacheAsync(productId, product.CategoryId);
}
return result;
}
private async Task InvalidateProductCacheAsync(int productId, int categoryId)
{
var tasks = new List<Task>
{
_cacheService.RemoveAsync($"product:{productId}"),
_cacheService.RemoveAsync($"products:category:{categoryId}"),
_cacheService.RemoveAsync("products:featured"),
_cacheService.RemoveAsync("products:popular")
};
await Task.WhenAll(tasks);
_logger.LogDebug($"Invalidated cache for product {productId}");
}
private TimeSpan GetCacheExpiration(Product product)
{
// Dynamic cache expiration based on product properties
if (product.IsFeatured)
return TimeSpan.FromMinutes(5); // Shorter cache for featured products
if (product.LastModified > DateTime.UtcNow.AddDays(-7))
return TimeSpan.FromMinutes(15); // Recently modified products
return TimeSpan.FromMinutes(30); // Standard cache duration
}
}
9.2 Security Best Practices
9.2.1 Cloud Security Implementation
// Security Headers Middleware
public class SecurityHeadersMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<SecurityHeadersMiddleware> _logger;
public SecurityHeadersMiddleware(RequestDelegate next, ILogger<SecurityHeadersMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
// Add security headers to response
context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
context.Response.Headers.Add("X-Frame-Options", "DENY");
context.Response.Headers.Add("X-XSS-Protection", "1; mode=block");
context.Response.Headers.Add("Referrer-Policy", "strict-origin-when-cross-origin");
// Content Security Policy
context.Response.Headers.Add("Content-Security-Policy",
"default-src 'self'; " +
"script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; " +
"style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; " +
"img-src 'self' data: https:; " +
"connect-src 'self' https://api.myapp.com;");
// Feature Policy
context.Response.Headers.Add("Feature-Policy",
"camera 'none'; " +
"microphone 'none'; " +
"geolocation 'none'");
await _next(context);
}
}
// Azure Key Vault Integration for Secrets Management
public class KeyVaultService : ISecretsService
{
private readonly SecretClient _secretClient;
private readonly ILogger<KeyVaultService> _logger;
private readonly Dictionary<string, string> _cache;
private readonly TimeSpan _cacheDuration;
public KeyVaultService(IConfiguration configuration, ILogger<KeyVaultService> logger)
{
var keyVaultUrl = configuration["KeyVault:Endpoint"];
_secretClient = new SecretClient(new Uri(keyVaultUrl), new DefaultAzureCredential());
_logger = logger;
_cache = new Dictionary<string, string>();
_cacheDuration = TimeSpan.FromMinutes(30);
}
public async Task<string> GetSecretAsync(string secretName)
{
// Check cache first
if (_cache.TryGetValue(secretName, out var cachedValue) &&
!string.IsNullOrEmpty(cachedValue))
{
return cachedValue;
}
try
{
var secret = await _secretClient.GetSecretAsync(secretName);
var secretValue = secret.Value.Value;
// Cache the secret
_cache[secretName] = secretValue;
// Set up cache expiration
_ = Task.Delay(_cacheDuration).ContinueWith(_ =>
{
_cache.Remove(secretName);
_logger.LogDebug($"Cache expired for secret: {secretName}");
});
return secretValue;
}
catch (RequestFailedException ex) when (ex.Status == 404)
{
_logger.LogWarning($"Secret not found: {secretName}");
return null;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to retrieve secret: {secretName}");
throw;
}
}
public async Task SetSecretAsync(string secretName, string secretValue)
{
try
{
await _secretClient.SetSecretAsync(secretName, secretValue);
// Update cache
_cache[secretName] = secretValue;
_logger.LogInformation($"Secret updated: {secretName}");
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to set secret: {secretName}");
throw;
}
}
}
// JWT Token Validation Service
public class JwtTokenService : ITokenService
{
private readonly IConfiguration _configuration;
private readonly ILogger<JwtTokenService> _logger;
private readonly TokenValidationParameters _validationParameters;
public JwtTokenService(IConfiguration configuration, ILogger<JwtTokenService> logger)
{
_configuration = configuration;
_logger = logger;
_validationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = configuration["Jwt:Issuer"],
ValidAudience = configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(configuration["Jwt:SecretKey"])),
ClockSkew = TimeSpan.Zero // No clock skew for production
};
}
public async Task<string> GenerateTokenAsync(User user)
{
try
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
new Claim(JwtRegisteredClaimNames.Email, user.Email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(ClaimTypes.Role, user.Role),
new Claim("region", user.Region)
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:SecretKey"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _configuration["Jwt:Issuer"],
audience: _configuration["Jwt:Audience"],
claims: claims,
expires: DateTime.UtcNow.AddMinutes(Convert.ToDouble(_configuration["Jwt:ExpiryMinutes"])),
signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to generate JWT token");
throw;
}
}
public async Task<ClaimsPrincipal> ValidateTokenAsync(string token)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var principal = tokenHandler.ValidateToken(token, _validationParameters, out var validatedToken);
return principal;
}
catch (SecurityTokenExpiredException ex)
{
_logger.LogWarning($"Token expired: {ex.Message}");
throw;
}
catch (SecurityTokenInvalidSignatureException ex)
{
_logger.LogWarning($"Invalid token signature: {ex.Message}");
throw;
}
catch (Exception ex)
{
_logger.LogError(ex, "Token validation failed");
throw;
}
}
public async Task<bool> IsTokenValidAsync(string token)
{
try
{
await ValidateTokenAsync(token);
return true;
}
catch
{
return false;
}
}
}
This comprehensive guide covers the essential aspects of cloud domination with ASP.NET Core, providing you with the knowledge and tools to build scalable, resilient, and high-performance applications in the cloud. The examples and patterns shown here are production-ready and can be adapted to your specific use cases.
Remember that cloud success requires continuous learning and adaptation. Stay updated with the latest cloud services, monitor your applications proactively, and always prioritize security and performance in your implementations.