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.
Debugs ASP.NET Core applications with systematic diagnostics for DI, middleware, EF Core, configuration, and authentication issues.
/plugin marketplace add SnakeO/claude-debug-and-refactor-skills-plugin/plugin install debug-and-refactor@snakeo-skillsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
This guide provides a systematic approach to debugging ASP.NET Core applications, covering common error patterns, diagnostic tools, and resolution strategies.
Symptoms:
InvalidOperationException: Unable to resolve service for type 'X'InvalidOperationException: A circular dependency was detectedObjectDisposedException: Cannot access a disposed objectCommon 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:
Program.cs or Startup.cs for service registrationIServiceProvider.GetRequiredService<T>() for explicit resolutionbuilder.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>());
Symptoms:
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:
app.Use(async (context, next) =>
{
Console.WriteLine($"Request: {context.Request.Path}");
await next();
Console.WriteLine($"Response: {context.Response.StatusCode}");
});
Response.HasStarted before modifying responseCorrect Middleware Order:
app.UseExceptionHandler("/error");
app.UseHsts();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.MapControllers();
Symptoms:
InvalidOperationException: The instance of entity type 'X' cannot be trackedDbUpdateConcurrencyExceptionCommon 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:
optionsBuilder
.EnableSensitiveDataLogging()
.EnableDetailedErrors()
.LogTo(Console.WriteLine, LogLevel.Information);
AsNoTracking() usage on read-only queriesInclude() statements for related entitiesResolution 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();
Symptoms:
InvalidOperationException when binding to optionsCommon 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:
foreach (var source in builder.Configuration.Sources)
{
Console.WriteLine(source.GetType().Name);
}
Console.WriteLine(builder.Configuration.GetDebugView());
ASPNETCORE_ENVIRONMENT is set correctlyResolution 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();
Symptoms:
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:
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;
}
};
});
User.Claims.Select(c => $"{c.Type}: {c.Value}")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());
});
Symptoms:
HostAbortedException during startupCommon 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:
<!-- web.config -->
<aspNetCore processPath="dotnet" stdoutLogEnabled="true" stdoutLogFile=".\logs\stdout" />
dotnet MyApp.dll --urls "http://localhost:5000"
ASPNETCORE_DETAILEDERRORS=true environment variableResolution 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);
});
});
Essential Features:
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 { }
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}"
}
]
}
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
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
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
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;
}
}
}
}
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;
}
}
}
Goals: Consistently reproduce the issue and narrow down the scope.
Actions:
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
Goals: Collect logs, traces, and diagnostic data.
Actions:
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);
});
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();
});
Goals: Implement fix with confidence and prevent regression.
Best Practices:
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));
}
# 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
# 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
# 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
# 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>
# 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
# 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
app.UseDeveloperExceptionPage())This skill should be used when the user asks about libraries, frameworks, API references, or needs code examples. Activates for setup questions, code generation involving libraries, or mentions of specific frameworks like React, Vue, Next.js, Prisma, Supabase, etc.
Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.