Skip to main content
Bitwarden Server follows consistent code style guidelines enforced through EditorConfig and automated formatting tools.

EditorConfig

The project uses .editorconfig to maintain consistent coding styles:
.editorconfig
# Top-most EditorConfig file
root = true

# All files
[*]
indent_style = space
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
guidelines = 120  # Line length guideline

# C# files
[*.{cs,csx}]
indent_size = 4
charset = utf-8-bom

# Project files
[*.{csproj,vbproj,vcxproj}]
indent_size = 2

# JSON files
[*.json]
indent_size = 2

Automatic Formatting

dotnet format

The project uses dotnet format for automatic code formatting. Run formatting manually:
dotnet format
Enable pre-commit hook:
git config --local core.hooksPath .git-hooks
This automatically formats code before each commit.

IDE Integration

Visual Studio Code:
  • Install C# Dev Kit extension
  • Format on save is configured in .vscode/settings.json
Visual Studio / Rider:
  • EditorConfig is automatically detected
  • Use built-in formatter (Ctrl+K, Ctrl+D)

C# Coding Conventions

Naming Conventions

PascalCase

Use PascalCase for:
  • Classes
  • Interfaces (with I prefix)
  • Methods
  • Properties
  • Events
  • Enums and enum values
  • Constants
public class UserService : IUserService
{
    public async Task<User> GetUserByIdAsync(Guid userId) { }
    public string UserName { get; set; }
    public event EventHandler UserCreated;
    
    public const int MaxRetries = 3;
}

public enum UserType
{
    Standard,
    Admin,
    Owner
}

camelCase

Use camelCase for:
  • Private fields (with _ prefix)
  • Method parameters
  • Local variables
public class UserRepository
{
    private readonly IDbConnection _connection;
    private readonly ILogger<UserRepository> _logger;
    
    public async Task<User> GetByEmailAsync(string emailAddress)
    {
        var normalizedEmail = emailAddress.ToLowerInvariant();
        return await QueryAsync(normalizedEmail);
    }
}

Interface Naming

Interfaces start with I:
public interface IUserRepository { }
public interface IUserService { }
public interface IMailService { }

Async Method Naming

Async methods end with Async:
// Good
public async Task<User> GetUserAsync(Guid id) { }
public async Task SaveUserAsync(User user) { }

// Bad
public async Task<User> GetUser(Guid id) { }  // Missing Async suffix

Code Organization

File-Scoped Namespaces

Use file-scoped namespaces (C# 10+):
// Good: File-scoped namespace
namespace Bit.Core.Entities;

public class User : ITableObject<Guid>
{
    // Class implementation
}
// Bad: Block-scoped namespace (don't use)
namespace Bit.Core.Entities
{
    public class User : ITableObject<Guid>
    {
        // Extra indentation level
    }
}

Using Directives

Sort using directives with System.* first:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Repositories;
using Microsoft.AspNetCore.Mvc;
Remove unused using directives (enforced by IDE0005 warning).

Member Ordering

Order class members:
  1. Fields (private, then protected)
  2. Constructors
  3. Properties (public, then protected/private)
  4. Methods (public, then protected/private)
  5. Nested types
public class UserService : IUserService
{
    // 1. Fields
    private readonly IUserRepository _userRepository;
    private readonly ILogger<UserService> _logger;
    
    // 2. Constructor
    public UserService(
        IUserRepository userRepository,
        ILogger<UserService> logger)
    {
        _userRepository = userRepository;
        _logger = logger;
    }
    
    // 3. Properties
    public bool IsInitialized { get; private set; }
    
    // 4. Public methods
    public async Task<User> GetUserAsync(Guid id)
    {
        return await _userRepository.GetByIdAsync(id);
    }
    
    // 5. Private methods
    private void LogOperation(string operation)
    {
        _logger.LogInformation("Operation: {Operation}", operation);
    }
}

Language Features

var Keyword

Prefer var when the type is obvious:
// Good: Type is obvious from right side
var user = new User();
var users = await _repository.GetAllAsync();
var count = users.Count();

// Acceptable: Explicit type for clarity
IUserRepository repository = GetRepository();

String Interpolation

Use string interpolation over concatenation:
// Good
var message = $"User {user.Email} created at {user.CreationDate}";

// Bad
var message = "User " + user.Email + " created at " + user.CreationDate;
Important: Specify culture for formatting:
// Good: Culture specified
var formatted = string.Format(CultureInfo.InvariantCulture, 
    "Price: {0:C}", price);

// Bad: Culture not specified (triggers CA1304, CA1305 warnings)
var formatted = string.Format("Price: {0:C}", price);

Null Handling

Null-conditional operators:
// Good
var email = user?.Email?.ToLowerInvariant();

// Bad
var email = user != null && user.Email != null 
    ? user.Email.ToLowerInvariant() 
    : null;
Null-coalescing operators:
// Good
var name = user.Name ?? "Unknown";
var count = users?.Count ?? 0;

// Use ??= for assignment
user.Name ??= "Default Name";
Nullable reference types:
#nullable enable

public class User
{
    public string Email { get; set; } = null!;  // Non-nullable
    public string? Name { get; set; }           // Nullable
}

Pattern Matching

Use pattern matching where appropriate:
// Good
if (obj is User user)
{
    Console.WriteLine(user.Email);
}

// Switch expressions
var typeName = cipherType switch
{
    CipherType.Login => "Login",
    CipherType.SecureNote => "Secure Note",
    CipherType.Card => "Card",
    CipherType.Identity => "Identity",
    _ => throw new ArgumentException("Unknown cipher type")
};

Braces and Formatting

Brace Style

Always use braces, even for single-line statements:
// Good
if (user == null)
{
    throw new NotFoundException();
}

// Bad
if (user == null)
    throw new NotFoundException();
Opening braces on new line (Allman style):
public async Task<User> GetUserAsync(Guid id)
{
    if (id == Guid.Empty)
    {
        throw new BadRequestException("Invalid user ID");
    }
    
    return await _repository.GetByIdAsync(id);
}

Line Length

Keep lines under 120 characters (guideline, not strict rule). Break long lines at logical points:
// Good: Broken at logical points
var user = await _userRepository.GetByEmailAsync(
    emailAddress.ToLowerInvariant().Trim());

// Method with many parameters
public async Task<Organization> CreateOrganizationAsync(
    string name,
    string billingEmail,
    PlanType planType,
    int seats,
    bool premium)
{
    // Implementation
}

Comments and Documentation

XML Documentation

Add XML comments for public APIs:
/// <summary>
/// Retrieves a user by their unique identifier.
/// </summary>
/// <param name="userId">The user's unique identifier.</param>
/// <returns>The user if found; null otherwise.</returns>
/// <exception cref="BadRequestException">Thrown when userId is empty.</exception>
public async Task<User?> GetUserByIdAsync(Guid userId)
{
    if (userId == Guid.Empty)
    {
        throw new BadRequestException("User ID cannot be empty.");
    }
    
    return await _userRepository.GetByIdAsync(userId);
}

Inline Comments

Use inline comments to explain “why”, not “what”:
// Good: Explains why
// U2F is deprecated and all keys should be migrated to WebAuthn.
// Remove U2F providers to prevent authentication issues.
_twoFactorProviders?.Remove(TwoFactorProviderType.U2f);

// Bad: States the obvious
// Remove U2F from the dictionary
_twoFactorProviders?.Remove(TwoFactorProviderType.U2f);

TODO Comments

// TODO: Implement caching for frequently accessed users
// FIXME: This method doesn't handle concurrent updates correctly
// HACK: Temporary workaround until API v3 is released
Include issue numbers when applicable:
// TODO: Remove this after migration to v2 encryption (BW-1234)

Error Handling

Exceptions

Use specific exception types:
// Good: Specific exceptions
if (string.IsNullOrWhiteSpace(email))
{
    throw new BadRequestException("Email is required.");
}

if (user == null)
{
    throw new NotFoundException("User not found.");
}

if (!await CanAccessAsync(userId, organizationId))
{
    throw new UnauthorizedException();
}

// Bad: Generic exceptions
throw new Exception("Something went wrong");

Try-Catch

Catch specific exceptions when possible:
// Good
try
{
    return JsonSerializer.Deserialize<Dictionary<string, string>>(json);
}
catch (JsonException ex)
{
    _logger.LogWarning(ex, "Failed to deserialize JSON");
    return null;
}

// Avoid catching all exceptions
try
{
    // Code
}
catch (Exception ex)  // Too broad
{
    // Handle
}

Async/Await

Async All the Way

// Good: Async all the way
public async Task<User> GetUserAsync(Guid id)
{
    return await _repository.GetByIdAsync(id);
}

// Bad: Blocking on async code
public User GetUser(Guid id)
{
    return _repository.GetByIdAsync(id).Result;  // Don't do this!
}

ConfigureAwait

Library code should use ConfigureAwait(false) (though not strictly enforced):
var user = await _repository.GetByIdAsync(id).ConfigureAwait(false);

LINQ and Collections

LINQ Style

// Method syntax (preferred for simple queries)
var activeUsers = users.Where(u => u.Enabled).ToList();

// Query syntax (for complex queries with multiple operations)
var result = from user in users
             where user.Enabled
             join org in organizations on user.Id equals org.UserId
             select new { user.Email, org.Name };

Collection Initialization

// Good: Collection initializers
var userTypes = new List<UserType>
{
    UserType.Owner,
    UserType.Admin,
    UserType.User
};

var settings = new Dictionary<string, string>
{
    ["timeout"] = "30",
    ["retries"] = "3"
};

Testing Code Style

Test Naming

Format: MethodName_Scenario_ExpectedOutcome
[Theory]
[BitAutoData]
public async Task GetUserAsync_ValidId_ReturnsUser(
    SutProvider<UserService> sutProvider,
    User user)
{
    // Arrange
    sutProvider.GetDependency<IUserRepository>()
        .GetByIdAsync(user.Id)
        .Returns(user);
    
    // Act
    var result = await sutProvider.Sut.GetUserAsync(user.Id);
    
    // Assert
    Assert.Equal(user.Id, result.Id);
}

[Theory]
[BitAutoData]
public async Task GetUserAsync_InvalidId_ThrowsNotFoundException(
    SutProvider<UserService> sutProvider)
{
    // Arrange
    sutProvider.GetDependency<IUserRepository>()
        .GetByIdAsync(Arg.Any<Guid>())
        .Returns((User?)null);
    
    // Act & Assert
    await Assert.ThrowsAsync<NotFoundException>(
        () => sutProvider.Sut.GetUserAsync(Guid.NewGuid()));
}

Arrange-Act-Assert

Always structure tests with clear AAA sections:
[Theory]
[BitAutoData]
public async Task TestMethod(
    SutProvider<Service> sutProvider)
{
    // Arrange - Set up test data and mocks
    var input = new Input();
    sutProvider.GetDependency<IDependency>()
        .MethodAsync()
        .Returns(expectedValue);
    
    // Act - Execute the method under test
    var result = await sutProvider.Sut.MethodUnderTest(input);
    
    // Assert - Verify the outcome
    Assert.NotNull(result);
    Assert.Equal(expectedValue, result.Value);
}

Common Analyzer Rules

The project enforces several code analysis rules:

CA1304 & CA1305: Specify CultureInfo

// Good: Culture specified
var lower = text.ToLower(CultureInfo.InvariantCulture);
var formatted = string.Format(CultureInfo.InvariantCulture, "Value: {0}", value);

// Bad: Culture not specified
var lower = text.ToLower();  // Warning CA1304
var formatted = string.Format("Value: {0}", value);  // Warning CA1305

IDE0005: Remove Unnecessary Usings

// All using directives must be used
using System;  // Used
using System.Linq;  // Used
using System.Collections.Generic;  // Must be used or removed

CS8509: Missing Switch Case

// All enum values must be handled
var result = status switch
{
    UserStatus.Active => "Active",
    UserStatus.Inactive => "Inactive",
    UserStatus.Suspended => "Suspended",
    _ => throw new ArgumentException("Unknown status")  // Handle unknown values
};

See Also

Build docs developers (and LLMs) love