tes explicitly.
public record OperationResult<TValue>
{
public TValue? Value { get; init; }
public string? Error { get; init; }
public bool IsSuccess => Error is null;
public static OperationResult<TValue> Success(TValue value) =>
new() { Value = value };
public static OperationResult<TValue> Failure(string error) =>
new() { Error = error };
}
Usage Pattern:
Services return OperationResult<T>. The AI is instructed to never throw for validation or business rule violations.
public async Task<OperationResult<UserDto>> GetUserAsync(Guid userId)
{
var user = await _repository.FindByIdAsync(userId);
if (user is null)
return OperationResult<UserDto>.Failure("User not found");
return OperationResult<UserDto>.Success(_mapper.Map<UserDto>(user));
}
Rationale: This pattern forces the caller to handle the error case explicitly. It prevents the AI from generating unhandled exception paths and ensures that error handling is visible in the method signature.
2. Dependency Injection Lifetime Enforcement
The most dangerous bug introduced by AI is the injection of scoped services (like DbContext) into singleton services. This creates a captive dependency, causing the scoped service to be held for the application lifetime, leading to memory leaks and concurrency issues.
Implementation:
Enforce a rule that singleton constructors must not accept scoped types. If a singleton requires scoped behavior, it must use IServiceScopeFactory to create scopes explicitly.
// BAD: AI often generates this
public class BackgroundProcessor : IBackgroundProcessor
{
private readonly ApplicationDbContext _context; // Scoped in Singleton!
public BackgroundProcessor(ApplicationDbContext context) => _context = context;
}
// GOOD: Guardrailed implementation
public class BackgroundProcessor : IBackgroundProcessor
{
private readonly IServiceScopeFactory _scopeFactory;
public BackgroundProcessor(IServiceScopeFactory scopeFactory) =>
_scopeFactory = scopeFactory;
public async Task ProcessAsync()
{
using var scope = _scopeFactory.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
// Use context within scope
}
}
Rationale: By mandating IServiceScopeFactory for singletons that need database access, you ensure that a new scope is created for each operation. This aligns with .NET's lifetime management and prevents stale context issues.
3. EF Core Query Optimization
AI models frequently generate Entity Framework Core queries without AsNoTracking(). For read-only operations, this causes the change tracker to monitor entities, consuming memory and CPU unnecessarily.
Implementation:
Create an extension method that enforces no-tracking for query methods returning DTOs or read-only projections.
public static class QueryableExtensions
{
public static IQueryable<T> AsReadOnly<T>(this IQueryable<T> source) =>
source.AsNoTracking();
}
Usage Pattern:
The AI is instructed to append .AsReadOnly() to any query that does not modify entities.
public async Task<IEnumerable<ProductSummary>> GetSummariesAsync()
{
return await _context.Products
.AsReadOnly()
.Select(p => new ProductSummary(p.Name, p.Price))
.ToListAsync();
}
Rationale: This optimization reduces memory allocation and improves query throughput. The extension method provides a clear semantic signal that the query is read-only, aiding both the AI and human reviewers.
4. Cancellation Token Propagation
AI models often omit CancellationToken parameters in async methods, breaking the cancellation chain. This can lead to operations continuing after a client disconnects, wasting resources.
Implementation:
Enforce a rule that all async methods must accept a CancellationToken with a default value.
public async Task<OperationResult<OrderDto>> CreateOrderAsync(
CreateOrderRequest request,
CancellationToken ct = default)
{
// Implementation
await _repository.SaveAsync(order, ct);
return OperationResult<OrderDto>.Success(mappedOrder);
}
Rationale: Propagating cancellation tokens ensures that long-running operations can be aborted efficiently. This is critical for API responsiveness and resource management in high-load scenarios.
5. Persistence Boundary Separation
AI models tend to leak infrastructure concerns into the API layer, such as writing LINQ queries directly in controllers. This violates separation of concerns and makes the code harder to test and maintain.
Implementation:
Enforce a strict boundary where controllers only orchestrate calls to application services. All IQueryable access must reside in the infrastructure layer.
// Controller: Orchestration only
[HttpGet("{id}")]
public async Task<IActionResult> GetProduct(Guid id)
{
var result = await _productService.GetDetailsAsync(id);
return result.IsSuccess ? Ok(result.Value) : NotFound(result.Error);
}
// Service: Business logic
public async Task<OperationResult<ProductDetails>> GetDetailsAsync(Guid id)
{
// Calls repository, applies business rules
return await _repository.GetDetailsAsync(id);
}
Rationale: This separation keeps the API layer thin and focused on HTTP concerns. It also ensures that business logic is encapsulated in testable service classes, independent of the web framework.
Pitfall Guide
The following pitfalls represent common failure modes when using AI assistants in .NET development. Each includes a detailed explanation and a remediation strategy.
| Pitfall Name | Explanation | Fix |
|---|
| Stack Trace Erasure | AI generates throw ex in catch blocks, which resets the stack trace and obscures the original error source. This occurs in ~40% of generated catch blocks. | Enforce the use of throw without arguments to preserve the stack trace. Only wrap exceptions explicitly if additional context is required. |
| Captive Dependencies | AI injects scoped services (e.g., DbContext) into singleton services. This causes the scoped service to live for the application lifetime, leading to memory leaks and concurrency bugs. | Audit singleton constructors. Require IServiceScopeFactory for singletons needing scoped services. Use static analysis to flag DI violations. |
| Tracking Overhead | AI generates EF Core queries without AsNoTracking() for read operations. This causes unnecessary change tracking, increasing memory usage and CPU load. | Append .AsNoTracking() or use a custom extension like .AsReadOnly() for all read-only queries. Enforce via code rules. |
| Cancellation Blackholes | AI omits CancellationToken in async method signatures or fails to pass it to downstream calls. This breaks cancellation propagation. | Require CancellationToken ct = default in all async signatures. Ensure tokens are forwarded to all I/O operations. |
| Leaky Abstractions | AI places database logic (e.g., IQueryable manipulation) in controllers or API endpoints. This couples the API layer to the data access implementation. | Enforce persistence boundaries. Controllers must only call application services. All data access logic must reside in the infrastructure layer. |
| Hallucination Spirals | AI enters a loop where it generates incorrect code, the developer corrects it, and the AI reverts to the error in subsequent prompts. | Implement a "circuit breaker" rule. If the AI repeats an error, stop the session, review the rule set, and provide explicit context before retrying. |
| Inconsistent Error Models | AI mixes throw exceptions with Result<T> returns, creating unpredictable error handling paths. | Enforce a single error model for business logic. Use Result<T> for expected failures and reserve exceptions for system-level errors. |
Production Bundle
This section provides actionable artifacts to harden your .NET AI workflow immediately.
Action Checklist
Decision Matrix
Use this matrix to determine the appropriate approach for common architectural decisions.
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Business Validation Failure | Return Result<T>.Failure(error) | Keeps error handling explicit and testable. Avoids exception overhead. | Low |
| System/Infrastructure Failure | Throw Exception | Reserved for unexpected errors (e.g., DB down). Allows global exception handling. | Medium |
| Read-Only Query | Use .AsNoTracking() | Reduces memory and CPU usage. Improves throughput. | Positive |
| Singleton Needs DB Access | Inject IServiceScopeFactory | Prevents captive dependencies. Ensures fresh context per operation. | Low |
| Controller Data Access | Delegate to Service Layer | Maintains separation of concerns. Improves testability. | Low |
Configuration Template
The following template defines a robust set of rules for Cursor. Save this as .cursorrules in your project root.
# .NET AI Guardrails
## Error Handling
- Use `Result<T>` for all business logic outcomes.
- Never throw exceptions for validation or expected failures.
- Preserve stack traces: use `throw`, not `throw ex`.
## Dependency Injection
- Never inject scoped services into singletons.
- Singletons requiring scoped services must use `IServiceScopeFactory`.
- Validate constructor parameters against service lifetimes.
## Data Access
- Append `.AsNoTracking()` to all read-only EF Core queries.
- Use `.AsReadOnly()` extension for semantic clarity.
- Keep `IQueryable` logic in the Infrastructure layer.
## Async Patterns
- All async methods must accept `CancellationToken ct = default`.
- Propagate cancellation tokens to all downstream I/O calls.
## Architecture
- Controllers must only orchestrate calls to application services.
- No database logic in API endpoints.
- Enforce persistence boundaries strictly.
## AI Behavior
- If an error is repeated, stop generation and review rules.
- Do not hallucinate API signatures; verify against existing interfaces.
Quick Start Guide
- Create Rules File: Add
.cursorrules to your project root with the configuration template above.
- Update DI Registration: Audit your
Program.cs or Startup.cs. Refactor any singleton services that depend on scoped types to use IServiceScopeFactory.
- Refactor Error Handling: Identify service methods that throw exceptions for business logic. Convert them to return
Result<T> and update callers to handle the result.
- Optimize Queries: Scan EF Core queries for read operations. Append
.AsNoTracking() or use the .AsReadOnly() extension.
- Verify Cancellation: Ensure all async methods accept and propagate
CancellationToken. Update AI prompts to include cancellation requirements.
By implementing these guardrails, you transform AI-assisted development from a source of risk into a reliable, high-velocity engineering practice. The rules enforce consistency, prevent critical runtime bugs, and optimize performance, allowing you to focus on delivering business value with confidence.