C# & .NET - Cheat Sheet¶
.NET Ecosystem Overview¶
.NET is a cross-platform development platform for building web, desktop, mobile, cloud, IoT, and gaming applications.
Main versions:
- .NET Framework (Windows only, legacy)
- .NET Core (cross-platform, deprecated)
- .NET 6/⅞/9+ (modern, cross-platform, LTS every 2 years)
dotnet CLI - Essential Commands¶
Project Management¶
# Create a new solution
dotnet new sln -n MySolution
# Create different types of projects
dotnet new console -n MyApp # Console application
dotnet new classlib -n MyLibrary # Class library
dotnet new web -n MyApi # Minimal Web API
dotnet new webapi -n MyApi # Web API with controllers
dotnet new mvc -n MyMvcApp # MVC application
dotnet new blazorserver -n MyBlazorApp # Blazor Server application
dotnet new xunit -n MyTests # xUnit test project
dotnet new nunit -n MyTests # NUnit test project
dotnet new mstest -n MyTests # MSTest test project
# List available templates
dotnet new list
# Add a project to a solution
dotnet sln add MyApp/MyApp.csproj
# Add a project reference
dotnet add MyApp/MyApp.csproj reference MyLibrary/MyLibrary.csproj
# Remove a project from the solution
dotnet sln remove MyApp/MyApp.csproj
Build and Execution¶
# Restore dependencies
dotnet restore
# Build the project/solution
dotnet build
dotnet build --configuration Release
dotnet build -c Release --no-restore
# Clean build artifacts
dotnet clean
# Run the application
dotnet run
dotnet run --project MyApp/MyApp.csproj
dotnet run --configuration Release
# Run with arguments
dotnet run -- arg1 arg2 --option=value
# Watch mode (automatic rebuild)
dotnet watch run
dotnet watch test
Publishing¶
# Publish the application
dotnet publish -c Release -o ./publish
# Publish as self-contained (includes runtime)
dotnet publish -c Release -r win-x64 --self-contained
dotnet publish -c Release -r linux-x64 --self-contained
dotnet publish -c Release -r osx-x64 --self-contained
# Publish as framework-dependent (requires .NET installed)
dotnet publish -c Release -r win-x64 --self-contained false
# Publish as single-file
dotnet publish -c Release -r win-x64 -p:PublishSingleFile=true
# Publish with trimming (size reduction)
dotnet publish -c Release -r linux-x64 -p:PublishTrimmed=true
# Publish as AOT (Ahead-of-Time compilation, .NET 7+)
dotnet publish -c Release -r linux-x64 -p:PublishAot=true
NuGet Package Management¶
# Add a NuGet package
dotnet add package Newtonsoft.Json
dotnet add package Microsoft.EntityFrameworkCore --version 8.0.0
# Remove a package
dotnet remove package Newtonsoft.Json
# List packages
dotnet list package
dotnet list package --outdated
dotnet list package --vulnerable
# Restore packages
dotnet restore
# Update packages
dotnet add package PackageName
.csproj File¶
The .csproj (C# Project) file defines the project configuration.
Basic Structure (modern .NET)¶
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
Common Properties¶
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Output type -->
<OutputType>Exe</OutputType> <!-- Exe, Library, WinExe -->
<!-- Target framework -->
<TargetFramework>net8.0</TargetFramework>
<!-- Multi-targeting -->
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
<!-- C# language version -->
<LangVersion>12.0</LangVersion> <!-- latest, preview, 12.0, 11.0 -->
<!-- Nullable reference types -->
<Nullable>enable</Nullable> <!-- enable, disable, warnings -->
<!-- Implicit usings -->
<ImplicitUsings>enable</ImplicitUsings>
<!-- Assembly info -->
<AssemblyName>MyApplication</AssemblyName>
<RootNamespace>MyApplication</RootNamespace>
<Version>1.0.0</Version>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
<!-- Package metadata -->
<PackageId>MyPackage</PackageId>
<Authors>Your Name</Authors>
<Company>Your Company</Company>
<Product>Your Product</Product>
<Description>Package description</Description>
<Copyright>Copyright © 2025</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/user/repo</PackageProjectUrl>
<RepositoryUrl>https://github.com/user/repo</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>tag1;tag2;tag3</PackageTags>
<!-- Build options -->
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors>CS8600;CS8602</WarningsAsErrors>
<NoWarn>CS1591</NoWarn>
<!-- Publishing -->
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PublishTrimmed>true</PublishTrimmed>
<PublishReadyToRun>true</PublishReadyToRun>
</PropertyGroup>
</Project>
NuGet Package References¶
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
<PackageReference Include="Serilog" Version="3.1.1" />
<!-- With specific options -->
<PackageReference Include="Moq" Version="4.20.70">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
Project References¶
<ItemGroup>
<ProjectReference Include="..\MyLibrary\MyLibrary.csproj" />
<ProjectReference Include="..\Shared\Shared.csproj" />
</ItemGroup>
Files to Include/Exclude¶
<ItemGroup>
<!-- Include specific files -->
<None Include="appsettings.json" CopyToOutputDirectory="PreserveNewest" />
<None Include="data\*.json" CopyToOutputDirectory="Always" />
<!-- Embedded content -->
<EmbeddedResource Include="Resources\**\*" />
<!-- Exclude files -->
<Compile Remove="OldCode\**" />
<EmbeddedResource Remove="OldCode\**" />
<None Remove="OldCode\**" />
</ItemGroup>
Complete Example - Web API¶
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
<UserSecretsId>aspnet-MyApi-12345</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MyApi.Core\MyApi.Core.csproj" />
<ProjectReference Include="..\MyApi.Infrastructure\MyApi.Infrastructure.csproj" />
</ItemGroup>
</Project>
Configuration - appsettings.json¶
Basic Structure¶
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Complete Configuration¶
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"System": "Warning"
},
"Console": {
"IncludeScopes": true,
"LogLevel": {
"Default": "Debug"
}
}
},
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=MyDb;User Id=sa;Password=P@ssw0rd;TrustServerCertificate=True",
"RedisConnection": "localhost:6379,password=redis123"
},
"AppSettings": {
"JwtSecret": "YourVeryLongAndSecureSecretKey",
"JwtExpirationDays": 7,
"ApiUrl": "https://api.example.com",
"MaxUploadSizeInMb": 10
},
"EmailSettings": {
"SmtpServer": "smtp.gmail.com",
"SmtpPort": 587,
"SenderEmail": "noreply@example.com",
"SenderName": "My Application",
"EnableSsl": true
},
"Cors": {
"AllowedOrigins": [
"https://localhost:4200",
"https://example.com"
]
},
"FeatureFlags": {
"EnableNewFeature": false,
"EnableBetaFeatures": true
},
"AllowedHosts": "*"
}
Configuration Hierarchy¶
appsettings.json (Base)
↓ (override)
appsettings.Development.json (Environment-specific)
↓ (override)
appsettings.{Environment}.json (Production, Staging, etc.)
↓ (override)
User Secrets (development)
↓ (override)
Environment Variables
↓ (override)
Command-line arguments
Usage in C¶
1. Strongly-typed configuration with IOptions:
// Configuration class
public class AppSettings
{
public string JwtSecret { get; set; } = string.Empty;
public int JwtExpirationDays { get; set; }
public string ApiUrl { get; set; } = string.Empty;
public int MaxUploadSizeInMb { get; set; }
}
// Program.cs
builder.Services.Configure<AppSettings>(
builder.Configuration.GetSection("AppSettings")
);
// Injection in a service
public class MyService
{
private readonly AppSettings _settings;
public MyService(IOptions<AppSettings> options)
{
_settings = options.Value;
}
public void DoSomething()
{
var url = _settings.ApiUrl;
var maxSize = _settings.MaxUploadSizeInMb;
}
}
2. Direct access via IConfiguration:
public class MyService
{
private readonly IConfiguration _configuration;
public MyService(IConfiguration configuration)
{
_configuration = configuration;
}
public void DoSomething()
{
// Simple read
var logLevel = _configuration["Logging:LogLevel:Default"];
// Read with default value
var maxSize = _configuration.GetValue<int>("AppSettings:MaxUploadSizeInMb", 10);
// Read ConnectionString
var connString = _configuration.GetConnectionString("DefaultConnection");
// Read section
var emailSettings = _configuration.GetSection("EmailSettings");
var smtpServer = emailSettings["SmtpServer"];
}
}
3. Bind to an object:
var emailSettings = new EmailSettings();
builder.Configuration.GetSection("EmailSettings").Bind(emailSettings);
User Secrets (development)¶
# Initialize secrets
dotnet user-secrets init
# Add a secret
dotnet user-secrets set "AppSettings:JwtSecret" "my-secret-key"
dotnet user-secrets set "ConnectionStrings:DefaultConnection" "Server=..."
# List secrets
dotnet user-secrets list
# Remove a secret
dotnet user-secrets remove "AppSettings:JwtSecret"
# Clear all secrets
dotnet user-secrets clear
Storage: ~/.microsoft/usersecrets/{UserSecretsId}/secrets.json
Environment Variables¶
# Linux/macOS
export AppSettings__JwtSecret="my-key"
export ConnectionStrings__DefaultConnection="Server=..."
# Windows (PowerShell)
$env:AppSettings__JwtSecret="my-key"
$env:ConnectionStrings__DefaultConnection="Server=..."
# Windows (CMD)
set AppSettings__JwtSecret=my-key
Note: Use __ (double underscore) for nested sections.
Tests¶
xUnit (most popular)¶
Installation:
dotnet new xunit -n MyTests
dotnet add package xunit
dotnet add package xunit.runner.visualstudio
dotnet add package Microsoft.NET.Test.Sdk
Test Examples:
using Xunit;
public class CalculatorTests
{
[Fact]
public void Addition_TwoNumbers_ReturnsCorrectResult()
{
// Arrange
var calculator = new Calculator();
// Act
var result = calculator.Add(2, 3);
// Assert
Assert.Equal(5, result);
}
[Theory]
[InlineData(2, 3, 5)]
[InlineData(0, 0, 0)]
[InlineData(-1, 1, 0)]
[InlineData(10, -5, 5)]
public void Addition_DifferentValues_ReturnsCorrectResult(
int a, int b, int expected)
{
// Arrange
var calculator = new Calculator();
// Act
var result = calculator.Add(a, b);
// Assert
Assert.Equal(expected, result);
}
[Fact]
public void Division_ByZero_ThrowsException()
{
// Arrange
var calculator = new Calculator();
// Act & Assert
Assert.Throws<DivideByZeroException>(() =>
calculator.Divide(10, 0)
);
}
[Fact]
public async Task GetUser_WithValidId_ReturnsUser()
{
// Arrange
var service = new UserService();
// Act
var user = await service.GetUserAsync(1);
// Assert
Assert.NotNull(user);
Assert.Equal("John", user.Name);
}
}
Common Assertions:
// Equality
Assert.Equal(expected, actual);
Assert.NotEqual(expected, actual);
// Null
Assert.Null(obj);
Assert.NotNull(obj);
// Booleans
Assert.True(condition);
Assert.False(condition);
// Collections
Assert.Empty(collection);
Assert.NotEmpty(collection);
Assert.Contains(item, collection);
Assert.DoesNotContain(item, collection);
Assert.Collection(collection,
item => Assert.Equal("a", item),
item => Assert.Equal("b", item)
);
// Strings
Assert.StartsWith("Hello", text);
Assert.EndsWith("World", text);
Assert.Contains("middle", text);
Assert.Matches(@"\d{3}", text); // Regex
// Exceptions
Assert.Throws<InvalidOperationException>(() => method());
var ex = Assert.Throws<ArgumentException>(() => method());
Assert.Equal("paramName", ex.ParamName);
// Types
Assert.IsType<string>(obj);
Assert.IsAssignableFrom<IEnumerable>(obj);
// Ranges
Assert.InRange(value, low, high);
Assert.NotInRange(value, low, high);
NUnit¶
dotnet add package NUnit
dotnet add package NUnit3TestAdapter
dotnet add package Microsoft.NET.Test.Sdk
using NUnit.Framework;
[TestFixture]
public class CalculatorTests
{
private Calculator _calculator;
[SetUp]
public void Setup()
{
_calculator = new Calculator();
}
[Test]
public void Addition_TwoNumbers_ReturnsCorrectResult()
{
var result = _calculator.Add(2, 3);
Assert.That(result, Is.EqualTo(5));
}
[TestCase(2, 3, 5)]
[TestCase(0, 0, 0)]
[TestCase(-1, 1, 0)]
public void Addition_DifferentValues(int a, int b, int expected)
{
var result = _calculator.Add(a, b);
Assert.That(result, Is.EqualTo(expected));
}
[TearDown]
public void Cleanup()
{
_calculator = null;
}
}
Moq - Mocking Framework¶
using Moq;
using Xunit;
public interface IUserRepository
{
Task<User> GetAsync(int id);
Task<bool> SaveAsync(User user);
}
public class UserServiceTests
{
[Fact]
public async Task GetUser_WithValidId_CallsRepository()
{
// Arrange
var mockRepo = new Mock<IUserRepository>();
mockRepo
.Setup(r => r.GetAsync(1))
.ReturnsAsync(new User { Id = 1, Name = "John" });
var service = new UserService(mockRepo.Object);
// Act
var user = await service.GetUserAsync(1);
// Assert
Assert.NotNull(user);
Assert.Equal("John", user.Name);
mockRepo.Verify(r => r.GetAsync(1), Times.Once);
}
[Fact]
public async Task SaveUser_WithEmptyName_ThrowsException()
{
// Arrange
var mockRepo = new Mock<IUserRepository>();
var service = new UserService(mockRepo.Object);
// Act & Assert
await Assert.ThrowsAsync<ArgumentException>(
() => service.SaveAsync(new User { Name = "" })
);
// Verify repository was not called
mockRepo.Verify(r => r.SaveAsync(It.IsAny<User>()), Times.Never);
}
}
Advanced Moq Setup:
// Return different values
mock.SetupSequence(m => m.GetValue())
.Returns(1)
.Returns(2)
.Returns(3);
// Callback
mock.Setup(m => m.Save(It.IsAny<User>()))
.Callback<User>(u => Console.WriteLine($"Saving {u.Name}"))
.Returns(true);
// With conditions
mock.Setup(m => m.GetById(It.Is<int>(id => id > 0)))
.Returns(new User());
// Properties
mock.SetupGet(m => m.Count).Returns(5);
mock.SetupSet(m => m.Name = "test");
// Exceptions
mock.Setup(m => m.Delete(It.IsAny<int>()))
.Throws<InvalidOperationException>();
Running Tests¶
# Run all tests
dotnet test
# With verbosity
dotnet test --verbosity normal
# Tests from a specific project
dotnet test MyTests/MyTests.csproj
# Filter by name
dotnet test --filter "FullyQualifiedName~CalculatorTests"
dotnet test --filter "Name~Addition"
# Filter by category
dotnet test --filter "Category=Integration"
# With code coverage
dotnet test --collect:"XPlat Code Coverage"
# Watch mode
dotnet watch test
Code Coverage with Coverlet¶
# Install coverlet
dotnet add package coverlet.collector
# Generate coverage
dotnet test --collect:"XPlat Code Coverage"
# With ReportGenerator for HTML
dotnet tool install -g dotnet-reportgenerator-globaltool
reportgenerator \
-reports:"**/coverage.cobertura.xml" \
-targetdir:"coveragereport" \
-reporttypes:Html
# View the report
open coveragereport/index.html
NuGet - Package Management¶
Creating a NuGet Package¶
1. Configure the .csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<!-- Package metadata -->
<PackageId>MyPackage.Utils</PackageId>
<Version>1.0.0</Version>
<Authors>Your Name</Authors>
<Company>Your Company</Company>
<Description>Utilities to simplify development</Description>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/user/repo</PackageProjectUrl>
<RepositoryUrl>https://github.com/user/repo</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>utils;helpers;extensions</PackageTags>
<PackageReleaseNotes>Initial version</PackageReleaseNotes>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageIcon>icon.png</PackageIcon>
<!-- Package generation -->
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="\" />
<None Include="icon.png" Pack="true" PackagePath="\" />
</ItemGroup>
</Project>
2. Create the package:
# Pack the project
dotnet pack -c Release
# Specify version
dotnet pack -c Release -p:PackageVersion=1.2.3
# Custom output
dotnet pack -c Release -o ./nupkgs
3. Publish to NuGet.org:
# Get an API key from nuget.org
# Publish the package
dotnet nuget push MyPackage.1.0.0.nupkg --api-key YOUR_API_KEY --source https://api.nuget.org/v3/index.json
Custom NuGet Sources¶
Configuration in nuget.config:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="MyPrivateFeed" value="https://pkgs.dev.azure.com/myorg/_packaging/myfeed/nuget/v3/index.json" />
<add key="LocalFeed" value="./local-packages" />
</packageSources>
<packageSourceCredentials>
<MyPrivateFeed>
<add key="Username" value="myusername" />
<add key="ClearTextPassword" value="mypassword" />
</MyPrivateFeed>
</packageSourceCredentials>
</configuration>
Commands:
# List sources
dotnet nuget list source
# Add a source
dotnet nuget add source https://api.myget.org/F/myfeed/api/v3/index.json -n MyGetFeed
# Remove a source
dotnet nuget remove source MyGetFeed
# Enable/disable a source
dotnet nuget enable source MyGetFeed
dotnet nuget disable source MyGetFeed
Local Feed¶
# Create a folder for packages
mkdir local-packages
# Copy packages
dotnet pack -o ./local-packages
# Add as source
dotnet nuget add source ./local-packages -n LocalFeed
# Use
dotnet add package MyPackage --source LocalFeed
Project Structure Examples¶
Minimal Web API (.NET 8)¶
Program.cs:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// Configuration
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))
);
// CORS
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAll", policy =>
{
policy.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});
var app = builder.Build();
// Middleware
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseCors("AllowAll");
// Endpoints
app.MapGet("/api/users", async (AppDbContext db) =>
{
return await db.Users.ToListAsync();
});
app.MapGet("/api/users/{id}", async (int id, AppDbContext db) =>
{
var user = await db.Users.FindAsync(id);
return user is not null ? Results.Ok(user) : Results.NotFound();
});
app.MapPost("/api/users", async (User user, AppDbContext db) =>
{
db.Users.Add(user);
await db.SaveChangesAsync();
return Results.Created($"/api/users/{user.Id}", user);
});
app.MapPut("/api/users/{id}", async (int id, User user, AppDbContext db) =>
{
var existingUser = await db.Users.FindAsync(id);
if (existingUser is null) return Results.NotFound();
existingUser.Name = user.Name;
existingUser.Email = user.Email;
await db.SaveChangesAsync();
return Results.NoContent();
});
app.MapDelete("/api/users/{id}", async (int id, AppDbContext db) =>
{
var user = await db.Users.FindAsync(id);
if (user is null) return Results.NotFound();
db.Users.Remove(user);
await db.SaveChangesAsync();
return Results.NoContent();
});
app.Run();
Web API with Controllers¶
Program.cs:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Dependency injection
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<IUserRepository, UserRepository>();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Controller:
using Microsoft.AspNetCore.Mvc;
[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;
}
[HttpGet]
public async Task<ActionResult<IEnumerable<UserDto>>> GetAll()
{
var users = await _userService.GetAllUsersAsync();
return Ok(users);
}
[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<UserDto>> GetById(int id)
{
var user = await _userService.GetUserByIdAsync(id);
if (user == null)
{
_logger.LogWarning("User {UserId} not found", id);
return NotFound();
}
return Ok(user);
}
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<UserDto>> Create([FromBody] CreateUserDto dto)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
var user = await _userService.CreateUserAsync(dto);
return CreatedAtAction(nameof(GetById), new { id = user.Id }, user);
}
[HttpPut("{id}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Update(int id, [FromBody] UpdateUserDto dto)
{
var success = await _userService.UpdateUserAsync(id, dto);
if (!success)
return NotFound();
return NoContent();
}
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
await _userService.DeleteUserAsync(id);
return NoContent();
}
}
Useful Tools¶
dotnet CLI Extensions¶
# Install global tools
dotnet tool install -g dotnet-ef # Entity Framework
dotnet tool install -g dotnet-outdated-tool # Check outdated packages
dotnet tool install -g dotnet-format # Code formatting
dotnet tool install -g dotnet-aspnet-codegenerator # Scaffolding
# List installed tools
dotnet tool list -g
# Update a tool
dotnet tool update -g dotnet-ef
Entity Framework Core¶
# Migrations
dotnet ef migrations add InitialCreate
dotnet ef migrations remove
dotnet ef migrations list
# Database
dotnet ef database update
dotnet ef database drop
# Scaffolding (database-first)
dotnet ef dbcontext scaffold "Connection String" Microsoft.EntityFrameworkCore.SqlServer -o Models
Formatting and Analysis¶
# Format code
dotnet format
# Analyze code
dotnet build --no-incremental /p:TreatWarningsAsErrors=true
# Code style analysis
dotnet build /p:EnforceCodeStyleInBuild=true
Best Practices¶
✅ Recommendations¶
- Use .NET 8 or newer (LTS)
- Enable nullable reference types (
<Nullable>enable</Nullable>) - Use appsettings.json for configuration
- User Secrets for sensitive data in development
- Dependency Injection for decoupling
- Structured logging (Serilog, NLog)
- Unit tests with coverage > 80%
- Semantic versioning for NuGet packages
- CI/CD with GitHub Actions, Azure DevOps, etc.
- XML documentation for public APIs
❌ To Avoid¶
- ❌ Storing secrets in appsettings.json
- ❌ Using
#pragma warning disable - ❌ Circular references between projects
- ❌ Business logic in controllers
- ❌ Generic catch without logging:
catch (Exception) { }