12 KiB
HTTP Troubleshooting
Common HTTP integration issues and solutions.
404 Not Found
Issue: Endpoint not found
Symptoms:
HTTP/1.1 404 Not Found
Possible Causes:
1. Endpoint Not Mapped
Problem:
var app = builder.Build();
// Missing MapSvrntyCommands() or MapSvrntyQueries()
app.Run();
Solution:
var app = builder.Build();
app.MapSvrntyCommands(); // Add this
app.MapSvrntyQueries(); // Add this
app.Run();
2. Wrong URL
Problem:
# Wrong
POST /api/command/CreateUser # Capital C
# Correct
POST /api/command/createUser # Lowercase c
URLs are case-sensitive in lowerCamelCase.
3. Command/Query Not Registered
Problem:
// Missing registration
// builder.Services.AddCommand<CreateUserCommand, int, CreateUserCommandHandler>();
Solution:
builder.Services.AddCommand<CreateUserCommand, int, CreateUserCommandHandler>();
4. [IgnoreCommand] or [IgnoreQuery]
Problem:
[IgnoreCommand] // This prevents endpoint generation
public record CreateUserCommand { }
Solution: Remove the attribute if you want an HTTP endpoint.
Debugging
// List all registered endpoints
var app = builder.Build();
app.MapSvrntyCommands();
app.MapSvrntyQueries();
// Before app.Run()
var endpoints = app.Services.GetRequiredService<EndpointDataSource>();
foreach (var endpoint in endpoints.Endpoints)
{
Console.WriteLine($"{endpoint.DisplayName}: {endpoint.Metadata}");
}
app.Run();
400 Bad Request
Issue: Validation failed
Symptoms:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Name": ["Name is required"]
}
}
Causes:
1. Missing Required Fields
Request:
{
"email": "john@example.com"
// Missing "name" field
}
Solution: Include all required fields.
2. Invalid Data Format
Problem:
{
"age": "twenty-five" // String instead of number
}
Solution:
{
"age": 25
}
3. Validation Rules Violated
Validator:
RuleFor(x => x.Age)
.GreaterThanOrEqualTo(18);
Request:
{
"age": 16 // Fails validation
}
Solution: Send valid data that meets all validation rules.
401 Unauthorized
Issue: Missing or invalid authentication
Symptoms:
HTTP/1.1 401 Unauthorized
Causes:
1. Missing Authentication Header
Problem:
curl -X POST http://localhost:5000/api/command/createUser
# No Authorization header
Solution:
curl -X POST http://localhost:5000/api/command/createUser \
-H "Authorization: Bearer eyJhbGc..."
2. Expired Token
Problem: JWT token has expired.
Solution: Refresh the token or obtain a new one.
3. Invalid Token
Problem: Token is malformed or invalid.
Solution: Verify token is correctly formatted and signed.
Debugging
app.Use(async (context, next) =>
{
var logger = context.RequestServices.GetRequiredService<ILogger<Program>>();
if (context.User.Identity?.IsAuthenticated == false)
{
logger.LogWarning("Unauthenticated request to {Path}", context.Request.Path);
}
await next();
});
403 Forbidden
Issue: Not authorized
Symptoms:
HTTP/1.1 403 Forbidden
Causes:
1. Authorization Service Denied
Authorization Service:
public Task<bool> CanExecuteAsync(...)
{
return Task.FromResult(false); // Always denies
}
Solution: Check authorization logic.
2. Missing Role
Problem:
app.MapSvrntyCommands().RequireAuthorization(policy =>
{
policy.RequireRole("Admin"); // User doesn't have Admin role
});
Solution: Ensure user has required role.
Debugging
public class DeleteUserCommandAuthorization : ICommandAuthorizationService<DeleteUserCommand>
{
private readonly ILogger<DeleteUserCommandAuthorization> _logger;
public async Task<bool> CanExecuteAsync(
DeleteUserCommand command,
ClaimsPrincipal user,
CancellationToken cancellationToken)
{
var isAdmin = user.IsInRole("Admin");
_logger.LogInformation(
"Authorization check for DeleteUser: UserId={UserId}, IsAdmin={IsAdmin}",
command.UserId,
isAdmin);
if (!isAdmin)
{
_logger.LogWarning(
"User {UserName} attempted to delete user {UserId} without Admin role",
user.Identity?.Name,
command.UserId);
}
return isAdmin;
}
}
415 Unsupported Media Type
Issue: Wrong Content-Type
Symptoms:
HTTP/1.1 415 Unsupported Media Type
Causes:
1. Missing Content-Type Header
Problem:
curl -X POST http://localhost:5000/api/command/createUser \
-d '{"name":"John"}'
# Missing -H "Content-Type: application/json"
Solution:
curl -X POST http://localhost:5000/api/command/createUser \
-H "Content-Type: application/json" \
-d '{"name":"John"}'
2. Wrong Content-Type
Problem:
-H "Content-Type: text/plain"
Solution:
-H "Content-Type: application/json"
500 Internal Server Error
Issue: Unhandled exception
Symptoms:
HTTP/1.1 500 Internal Server Error
Common Causes:
1. Null Reference Exception
Problem:
public async Task<UserDto> HandleAsync(GetUserQuery query, CancellationToken cancellationToken)
{
var user = await _repository.GetByIdAsync(query.UserId, cancellationToken);
return MapToDto(user); // user is null!
}
Solution:
public async Task<UserDto> HandleAsync(GetUserQuery query, CancellationToken cancellationToken)
{
var user = await _repository.GetByIdAsync(query.UserId, cancellationToken);
if (user == null)
throw new KeyNotFoundException($"User {query.UserId} not found");
return MapToDto(user);
}
2. Database Connection Issues
Problem:
Connection to database failed
Solution:
- Check connection string
- Verify database is running
- Check firewall rules
- Verify credentials
3. Missing Dependency
Problem:
Cannot resolve service for type 'IUserRepository'
Solution:
builder.Services.AddScoped<IUserRepository, UserRepository>();
Debugging
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
var exceptionHandlerFeature = context.Features.Get<IExceptionHandlerFeature>();
var exception = exceptionHandlerFeature?.Error;
var logger = context.RequestServices.GetRequiredService<ILogger<Program>>();
logger.LogError(exception, "Unhandled exception occurred");
context.Response.StatusCode = 500;
context.Response.ContentType = "application/json";
await context.Response.WriteAsJsonAsync(new
{
error = app.Environment.IsDevelopment() ? exception?.Message : "An error occurred",
stackTrace = app.Environment.IsDevelopment() ? exception?.StackTrace : null
});
});
});
CORS Errors
Issue: CORS policy blocks request
Browser Console:
Access to fetch at 'http://localhost:5000/api/command/createUser' from origin
'http://localhost:3000' has been blocked by CORS policy
Causes:
1. CORS Not Configured
Problem:
// Missing CORS configuration
var app = builder.Build();
app.MapSvrntyCommands();
Solution:
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
{
policy.WithOrigins("http://localhost:3000")
.AllowAnyMethod()
.AllowAnyHeader();
});
});
var app = builder.Build();
app.UseCors(); // Add before MapSvrntyCommands
app.MapSvrntyCommands();
2. Wrong Order
Problem:
app.MapSvrntyCommands();
app.UseCors(); // Too late!
Solution:
app.UseCors(); // Must be before MapSvrntyCommands
app.MapSvrntyCommands();
3. Credentials Without Specific Origin
Problem:
policy.AllowAnyOrigin()
.AllowCredentials(); // Error: Can't use both
Solution:
policy.WithOrigins("https://example.com")
.AllowCredentials();
JSON Serialization Issues
Issue: Properties not deserializing
Problem:
public record CreateUserCommand
{
public string Name { get; init; } = string.Empty;
}
Request:
{
"name": "John Doe" // Lowercase 'name'
}
Result: Name is empty string (default value)
Solution:
JSON property names are case-insensitive by default in ASP.NET Core, but ensure:
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.PropertyNameCaseInsensitive = true; // Default
});
Issue: Circular reference
Problem:
public class User
{
public int Id { get; set; }
public List<Order> Orders { get; set; }
}
public class Order
{
public int Id { get; set; }
public User User { get; set; } // Circular reference!
}
Solution: Use DTOs without circular references:
public record UserDto
{
public int Id { get; init; }
public List<OrderSummaryDto> Orders { get; init; } = new();
}
public record OrderSummaryDto
{
public int Id { get; init; }
public decimal TotalAmount { get; init; }
// No User property - breaks the cycle
}
Handler Not Found
Issue: Handler not resolved from DI
Symptoms:
Unable to resolve service for type 'ICommandHandler<CreateUserCommand, int>'
Causes:
1. Handler Not Registered
Problem:
// Missing handler registration
Solution:
builder.Services.AddCommand<CreateUserCommand, int, CreateUserCommandHandler>();
2. Wrong Lifetime
Problem:
// Handler depends on Scoped service but registered as Singleton
builder.Services.AddSingleton<CreateUserCommandHandler>();
Solution:
builder.Services.AddCommand<CreateUserCommand, int, CreateUserCommandHandler>();
// Uses Scoped lifetime by default
Debugging Tools
Enable Detailed Errors
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
Logging
builder.Logging.AddConsole();
builder.Logging.SetMinimumLevel(LogLevel.Debug);
Request/Response Logging
app.Use(async (context, next) =>
{
var logger = context.RequestServices.GetRequiredService<ILogger<Program>>();
logger.LogInformation(
"Request: {Method} {Path}",
context.Request.Method,
context.Request.Path);
await next();
logger.LogInformation(
"Response: {StatusCode}",
context.Response.StatusCode);
});
Common Mistakes
❌ Wrong HTTP Method
# Wrong - Queries support GET/POST, not PUT
curl -X PUT http://localhost:5000/api/query/getUser
❌ Missing await
public async Task<UserDto> HandleAsync(GetUserQuery query, CancellationToken cancellationToken)
{
var user = _repository.GetByIdAsync(query.UserId, cancellationToken); // Missing await!
return MapToDto(user); // Error: user is Task, not User
}
❌ Blocking async calls
// Don't do this
var result = handler.HandleAsync(query, cancellationToken).Result; // Blocks thread
// Do this
var result = await handler.HandleAsync(query, cancellationToken);
Best Practices
✅ DO
- Enable detailed errors in development
- Use proper logging
- Handle exceptions gracefully
- Return appropriate status codes
- Test with Swagger UI
- Use integration tests
- Log authorization failures
❌ DON'T
- Don't expose detailed errors in production
- Don't ignore validation errors
- Don't skip authentication/authorization
- Don't block async calls with .Result or .Wait()
- Don't swallow exceptions