dotnet-development
.NET Development Standards
Apply these standards when developing .NET applications. For C# language-specific standards, also refer to the csharp-standards skill.
Prerequisites
This skill extends C# coding standards. Follow all C# naming and formatting conventions unless specifically overridden here.
Project Structure
1. Organization by Feature
Organize code by feature or module:
MyApp/
├── MyApp.Api/ # Web API project
│ ├── Controllers/
│ ├── Middleware/
│ └── Program.cs
├── MyApp.Application/ # Application layer
│ ├── Users/
│ │ ├── Commands/
│ │ ├── Queries/
│ │ └── UserService.cs
│ └── Orders/
│ ├── Commands/
│ ├── Queries/
│ └── OrderService.cs
├── MyApp.Domain/ # Domain models
│ ├── Users/
│ │ └── User.cs
│ └── Orders/
│ └── Order.cs
├── MyApp.Infrastructure/ # Data access, external services
│ ├── Persistence/
│ └── Services/
└── MyApp.Tests/ # Tests
├── Unit/
└── Integration/
2. Solution Folders
Use solution folders to mirror logical structure:
Solution 'MyApp'
├── src/
│ ├── MyApp.Api
│ ├── MyApp.Application
│ ├── MyApp.Domain
│ └── MyApp.Infrastructure
├── tests/
│ ├── MyApp.Tests.Unit
│ └── MyApp.Tests.Integration
└── docs/
└── README.md
3. Naming Conventions
- Projects: PascalCase -
MyApp.Application - Namespaces: PascalCase -
MyApp.Application.Users - Folders: PascalCase -
Controllers,Services
Using Directives
4. Using Directives Organization
Place using directives outside the namespace and sort alphabetically:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using MyApp.Application.Users;
using MyApp.Domain.Users;
namespace MyApp.Api.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
// Implementation
}
}
Dependency Injection
5. Constructor Injection
Prefer constructor injection for dependencies:
public class UserService : IUserService
{
private readonly IUserRepository _repository;
private readonly ILogger<UserService> _logger;
private readonly IEmailService _emailService;
public UserService(
IUserRepository repository,
ILogger<UserService> logger,
IEmailService emailService)
{
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_emailService = emailService ?? throw new ArgumentNullException(nameof(emailService));
}
public async Task<User> CreateUserAsync(User user)
{
_logger.LogInformation("Creating user {Email}", user.Email);
var createdUser = await _repository.AddAsync(user);
await _emailService.SendWelcomeEmailAsync(user.Email);
return createdUser;
}
}
6. Service Registration
Register services in Program.cs (or Startup.cs for older projects):
var builder = WebApplication.CreateBuilder(args);
// Add services to the container
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Register application services
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<IEmailService, EmailService>();
// Add DbContext
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
// Add logging
builder.Services.AddLogging(config =>
{
config.AddConsole();
config.AddDebug();
});
var app = builder.Build();
// Configure the HTTP request pipeline
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Async/Await Patterns
7. Async Methods
Always use async/await for I/O operations:
public class OrderService : IOrderService
{
private readonly IOrderRepository _repository;
private readonly IPaymentService _paymentService;
public OrderService(IOrderRepository repository, IPaymentService paymentService)
{
_repository = repository;
_paymentService = paymentService;
}
public async Task<Order> ProcessOrderAsync(Order order)
{
// Validate order
ValidateOrder(order);
// Process payment
var paymentResult = await _paymentService.ProcessPaymentAsync(order.PaymentInfo);
if (!paymentResult.Success)
{
throw new PaymentFailedException(paymentResult.Message);
}
// Save order
order.Status = OrderStatus.Paid;
await _repository.UpdateAsync(order);
return order;
}
private void ValidateOrder(Order order)
{
if (order == null)
{
throw new ArgumentNullException(nameof(order));
}
if (order.Items.Count == 0)
{
throw new ValidationException("Order must contain at least one item.");
}
}
}
8. Cancellation Tokens
Support cancellation in async methods:
public async Task<List<User>> GetActiveUsersAsync(CancellationToken cancellationToken = default)
{
return await _dbContext.Users
.Where(u => u.IsActive)
.ToListAsync(cancellationToken);
}
public async Task<User> GetUserByIdAsync(int id, CancellationToken cancellationToken = default)
{
var user = await _dbContext.Users
.FirstOrDefaultAsync(u => u.Id == id, cancellationToken);
if (user == null)
{
throw new NotFoundException($"User with ID {id} not found.");
}
return user;
}
Configuration
9. Strongly-Typed Configuration
Use strongly-typed configuration:
// appsettings.json
{
"EmailSettings": {
"SmtpServer": "smtp.example.com",
"Port": 587,
"FromAddress": "noreply@example.com"
}
}
// Configuration class
public class EmailSettings
{
public string SmtpServer { get; set; }
public int Port { get; set; }
public string FromAddress { get; set; }
}
// Program.cs
builder.Services.Configure<EmailSettings>(
builder.Configuration.GetSection("EmailSettings"));
// Usage in service
public class EmailService : IEmailService
{
private readonly EmailSettings _settings;
public EmailService(IOptions<EmailSettings> settings)
{
_settings = settings.Value;
}
public async Task SendEmailAsync(string to, string subject, string body)
{
// Use _settings.SmtpServer, _settings.Port, etc.
}
}
Exception Handling
10. Global Exception Handling
Use middleware for global exception handling:
public class ExceptionHandlingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionHandlingMiddleware> _logger;
public ExceptionHandlingMiddleware(
RequestDelegate next,
ILogger<ExceptionHandlingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(ex, "An unhandled exception occurred.");
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
var statusCode = exception switch
{
NotFoundException => StatusCodes.Status404NotFound,
ValidationException => StatusCodes.Status400BadRequest,
UnauthorizedException => StatusCodes.Status401Unauthorized,
_ => StatusCodes.Status500InternalServerError
};
context.Response.ContentType = "application/json";
context.Response.StatusCode = statusCode;
var response = new
{
error = exception.Message,
statusCode
};
return context.Response.WriteAsJsonAsync(response);
}
}
// Register in Program.cs
app.UseMiddleware<ExceptionHandlingMiddleware>();
Entity Framework Core
11. DbContext Configuration
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<User> Users { get; set; }
public DbSet<Order> Orders { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Email).IsRequired().HasMaxLength(255);
entity.HasIndex(e => e.Email).IsUnique();
});
modelBuilder.Entity<Order>(entity =>
{
entity.HasKey(e => e.Id);
entity.HasOne(e => e.User)
.WithMany(u => u.Orders)
.HasForeignKey(e => e.UserId);
});
}
}
Complete API Controller Example
using Microsoft.AspNetCore.Mvc;
using MyApp.Application.Users;
using MyApp.Domain.Users;
namespace MyApp.Api.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
private readonly ILogger<UsersController> _logger;
public UsersController(IUserService userService, ILogger<UsersController> logger)
{
_userService = userService;
_logger = logger;
}
/// <summary>
/// Gets all users.
/// </summary>
[HttpGet]
[ProducesResponseType(typeof(List<User>), StatusCodes.Status200OK)]
public async Task<ActionResult<List<User>>> GetUsers(CancellationToken cancellationToken)
{
var users = await _userService.GetAllUsersAsync(cancellationToken);
return Ok(users);
}
/// <summary>
/// Gets a user by ID.
/// </summary>
[HttpGet("{id}")]
[ProducesResponseType(typeof(User), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<User>> GetUser(int id, CancellationToken cancellationToken)
{
var user = await _userService.GetUserByIdAsync(id, cancellationToken);
return Ok(user);
}
/// <summary>
/// Creates a new user.
/// </summary>
[HttpPost]
[ProducesResponseType(typeof(User), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<User>> CreateUser(
[FromBody] CreateUserRequest request,
CancellationToken cancellationToken)
{
var user = await _userService.CreateUserAsync(request, cancellationToken);
return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
}
/// <summary>
/// Updates an existing user.
/// </summary>
[HttpPut("{id}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> UpdateUser(
int id,
[FromBody] UpdateUserRequest request,
CancellationToken cancellationToken)
{
await _userService.UpdateUserAsync(id, request, cancellationToken);
return NoContent();
}
/// <summary>
/// Deletes a user.
/// </summary>
[HttpDelete("{id}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> DeleteUser(int id, CancellationToken cancellationToken)
{
await _userService.DeleteUserAsync(id, cancellationToken);
return NoContent();
}
}
}
Best Practices Summary
- ✅ Organize code by feature/module
- ✅ Use dependency injection for all services
- ✅ Always use async/await for I/O operations
- ✅ Support cancellation tokens
- ✅ Use strongly-typed configuration
- ✅ Implement global exception handling
- ✅ Document public APIs with XML comments
- ✅ Place using directives outside namespace
- ✅ Avoid regions except for large files
- ✅ Use .editorconfig for formatting
When to Apply
Apply these standards when:
- Creating .NET projects
- Building ASP.NET Core APIs
- Implementing services and repositories
- Setting up dependency injection
- Working with Entity Framework Core
- User asks about .NET architecture
More from devbyray/github-copilot-starter
html-standards
Guidelines for writing HTML that is accessible, semantic, maintainable, and secure. Use when working with .html, .vue, .twig, .php, .cshtml, .svelte files, or when the user asks about HTML structure, semantic markup, accessibility, or web security best practices.
10markdown-standards
Documentation and content creation standards for Markdown files. Use when creating or editing .md files, writing documentation, README files, or when the user asks about Markdown formatting, content structure, front matter, or documentation best practices.
6css-standards
Guidelines for writing CSS that is simple, readable, and maintainable. Use when working with .css, .scss, .sass files, CSS-in-JS, styled-components, or when the user asks about CSS organization, selectors, specificity, reusability, performance, or responsive design patterns.
4javascript-typescript-standards
Guidelines for writing JavaScript and TypeScript code that is simple, readable, and maintainable. Use when working with .js, .jsx, .ts, .tsx, .mjs, .cjs files, or when the user asks about JavaScript/TypeScript coding standards, ES2022 features, Node.js modules, async/await patterns, or testing with Vitest.
4nuxt-development
Standards for Nuxt.js 3 development with auto-imports, file-based routing, and SSR/SSG capabilities. Use when working with Nuxt projects, pages/, layouts/, composables/ directories, or when the user asks about Nuxt-specific patterns, server-side rendering, or Nuxt configuration.
3testing-tdd
Test-Driven Development workflow and testing standards. Use when writing tests, implementing TDD, working with Vitest (frontend), xUnit or MsTest (backend), or when the user asks about testing strategies, test coverage, or TDD methodology.
3