
debug:dotnet
Debug ASP.NET Core and .NET applications with systematic diagnostic approaches. This skill covers troubleshooting dependency injection container errors, middleware pipeline issues, Entity Framework Core query problems, configuration binding failures, authentication/authorization issues, and startup failures. Includes Visual Studio and VS Code debugging, dotnet-trace, dotnet-dump, dotnet-counters tools, Serilog configuration, Application Insights integration, and four-phase debugging methodology.
Debug ASP.NET Core and .NET applications with systematic diagnostic approaches. This skill covers troubleshooting dependency injection container errors, middleware pipeline issues, Entity Framework Core query problems, configuration binding failures, authentication/authorization issues, and startup failures. Includes Visual Studio and VS Code debugging, dotnet-trace, dotnet-dump, dotnet-counters tools, Serilog configuration, Application Insights integration, and four-phase debugging methodology.
ASP.NET Core Debugging Guide
This guide provides a systematic approach to debugging ASP.NET Core applications, covering common error patterns, diagnostic tools, and resolution strategies.
Common Error Patterns
1. Dependency Injection (DI) Container Errors
Symptoms:
InvalidOperationException: Unable to resolve service for type 'X'InvalidOperationException: A circular dependency was detectedObjectDisposedException: Cannot access a disposed object
Common Causes:
// Missing service registration
public class MyController
{
public MyController(IMyService service) { } // Not registered in DI
}
// Circular dependency
public class ServiceA
{
public ServiceA(ServiceB b) { }
}
public class ServiceB
{
public ServiceB(ServiceA a) { } // Circular!
}
// Captive dependency (singleton holding scoped)
services.AddSingleton<ISingletonService, SingletonService>();
services.AddScoped<IScopedService, ScopedService>(); // Captured by singleton!
Debugging Steps:
- Check
Program.csorStartup.csfor service registration - Verify service lifetime compatibility (Singleton > Scoped > Transient)
- Use
IServiceProvider.GetRequiredService<T>()for explicit resolution - Enable detailed DI errors in development:
builder.Host.UseDefaultServiceProvider(options =>
{
options.ValidateScopes = true;
options.ValidateOnBuild = true;
});
Resolution Patterns:
// Register missing service
builder.Services.AddScoped<IMyService, MyService>();
// Break circular dependency with Lazy<T> or factory
builder.Services.AddScoped<ServiceA>();
builder.Services.AddScoped<ServiceB>(sp =>
new ServiceB(() => sp.GetRequiredService<ServiceA>()));
// Fix captive dependency - make both singleton or use factory
builder.Services.AddSingleton<IScopedService>(sp =>
sp.CreateScope().ServiceProvider.GetRequiredService<ScopedService>());
2. Middleware Pipeline Issues
Symptoms:
- Requests returning unexpected status codes
- Authentication/authorization not working
- CORS errors
- Request body already read exceptions
- Response already started exceptions
Common Causes:
// Wrong middleware order
app.UseAuthorization(); // Must come AFTER UseAuthentication!
app.UseAuthentication();
// Missing middleware
app.UseRouting();
// Missing UseAuthentication() and UseAuthorization()
app.MapControllers();
// Response already started
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("Hello"); // Response started
await next(); // Next middleware tries to modify headers - ERROR
});
Debugging Steps:
- Add diagnostic middleware to trace pipeline:
app.Use(async (context, next) =>
{
Console.WriteLine($"Request: {context.Request.Path}");
await next();
Console.WriteLine($"Response: {context.Response.StatusCode}");
});
- Check middleware order matches recommended sequence
- Enable developer exception page in development
- Check for
Response.HasStartedbefore modifying response
Correct Middleware Order:
app.UseExceptionHandler("/error");
app.UseHsts();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.MapControllers();
3. Entity Framework Core Query Problems
Symptoms:
InvalidOperationException: The instance of entity type 'X' cannot be tracked- N+1 query problems (excessive database queries)
DbUpdateConcurrencyException- Lazy loading returning null
- Query performance issues
Common Causes:
// N+1 problem
var orders = context.Orders.ToList();
foreach (var order in orders)
{
Console.WriteLine(order.Customer.Name); // Each access = new query
}
// Tracking conflict
var entity = context.Products.Find(1);
context.Products.Update(new Product { Id = 1 }); // Already tracked!
// Missing Include for related data
var order = context.Orders.First(); // Customer is null!
Debugging Steps:
- Enable sensitive data logging:
optionsBuilder
.EnableSensitiveDataLogging()
.EnableDetailedErrors()
.LogTo(Console.WriteLine, LogLevel.Information);
- Use SQL Server Profiler or EF Core logging to inspect queries
- Check for
AsNoTracking()usage on read-only queries - Verify
Include()statements for related entities
Resolution Patterns:
// Eager loading to prevent N+1
var orders = context.Orders
.Include(o => o.Customer)
.Include(o => o.OrderItems)
.ToList();
// Use AsNoTracking for read-only queries
var products = context.Products
.AsNoTracking()
.Where(p => p.Price > 100)
.ToList();
// Handle concurrency with retry
try
{
await context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException ex)
{
var entry = ex.Entries.Single();
await entry.ReloadAsync();
// Retry or merge changes
}
// Split queries for complex includes
var orders = context.Orders
.Include(o => o.OrderItems)
.AsSplitQuery()
.ToList();
4. Configuration Binding Failures
Symptoms:
- Configuration values are null or default
InvalidOperationExceptionwhen binding to options- Environment-specific settings not loading
- Secrets not being read
Common Causes:
// Mismatched property names
public class MyOptions
{
public string ConnectionString { get; set; } // But appsettings has "connectionString"
}
// Missing section
services.Configure<MyOptions>(config.GetSection("NonExistent"));
// Wrong environment file
// appsettings.Development.json not loading in Development
Debugging Steps:
- Log all configuration sources:
foreach (var source in builder.Configuration.Sources)
{
Console.WriteLine(source.GetType().Name);
}
- Dump effective configuration:
Console.WriteLine(builder.Configuration.GetDebugView());
- Verify
ASPNETCORE_ENVIRONMENTis set correctly - Check file names match exactly (case-sensitive on Linux)
Resolution Patterns:
// Validate options on startup
services.AddOptions<MyOptions>()
.Bind(config.GetSection("MyOptions"))
.ValidateDataAnnotations()
.ValidateOnStart();
// Use strongly-typed configuration
public class MyOptions
{
public const string SectionName = "MySettings";
[Required]
public string ApiKey { get; set; } = string.Empty;
[Range(1, 100)]
public int MaxRetries { get; set; } = 3;
}
// Load with validation
builder.Services.AddOptions<MyOptions>()
.BindConfiguration(MyOptions.SectionName)
.ValidateDataAnnotations()
.ValidateOnStart();
5. Authentication/Authorization Issues
Symptoms:
- 401 Unauthorized when credentials are correct
- 403 Forbidden with correct roles
- JWT token validation failures
- Cookie authentication not persisting
- Claims not available in controllers
Common Causes:
// Missing authentication scheme
[Authorize] // No scheme specified, uses default
public class MyController { }
// Wrong claim type for roles
options.TokenValidationParameters = new TokenValidationParameters
{
RoleClaimType = "role", // But token has "roles"
};
// Cookie not being sent (SameSite issues)
options.Cookie.SameSite = SameSiteMode.Strict; // Blocks cross-site
Debugging Steps:
- Log authentication events:
services.AddAuthentication()
.AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
Console.WriteLine($"Auth failed: {context.Exception}");
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
Console.WriteLine($"Token validated for: {context.Principal?.Identity?.Name}");
return Task.CompletedTask;
}
};
});
- Inspect JWT tokens at jwt.io
- Check claims in controller:
User.Claims.Select(c => $"{c.Type}: {c.Value}") - Verify HTTPS is configured correctly for secure cookies
Resolution Patterns:
// Configure JWT properly
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "your-issuer",
ValidateAudience = true,
ValidAudience = "your-audience",
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes("your-secret-key-at-least-32-chars")),
RoleClaimType = ClaimTypes.Role,
NameClaimType = ClaimTypes.Name
};
});
// Configure CORS for cookie auth
services.AddCors(options =>
{
options.AddPolicy("AllowFrontend", builder =>
builder.WithOrigins("https://frontend.com")
.AllowCredentials()
.AllowAnyHeader()
.AllowAnyMethod());
});
6. Startup and Hosting Failures
Symptoms:
- 502.5 Process Failure on IIS/Azure
- Application fails to start with no clear error
HostAbortedExceptionduring startup- Port already in use errors
Common Causes:
// Missing required service
var myService = app.Services.GetRequiredService<IMyService>(); // Throws on startup
// Database not available during startup
await context.Database.MigrateAsync(); // DB not ready
// Incorrect program entry
public static void Main(string[] args) { } // Missing async/await
Debugging Steps:
- Enable stdout logging for IIS:
<!-- web.config -->
<aspNetCore processPath="dotnet" stdoutLogEnabled="true" stdoutLogFile=".\logs\stdout" />
- Check Windows Event Viewer for .NET Runtime errors
- Run application directly from command line:
dotnet MyApp.dll --urls "http://localhost:5000"
- Use
ASPNETCORE_DETAILEDERRORS=trueenvironment variable
Resolution Patterns:
// Graceful startup with error handling
try
{
var builder = WebApplication.CreateBuilder(args);
// Configure services
var app = builder.Build();
// Configure pipeline
await app.RunAsync();
}
catch (Exception ex)
{
Log.Fatal(ex, "Application terminated unexpectedly");
}
finally
{
Log.CloseAndFlush();
}
// Handle database not ready
services.AddDbContext<AppDbContext>(options =>
{
options.UseSqlServer(connectionString, sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 5,
maxRetryDelay: TimeSpan.FromSeconds(30),
errorNumbersToAdd: null);
});
});
Debugging Tools
Visual Studio Debugger
Essential Features:
- Breakpoints: Conditional, hit count, filter by thread
- Exception Settings: Break on specific exceptions (Debug > Windows > Exception Settings)
- Immediate Window: Evaluate expressions during debugging
- Diagnostic Tools: CPU, memory, events timeline
- Hot Reload: Edit code during debugging (supported operations)
Advanced Techniques:
// Conditional breakpoint expression
User.IsAuthenticated && Request.Path.StartsWithSegments("/api")
// Tracepoint (log without stopping)
// Right-click breakpoint > Actions > Log a message
"Request to {Request.Path} at {DateTime.Now}"
// DebuggerDisplay attribute for better visualization
[DebuggerDisplay("Order {Id}: {Customer.Name} - ${Total}")]
public class Order { }
Visual Studio Code Debugging
launch.json Configuration:
{
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (web)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/bin/Debug/net8.0/MyApp.dll",
"args": [],
"cwd": "${workspaceFolder}",
"stopAtEntry": false,
"env": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_URLS": "https://localhost:5001"
}
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach",
"processId": "${command:pickProcess}"
}
]
}
dotnet-trace
Performance tracing and diagnostics:
# Install
dotnet tool install --global dotnet-trace
# Collect trace from running process
dotnet-trace collect --process-id <PID>
# Collect with specific providers
dotnet-trace collect -p <PID> --providers Microsoft-Extensions-Logging
# Collect CPU profile
dotnet-trace collect -p <PID> --profile cpu-sampling
# Convert to speedscope format for visualization
dotnet-trace convert trace.nettrace --format speedscope
dotnet-dump
Memory dump analysis:
# Install
dotnet tool install --global dotnet-dump
# Collect dump from running process
dotnet-dump collect -p <PID>
# Analyze dump
dotnet-dump analyze dump.dmp
# Common SOS commands in analyze mode
> dumpheap -stat # Heap statistics
> dumpheap -type MyClass # Find specific type instances
> gcroot <address> # Find GC roots for object
> threadpool # Thread pool info
> eestack # Managed stack traces
dotnet-counters
Real-time performance monitoring:
# Install
dotnet tool install --global dotnet-counters
# Monitor default counters
dotnet-counters monitor -p <PID>
# Monitor specific counters
dotnet-counters monitor -p <PID> --counters \
System.Runtime,\
Microsoft.AspNetCore.Hosting,\
Microsoft-AspNetCore-Server-Kestrel
# Export to CSV
dotnet-counters collect -p <PID> --format csv -o counters.csv
ILogger and Logging Frameworks
Built-in Logging Configuration:
// Program.cs
builder.Logging
.ClearProviders()
.AddConsole()
.AddDebug()
.SetMinimumLevel(LogLevel.Debug);
// Filter by category
builder.Logging.AddFilter("Microsoft.EntityFrameworkCore", LogLevel.Warning);
builder.Logging.AddFilter("MyApp", LogLevel.Debug);
Structured Logging with Serilog:
// Install: Serilog.AspNetCore
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.Enrich.FromLogContext()
.WriteTo.Console(outputTemplate:
"[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}")
.WriteTo.File("logs/app-.log",
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: 7)
.WriteTo.Seq("http://localhost:5341") // Centralized logging
.CreateLogger();
builder.Host.UseSerilog();
Logging Best Practices:
public class OrderService
{
private readonly ILogger<OrderService> _logger;
public async Task<Order> ProcessOrderAsync(int orderId)
{
// Use structured logging with meaningful context
using (_logger.BeginScope(new Dictionary<string, object>
{
["OrderId"] = orderId,
["CorrelationId"] = Activity.Current?.Id
}))
{
_logger.LogInformation("Processing order {OrderId}", orderId);
try
{
var order = await GetOrderAsync(orderId);
_logger.LogDebug("Order details: {@Order}", order);
return order;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to process order {OrderId}", orderId);
throw;
}
}
}
}
Application Insights
Setup and Configuration:
// Install: Microsoft.ApplicationInsights.AspNetCore
builder.Services.AddApplicationInsightsTelemetry();
// Configure in appsettings.json
{
"ApplicationInsights": {
"ConnectionString": "InstrumentationKey=xxx;IngestionEndpoint=xxx"
}
}
Custom Telemetry:
public class PaymentService
{
private readonly TelemetryClient _telemetry;
public async Task ProcessPaymentAsync(Payment payment)
{
using var operation = _telemetry.StartOperation<RequestTelemetry>("ProcessPayment");
_telemetry.TrackEvent("PaymentStarted", new Dictionary<string, string>
{
["PaymentId"] = payment.Id.ToString(),
["Amount"] = payment.Amount.ToString("C")
});
try
{
// Process payment
_telemetry.TrackMetric("PaymentAmount", (double)payment.Amount);
}
catch (Exception ex)
{
_telemetry.TrackException(ex);
operation.Telemetry.Success = false;
throw;
}
}
}
The Four Phases of ASP.NET Core Debugging
Phase 1: Reproduce and Isolate
Goals: Consistently reproduce the issue and narrow down the scope.
Actions:
- Capture exact steps to reproduce
- Note environment details (OS, .NET version, configuration)
- Check if issue occurs in Development vs Production
- Isolate to specific endpoint, service, or component
Commands:
# Check .NET version and environment
dotnet --info
# Verify configuration
dotnet run --environment Development
# Test specific endpoint
curl -v https://localhost:5001/api/health
# Check if issue is environment-specific
ASPNETCORE_ENVIRONMENT=Development dotnet run
ASPNETCORE_ENVIRONMENT=Production dotnet run
Phase 2: Gather Evidence
Goals: Collect logs, traces, and diagnostic data.
Actions:
- Enable verbose logging
- Capture request/response details
- Monitor performance counters
- Collect memory dumps if needed
Configuration for Maximum Diagnostics:
// appsettings.Development.json
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft.AspNetCore": "Debug",
"Microsoft.EntityFrameworkCore": "Debug",
"Microsoft.EntityFrameworkCore.Database.Command": "Information"
}
}
}
Middleware for Request Logging:
app.Use(async (context, next) =>
{
var logger = context.RequestServices.GetRequiredService<ILogger<Program>>();
logger.LogDebug("Request: {Method} {Path} {Query}",
context.Request.Method,
context.Request.Path,
context.Request.QueryString);
// Capture request body (careful with large bodies)
context.Request.EnableBuffering();
var stopwatch = Stopwatch.StartNew();
await next();
stopwatch.Stop();
logger.LogDebug("Response: {StatusCode} in {ElapsedMs}ms",
context.Response.StatusCode,
stopwatch.ElapsedMilliseconds);
});
Phase 3: Analyze and Hypothesize
Goals: Form theories based on evidence and test them.
Common Analysis Patterns:
For DI Errors:
// List all registered services
foreach (var service in builder.Services)
{
Console.WriteLine($"{service.ServiceType.Name} -> {service.ImplementationType?.Name} ({service.Lifetime})");
}
For EF Core Issues:
// Log generated SQL
optionsBuilder.LogTo(
message => Debug.WriteLine(message),
new[] { DbLoggerCategory.Database.Command.Name },
LogLevel.Information);
For Authentication Issues:
// Dump all claims
app.Use(async (context, next) =>
{
if (context.User.Identity?.IsAuthenticated == true)
{
foreach (var claim in context.User.Claims)
{
Console.WriteLine($"Claim: {claim.Type} = {claim.Value}");
}
}
await next();
});
Phase 4: Fix and Verify
Goals: Implement fix with confidence and prevent regression.
Best Practices:
- Write a failing test that reproduces the bug
- Implement the minimal fix
- Verify the test passes
- Check for side effects
- Add logging/monitoring for the fixed code path
Example Test-Driven Fix:
[Fact]
public async Task ProcessOrder_WhenCustomerNotFound_ThrowsNotFoundException()
{
// Arrange - This test should fail before fix
var mockRepo = new Mock<ICustomerRepository>();
mockRepo.Setup(r => r.GetByIdAsync(It.IsAny<int>()))
.ReturnsAsync((Customer)null);
var service = new OrderService(mockRepo.Object);
// Act & Assert
await Assert.ThrowsAsync<CustomerNotFoundException>(
() => service.ProcessOrderAsync(1, customerId: 999));
}
Quick Reference Commands
Build and Run
# Build project
dotnet build
# Build in Release mode
dotnet build -c Release
# Run with hot reload
dotnet watch run
# Run specific project
dotnet run --project src/MyApp/MyApp.csproj
# Run with specific environment
ASPNETCORE_ENVIRONMENT=Development dotnet run
# Publish for deployment
dotnet publish -c Release -o ./publish
Entity Framework Core
# List migrations
dotnet ef migrations list
# Add new migration
dotnet ef migrations add AddUserTable
# Update database
dotnet ef database update
# Generate SQL script
dotnet ef migrations script --idempotent
# Remove last migration
dotnet ef migrations remove
# Drop database
dotnet ef database drop --force
# Scaffold from existing database
dotnet ef dbcontext scaffold "Server=.;Database=MyDb;Trusted_Connection=True" Microsoft.EntityFrameworkCore.SqlServer
Testing
# Run all tests
dotnet test
# Run with verbosity
dotnet test -v detailed
# Run specific test
dotnet test --filter "FullyQualifiedName~OrderServiceTests"
# Run with coverage
dotnet test --collect:"XPlat Code Coverage"
# Generate coverage report
reportgenerator -reports:coverage.cobertura.xml -targetdir:coveragereport
Diagnostics
# List running .NET processes
dotnet-trace ps
# Collect trace
dotnet-trace collect -p <PID> --duration 00:00:30
# Monitor counters
dotnet-counters monitor -p <PID>
# Collect memory dump
dotnet-dump collect -p <PID>
# Analyze dump
dotnet-dump analyze core_dump.dmp
# GC dump
dotnet-gcdump collect -p <PID>
Package Management
# Add package
dotnet add package Serilog.AspNetCore
# Remove package
dotnet remove package OldPackage
# List packages
dotnet list package
# List outdated packages
dotnet list package --outdated
# Restore packages
dotnet restore
# Clear NuGet cache
dotnet nuget locals all --clear
Secrets Management
# Initialize user secrets
dotnet user-secrets init
# Set a secret
dotnet user-secrets set "ConnectionStrings:Default" "Server=..."
# List secrets
dotnet user-secrets list
# Remove a secret
dotnet user-secrets remove "ConnectionStrings:Default"
# Clear all secrets
dotnet user-secrets clear
Troubleshooting Checklist
Before Debugging
- [ ] Reproduce the issue consistently
- [ ] Check if issue occurs in Development mode
- [ ] Verify .NET SDK/runtime version matches
- [ ] Confirm database is accessible
- [ ] Check environment variables are set
During Debugging
- [ ] Enable detailed error pages (
app.UseDeveloperExceptionPage()) - [ ] Set logging to Debug level
- [ ] Check for null reference exceptions
- [ ] Verify DI service registrations
- [ ] Inspect middleware pipeline order
- [ ] Check EF Core generated SQL
- [ ] Verify authentication claims
After Fixing
- [ ] Write test to prevent regression
- [ ] Update logging for the fixed code path
- [ ] Document the issue and solution
- [ ] Check for similar issues elsewhere
- [ ] Consider adding health checks
Resources
You Might Also Like
Related Skills

fix
Use when you have lint errors, formatting issues, or before committing code to ensure it passes CI.
facebook
frontend-testing
Generate Vitest + React Testing Library tests for Dify frontend components, hooks, and utilities. Triggers on testing, spec files, coverage, Vitest, RTL, unit tests, integration tests, or write/review test requests.
langgenius
frontend-code-review
Trigger when the user requests a review of frontend files (e.g., `.tsx`, `.ts`, `.js`). Support both pending-change reviews and focused file reviews while applying the checklist rules.
langgenius
code-reviewer
Use this skill to review code. It supports both local changes (staged or working tree) and remote Pull Requests (by ID or URL). It focuses on correctness, maintainability, and adherence to project standards.
google-gemini
session-logs
Search and analyze your own session logs (older/parent conversations) using jq.
moltbot
