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: IConfigurationappsettings.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

Popular posts from this blog

Tree view in winforms using c#

how to Replace null value with 0 Using C#

how to fetch all HTML Table Records and modify all records accordingly using jquery and Javascript