| name | debug:dotnet |
| description | 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 detected
ObjectDisposedException: Cannot access a disposed object
Common Causes:
public class MyController
{
public MyController(IMyService service) { }
}
public class ServiceA
{
public ServiceA(ServiceB b) { }
}
public class ServiceB
{
public ServiceB(ServiceA a) { }
}
services.AddSingleton<ISingletonService, SingletonService>();
services.AddScoped<IScopedService, ScopedService>();
Debugging Steps:
- Check
Program.cs or Startup.cs for 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:
builder.Services.AddScoped<IMyService, MyService>();
builder.Services.AddScoped<ServiceA>();
builder.Services.AddScoped<ServiceB>(sp =>
new ServiceB(() => sp.GetRequiredService<ServiceA>()));
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:
app.UseAuthorization();
app.UseAuthentication();
app.UseRouting();
app.MapControllers();
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("Hello");
await next();
});
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.HasStarted before 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:
var orders = context.Orders.ToList();
foreach (var order in orders)
{
Console.WriteLine(order.Customer.Name);
}
var entity = context.Products.Find(1);
context.Products.Update(new Product { Id = 1 });
var order = context.Orders.First();
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:
var orders = context.Orders
.Include(o => o.Customer)
.Include(o => o.OrderItems)
.ToList();
var products = context.Products
.AsNoTracking()
.Where(p => p.Price > 100)
.ToList();
try
{
await context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException ex)
{
var entry = ex.Entries.Single();
await entry.ReloadAsync();
}
var orders = context.Orders
.Include(o => o.OrderItems)
.AsSplitQuery()
.ToList();
4. Configuration Binding Failures
Symptoms:
- Configuration values are null or default
InvalidOperationException when binding to options
- Environment-specific settings not loading
- Secrets not being read
Common Causes:
public class MyOptions
{
public string ConnectionString { get; set; }
}
services.Configure<MyOptions>(config.GetSection("NonExistent"));
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_ENVIRONMENT is set correctly
- Check file names match exactly (case-sensitive on Linux)
Resolution Patterns:
services.AddOptions<MyOptions>()
.Bind(config.GetSection("MyOptions"))
.ValidateDataAnnotations()
.ValidateOnStart();
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;
}
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:
[Authorize]
public class MyController { }
options.TokenValidationParameters = new TokenValidationParameters
{
RoleClaimType = "role",
};
options.Cookie.SameSite = SameSiteMode.Strict;
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:
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
};
});
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
HostAbortedException during startup
- Port already in use errors
Common Causes:
var myService = app.Services.GetRequiredService<IMyService>();
await context.Database.MigrateAsync();
public static void Main(string[] args) { }
Debugging Steps:
- Enable stdout logging for IIS:
<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=true environment variable
Resolution Patterns:
try
{
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
await app.RunAsync();
}
catch (Exception ex)
{
Log.Fatal(ex, "Application terminated unexpectedly");
}
finally
{
Log.CloseAndFlush();
}
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:
User.IsAuthenticated && Request.Path.StartsWithSegments("/api")
"Request to {Request.Path} at {DateTime.Now}"
[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:
dotnet tool install --global dotnet-trace
dotnet-trace collect --process-id <PID>
dotnet-trace collect -p <PID> --providers Microsoft-Extensions-Logging
dotnet-trace collect -p <PID> --profile cpu-sampling
dotnet-trace convert trace.nettrace --format speedscope
dotnet-dump
Memory dump analysis:
dotnet tool install --global dotnet-dump
dotnet-dump collect -p <PID>
dotnet-dump analyze dump.dmp
> dumpheap -stat
> dumpheap -type MyClass
> gcroot <address>
> threadpool
> eestack
dotnet-counters
Real-time performance monitoring:
dotnet tool install --global dotnet-counters
dotnet-counters monitor -p <PID>
dotnet-counters monitor -p <PID> --counters \
System.Runtime,\
Microsoft.AspNetCore.Hosting,\
Microsoft-AspNetCore-Server-Kestrel
dotnet-counters collect -p <PID> --format csv -o counters.csv
ILogger and Logging Frameworks
Built-in Logging Configuration:
builder.Logging
.ClearProviders()
.AddConsole()
.AddDebug()
.SetMinimumLevel(LogLevel.Debug);
builder.Logging.AddFilter("Microsoft.EntityFrameworkCore", LogLevel.Warning);
builder.Logging.AddFilter("MyApp", LogLevel.Debug);
Structured Logging with Serilog:
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")
.CreateLogger();
builder.Host.UseSerilog();
Logging Best Practices:
public class OrderService
{
private readonly ILogger<OrderService> _logger;
public async Task<Order> ProcessOrderAsync(int orderId)
{
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:
builder.Services.AddApplicationInsightsTelemetry();
{
"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
{
_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:
dotnet --info
dotnet run --environment Development
curl -v https://localhost:5001/api/health
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:
{
"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);
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:
foreach (var service in builder.Services)
{
Console.WriteLine($"{service.ServiceType.Name} -> {service.ImplementationType?.Name} ({service.Lifetime})");
}
For EF Core Issues:
optionsBuilder.LogTo(
message => Debug.WriteLine(message),
new[] { DbLoggerCategory.Database.Command.Name },
LogLevel.Information);
For Authentication Issues:
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()
{
var mockRepo = new Mock<ICustomerRepository>();
mockRepo.Setup(r => r.GetByIdAsync(It.IsAny<int>()))
.ReturnsAsync((Customer)null);
var service = new OrderService(mockRepo.Object);
await Assert.ThrowsAsync<CustomerNotFoundException>(
() => service.ProcessOrderAsync(1, customerId: 999));
}
Quick Reference Commands
Build and Run
dotnet build
dotnet build -c Release
dotnet watch run
dotnet run --project src/MyApp/MyApp.csproj
ASPNETCORE_ENVIRONMENT=Development dotnet run
dotnet publish -c Release -o ./publish
Entity Framework Core
dotnet ef migrations list
dotnet ef migrations add AddUserTable
dotnet ef database update
dotnet ef migrations script --idempotent
dotnet ef migrations remove
dotnet ef database drop --force
dotnet ef dbcontext scaffold "Server=.;Database=MyDb;Trusted_Connection=True" Microsoft.EntityFrameworkCore.SqlServer
Testing
dotnet test
dotnet test -v detailed
dotnet test --filter "FullyQualifiedName~OrderServiceTests"
dotnet test --collect:"XPlat Code Coverage"
reportgenerator -reports:coverage.cobertura.xml -targetdir:coveragereport
Diagnostics
dotnet-trace ps
dotnet-trace collect -p <PID> --duration 00:00:30
dotnet-counters monitor -p <PID>
dotnet-dump collect -p <PID>
dotnet-dump analyze core_dump.dmp
dotnet-gcdump collect -p <PID>
Package Management
dotnet add package Serilog.AspNetCore
dotnet remove package OldPackage
dotnet list package
dotnet list package --outdated
dotnet restore
dotnet nuget locals all --clear
Secrets Management
dotnet user-secrets init
dotnet user-secrets set "ConnectionStrings:Default" "Server=..."
dotnet user-secrets list
dotnet user-secrets remove "ConnectionStrings:Default"
dotnet user-secrets clear
Troubleshooting Checklist
Before Debugging
During Debugging
After Fixing
Resources