Deploying a .NET App
What is .NET?
.NET is a modern, cross-platform framework for building fast, reliable, and scalable applications. Developed by Microsoft, .NET provides a unified platform for building web applications, APIs, desktop applications, mobile applications, and cloud services.
Key features include:
- Unified platform for Windows, Linux, and macOS
- High-performance ASP.NET Core for web and API applications
- Entity Framework Core for object-relational mapping
- Strong type system and garbage collection
- Comprehensive dependency injection support
- Built-in authentication and authorization with Identity
- Middleware pipeline for request processing
- Comprehensive logging and configuration management
- Health checks and diagnostics
- Async/await patterns for non-blocking operations
- LINQ (Language Integrated Query) for data operations
- Comprehensive testing frameworks (xUnit, NUnit, MSTest)
- NuGet package management
- Docker containerization support
- Cloud-native features (Azure integration, health probes)
- Real-time communication with SignalR
- Background jobs and hosted services
- OpenAPI/Swagger support for API documentation
- Built-in dependency injection container
.NET is ideal for building RESTful APIs, microservices, enterprise web applications, real-time applications, cloud services, e-commerce platforms, and large-scale business applications.
Prerequisites
Before deploying a .NET application to Klutch.sh, ensure you have:
- .NET 7.0 SDK or later installed on your local machine
- C# programming knowledge
- Git and a GitHub account
- A Klutch.sh account with dashboard access
- Optional: PostgreSQL or SQL Server for data persistence
- Optional: Redis for caching and session management
Getting Started with .NET
Step 1: Create a .NET Web API Project
Using the .NET CLI, create a new minimal API project:
dotnet new webapi -n DotnetApp -f net8.0cd DotnetAppThis creates a project structure:
DotnetApp/├── Controllers/│ └── WeatherForecastController.cs├── Properties/│ └── launchSettings.json├── appsettings.json├── appsettings.Development.json├── DotnetApp.csproj├── Program.cs└── WeatherForecast.csStep 2: Configure the Main Application
Update Program.cs:
using DotnetApp.Models;using DotnetApp.Repositories;using DotnetApp.Services;using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// Add services to the containerbuilder.Services.AddControllers();builder.Services.AddEndpointsApiExplorer();builder.Services.AddSwaggerGen();
// Database configurationvar connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? "Host=localhost;Port=5432;Database=dotnet_app;Username=postgres;Password=postgres";builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseNpgsql(connectionString));
// Add custom servicesbuilder.Services.AddScoped<IItemRepository, ItemRepository>();builder.Services.AddScoped<IItemService, ItemService>();
// Add CORSbuilder.Services.AddCors(options => options.AddPolicy("AllowAll", policy =>{ policy.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader();}));
// Add cachingbuilder.Services.AddMemoryCache();builder.Services.AddStackExchangeRedisCache(options =>{ options.Configuration = builder.Configuration.GetConnectionString("Redis") ?? "localhost:6379";});
// Add loggingbuilder.Logging.ClearProviders();builder.Logging.AddConsole();builder.Logging.AddDebug();
var app = builder.Build();
// Configure the HTTP request pipelineif (app.Environment.IsDevelopment()){ app.UseSwagger(); app.UseSwaggerUI();}
app.UseHttpsRedirection();app.UseCors("AllowAll");app.UseAuthorization();app.MapControllers();
// Health check endpointapp.MapGet("/health", () => Results.Ok(new { status = "healthy", service = "dotnet-app" })) .WithName("Health") .WithOpenApi();
app.Run();Step 3: Create Entity Models
Create Models/Item.cs:
using System.ComponentModel.DataAnnotations;using System.ComponentModel.DataAnnotations.Schema;
namespace DotnetApp.Models{ [Table("items")] public class Item { [Key] public long Id { get; set; }
[Required] [StringLength(100)] [Column("name")] public string Name { get; set; } = string.Empty;
[StringLength(500)] [Column("description")] public string? Description { get; set; }
[Required] [Range(0, int.MaxValue)] [Column("price")] public int Price { get; set; }
[Column("created_at")] public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
[Column("updated_at")] public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; }}Step 4: Create Database Context
Create Data/ApplicationDbContext.cs:
using DotnetApp.Models;using Microsoft.EntityFrameworkCore;
namespace DotnetApp.Data{ public class ApplicationDbContext : DbContext { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
public DbSet<Item> Items { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Item>(entity => { entity.HasKey(e => e.Id); entity.Property(e => e.Name).IsRequired().HasMaxLength(100); entity.Property(e => e.Description).HasMaxLength(500); entity.Property(e => e.Price).IsRequired(); entity.HasIndex(e => e.Name).IsUnique(); }); } }}Step 5: Create Repository Pattern
Create Repositories/IItemRepository.cs:
using DotnetApp.Models;
namespace DotnetApp.Repositories{ public interface IItemRepository { Task<IEnumerable<Item>> GetAllAsync(int page = 0, int pageSize = 10); Task<Item?> GetByIdAsync(long id); Task<Item> CreateAsync(Item item); Task<Item> UpdateAsync(Item item); Task<bool> DeleteAsync(long id); Task<long> GetCountAsync(); }}Create Repositories/ItemRepository.cs:
using DotnetApp.Data;using DotnetApp.Models;using Microsoft.EntityFrameworkCore;
namespace DotnetApp.Repositories{ public class ItemRepository : IItemRepository { private readonly ApplicationDbContext _context;
public ItemRepository(ApplicationDbContext context) { _context = context; }
public async Task<IEnumerable<Item>> GetAllAsync(int page = 0, int pageSize = 10) { return await _context.Items .Skip(page * pageSize) .Take(pageSize) .ToListAsync(); }
public async Task<Item?> GetByIdAsync(long id) { return await _context.Items.FindAsync(id); }
public async Task<Item> CreateAsync(Item item) { item.CreatedAt = DateTime.UtcNow; item.UpdatedAt = DateTime.UtcNow; _context.Items.Add(item); await _context.SaveChangesAsync(); return item; }
public async Task<Item> UpdateAsync(Item item) { item.UpdatedAt = DateTime.UtcNow; _context.Items.Update(item); await _context.SaveChangesAsync(); return item; }
public async Task<bool> DeleteAsync(long id) { var item = await _context.Items.FindAsync(id); if (item == null) return false;
_context.Items.Remove(item); await _context.SaveChangesAsync(); return true; }
public async Task<long> GetCountAsync() { return await _context.Items.CountAsync(); } }}Step 6: Create Service Layer
Create Services/IItemService.cs:
using DotnetApp.Models;
namespace DotnetApp.Services{ public interface IItemService { Task<IEnumerable<Item>> GetAllItemsAsync(int page = 0, int pageSize = 10); Task<Item?> GetItemByIdAsync(long id); Task<Item> CreateItemAsync(Item item); Task<Item> UpdateItemAsync(long id, Item item); Task<bool> DeleteItemAsync(long id); Task<Dictionary<string, object>> GetStatisticsAsync(); }}Create Services/ItemService.cs:
using DotnetApp.Models;using DotnetApp.Repositories;using Microsoft.Extensions.Caching.Distributed;
namespace DotnetApp.Services{ public class ItemService : IItemService { private readonly IItemRepository _repository; private readonly IDistributedCache _cache; private readonly ILogger<ItemService> _logger;
public ItemService(IItemRepository repository, IDistributedCache cache, ILogger<ItemService> logger) { _repository = repository; _cache = cache; _logger = logger; }
public async Task<IEnumerable<Item>> GetAllItemsAsync(int page = 0, int pageSize = 10) { _logger.LogInformation("Fetching all items (page: {Page}, pageSize: {PageSize})", page, pageSize); return await _repository.GetAllAsync(page, pageSize); }
public async Task<Item?> GetItemByIdAsync(long id) { _logger.LogInformation("Fetching item with id: {Id}", id); return await _repository.GetByIdAsync(id); }
public async Task<Item> CreateItemAsync(Item item) { _logger.LogInformation("Creating item: {Name}", item.Name); var createdItem = await _repository.CreateAsync(item); await InvalidateCacheAsync(); return createdItem; }
public async Task<Item> UpdateItemAsync(long id, Item item) { _logger.LogInformation("Updating item with id: {Id}", id); var existingItem = await _repository.GetByIdAsync(id); if (existingItem == null) throw new KeyNotFoundException($"Item with id {id} not found");
existingItem.Name = item.Name; existingItem.Description = item.Description; existingItem.Price = item.Price;
var updatedItem = await _repository.UpdateAsync(existingItem); await InvalidateCacheAsync(); return updatedItem; }
public async Task<bool> DeleteItemAsync(long id) { _logger.LogInformation("Deleting item with id: {Id}", id); var result = await _repository.DeleteAsync(id); if (result) await InvalidateCacheAsync(); return result; }
public async Task<Dictionary<string, object>> GetStatisticsAsync() { var count = await _repository.GetCountAsync(); return new Dictionary<string, object> { { "total_items", count }, { "timestamp", DateTime.UtcNow } }; }
private async Task InvalidateCacheAsync() { await _cache.RemoveAsync("items_cache"); } }}Step 7: Create REST API Controller
Create Controllers/ItemsController.cs:
using DotnetApp.Models;using DotnetApp.Services;using Microsoft.AspNetCore.Mvc;
namespace DotnetApp.Controllers{ [ApiController] [Route("api/[controller]")] public class ItemsController : ControllerBase { private readonly IItemService _itemService; private readonly ILogger<ItemsController> _logger;
public ItemsController(IItemService itemService, ILogger<ItemsController> logger) { _itemService = itemService; _logger = logger; }
[HttpGet] public async Task<ActionResult<IEnumerable<Item>>> GetItems( [FromQuery] int page = 0, [FromQuery] int pageSize = 10) { _logger.LogInformation("Getting items (page: {Page})", page); var items = await _itemService.GetAllItemsAsync(page, pageSize); return Ok(items); }
[HttpGet("{id}")] public async Task<ActionResult<Item>> GetItem(long id) { _logger.LogInformation("Getting item {Id}", id); var item = await _itemService.GetItemByIdAsync(id);
if (item == null) return NotFound();
return Ok(item); }
[HttpPost] public async Task<ActionResult<Item>> PostItem(Item item) { _logger.LogInformation("Creating new item: {Name}", item.Name); var createdItem = await _itemService.CreateItemAsync(item); return CreatedAtAction(nameof(GetItem), new { id = createdItem.Id }, createdItem); }
[HttpPut("{id}")] public async Task<IActionResult> PutItem(long id, Item item) { _logger.LogInformation("Updating item {Id}", id); try { var updatedItem = await _itemService.UpdateItemAsync(id, item); return Ok(updatedItem); } catch (KeyNotFoundException) { return NotFound(); } }
[HttpDelete("{id}")] public async Task<IActionResult> DeleteItem(long id) { _logger.LogInformation("Deleting item {Id}", id); var result = await _itemService.DeleteItemAsync(id);
if (!result) return NotFound();
return NoContent(); }
[HttpGet("stats")] public async Task<ActionResult<Dictionary<string, object>>> GetStatistics() { _logger.LogInformation("Getting statistics"); var stats = await _itemService.GetStatisticsAsync(); return Ok(stats); } }}Step 8: Configure Application Settings
Update appsettings.json:
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.EntityFrameworkCore": "Information" } }, "ConnectionStrings": { "DefaultConnection": "Host=localhost;Port=5432;Database=dotnet_app;Username=postgres;Password=postgres", "Redis": "localhost:6379" }, "AllowedHosts": "*"}Create appsettings.Production.json:
{ "Logging": { "LogLevel": { "Default": "Warning", "Microsoft": "Warning" } }, "ConnectionStrings": { "DefaultConnection": "Server={DATABASE_URL}", "Redis": "{REDIS_URL}" }}Step 9: Add Required NuGet Packages
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQLdotnet add package Microsoft.EntityFrameworkCore.Toolsdotnet add package StackExchange.Redisdotnet add package Microsoft.Extensions.Caching.StackExchangeRedisStep 10: Build and Test Locally
# Restore dependenciesdotnet restore
# Build the applicationdotnet build
# Create database (requires PostgreSQL running locally)dotnet ef database update
# Run the applicationdotnet run
# Access health endpointcurl http://localhost:5000/healthDeploying Without a Dockerfile
Klutch.sh uses Nixpacks to automatically detect and build your .NET application from your source code.
Prepare Your Repository
- Initialize a Git repository and commit your code:
git initgit add .git commit -m "Initial .NET app commit"- Create a
.gitignorefile:
bin/obj/*.csproj.user.vs/.vscode/*.db*.sqliteappsettings.*.json!appsettings.Development.json!appsettings.Production.jsonlogs/*.log-
Ensure your project structure includes:
.csprojfile in the root directoryProgram.cswith proper configurationappsettings.jsonfor configuration
-
Push to GitHub:
git remote add origin https://github.com/YOUR_USERNAME/dotnet-app.gitgit branch -M maingit push -u origin mainDeploy to Klutch.sh
-
Log in to Klutch.sh dashboard.
-
Click “Create a new project” and provide a project name.
-
Inside your project, click “Create a new app”.
-
Repository Configuration:
- Select your GitHub repository containing the .NET app
- Select the branch to deploy (typically
main)
-
Traffic Settings:
- Select “HTTP” as the traffic type
-
Port Configuration:
- Set the internal port to 8080 (Nixpacks defaults to port 8080 for .NET apps)
-
Environment Variables: Set the following environment variables in the Klutch.sh dashboard:
ASPNETCORE_ENVIRONMENT: Set toProductionfor production deploymentsConnectionStrings__DefaultConnection: Your PostgreSQL connection stringConnectionStrings__Redis: Your Redis connection URL (if using Redis)ASPNETCORE_URLS:http://+:8080(sets the binding address)
-
Build and Start Commands (Optional): If you need to customize the build or start command, set these environment variables:
BUILD_COMMAND: Default runsdotnet publish -c Release -o /outSTART_COMMAND: Default is/out/DotnetApp(replace with your project name)
-
Region, Compute, and Instances:
- Choose your desired region for optimal latency
- Select compute resources (Starter for prototypes, Pro/Premium for production)
- Set the number of instances (start with 1-2, scale as needed based on traffic)
-
Click “Create” to deploy. Klutch.sh will automatically build your application using Nixpacks and deploy it.
-
Once deployment completes, your app will be accessible at
example-app.klutch.sh.
Verifying the Deployment
Test your deployed app:
curl https://example-app.klutch.sh/healthYou should receive:
{ "status": "healthy", "service": "dotnet-app"}Access Swagger UI:
https://example-app.klutch.sh/swagger/index.htmlDeploying With a Dockerfile
If you prefer more control over your build environment, you can provide a custom Dockerfile. Klutch.sh automatically detects and uses a Dockerfile in your repository’s root directory.
Create a Multi-Stage Dockerfile
Create a Dockerfile in your project root:
# Build stageFROM mcr.microsoft.com/dotnet/sdk:8.0 as builder
WORKDIR /src
# Copy project fileCOPY ["DotnetApp.csproj", "./"]
# Restore dependenciesRUN dotnet restore "DotnetApp.csproj"
# Copy source codeCOPY . .
# Build applicationRUN dotnet publish -c Release -o /app/publish
# Runtime stageFROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
# Install curl for health checksRUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
# Copy published app from builderCOPY --from=builder /app/publish .
# Create non-root user for securityRUN useradd -m -u 1000 dotnetapp && chown -R dotnetapp:dotnetapp /appUSER dotnetapp
# Health checkHEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ CMD curl -f http://localhost:8080/health || exit 1
# Expose portEXPOSE 8080
# Set environment variablesENV ASPNETCORE_URLS=http://+:8080ENV ASPNETCORE_ENVIRONMENT=Production
# Start the applicationENTRYPOINT ["dotnet", "DotnetApp.dll"]Deploy the Dockerfile Version
- Push your code with the Dockerfile to GitHub:
git add Dockerfilegit commit -m "Add Dockerfile for custom build"git push-
Log in to Klutch.sh dashboard.
-
- Select your GitHub repository and branch
- Set traffic type to “HTTP”
- Set the internal port to 8080
- Add environment variables
- Click “Create”
-
Klutch.sh will automatically detect your Dockerfile and use it for building and deployment.
Database Configuration
PostgreSQL Setup
PostgreSQL is the recommended database for .NET applications. To use a PostgreSQL instance:
- Deploy a PostgreSQL instance on Klutch.sh (from the marketplace)
- Get the connection details from the PostgreSQL dashboard
- Set the ConnectionStrings__DefaultConnection environment variable:
Server=postgres-host;Port=5432;Database=dotnet_app;User Id=postgres;Password=your_password;
Database Migrations
Create migrations with Entity Framework Core:
# Create a new migrationdotnet ef migrations add InitialCreate
# Apply migrations to production databasedotnet ef database updateOr execute migrations programmatically in Program.cs:
using (var scope = app.Services.CreateScope()){ var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); dbContext.Database.Migrate();}Authentication and Authorization
Identity Configuration
Add authentication to Program.cs:
builder.Services.AddIdentityApiEndpoints<IdentityUser>() .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddAuthorization();
var app = builder.Build();
// Map identity endpointsapp.MapIdentityApi<IdentityUser>();
app.MapControllers();app.Run();Protecting Endpoints
Use the [Authorize] attribute on controllers:
[ApiController][Route("api/[controller]")][Authorize]public class ItemsController : ControllerBase{ // Protected endpoints}Caching Configuration
Redis Integration
Configure Redis in Program.cs:
builder.Services.AddStackExchangeRedisCache(options =>{ var redisUrl = builder.Configuration.GetConnectionString("Redis") ?? "localhost:6379"; options.Configuration = redisUrl;});Use caching in services:
public async Task<IEnumerable<Item>> GetAllItemsAsync(int page, int pageSize){ var cacheKey = $"items_page_{page}_{pageSize}";
if (!await _cache.GetAsync(cacheKey, out byte[] cachedData)) { var items = await _repository.GetAllAsync(page, pageSize); await _cache.SetAsync(cacheKey, JsonSerializer.SerializeToUtf8Bytes(items), new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1) }); return items; }
return JsonSerializer.Deserialize<IEnumerable<Item>>(cachedData) ?? new List<Item>();}Logging and Monitoring
Structured Logging
Configure Serilog for structured logging in Program.cs:
builder.Logging.ClearProviders();builder.Logging .AddConsole() .AddDebug();
var logger = builder.Services.BuildServiceProvider() .GetRequiredService<ILogger<Program>>();logger.LogInformation("Application starting...");Use logging in services:
_logger.LogInformation("Processing request for item {ItemId}", itemId);_logger.LogError(exception, "Error processing request");_logger.LogDebug("Cache hit for key {CacheKey}", cacheKey);Health Checks
Add health checks to Program.cs:
builder.Services.AddHealthChecks() .AddDbContextCheck<ApplicationDbContext>() .AddRedis(builder.Configuration.GetConnectionString("Redis") ?? "localhost:6379");
app.MapHealthChecks("/health");Persistent Storage for Logs and Data
Adding Persistent Volume
- In the Klutch.sh app dashboard, navigate to “Persistent Storage” or “Volumes”
- Click “Add Volume”
- Set the mount path:
/app/logs(for application logs) or/app/data(for data files) - Set the size based on your needs (e.g., 10 GB for logs, 20 GB for data)
- Save and redeploy
Logging to Persistent Storage
Configure logging file path:
{ "Logging": { "LogLevel": { "Default": "Information" }, "File": { "Path": "/app/logs/dotnet-app.log" } }}Custom Domains
To serve your .NET application from a custom domain:
- In the Klutch.sh app dashboard, navigate to “Custom Domains”
- Click “Add Custom Domain”
- Enter your domain (e.g.,
api.example.com) - Follow the DNS configuration instructions provided
Example DNS configuration:
api.example.com CNAME example-app.klutch.shTroubleshooting
Issue 1: Port Already in Use
Problem: Application fails to start with “port 8080 already in use” error.
Solution:
- Ensure
ASPNETCORE_URLSenvironment variable is set correctly - Verify no other application is using port 8080
- Check for multiple app instances consuming the same port
- Use
netstatto find which process is using the port
Issue 2: Database Connection Failures
Problem: “Unable to connect to database” errors on startup.
Solution:
- Verify PostgreSQL connection string in environment variables
- Test connection string format:
Server=host;Port=5432;Database=name;User Id=user;Password=pass; - Ensure database user has proper permissions
- Check firewall rules allow the connection
- Verify PostgreSQL instance is running and accessible
Issue 3: Application Crashes on Startup
Problem: Application exits immediately after starting.
Solution:
- Check application logs for errors
- Verify all environment variables are set correctly
- Ensure database migrations have been applied
- Check that
appsettings.Production.jsonis valid JSON - Review startup exceptions in logs
Issue 4: Out of Memory Errors
Problem: Application crashes with OutOfMemoryException.
Solution:
- Increase instance compute resources
- Optimize database queries and add indexing
- Implement caching for frequently accessed data
- Configure connection pool limits
- Monitor memory usage via dashboard metrics
Issue 5: Slow Application Response
Problem: API endpoints respond slowly.
Solution:
- Add database query optimization and indexes
- Enable response compression in application
- Implement Redis caching for frequently accessed data
- Use async/await patterns in all I/O operations
- Monitor performance metrics in dashboard
- Consider scaling to additional instances
Best Practices for Production Deployment
-
Use Dependency Injection: Leverage built-in DI container
builder.Services.AddScoped<IItemRepository, ItemRepository>(); -
Implement Async/Await: Always use async operations
public async Task<Item> GetItemAsync(long id) => await _repository.GetByIdAsync(id); -
Configure Logging: Use structured logging with appropriate levels
_logger.LogInformation("Item created: {@Item}", item); -
Database Connection Pooling: Configure optimal pool size
optionsBuilder.UseNpgsql(connectionString, options =>options.MaxPoolSize(20).MinPoolSize(5)); -
Enable HTTPS: Enforce HTTPS in production
if (!app.Environment.IsDevelopment())app.UseHttpsRedirection(); -
Input Validation: Validate all user inputs
[Required][StringLength(100)]public string Name { get; set; } -
Error Handling: Implement global exception handling
app.UseExceptionHandler("/error"); -
Security Headers: Add security headers middleware
app.Use(async (context, next) =>{context.Response.Headers.Add("X-Content-Type-Options", "nosniff");await next();}); -
Graceful Shutdown: Configure shutdown timeout
var cts = new CancellationTokenSource();app.Lifetime.ApplicationStopping.Register(() => cts.Cancel()); -
Rate Limiting: Implement rate limiting for APIs
builder.Services.AddRateLimiter(options =>options.AddFixedWindowLimiter("fixed", policy =>policy.PermitLimit = 100;
Resources
- .NET Official Documentation
- ASP.NET Core Documentation
- Entity Framework Core Documentation
- Building REST APIs with ASP.NET Core
- Security in ASP.NET Core
- Dependency Injection in ASP.NET Core
- C# Language Documentation
Conclusion
Deploying .NET applications to Klutch.sh provides a robust, scalable platform for building and running modern web applications and APIs. .NET’s cross-platform support, performance, and comprehensive ecosystem make it ideal for enterprise-grade applications.
Key takeaways:
- Use Nixpacks for quick deployments with automatic .NET detection
- Use Docker for complete control over dependencies and runtime
- Leverage Entity Framework Core for efficient data persistence
- Implement proper logging and monitoring with structured logging
- Use the built-in dependency injection container for loosely coupled code
- Enable caching strategies for improved performance
- Implement authentication and authorization with Identity
- Configure persistent storage for logs and application data
- Monitor application health via health check endpoints
- Keep dependencies updated for security patches
For additional help, refer to the .NET documentation or Klutch.sh support resources.