Backend-utveckling

C# & .NET

Modern C#-utveckling med .NET: dependency injection, design patterns och praktiska exempel.

01 · Översikt

Vad du hittar här

Utvecklingsmönster i C# och .NET, från dependency injection till design patterns, med kodexempel och lösningar på problem som dyker upp i backend-kod.

3
Kodexempel
2
Design patterns
4
Best practices
4
Vanliga problem

02 · Kod

Kodexempel

Dependency Injection Setup

Configure dependency injection container in .NET applications.

C#
// Program.cs (.NET 6+)
var builder = WebApplication.CreateBuilder(args);

// Register services
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddSingleton<ICacheService, MemoryCacheService>();
builder.Services.AddTransient<IEmailService, EmailService>();

// Add Entity Framework
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

var app = builder.Build();

Async/Await Pattern

Proper async/await implementation for database operations.

C#
public async Task<User> GetUserAsync(int userId)
{
    try
    {
        using var connection = new SqlConnection(connectionString);
        await connection.OpenAsync();
        
        var sql = "SELECT * FROM Users WHERE Id = @UserId";
        var user = await connection.QueryFirstOrDefaultAsync<User>(sql, 
            new { UserId = userId });
            
        return user;
    }
    catch (SqlException ex)
    {
        _logger.LogError(ex, "Database error while fetching user {UserId}", userId);
        throw;
    }
}

Configuration Pattern

Strongly-typed configuration using IOptions pattern.

C#
// appsettings.json
{
  "EmailSettings": {
    "SmtpHost": "smtp.example.com",
    "SmtpPort": 587,
    "Username": "user@example.com",
    "EnableSsl": true
  }
}

// EmailSettings.cs
public class EmailSettings
{
    public string SmtpHost { get; set; }
    public int SmtpPort { get; set; }
    public string Username { get; set; }
    public bool EnableSsl { get; set; }
}

// Program.cs
builder.Services.Configure<EmailSettings>(
    builder.Configuration.GetSection("EmailSettings"));

// Usage in service
public class EmailService
{
    private readonly EmailSettings _emailSettings;
    
    public EmailService(IOptions<EmailSettings> emailSettings)
    {
        _emailSettings = emailSettings.Value;
    }
}

03 · Mönster

Design patterns

Repository Pattern

Abstracts data access logic and provides a consistent interface.

C#
public interface IUserRepository
{
    Task<User> GetByIdAsync(int id);
    Task<IEnumerable<User>> GetAllAsync();
    Task<User> CreateAsync(User user);
    Task UpdateAsync(User user);
    Task DeleteAsync(int id);
}

public class UserRepository : IUserRepository
{
    private readonly ApplicationDbContext _context;
    
    public UserRepository(ApplicationDbContext context)
    {
        _context = context;
    }
    
    public async Task<User> GetByIdAsync(int id)
    {
        return await _context.Users
            .FirstOrDefaultAsync(u => u.Id == id);
    }
    
    public async Task<User> CreateAsync(User user)
    {
        _context.Users.Add(user);
        await _context.SaveChangesAsync();
        return user;
    }
}

Fördelar

  • Testability
  • Separation of concerns
  • Consistent data access API

Unit of Work Pattern

Manages transactions and coordinates multiple repository operations.

C#
public interface IUnitOfWork : IDisposable
{
    IUserRepository Users { get; }
    IOrderRepository Orders { get; }
    Task<int> SaveChangesAsync();
    Task BeginTransactionAsync();
    Task CommitTransactionAsync();
    Task RollbackTransactionAsync();
}

public class UnitOfWork : IUnitOfWork
{
    private readonly ApplicationDbContext _context;
    private IDbContextTransaction _transaction;
    
    public UnitOfWork(ApplicationDbContext context)
    {
        _context = context;
        Users = new UserRepository(context);
        Orders = new OrderRepository(context);
    }
    
    public IUserRepository Users { get; }
    public IOrderRepository Orders { get; }
    
    public async Task<int> SaveChangesAsync()
    {
        return await _context.SaveChangesAsync();
    }
    
    public void Dispose()
    {
        _transaction?.Dispose();
        _context.Dispose();
    }
}

Fördelar

  • Transaction management
  • Consistency across repositories
  • Resource cleanup

04 · Praxis

Best practices

  • Use Nullable Reference Types

    Enable nullable reference types to catch null reference exceptions at compile time

    Exempel: <Nullable>enable</Nullable> in project file and use string? for nullable strings

  • Implement Proper Logging

    Use structured logging with appropriate log levels and contexts

    Exempel: _logger.LogInformation("User {UserId} performed action {Action}", userId, action)

  • Follow SOLID Principles

    Design classes with single responsibility and dependency inversion

    Exempel: Use interfaces for dependencies and keep classes focused on one responsibility

  • Use Configuration Validation

    Validate configuration values at startup to fail fast

    Exempel: Use IValidateOptions<T> to validate configuration objects during registration

05 · Prestanda

Prestanda

  • Database Connection Pooling

    Optimize database connections by using connection pooling properly

    Lösning: Use connection strings with appropriate pooling settings and avoid holding connections too long

  • Async All The Way

    Use async/await throughout the call chain to avoid thread pool starvation

    Lösning: Never mix async and sync code (avoid .Result or .Wait())

  • Memory Management

    Minimize allocations and implement IDisposable for resources

    Lösning: Use object pooling, span/memory for large data, and proper disposal patterns

  • Caching Strategies

    Implement appropriate caching for frequently accessed data

    Lösning: Use IMemoryCache for local caching and distributed cache for scalable solutions

06 · Felsökning

Vanliga problem och lösningar

  • NullReferenceException

    Most common runtime exception in C# applications

    Lösning: Enable nullable reference types and use null-conditional operators (?. and ??)

  • Memory Leaks

    Objects not being garbage collected due to event handlers or static references

    Lösning: Properly unsubscribe from events and avoid keeping references in static collections

  • Deadlocks in Async Code

    Mixing synchronous and asynchronous code can cause deadlocks

    Lösning: Use ConfigureAwait(false) in library code and avoid .Result/.Wait()

  • Entity Framework N+1 Problem

    Executing multiple queries when one would suffice

    Lösning: Use Include() for related data or projection with Select() to avoid lazy loading issues

07 · Tips

Tips för C#-utveckling

Använd Code Analysis
Aktivera kod-analys och behandla warnings som errors för att fånga problem tidigt.
Skriv enhetstester
Använd xUnit eller NUnit för att skriva enhetstester och säkerställa kodkvalitet.
Följ .NET conventions
Använd PascalCase för publika medlemmar och camelCase för privata fält.