397 lines
8.9 KiB
Markdown
397 lines
8.9 KiB
Markdown
# Development Guide
|
|
|
|
## Quick Start Commands
|
|
|
|
### Build Solution
|
|
```bash
|
|
dotnet build PowderCoatingApp.sln
|
|
```
|
|
|
|
### Run Web Application
|
|
```bash
|
|
cd src/PowderCoating.Web
|
|
dotnet run
|
|
```
|
|
|
|
### Run API
|
|
```bash
|
|
cd src/PowderCoating.Api
|
|
dotnet run
|
|
```
|
|
|
|
### Watch Mode (Auto-reload)
|
|
```bash
|
|
cd src/PowderCoating.Web
|
|
dotnet watch run
|
|
```
|
|
|
|
## Entity Framework Core Commands
|
|
|
|
### Add Migration
|
|
```bash
|
|
cd src/PowderCoating.Web
|
|
dotnet ef migrations add MigrationName --project ../PowderCoating.Infrastructure
|
|
```
|
|
|
|
### Update Database
|
|
```bash
|
|
dotnet ef database update --project ../PowderCoating.Infrastructure
|
|
```
|
|
|
|
### Remove Last Migration
|
|
```bash
|
|
dotnet ef migrations remove --project ../PowderCoating.Infrastructure
|
|
```
|
|
|
|
### Generate SQL Script
|
|
```bash
|
|
dotnet ef migrations script --project ../PowderCoating.Infrastructure --output migration.sql
|
|
```
|
|
|
|
### View Migrations
|
|
```bash
|
|
dotnet ef migrations list --project ../PowderCoating.Infrastructure
|
|
```
|
|
|
|
## Project Management
|
|
|
|
### Add New NuGet Package
|
|
```bash
|
|
cd src/PowderCoating.Core
|
|
dotnet add package PackageName
|
|
```
|
|
|
|
### Restore Packages
|
|
```bash
|
|
dotnet restore
|
|
```
|
|
|
|
### Clean Solution
|
|
```bash
|
|
dotnet clean
|
|
```
|
|
|
|
## Creating New Components
|
|
|
|
### Add New Entity
|
|
|
|
1. Create entity class in `PowderCoating.Core/Entities/`
|
|
2. Inherit from `BaseEntity`
|
|
3. Add DbSet to `ApplicationDbContext`
|
|
4. Add repository to `IUnitOfWork` and `UnitOfWork`
|
|
5. Create migration
|
|
6. Update database
|
|
|
|
Example:
|
|
```csharp
|
|
// 1. Create entity
|
|
public class NewEntity : BaseEntity
|
|
{
|
|
public string Name { get; set; } = string.Empty;
|
|
// ... properties
|
|
}
|
|
|
|
// 2. Add to DbContext
|
|
public DbSet<NewEntity> NewEntities { get; set; }
|
|
|
|
// 3. Add to IUnitOfWork
|
|
IRepository<NewEntity> NewEntities { get; }
|
|
|
|
// 4. Add to UnitOfWork
|
|
private IRepository<NewEntity>? _newEntities;
|
|
public IRepository<NewEntity> NewEntities =>
|
|
_newEntities ??= new Repository<NewEntity>(_context);
|
|
|
|
// 5. Add migration
|
|
dotnet ef migrations add AddNewEntity --project ../PowderCoating.Infrastructure
|
|
|
|
// 6. Update database
|
|
dotnet ef database update --project ../PowderCoating.Infrastructure
|
|
```
|
|
|
|
### Add New Controller (Web)
|
|
|
|
1. Create in `PowderCoating.Web/Controllers/`
|
|
2. Inherit from `Controller`
|
|
3. Inject `IUnitOfWork` and `IMapper`
|
|
4. Create corresponding views in `Views/[ControllerName]/`
|
|
|
|
Example:
|
|
```csharp
|
|
public class NewController : Controller
|
|
{
|
|
private readonly IUnitOfWork _unitOfWork;
|
|
private readonly IMapper _mapper;
|
|
|
|
public NewController(IUnitOfWork unitOfWork, IMapper mapper)
|
|
{
|
|
_unitOfWork = unitOfWork;
|
|
_mapper = mapper;
|
|
}
|
|
|
|
public async Task<IActionResult> Index()
|
|
{
|
|
var items = await _unitOfWork.NewEntities.GetAllAsync();
|
|
return View(items);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Add New API Controller
|
|
|
|
1. Create in `PowderCoating.Api/Controllers/`
|
|
2. Add `[ApiController]` and `[Route("api/[controller]")]` attributes
|
|
3. Inject dependencies
|
|
4. Return appropriate `ActionResult<T>` types
|
|
|
|
Example:
|
|
```csharp
|
|
[ApiController]
|
|
[Route("api/[controller]")]
|
|
[Authorize]
|
|
public class NewApiController : ControllerBase
|
|
{
|
|
private readonly IUnitOfWork _unitOfWork;
|
|
private readonly IMapper _mapper;
|
|
|
|
public NewApiController(IUnitOfWork unitOfWork, IMapper mapper)
|
|
{
|
|
_unitOfWork = unitOfWork;
|
|
_mapper = mapper;
|
|
}
|
|
|
|
[HttpGet]
|
|
public async Task<ActionResult<IEnumerable<NewDto>>> GetAll()
|
|
{
|
|
var items = await _unitOfWork.NewEntities.GetAllAsync();
|
|
var dtos = _mapper.Map<IEnumerable<NewDto>>(items);
|
|
return Ok(dtos);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Add New DTO
|
|
|
|
1. Create in `PowderCoating.Application/DTOs/[Module]/`
|
|
2. Create AutoMapper profile
|
|
|
|
Example:
|
|
```csharp
|
|
// DTO
|
|
public class NewDto
|
|
{
|
|
public int Id { get; set; }
|
|
public string Name { get; set; } = string.Empty;
|
|
}
|
|
|
|
// AutoMapper Profile
|
|
public class NewProfile : Profile
|
|
{
|
|
public NewProfile()
|
|
{
|
|
CreateMap<NewEntity, NewDto>();
|
|
CreateMap<CreateNewDto, NewEntity>();
|
|
}
|
|
}
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### Code Style
|
|
- Follow C# coding conventions
|
|
- Use meaningful variable and method names
|
|
- Add XML documentation for public APIs
|
|
- Keep methods focused (Single Responsibility)
|
|
- Use async/await for I/O operations
|
|
|
|
### Entity Framework
|
|
- Use async methods (`ToListAsync`, `FirstOrDefaultAsync`, etc.)
|
|
- Include related entities when needed (`Include`, `ThenInclude`)
|
|
- Use `.AsNoTracking()` for read-only queries
|
|
- Handle concurrency conflicts
|
|
- Use transactions for multi-step operations
|
|
|
|
### API Development
|
|
- Use appropriate HTTP status codes
|
|
- Validate input with FluentValidation
|
|
- Use DTOs, never expose entities directly
|
|
- Document endpoints with XML comments for Swagger
|
|
- Implement proper error handling
|
|
|
|
### Security
|
|
- Always validate user input
|
|
- Use parameterized queries (EF Core does this)
|
|
- Implement authorization on controllers/actions
|
|
- Don't expose sensitive data in responses
|
|
- Use HTTPS in production
|
|
- Store secrets in user secrets or Azure Key Vault
|
|
|
|
### Performance
|
|
- Use pagination for large datasets
|
|
- Implement caching where appropriate
|
|
- Use projection (Select) to retrieve only needed fields
|
|
- Profile and optimize slow queries
|
|
- Use async for I/O-bound operations
|
|
|
|
## Common Patterns
|
|
|
|
### Service Pattern
|
|
```csharp
|
|
public interface IJobService
|
|
{
|
|
Task<JobDto> GetByIdAsync(int id);
|
|
Task<IEnumerable<JobDto>> GetAllAsync();
|
|
Task<JobDto> CreateAsync(CreateJobDto dto);
|
|
Task UpdateAsync(UpdateJobDto dto);
|
|
Task DeleteAsync(int id);
|
|
}
|
|
|
|
public class JobService : IJobService
|
|
{
|
|
private readonly IUnitOfWork _unitOfWork;
|
|
private readonly IMapper _mapper;
|
|
|
|
public JobService(IUnitOfWork unitOfWork, IMapper mapper)
|
|
{
|
|
_unitOfWork = unitOfWork;
|
|
_mapper = mapper;
|
|
}
|
|
|
|
// Implementation...
|
|
}
|
|
```
|
|
|
|
### Repository Pattern (Already Implemented)
|
|
```csharp
|
|
var customers = await _unitOfWork.Customers.FindAsync(c => c.IsActive);
|
|
var customer = await _unitOfWork.Customers.GetByIdAsync(id);
|
|
await _unitOfWork.Customers.AddAsync(newCustomer);
|
|
await _unitOfWork.SaveChangesAsync();
|
|
```
|
|
|
|
### Unit of Work with Transaction
|
|
```csharp
|
|
try
|
|
{
|
|
await _unitOfWork.BeginTransactionAsync();
|
|
|
|
// Multiple operations
|
|
await _unitOfWork.Jobs.AddAsync(job);
|
|
await _unitOfWork.JobItems.AddRangeAsync(items);
|
|
|
|
await _unitOfWork.CommitTransactionAsync();
|
|
}
|
|
catch
|
|
{
|
|
await _unitOfWork.RollbackTransactionAsync();
|
|
throw;
|
|
}
|
|
```
|
|
|
|
## Debugging Tips
|
|
|
|
### Enable Sensitive Data Logging
|
|
In `appsettings.Development.json`:
|
|
```json
|
|
"Logging": {
|
|
"LogLevel": {
|
|
"Microsoft.EntityFrameworkCore.Database.Command": "Information"
|
|
}
|
|
}
|
|
```
|
|
|
|
In `DbContext` configuration (development only):
|
|
```csharp
|
|
options.UseSqlServer(connectionString)
|
|
.EnableSensitiveDataLogging()
|
|
.EnableDetailedErrors();
|
|
```
|
|
|
|
### View Generated SQL
|
|
```csharp
|
|
var query = _context.Jobs.Where(j => j.Status == JobStatus.Pending);
|
|
var sql = query.ToQueryString();
|
|
Console.WriteLine(sql);
|
|
```
|
|
|
|
### Common Issues
|
|
|
|
**Issue**: Migration fails with "object already exists"
|
|
```bash
|
|
# Solution: Remove migration and try again
|
|
dotnet ef migrations remove --project ../PowderCoating.Infrastructure
|
|
dotnet ef migrations add NewMigration --project ../PowderCoating.Infrastructure
|
|
```
|
|
|
|
**Issue**: DbContext is disposed
|
|
```bash
|
|
# Solution: Ensure you're using proper DI scoping and not storing DbContext
|
|
# Use IUnitOfWork which has proper lifetime management
|
|
```
|
|
|
|
**Issue**: Circular reference in JSON
|
|
```csharp
|
|
// Solution: Use DTOs or configure JSON serialization
|
|
services.AddControllers()
|
|
.AddJsonOptions(options =>
|
|
{
|
|
options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
|
|
});
|
|
```
|
|
|
|
## Testing
|
|
|
|
### Unit Test Example
|
|
```csharp
|
|
public class CustomerServiceTests
|
|
{
|
|
[Fact]
|
|
public async Task GetByIdAsync_ReturnsCustomer_WhenExists()
|
|
{
|
|
// Arrange
|
|
var mockUnitOfWork = new Mock<IUnitOfWork>();
|
|
var customer = new Customer { Id = 1, CompanyName = "Test" };
|
|
|
|
mockUnitOfWork
|
|
.Setup(x => x.Customers.GetByIdAsync(1))
|
|
.ReturnsAsync(customer);
|
|
|
|
var service = new CustomerService(mockUnitOfWork.Object, mapper);
|
|
|
|
// Act
|
|
var result = await service.GetByIdAsync(1);
|
|
|
|
// Assert
|
|
Assert.NotNull(result);
|
|
Assert.Equal("Test", result.CompanyName);
|
|
}
|
|
}
|
|
```
|
|
|
|
## Deployment
|
|
|
|
### Publish Web Application
|
|
```bash
|
|
cd src/PowderCoating.Web
|
|
dotnet publish -c Release -o ./publish
|
|
```
|
|
|
|
### Publish API
|
|
```bash
|
|
cd src/PowderCoating.Api
|
|
dotnet publish -c Release -o ./publish
|
|
```
|
|
|
|
### Generate Database Script for Production
|
|
```bash
|
|
cd src/PowderCoating.Web
|
|
dotnet ef migrations script --project ../PowderCoating.Infrastructure --idempotent --output deploy.sql
|
|
```
|
|
|
|
## Resources
|
|
|
|
- [ASP.NET Core Documentation](https://docs.microsoft.com/aspnet/core)
|
|
- [Entity Framework Core Documentation](https://docs.microsoft.com/ef/core)
|
|
- [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)
|
|
- [C# Coding Conventions](https://docs.microsoft.com/dotnet/csharp/fundamentals/coding-style/coding-conventions)
|