UNPKG

@re-shell/cli

Version:

Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja

1,210 lines (1,051 loc) 39.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.aspnetCoreMinimalTemplate = void 0; exports.aspnetCoreMinimalTemplate = { id: 'aspnet-core-minimal', name: 'ASP.NET Core Minimal API', displayName: 'ASP.NET Core Minimal API', language: 'csharp', framework: 'aspnet-core-minimal', description: 'Lightweight ASP.NET Core Minimal API with functional endpoints, Entity Framework, JWT authentication, and performance optimization', version: '1.0.0', tags: ['csharp', 'dotnet', 'aspnet-core', 'minimal-api', 'lightweight', 'performance', 'jwt', 'entity-framework'], port: 5000, files: { // Project file optimized for minimal API [`\${projectName}.csproj`]: `<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> <GenerateDocumentationFile>true</GenerateDocumentationFile> <DocumentationFile>bin\\Debug\\net8.0\\\${projectName}.xml</DocumentationFile> <NoWarn>\$(NoWarn);1591</NoWarn> <PublishAot>true</PublishAot> <InvariantGlobalization>true</InvariantGlobalization> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.0" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" /> <PackageReference Include="Serilog.AspNetCore" Version="8.0.0" /> <PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" /> <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" /> <PackageReference Include="FluentValidation" Version="11.8.0" /> <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.0.3" /> <PackageReference Include="StackExchange.Redis" Version="2.7.10" /> <PackageReference Include="Microsoft.AspNetCore.RateLimiting" Version="8.0.0" /> <PackageReference Include="Microsoft.AspNetCore.OutputCaching" Version="8.0.0" /> </ItemGroup> </Project>`, // Program.cs - Main entry point with minimal API endpoints 'Program.cs': `using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.OutputCaching; using Microsoft.AspNetCore.RateLimiting; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; using Serilog; using System.ComponentModel.DataAnnotations; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using System.Threading.RateLimiting; using \${projectName}.Data; using \${projectName}.Models; using \${projectName}.Services; // Configure Serilog Log.Logger = new LoggerConfiguration() .WriteTo.Console() .WriteTo.File("logs/log-.txt", rollingInterval: RollingInterval.Day) .CreateLogger(); try { var builder = WebApplication.CreateBuilder(args); // Add Serilog builder.Host.UseSerilog(); // Database context builder.Services.AddDbContext<AppDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); // JWT Authentication var jwtSettings = builder.Configuration.GetSection("JwtSettings"); builder.Services.AddAuthentication(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(jwtSettings["SecretKey"]!)), ClockSkew = TimeSpan.Zero }; }); builder.Services.AddAuthorization(); // Services builder.Services.AddScoped<IProductService, ProductService>(); builder.Services.AddScoped<IAuthService, AuthService>(); // Output caching builder.Services.AddOutputCache(options => { options.AddBasePolicy(policy => policy.Expire(TimeSpan.FromMinutes(10))); options.AddPolicy("ProductsCache", policy => policy.Expire(TimeSpan.FromMinutes(5))); }); // Rate limiting builder.Services.AddRateLimiter(options => { options.AddFixedWindowLimiter("GlobalRateLimit", limiterOptions => { limiterOptions.PermitLimit = 100; limiterOptions.Window = TimeSpan.FromMinutes(1); limiterOptions.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; limiterOptions.QueueLimit = 10; }); options.AddFixedWindowLimiter("AuthRateLimit", limiterOptions => { limiterOptions.PermitLimit = 10; limiterOptions.Window = TimeSpan.FromMinutes(1); }); }); // Redis cache builder.Services.AddStackExchangeRedisCache(options => { options.Configuration = builder.Configuration.GetConnectionString("Redis"); }); // Swagger/OpenAPI builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "\${projectName} Minimal API", Version = "v1", Description = "High-performance ASP.NET Core Minimal API with functional endpoints" }); c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { Description = "JWT Authorization header using the Bearer scheme", Name = "Authorization", In = ParameterLocation.Header, Type = SecuritySchemeType.ApiKey, Scheme = "Bearer" }); c.AddSecurityRequirement(new OpenApiSecurityRequirement() { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } }, Array.Empty<string>() } }); }); // CORS builder.Services.AddCors(options => { options.AddDefaultPolicy(policy => { policy.WithOrigins("http://localhost:3000", "https://localhost:3000") .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); }); }); var app = builder.Build(); // Configure the HTTP request pipeline if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "\${projectName} Minimal API V1"); c.RoutePrefix = string.Empty; }); } app.UseHttpsRedirection(); app.UseCors(); app.UseAuthentication(); app.UseAuthorization(); app.UseRateLimiter(); app.UseOutputCache(); // Health check endpoint app.MapGet("/health", () => Results.Ok(new { status = "healthy", timestamp = DateTime.UtcNow, version = "1.0.0", environment = app.Environment.EnvironmentName })) .WithName("HealthCheck") .WithOpenApi(); // Authentication endpoints app.MapPost("/auth/login", async (LoginRequest request, IAuthService authService) => { var result = await authService.LoginAsync(request.Email, request.Password); if (!result.Success) return Results.Unauthorized(); return Results.Ok(new { token = result.Token, user = result.User }); }) .WithName("Login") .WithOpenApi() .RequireRateLimiting("AuthRateLimit"); app.MapPost("/auth/register", async (RegisterRequest request, IAuthService authService) => { var result = await authService.RegisterAsync(request.Email, request.Password, request.FirstName, request.LastName); if (!result.Success) return Results.BadRequest(new { errors = result.Errors }); return Results.Created($"/users/{result.User?.Id}", new { message = "User created successfully", user = result.User }); }) .WithName("Register") .WithOpenApi() .RequireRateLimiting("AuthRateLimit"); // Products endpoints app.MapGet("/products", async (IProductService productService) => { var products = await productService.GetAllAsync(); return Results.Ok(new { data = products, count = products.Count() }); }) .WithName("GetProducts") .WithOpenApi() .CacheOutput("ProductsCache"); app.MapGet("/products/{id:int}", async (int id, IProductService productService) => { var product = await productService.GetByIdAsync(id); return product != null ? Results.Ok(new { data = product }) : Results.NotFound(); }) .WithName("GetProduct") .WithOpenApi() .CacheOutput(policy => policy.Expire(TimeSpan.FromMinutes(15))); app.MapGet("/products/category/{categoryId:int}", async (int categoryId, IProductService productService) => { var products = await productService.GetByCategoryAsync(categoryId); return Results.Ok(new { data = products, count = products.Count() }); }) .WithName("GetProductsByCategory") .WithOpenApi() .CacheOutput("ProductsCache"); app.MapPost("/products", [Authorize] async (CreateProductRequest request, IProductService productService) => { var product = await productService.CreateAsync(request); return product != null ? Results.Created($"/products/{product.Id}", new { data = product }) : Results.BadRequest(new { message = "Failed to create product" }); }) .WithName("CreateProduct") .WithOpenApi() .RequireAuthorization(); app.MapPut("/products/{id:int}", [Authorize] async (int id, UpdateProductRequest request, IProductService productService) => { var product = await productService.UpdateAsync(id, request); return product != null ? Results.Ok(new { data = product, message = "Product updated successfully" }) : Results.NotFound(); }) .WithName("UpdateProduct") .WithOpenApi() .RequireAuthorization(); app.MapDelete("/products/{id:int}", [Authorize] async (int id, IProductService productService) => { var result = await productService.DeleteAsync(id); return result ? Results.Ok(new { message = "Product deleted successfully" }) : Results.NotFound(); }) .WithName("DeleteProduct") .WithOpenApi() .RequireAuthorization(); // Categories endpoints app.MapGet("/categories", async (AppDbContext db) => { var categories = await db.Categories .Where(c => c.IsActive) .Select(c => new { c.Id, c.Name, c.Description, c.CreatedAt }) .ToListAsync(); return Results.Ok(new { data = categories, count = categories.Count }); }) .WithName("GetCategories") .WithOpenApi() .CacheOutput(policy => policy.Expire(TimeSpan.FromMinutes(30))); // Statistics endpoint app.MapGet("/stats", [Authorize] async (AppDbContext db) => { var stats = new { TotalProducts = await db.Products.CountAsync(p => p.IsActive), TotalCategories = await db.Categories.CountAsync(c => c.IsActive), TotalUsers = await db.Users.CountAsync(u => u.IsActive), LowStockProducts = await db.Products.CountAsync(p => p.IsActive && p.Stock < 10), LastUpdated = DateTime.UtcNow }; return Results.Ok(new { data = stats }); }) .WithName("GetStatistics") .WithOpenApi() .RequireAuthorization() .CacheOutput(policy => policy.Expire(TimeSpan.FromMinutes(5))); // Ensure database is created using (var scope = app.Services.CreateScope()) { var context = scope.ServiceProvider.GetRequiredService<AppDbContext>(); await context.Database.EnsureCreatedAsync(); } app.Run(); } catch (Exception ex) { Log.Fatal(ex, "Application terminated unexpectedly"); } finally { Log.CloseAndFlush(); } // Request/Response DTOs public record LoginRequest( [Required, EmailAddress] string Email, [Required, MinLength(6)] string Password ); public record RegisterRequest( [Required, EmailAddress] string Email, [Required, MinLength(6)] string Password, [Required, MaxLength(50)] string FirstName, [Required, MaxLength(50)] string LastName ); public record CreateProductRequest( [Required, MaxLength(200)] string Name, [MaxLength(1000)] string? Description, [Required, Range(0.01, double.MaxValue)] decimal Price, [Range(0, int.MaxValue)] int Stock, [Required] int CategoryId ); public record UpdateProductRequest( [Required, MaxLength(200)] string Name, [MaxLength(1000)] string? Description, [Required, Range(0.01, double.MaxValue)] decimal Price, [Range(0, int.MaxValue)] int Stock, [Required] int CategoryId, bool IsActive = true );`, // Database context - simplified for minimal API 'Data/AppDbContext.cs': `using Microsoft.EntityFrameworkCore; using \${projectName}.Models; namespace \${projectName}.Data; public class AppDbContext : DbContext { public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { } public DbSet<User> Users { get; set; } public DbSet<Product> Products { get; set; } public DbSet<Category> Categories { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // User entity configuration modelBuilder.Entity<User>(entity => { entity.HasKey(e => e.Id); entity.Property(e => e.Email).IsRequired().HasMaxLength(100); entity.Property(e => e.FirstName).IsRequired().HasMaxLength(50); entity.Property(e => e.LastName).IsRequired().HasMaxLength(50); entity.Property(e => e.PasswordHash).IsRequired(); entity.Property(e => e.CreatedAt).HasDefaultValueSql("GETUTCDATE()"); entity.HasIndex(e => e.Email).IsUnique(); }); // Product entity configuration modelBuilder.Entity<Product>(entity => { entity.HasKey(e => e.Id); entity.Property(e => e.Name).IsRequired().HasMaxLength(200); entity.Property(e => e.Description).HasMaxLength(1000); entity.Property(e => e.Price).HasColumnType("decimal(18,2)"); entity.Property(e => e.CreatedAt).HasDefaultValueSql("GETUTCDATE()"); entity.HasOne(p => p.Category) .WithMany(c => c.Products) .HasForeignKey(p => p.CategoryId); }); // Category entity configuration modelBuilder.Entity<Category>(entity => { entity.HasKey(e => e.Id); entity.Property(e => e.Name).IsRequired().HasMaxLength(100); entity.Property(e => e.Description).HasMaxLength(500); entity.Property(e => e.CreatedAt).HasDefaultValueSql("GETUTCDATE()"); }); // Seed data modelBuilder.Entity<Category>().HasData( new Category { Id = 1, Name = "Electronics", Description = "Electronic devices and accessories", CreatedAt = DateTime.UtcNow }, new Category { Id = 2, Name = "Clothing", Description = "Apparel and fashion items", CreatedAt = DateTime.UtcNow }, new Category { Id = 3, Name = "Books", Description = "Books and educational materials", CreatedAt = DateTime.UtcNow } ); modelBuilder.Entity<Product>().HasData( new Product { Id = 1, Name = "Laptop", Description = "High-performance laptop for development", Price = 1299.99m, CategoryId = 1, Stock = 50, IsActive = true, CreatedAt = DateTime.UtcNow }, new Product { Id = 2, Name = "T-Shirt", Description = "Comfortable cotton t-shirt", Price = 29.99m, CategoryId = 2, Stock = 100, IsActive = true, CreatedAt = DateTime.UtcNow } ); } }`, // Models - simplified for minimal API 'Models/User.cs': `namespace \${projectName}.Models; public class User { public int Id { get; set; } public string Email { get; set; } = string.Empty; public string FirstName { get; set; } = string.Empty; public string LastName { get; set; } = string.Empty; public string PasswordHash { get; set; } = string.Empty; public bool IsActive { get; set; } = true; public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime? LastLoginAt { get; set; } }`, 'Models/Product.cs': `namespace \${projectName}.Models; public class Product { public int Id { get; set; } public string Name { get; set; } = string.Empty; public string? Description { get; set; } public decimal Price { get; set; } public int Stock { get; set; } public bool IsActive { get; set; } = true; public int CategoryId { get; set; } public Category? Category { get; set; } public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime? UpdatedAt { get; set; } }`, 'Models/Category.cs': `namespace \${projectName}.Models; public class Category { public int Id { get; set; } public string Name { get; set; } = string.Empty; public string? Description { get; set; } public bool IsActive { get; set; } = true; public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime? UpdatedAt { get; set; } public virtual ICollection<Product> Products { get; set; } = new List<Product>(); }`, // Services for business logic 'Services/IAuthService.cs': `using \${projectName}.Models; namespace \${projectName}.Services; public interface IAuthService { Task<AuthResult> LoginAsync(string email, string password); Task<AuthResult> RegisterAsync(string email, string password, string firstName, string lastName); string GenerateJwtToken(User user); } public class AuthResult { public bool Success { get; set; } public string? Token { get; set; } public User? User { get; set; } public List<string> Errors { get; set; } = new(); }`, 'Services/AuthService.cs': `using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Security.Cryptography; using System.Text; using \${projectName}.Data; using \${projectName}.Models; namespace \${projectName}.Services; public class AuthService : IAuthService { private readonly AppDbContext _context; private readonly IConfiguration _configuration; private readonly ILogger<AuthService> _logger; public AuthService(AppDbContext context, IConfiguration configuration, ILogger<AuthService> logger) { _context = context; _configuration = configuration; _logger = logger; } public async Task<AuthResult> LoginAsync(string email, string password) { try { var user = await _context.Users.FirstOrDefaultAsync(u => u.Email == email && u.IsActive); if (user == null || !VerifyPassword(password, user.PasswordHash)) { return new AuthResult { Success = false, Errors = ["Invalid credentials"] }; } // Update last login user.LastLoginAt = DateTime.UtcNow; await _context.SaveChangesAsync(); var token = GenerateJwtToken(user); _logger.LogInformation("User {Email} logged in successfully", email); return new AuthResult { Success = true, Token = token, User = user }; } catch (Exception ex) { _logger.LogError(ex, "Error during login for {Email}", email); return new AuthResult { Success = false, Errors = ["Login failed"] }; } } public async Task<AuthResult> RegisterAsync(string email, string password, string firstName, string lastName) { try { // Check if user exists if (await _context.Users.AnyAsync(u => u.Email == email)) { return new AuthResult { Success = false, Errors = ["Email already exists"] }; } var user = new User { Email = email, FirstName = firstName, LastName = lastName, PasswordHash = HashPassword(password), CreatedAt = DateTime.UtcNow, IsActive = true }; _context.Users.Add(user); await _context.SaveChangesAsync(); _logger.LogInformation("User {Email} registered successfully", email); return new AuthResult { Success = true, User = user }; } catch (Exception ex) { _logger.LogError(ex, "Error during registration for {Email}", email); return new AuthResult { Success = false, Errors = ["Registration failed"] }; } } public string GenerateJwtToken(User user) { var jwtSettings = _configuration.GetSection("JwtSettings"); var secretKey = Encoding.UTF8.GetBytes(jwtSettings["SecretKey"]!); var claims = new[] { new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), new Claim(ClaimTypes.Email, user.Email), new Claim(ClaimTypes.GivenName, user.FirstName), new Claim(ClaimTypes.Surname, user.LastName), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64) }; var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(claims), Expires = DateTime.UtcNow.AddMinutes(Convert.ToDouble(jwtSettings["ExpirationInMinutes"])), Issuer = jwtSettings["Issuer"], Audience = jwtSettings["Audience"], SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(secretKey), SecurityAlgorithms.HmacSha256Signature) }; var tokenHandler = new JwtSecurityTokenHandler(); var token = tokenHandler.CreateToken(tokenDescriptor); return tokenHandler.WriteToken(token); } private static string HashPassword(string password) { using var hmac = new HMACSHA512(); var salt = hmac.Key; var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(password)); return Convert.ToBase64String(salt) + ":" + Convert.ToBase64String(hash); } private static bool VerifyPassword(string password, string storedHash) { var parts = storedHash.Split(':'); if (parts.Length != 2) return false; var salt = Convert.FromBase64String(parts[0]); var hash = Convert.FromBase64String(parts[1]); using var hmac = new HMACSHA512(salt); var computedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(password)); return computedHash.SequenceEqual(hash); } }`, 'Services/IProductService.cs': `using \${projectName}.Models; namespace \${projectName}.Services; public interface IProductService { Task<Product?> CreateAsync(CreateProductRequest request); Task<Product?> GetByIdAsync(int id); Task<IEnumerable<Product>> GetAllAsync(); Task<IEnumerable<Product>> GetByCategoryAsync(int categoryId); Task<Product?> UpdateAsync(int id, UpdateProductRequest request); Task<bool> DeleteAsync(int id); }`, 'Services/ProductService.cs': `using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Distributed; using Newtonsoft.Json; using \${projectName}.Data; using \${projectName}.Models; namespace \${projectName}.Services; public class ProductService : IProductService { private readonly AppDbContext _context; private readonly IDistributedCache _cache; private readonly ILogger<ProductService> _logger; private const int CacheExpirationMinutes = 15; public ProductService(AppDbContext context, IDistributedCache cache, ILogger<ProductService> logger) { _context = context; _cache = cache; _logger = logger; } public async Task<Product?> CreateAsync(CreateProductRequest request) { try { var product = new Product { Name = request.Name, Description = request.Description, Price = request.Price, Stock = request.Stock, CategoryId = request.CategoryId, CreatedAt = DateTime.UtcNow, IsActive = true }; _context.Products.Add(product); await _context.SaveChangesAsync(); // Clear cache await _cache.RemoveAsync("products_all"); _logger.LogInformation("Product {ProductName} created with ID {ProductId}", product.Name, product.Id); return await GetByIdAsync(product.Id); } catch (Exception ex) { _logger.LogError(ex, "Error creating product"); return null; } } public async Task<Product?> GetByIdAsync(int id) { try { var cacheKey = $"product_{id}"; var cachedProduct = await _cache.GetStringAsync(cacheKey); if (!string.IsNullOrEmpty(cachedProduct)) { return JsonConvert.DeserializeObject<Product>(cachedProduct); } var product = await _context.Products .Include(p => p.Category) .FirstOrDefaultAsync(p => p.Id == id && p.IsActive); if (product != null) { // Cache the result var cacheOptions = new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(CacheExpirationMinutes) }; await _cache.SetStringAsync(cacheKey, JsonConvert.SerializeObject(product), cacheOptions); } return product; } catch (Exception ex) { _logger.LogError(ex, "Error getting product {ProductId}", id); return null; } } public async Task<IEnumerable<Product>> GetAllAsync() { try { const string cacheKey = "products_all"; var cachedProducts = await _cache.GetStringAsync(cacheKey); if (!string.IsNullOrEmpty(cachedProducts)) { return JsonConvert.DeserializeObject<IEnumerable<Product>>(cachedProducts) ?? new List<Product>(); } var products = await _context.Products .Include(p => p.Category) .Where(p => p.IsActive) .OrderBy(p => p.Name) .ToListAsync(); // Cache the result var cacheOptions = new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(CacheExpirationMinutes) }; await _cache.SetStringAsync(cacheKey, JsonConvert.SerializeObject(products), cacheOptions); return products; } catch (Exception ex) { _logger.LogError(ex, "Error getting all products"); return new List<Product>(); } } public async Task<IEnumerable<Product>> GetByCategoryAsync(int categoryId) { try { var products = await _context.Products .Include(p => p.Category) .Where(p => p.CategoryId == categoryId && p.IsActive) .OrderBy(p => p.Name) .ToListAsync(); return products; } catch (Exception ex) { _logger.LogError(ex, "Error getting products for category {CategoryId}", categoryId); return new List<Product>(); } } public async Task<Product?> UpdateAsync(int id, UpdateProductRequest request) { try { var product = await _context.Products.FindAsync(id); if (product == null || !product.IsActive) { return null; } product.Name = request.Name; product.Description = request.Description; product.Price = request.Price; product.Stock = request.Stock; product.CategoryId = request.CategoryId; product.IsActive = request.IsActive; product.UpdatedAt = DateTime.UtcNow; await _context.SaveChangesAsync(); // Clear cache await _cache.RemoveAsync($"product_{id}"); await _cache.RemoveAsync("products_all"); _logger.LogInformation("Product {ProductId} updated", id); return product; } catch (Exception ex) { _logger.LogError(ex, "Error updating product {ProductId}", id); return null; } } public async Task<bool> DeleteAsync(int id) { try { var product = await _context.Products.FindAsync(id); if (product == null) { return false; } product.IsActive = false; product.UpdatedAt = DateTime.UtcNow; await _context.SaveChangesAsync(); // Clear cache await _cache.RemoveAsync($"product_{id}"); await _cache.RemoveAsync("products_all"); _logger.LogInformation("Product {ProductId} deleted", id); return true; } catch (Exception ex) { _logger.LogError(ex, "Error deleting product {ProductId}", id); return false; } } }`, // Configuration files 'appsettings.json': `{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning", "Microsoft.EntityFrameworkCore": "Information" } }, "AllowedHosts": "*", "ConnectionStrings": { "DefaultConnection": "Server=(localdb)\\\\mssqllocaldb;Database=\${projectName}MinimalDb;Trusted_Connection=true;MultipleActiveResultSets=true", "Redis": "localhost:6379" }, "JwtSettings": { "SecretKey": "YourSuperSecretKeyThatIsAtLeast32CharactersLong!", "Issuer": "\${projectName}-minimal-api", "Audience": "\${projectName}-client", "ExpirationInMinutes": 60 } }`, 'appsettings.Development.json': `{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning", "Microsoft.EntityFrameworkCore": "Information" } }, "ConnectionStrings": { "DefaultConnection": "Server=(localdb)\\\\mssqllocaldb;Database=\${projectName}MinimalDevDb;Trusted_Connection=true;MultipleActiveResultSets=true", "Redis": "localhost:6379" } }`, // Docker support 'Dockerfile': `FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base WORKDIR /app EXPOSE 8080 FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build ARG BUILD_CONFIGURATION=Release WORKDIR /src COPY ["\${projectName}.csproj", "."] RUN dotnet restore "./\${projectName}.csproj" COPY . . WORKDIR "/src/." RUN dotnet build "./\${projectName}.csproj" -c $BUILD_CONFIGURATION -o /app/build FROM build AS publish ARG BUILD_CONFIGURATION=Release RUN dotnet publish "./\${projectName}.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false /p:PublishAot=true FROM base AS final WORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "\${projectName}.dll"]`, '.dockerignore': `**/.dockerignore **/.env **/.git **/.gitignore **/.project **/.settings **/.toolstarget **/.vs **/.vscode **/*.*proj.user **/*.dbmdl **/*.jfm **/azds.yaml **/bin **/charts **/docker-compose* **/Dockerfile* **/node_modules **/npm-debug.log **/obj **/secrets.dev.yaml **/values.dev.yaml LICENSE README.md`, // Development setup 'launchSettings.json': `{ "$schema": "http://json.schemastore.org/launchsettings.json", "profiles": { "http": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, "applicationUrl": "http://localhost:\${port || 5000}", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "https": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, "applicationUrl": "https://localhost:\${(port ? parseInt(port) + 1 : 5001)};http://localhost:\${port || 5000}", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } }`, // README 'README.md': `# \${projectName} - ASP.NET Core Minimal API High-performance ASP.NET Core Minimal API with functional endpoints, JWT authentication, output caching, rate limiting, and comprehensive features. ## Features - **Minimal API**: Functional endpoint definitions with high performance - **Authentication**: JWT-based authentication with custom auth service - **Output Caching**: Response caching with configurable policies - **Rate Limiting**: Global and endpoint-specific rate limiting - **Database**: Entity Framework Core with SQL Server - **Caching**: Redis distributed caching - **Logging**: Serilog with file and console outputs - **API Documentation**: Swagger/OpenAPI integration - **Performance**: AOT compilation support for optimal performance - **Docker**: Containerization with AOT-optimized image - **CORS**: Cross-origin resource sharing support ## Getting Started ### Prerequisites - .NET 8.0 SDK - SQL Server (LocalDB for development) - Redis (optional, for caching) ### Installation 1. Clone the repository 2. Navigate to the project directory 3. Restore dependencies: \`\`\`bash dotnet restore \`\`\` 4. Update the database: \`\`\`bash dotnet ef database update \`\`\` 5. Run the application: \`\`\`bash dotnet run \`\`\` ### Development For hot reload during development: \`\`\`bash dotnet watch run \`\`\` ### Docker Build and run with Docker (AOT optimized): \`\`\`bash docker build -t \${projectName.toLowerCase()}-minimal-api . docker run -p 8080:8080 \${projectName.toLowerCase()}-minimal-api \`\`\` ## API Endpoints ### Health & Information - \`GET /health\` - Health check endpoint ### Authentication - \`POST /auth/login\` - Login user - \`POST /auth/register\` - Register new user ### Products - \`GET /products\` - Get all products (cached) - \`GET /products/{id}\` - Get product by ID (cached) - \`GET /products/category/{categoryId}\` - Get products by category - \`POST /products\` - Create new product (requires authentication) - \`PUT /products/{id}\` - Update product (requires authentication) - \`DELETE /products/{id}\` - Delete product (requires authentication) ### Categories - \`GET /categories\` - Get all categories (cached) ### Statistics - \`GET /stats\` - Get system statistics (requires authentication, cached) ## Performance Features ### Output Caching - Global cache policy: 10 minutes - Products cache: 5 minutes - Categories cache: 30 minutes - Statistics cache: 5 minutes ### Rate Limiting - Global rate limit: 100 requests per minute - Auth endpoints: 10 requests per minute - Configurable queue with FIFO processing ### AOT Compilation The project supports Ahead-of-Time (AOT) compilation for optimal performance: \`\`\`bash dotnet publish -c Release \`\`\` ## Configuration ### Database Connection Update the connection string in \`appsettings.json\`: \`\`\`json { "ConnectionStrings": { "DefaultConnection": "Your SQL Server connection string" } } \`\`\` ### JWT Settings Configure JWT settings in \`appsettings.json\`: \`\`\`json { "JwtSettings": { "SecretKey": "Your secret key (min 32 characters)", "Issuer": "your-issuer", "Audience": "your-audience", "ExpirationInMinutes": 60 } } \`\`\` ### Redis Cache Configure Redis connection: \`\`\`json { "ConnectionStrings": { "Redis": "localhost:6379" } } \`\`\` ## Project Structure \`\`\` \${projectName}/ ├── Data/ # Database context ├── Models/ # Domain models ├── Services/ # Business logic services ├── Program.cs # Minimal API endpoints and configuration ├── appsettings.json # Configuration └── README.md \`\`\` ## Technologies Used - **ASP.NET Core 8.0** - Minimal API framework - **Entity Framework Core** - ORM - **JWT Bearer** - Token-based authentication - **Serilog** - Structured logging - **Output Caching** - Response caching - **Rate Limiting** - Request throttling - **Swagger/OpenAPI** - API documentation - **Redis** - Distributed caching - **AOT Compilation** - Performance optimization - **Docker** - Containerization ## Performance Characteristics - **Fast Startup**: Minimal overhead with functional endpoints - **Low Memory**: Optimized for cloud deployments - **High Throughput**: Async endpoints with caching - **AOT Ready**: Native compilation support for maximum performance ## License This project is licensed under the MIT License.` }, dependencies: { 'Microsoft.AspNetCore.OpenApi': '^8.0.0', 'Microsoft.EntityFrameworkCore': '^8.0.0', 'Microsoft.EntityFrameworkCore.SqlServer': '^8.0.0', 'Microsoft.EntityFrameworkCore.Tools': '^8.0.0', 'Microsoft.EntityFrameworkCore.Design': '^8.0.0', 'Microsoft.AspNetCore.Authentication.JwtBearer': '^8.0.0', 'Swashbuckle.AspNetCore': '^6.5.0', 'Serilog.AspNetCore': '^8.0.0', 'FluentValidation': '^11.8.0', 'StackExchange.Redis': '^2.7.10', 'Microsoft.AspNetCore.RateLimiting': '^8.0.0', 'Microsoft.AspNetCore.OutputCaching': '^8.0.0' }, devDependencies: { 'Microsoft.EntityFrameworkCore.InMemory': '^8.0.0' }, features: [ 'authentication', 'database', 'caching', 'logging', 'documentation', 'security', 'validation', 'rate-limiting', 'cors', 'rest-api', 'docker' ] };