DotNET Best Practices Every Developer Should Know
1. Use Dependency Injection (DI) Properly
Why: Keeps your code loosely coupled and testable. Without DI, you’re tightly coupling your logic, making your code rigid and hard to test.
Bad:
var service = new MyService();
Good:
public class MyController
{
private readonly IMyService _service;
public MyController(IMyService service)
{
_service = service;
}
}
🔍 My experience: I once joined a project that had no DI — every service was created with new
. We couldn’t mock anything for tests. Moving to DI not only made things testable, but helped separate concerns much better.
2. Write Unit Tests — Even for Business Logic
Why: You’ll catch bugs early and protect against regressions. Focus especially on core business rules.
Use: xUnit, Moq, or NSubstitute.
[Fact]
public void Should_Return_True_When_Valid()
{
var validator = new OrderValidator();
var result = validator.IsValid("ORD123");
Assert.True(result);
}
3. Avoid Business Logic in Controllers
Why: Controllers should only orchestrate. Business logic in controllers bloats them and makes logic untestable and duplicated.
Bad:
public IActionResult Save(Order order)
{
if(order.Amount > 1000)
order.IsDiscounted = true;
// Save to DB
}
Better:
public IActionResult Save(Order order)
{
_orderService.ApplyDiscount(order);
_orderService.Save(order);
}
4. Never Hardcode Configuration or Secrets
Why: Secrets in code are a major security risk. It also makes environment management difficult.
Use: IConfiguration
, appsettings.json
, or Azure Key Vault.
Wrong:
var apiKey = "SECRET123";
Right:
// appsettings.json
"MyService": {
"ApiKey": "xyz"
}
var key = _config["MyService:ApiKey"];
5. Use Global Exception Handling
Why: Centralized error handling avoids duplicate try-catch and lets you log, track, and return proper messages.
app.UseExceptionHandler("/error");
Or write middleware:
public class ErrorHandlingMiddleware
{
public async Task Invoke(HttpContext context)
{
try { await _next(context); }
catch(Exception ex)
{
_logger.LogError(ex, "Unhandled Exception");
context.Response.StatusCode = 500;
}
}
}
6. Follow SOLID Principles
Why: These principles create maintainable, extensible, and testable code.
- Single Responsibility: One class = one job
- Open/Closed: Open for extension, closed for modification
- Liskov: Subtypes should substitute base types
- Interface Segregation: Don’t force implementation of unused methods
- Dependency Inversion: Depend on abstractions
7. Use Async/Await Properly
Why: Prevents thread blocking. Avoid .Result
or .Wait()
.
Bad:
var data = service.GetData().Result;
Good:
var data = await service.GetDataAsync();
💡 Real Bug: We had a deadlock in production because of .Result
. Took 2 hours to track it.
8. Dispose What You Use
Why: Leaking resources causes memory and performance issues.
Use using
:
using (var client = new HttpClient()) { ... }
Use IHttpClientFactory
to avoid frequent instantiation.
9. Use IHttpClientFactory
Why: It avoids socket exhaustion and improves testability.
services.AddHttpClient<IMyService, MyService>();
10. Don’t Swallow Exceptions
Why: Silently catching exceptions hides bugs.
Bad:
try { ... } catch { }
Good:
catch(Exception ex)
{
_logger.LogError(ex, "Operation failed");
}
11. Use DTOs Instead of EF Models
Why: Prevents over-posting and reduces tight coupling with DB.
public class OrderDto
{
public int Id { get; set; }
public decimal Amount { get; set; }
}
12. Keep Method Size Small
Why: Easier to understand, test, and maintain.
Split large methods into smaller ones.
13. Use FluentValidation
Why: Keeps validation logic out of models and controllers.
public class OrderValidator : AbstractValidator<OrderDto>
{
public OrderValidator()
{
RuleFor(x => x.Amount).GreaterThan(0);
}
}
14. Always Validate Inputs
Why: Prevents injection and crashes.
Use [ApiController]
and model validation.
15. Use Meaningful Names
Why: Code should explain itself.
Avoid DoTask()
, prefer GenerateReportForUser()
.
16. Secure APIs with [Authorize]
Why: Prevents unauthorized access.
[Authorize(Roles = "Admin")]
17. Clean Project Structure
Why: Easier navigation and scaling.
Separate:
- Controllers
- Services
- Repositories
- Models/DTOs
18. Implement Logging
Why: Logs help debug and monitor apps.
_logger.LogInformation("Order processed: {@order}", order);
19. Prefer Configuration Binding
Why: Avoid magic strings.
services.Configure<MySettings>(config.GetSection("MySettings"));
20. Use Middleware for Cross-Cutting Concerns
Why: Cleaner controller code. Examples: Logging, CORS, Auth, Exception handling.
21. Cache Smartly
Why: Improves performance for frequent requests.
IMemoryCache.TryGetValue(key, out result);
22. Paginate API Responses
Why: Prevents OOM and improves performance.
.Skip(page * pageSize).Take(pageSize);
23. Profile with MiniProfiler or BenchmarkDotNet
Why: Spot slow queries/methods.
24. Handle Nulls with Null-Conditional Operator
Why: Prevents NullReferenceException
.
var name = user?.Profile?.Name;
25. Avoid Static State
Why: Not thread-safe. Breaks testability.
Use DI with Singleton or Scoped services.
26. Version Your APIs
Why: Prevents breaking existing clients.
[Route("api/v1/[controller]")]
27. Use Mapperly
Why: Reduces boilerplate mapping code.
var userDto = userMapper.ToUserDto(user);
28. Don’t Overuse Try-Catch
Why: Hides real problems. Only wrap risky calls.
29. Write Integration Tests
Why: End-to-end safety net.
var client = factory.CreateClient();
30. Avoid Logging Sensitive Info
Why: Security risk.
Use masking:
_logger.Log("Token: ****");
31. Use Swagger for API Documentation
Why: Helps devs test and understand APIs.
builder.Services.AddSwaggerGen();
32. Use CancellationToken
Why: Allows graceful shutdowns and responsive APIs.
public async Task GetOrders(CancellationToken cancellationToken)
Final Thoughts
Writing clean, maintainable code is a habit. Best practices are your map — ignore them, and you’ll wander into a jungle of technical debt.
Please Follow @sunita.rawat.cgi
https://medium.com/@sunita.rawat.cgi/30-net-best-practices-every-developer-should-know-from-real-projects-7c7569ada0ec
Comments
Post a Comment