671 lines
12 KiB
Markdown
671 lines
12 KiB
Markdown
# 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:**
|
|
```csharp
|
|
var app = builder.Build();
|
|
|
|
// Missing MapSvrntyCommands() or MapSvrntyQueries()
|
|
app.Run();
|
|
```
|
|
|
|
**Solution:**
|
|
```csharp
|
|
var app = builder.Build();
|
|
|
|
app.MapSvrntyCommands(); // Add this
|
|
app.MapSvrntyQueries(); // Add this
|
|
|
|
app.Run();
|
|
```
|
|
|
|
#### 2. Wrong URL
|
|
|
|
**Problem:**
|
|
```bash
|
|
# 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:**
|
|
```csharp
|
|
// Missing registration
|
|
// builder.Services.AddCommand<CreateUserCommand, int, CreateUserCommandHandler>();
|
|
```
|
|
|
|
**Solution:**
|
|
```csharp
|
|
builder.Services.AddCommand<CreateUserCommand, int, CreateUserCommandHandler>();
|
|
```
|
|
|
|
#### 4. [IgnoreCommand] or [IgnoreQuery]
|
|
|
|
**Problem:**
|
|
```csharp
|
|
[IgnoreCommand] // This prevents endpoint generation
|
|
public record CreateUserCommand { }
|
|
```
|
|
|
|
**Solution:**
|
|
Remove the attribute if you want an HTTP endpoint.
|
|
|
|
### Debugging
|
|
|
|
```csharp
|
|
// 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:**
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{
|
|
"email": "john@example.com"
|
|
// Missing "name" field
|
|
}
|
|
```
|
|
|
|
**Solution:**
|
|
Include all required fields.
|
|
|
|
#### 2. Invalid Data Format
|
|
|
|
**Problem:**
|
|
```json
|
|
{
|
|
"age": "twenty-five" // String instead of number
|
|
}
|
|
```
|
|
|
|
**Solution:**
|
|
```json
|
|
{
|
|
"age": 25
|
|
}
|
|
```
|
|
|
|
#### 3. Validation Rules Violated
|
|
|
|
**Validator:**
|
|
```csharp
|
|
RuleFor(x => x.Age)
|
|
.GreaterThanOrEqualTo(18);
|
|
```
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"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:**
|
|
```bash
|
|
curl -X POST http://localhost:5000/api/command/createUser
|
|
# No Authorization header
|
|
```
|
|
|
|
**Solution:**
|
|
```bash
|
|
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
|
|
|
|
```csharp
|
|
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:**
|
|
```csharp
|
|
public Task<bool> CanExecuteAsync(...)
|
|
{
|
|
return Task.FromResult(false); // Always denies
|
|
}
|
|
```
|
|
|
|
**Solution:**
|
|
Check authorization logic.
|
|
|
|
#### 2. Missing Role
|
|
|
|
**Problem:**
|
|
```csharp
|
|
app.MapSvrntyCommands().RequireAuthorization(policy =>
|
|
{
|
|
policy.RequireRole("Admin"); // User doesn't have Admin role
|
|
});
|
|
```
|
|
|
|
**Solution:**
|
|
Ensure user has required role.
|
|
|
|
### Debugging
|
|
|
|
```csharp
|
|
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:**
|
|
```bash
|
|
curl -X POST http://localhost:5000/api/command/createUser \
|
|
-d '{"name":"John"}'
|
|
# Missing -H "Content-Type: application/json"
|
|
```
|
|
|
|
**Solution:**
|
|
```bash
|
|
curl -X POST http://localhost:5000/api/command/createUser \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"name":"John"}'
|
|
```
|
|
|
|
#### 2. Wrong Content-Type
|
|
|
|
**Problem:**
|
|
```bash
|
|
-H "Content-Type: text/plain"
|
|
```
|
|
|
|
**Solution:**
|
|
```bash
|
|
-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:**
|
|
```csharp
|
|
public async Task<UserDto> HandleAsync(GetUserQuery query, CancellationToken cancellationToken)
|
|
{
|
|
var user = await _repository.GetByIdAsync(query.UserId, cancellationToken);
|
|
return MapToDto(user); // user is null!
|
|
}
|
|
```
|
|
|
|
**Solution:**
|
|
```csharp
|
|
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:**
|
|
```csharp
|
|
builder.Services.AddScoped<IUserRepository, UserRepository>();
|
|
```
|
|
|
|
### Debugging
|
|
|
|
```csharp
|
|
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:**
|
|
```csharp
|
|
// Missing CORS configuration
|
|
var app = builder.Build();
|
|
app.MapSvrntyCommands();
|
|
```
|
|
|
|
**Solution:**
|
|
```csharp
|
|
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:**
|
|
```csharp
|
|
app.MapSvrntyCommands();
|
|
app.UseCors(); // Too late!
|
|
```
|
|
|
|
**Solution:**
|
|
```csharp
|
|
app.UseCors(); // Must be before MapSvrntyCommands
|
|
app.MapSvrntyCommands();
|
|
```
|
|
|
|
#### 3. Credentials Without Specific Origin
|
|
|
|
**Problem:**
|
|
```csharp
|
|
policy.AllowAnyOrigin()
|
|
.AllowCredentials(); // Error: Can't use both
|
|
```
|
|
|
|
**Solution:**
|
|
```csharp
|
|
policy.WithOrigins("https://example.com")
|
|
.AllowCredentials();
|
|
```
|
|
|
|
## JSON Serialization Issues
|
|
|
|
### Issue: Properties not deserializing
|
|
|
|
**Problem:**
|
|
```csharp
|
|
public record CreateUserCommand
|
|
{
|
|
public string Name { get; init; } = string.Empty;
|
|
}
|
|
```
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```csharp
|
|
builder.Services.ConfigureHttpJsonOptions(options =>
|
|
{
|
|
options.SerializerOptions.PropertyNameCaseInsensitive = true; // Default
|
|
});
|
|
```
|
|
|
|
### Issue: Circular reference
|
|
|
|
**Problem:**
|
|
```csharp
|
|
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:
|
|
|
|
```csharp
|
|
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:**
|
|
```csharp
|
|
// Missing handler registration
|
|
```
|
|
|
|
**Solution:**
|
|
```csharp
|
|
builder.Services.AddCommand<CreateUserCommand, int, CreateUserCommandHandler>();
|
|
```
|
|
|
|
#### 2. Wrong Lifetime
|
|
|
|
**Problem:**
|
|
```csharp
|
|
// Handler depends on Scoped service but registered as Singleton
|
|
builder.Services.AddSingleton<CreateUserCommandHandler>();
|
|
```
|
|
|
|
**Solution:**
|
|
```csharp
|
|
builder.Services.AddCommand<CreateUserCommand, int, CreateUserCommandHandler>();
|
|
// Uses Scoped lifetime by default
|
|
```
|
|
|
|
## Debugging Tools
|
|
|
|
### Enable Detailed Errors
|
|
|
|
```csharp
|
|
if (app.Environment.IsDevelopment())
|
|
{
|
|
app.UseDeveloperExceptionPage();
|
|
}
|
|
```
|
|
|
|
### Logging
|
|
|
|
```csharp
|
|
builder.Logging.AddConsole();
|
|
builder.Logging.SetMinimumLevel(LogLevel.Debug);
|
|
```
|
|
|
|
### Request/Response Logging
|
|
|
|
```csharp
|
|
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
|
|
|
|
```bash
|
|
# Wrong - Queries support GET/POST, not PUT
|
|
curl -X PUT http://localhost:5000/api/query/getUser
|
|
```
|
|
|
|
### ❌ Missing await
|
|
|
|
```csharp
|
|
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
|
|
|
|
```csharp
|
|
// 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
|
|
|
|
## See Also
|
|
|
|
- [HTTP Integration Overview](README.md)
|
|
- [Endpoint Mapping](endpoint-mapping.md)
|
|
- [HTTP Configuration](http-configuration.md)
|
|
- [Swagger Integration](swagger-integration.md)
|