# Backend Quick Start Guide ## 🚀 Adding a New Feature (Step-by-Step) ### Example: Adding a "Product" Module --- ## Step 1: Create Entity (Data Layer) **File**: `Data.SeaHavenIndustries/Models/Product.cs` ```csharp 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 bool IsActive { get; set; } // Audit fields (REQUIRED) public string CreatedBy { get; set; } = string.Empty; public DateTime CreatedDate { get; set; } public string ModifiedBy { get; set; } = string.Empty; public DateTime ModifiedDate { get; set; } } ``` **Add to DbContext**: `Data.SeaHavenIndustries/ApplicationDbContext.cs` ```csharp public DbSet Products { get; set; } ``` --- ## Step 2: Create Migration ```bash cd backend/shoc-backend dotnet ef migrations add AddProductTable --project Data.SeaHavenIndustries --startup-project Api.SeaHavenIndustries dotnet ef database update --project Data.SeaHavenIndustries --startup-project Api.SeaHavenIndustries ``` --- ## Step 3: Create DTOs ### 3A. Data Layer DTOs (`SeaHaven.DataServices/DTOs/`) **CreateProductDataDTO.cs** ```csharp public class CreateProductDataDTO { public string Name { get; set; } = string.Empty; public string? Description { get; set; } public decimal Price { get; set; } } ``` **ProductDataDTO.cs** ```csharp public class ProductDataDTO { public int Id { get; set; } public string Name { get; set; } = string.Empty; public string? Description { get; set; } public decimal Price { get; set; } public bool IsActive { get; set; } } ``` ### 3B. Service Layer DTOs (`SeaHaven.Services/DTOs/`) **CreateProductDTO.cs** ```csharp public class CreateProductDTO { public string Name { get; set; } = string.Empty; public string? Description { get; set; } public decimal Price { get; set; } } ``` **ProductDTO.cs** ```csharp public class ProductDTO { public int Id { get; set; } public string Name { get; set; } = string.Empty; public string? Description { get; set; } public decimal Price { get; set; } public bool IsActive { get; set; } } ``` ### 3C. API Layer DTOs (`Api.SeaHavenIndustries/DTOs/`) **Product_DTO.cs** ```csharp public class Product_DTO { public string Name { get; set; } = string.Empty; public string? Description { get; set; } public decimal Price { get; set; } } ``` --- ## Step 4: Create FluentValidation Validator **File**: `SeaHaven.Services/Validators/CreateProductDTOValidator.cs` ```csharp using FluentValidation; using SeaHaven.Services.DTOs; public class CreateProductDTOValidator : AbstractValidator { public CreateProductDTOValidator() { RuleFor(x => x.Name) .NotEmpty().WithMessage("Product name is required") .MaximumLength(100).WithMessage("Name cannot exceed 100 characters"); RuleFor(x => x.Price) .GreaterThan(0).WithMessage("Price must be greater than 0"); } } ``` --- ## Step 5: Create DataService ### 5A. Interface (`SeaHaven.DataServices/Interfaces/IProductDataService.cs`) ```csharp public interface IProductDataService { Task CreateAsync(CreateProductDataDTO dto, string userId); Task UpdateAsync(int id, UpdateProductDataDTO dto, string userId); Task DeleteAsync(int id, string userId); Task GetByIdAsync(int id); Task> GetPagedAsync(int page, int pageSize, string? search); } ``` ### 5B. Implementation (`SeaHaven.DataServices/Implementation/ProductDataService.cs`) ```csharp using Data.SeaHavenIndustries; using Data.SeaHavenIndustries.Models; using Microsoft.EntityFrameworkCore; public class ProductDataService : IProductDataService { private readonly ApplicationDbContext _db; public ProductDataService(ApplicationDbContext db) { _db = db; } public async Task CreateAsync(CreateProductDataDTO dto, string userId) { var product = new Product { Name = dto.Name, Description = dto.Description, Price = dto.Price, IsActive = true, CreatedBy = userId, CreatedDate = DateTime.UtcNow, ModifiedBy = userId, ModifiedDate = DateTime.UtcNow }; _db.Products.Add(product); await _db.SaveChangesAsync(); return new ProductDataDTO { Id = product.Id, Name = product.Name, Description = product.Description, Price = product.Price, IsActive = product.IsActive }; } public async Task GetByIdAsync(int id) { var product = await _db.Products.FindAsync(id); if (product == null) return null; return new ProductDataDTO { Id = product.Id, Name = product.Name, Description = product.Description, Price = product.Price, IsActive = product.IsActive }; } // Implement other methods... } ``` --- ## Step 6: Create Service ### 6A. Interface (`SeaHaven.Services/Interfaces/IProductService.cs`) ```csharp public interface IProductService { Task CreateProductAsync(CreateProductDTO dto, string userId); Task UpdateProductAsync(int id, UpdateProductDTO dto, string userId); Task DeleteProductAsync(int id, string userId); Task GetProductByIdAsync(int id); Task> GetProductsPagedAsync(int page, int pageSize, string? search); } ``` ### 6B. Implementation (`SeaHaven.Services/Implementation/ProductService.cs`) ```csharp using FluentValidation; using SeaHaven.DataServices.Interfaces; using SeaHaven.Services.DTOs; using SeaHaven.Services.Interfaces; public class ProductService : IProductService { private readonly IProductDataService _dataService; private readonly IValidator _createValidator; public ProductService( IProductDataService dataService, IValidator createValidator) { _dataService = dataService; _createValidator = createValidator; } public async Task CreateProductAsync(CreateProductDTO dto, string userId) { // Validate var validationResult = await _createValidator.ValidateAsync(dto); if (!validationResult.IsValid) { throw new ValidationException(validationResult.Errors); } // Map to Data DTO var dataDto = new CreateProductDataDTO { Name = dto.Name, Description = dto.Description, Price = dto.Price }; // Call DataService var created = await _dataService.CreateAsync(dataDto, userId); // Map to Service DTO return new ProductDTO { Id = created.Id, Name = created.Name, Description = created.Description, Price = created.Price, IsActive = created.IsActive }; } public async Task GetProductByIdAsync(int id) { var product = await _dataService.GetByIdAsync(id); if (product == null) throw new KeyNotFoundException($"Product with ID {id} not found"); return new ProductDTO { Id = product.Id, Name = product.Name, Description = product.Description, Price = product.Price, IsActive = product.IsActive }; } // Implement other methods... } ``` --- ## Step 7: Create Controller **File**: `Api.SeaHavenIndustries/Controllers/ProductController.cs` ```csharp using Api.SeaHavenIndustries.DTOs; using FluentValidation; using Microsoft.AspNetCore.Mvc; using SeaHaven.Services.Interfaces; using System.Security.Claims; [ApiController] [Route("api/[controller]")] public class ProductController : Controller { private readonly IProductService _productService; public ProductController(IProductService productService) { _productService = productService; } [HttpPost("Create")] public async Task Create([FromBody] Product_DTO dto) { try { var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); if (userId == null) return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" }); var serviceDto = new CreateProductDTO { Name = dto.Name, Description = dto.Description, Price = dto.Price }; await _productService.CreateProductAsync(serviceDto, userId); return Ok(new Response { Status = "Success", Message = "Product created successfully" }); } catch (ValidationException vex) { var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage)); return BadRequest(new Response { Status = "Validation Error", Message = errors }); } catch (Exception ex) { return StatusCode(500, new Response { Status = "Error", Message = ex.Message }); } } [HttpGet("GetById")] public async Task GetById(int id) { try { var product = await _productService.GetProductByIdAsync(id); return Ok(product); } catch (KeyNotFoundException) { return NotFound(new Response { Status = "Error", Message = "Product not found" }); } catch (Exception ex) { return StatusCode(500, new Response { Status = "Error", Message = ex.Message }); } } // Add other endpoints... } ``` --- ## Step 8: Services Auto-Registered ✅ **No action needed!** Services are automatically registered by assembly scanning. If you followed naming conventions (`IProductService` → `ProductService`), your services are already registered. **How?** These lines in `Program.cs` scan assemblies: ```csharp builder.Services.AddDataServices(); // Scans SeaHaven.DataServices builder.Services.AddBusinessServices(); // Scans SeaHaven.Services + Validators ``` --- ## Step 9: Test ```bash # Build dotnet build # Run dotnet run --project Api.SeaHavenIndustries # Test endpoint curl -X POST "http://localhost:5141/api/Product/Create" \ -H "Content-Type: application/json" \ -d '{"name":"Test Product","description":"Test","price":99.99}' ``` --- ## ✅ Checklist - [ ] Entity created with audit fields - [ ] DbSet added to DbContext - [ ] Migration created and applied - [ ] Data DTOs created - [ ] Service DTOs created - [ ] API DTOs created - [ ] FluentValidation validator created - [ ] DataService interface created - [ ] DataService implementation created - [ ] Service interface created - [ ] Service implementation created - [ ] Controller created - [ ] Services registered in DI container - [ ] Exception handling implemented - [ ] User ID authentication added - [ ] Build succeeds - [ ] Endpoints tested --- ## 🎯 Common Patterns ### Get List with Pagination ```csharp [HttpGet("GetList")] public async Task GetList(string? search = "", int page = 1, int pageSize = 10) { var result = await _productService.GetProductsPagedAsync(page, pageSize, search); return Ok(new Pagination_DTO { Data = result.Items, PageNumber = page, PageSize = pageSize, TotalCount = result.TotalCount, TotalPages = (int)Math.Ceiling(result.TotalCount / (double)pageSize) }); } ``` ### Update ```csharp [HttpPost("Update")] public async Task Update([FromBody] EditProduct_DTO dto) { try { var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); if (userId == null) return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" }); await _productService.UpdateProductAsync(dto.Id, dto.ToServiceDTO(), userId); return Ok(new Response { Status = "Success", Message = "Updated" }); } catch (ValidationException vex) { var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage)); return BadRequest(new Response { Status = "Validation Error", Message = errors }); } catch (KeyNotFoundException) { return NotFound(new Response { Status = "Error", Message = "Not found" }); } catch (Exception ex) { return StatusCode(500, new Response { Status = "Error", Message = ex.Message }); } } ``` ### Delete ```csharp [HttpPost("Delete")] public async Task Delete(int id) { try { var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); if (userId == null) return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" }); await _productService.DeleteProductAsync(id, userId); return Ok(new Response { Status = "Success", Message = "Deleted" }); } catch (KeyNotFoundException) { return NotFound(new Response { Status = "Error", Message = "Not found" }); } catch (Exception ex) { return StatusCode(500, new Response { Status = "Error", Message = ex.Message }); } } ``` --- ## 📚 Reference Implementations For complete working examples, see: - `ContactController`, `ContactService`, `ContactDataService` - `EmployeeController`, `EmployeeService`, `EmployeeDataService` - `AssetController`, `AssetService`, `AssetDataService`