Why You Should Never Access HttpContext from DbContext

Sasanga Edirisinghe
7 min readFeb 2, 2025

In modern web applications built with ASP.NET and Entity Framework, it’s common to need information about the current user or request. Some developers may feel tempted to access the current HTTP context (via HttpContext) directly from their data access layer (specifically, from within the DbContext). However, doing so is considered a bad practice. In this post, we explore why you should never access HttpContext from your DbContext, and what better alternatives exist.

Understanding the Two Worlds

What Is HttpContext?

HttpContext is an ASP.NET construct that encapsulates all information about an individual HTTP request. It holds details such as:

  • The current user’s identity and roles
  • Request headers, cookies, and session data
  • Other per-request information relevant to web operations

Because HttpContext is tied to the lifecycle of a web request, its availability and validity are limited to that specific context.

What is DbContext?

Provided by Entity Framework, DbContext is the primary class used for interacting with the database. It manages the entity lifecycle, tracks changes, and translates your LINQ queries to SQL commands.

Following are the purposes of DbContext:

  • Data Access and Persistence:
    DbContext manages the connection to the database and is used to query and save instances of your entities. It essentially orchestrates data operations like retrieving, inserting, updating, and deleting records.
  • Change Tracking:
    It tracks changes made to your entities so that when you call methods like SaveChanges(), EF Core knows which rows in the database need to be updated, inserted, or deleted.
  • Configuration and Mapping:
    It provides the means to configure how your domain models map to database tables and relationships (often using the OnModelCreating method).

Accessing HttpContext from DbContext

Refer the following project structure:

In the architecture of the CashFlowApp, the CashFlowApp.Db is implemented as a separate Class Library project, while the CashFlowApp.API is structured as a Web Application project. This design ensures that all request-response operations are handled directly by the CashFlowApp.API project.

First you need Microsoft.AspNetCore.HttpNuGet package to access the HttpContext from the DbContext class.

Here is how you access HttpContext from DbContext class:

public class CashFlowContext(DbContextOptions<CashFlowContext> options, 
IHttpContextAccessor httpContextAccessor) : DbContext(options)
{
public override async Task<int> SaveChangesAsync(
CancellationToken cancellationToken = default)
{
SaveAuditData();
return await base.SaveChangesAsync(cancellationToken);
}

private void SaveAuditData()
{
var entries = ChangeTracker.Entries().Where(x => x.Entity
is BaseEntity
&& (x.State == EntityState.Added ||
x.State == EntityState.Modified));

var userId = httpContextAccessor.HttpContext.Items["UserId"] as int?;

foreach (var entry in entries)
{
if (entry.State == EntityState.Added)
{
((BaseEntity)entry.Entity).CreatedBy = userId;
}
else
{
((BaseEntity)entry.Entity).UpdatedBy = userId;
}
}
}
}

This C# code defines a CashFlowContext class, which is a custom implementation of DbContext in Entity Framework Core. It extends DbContext to include auditing functionality by automatically tracking the user who created or modified an entity. IHttpContextAccessor httpContextAccessor helps access the HTTP context (e.g., userId).

Overriding SaveChangesAsync method, intercepts SaveChangesAsync before saving data to apply audit tracking.

‘userId’ is stored in the HttpCotext by using an authorization filter, which is extracted from a JWT.

Authorization Fiter:

[AttributeUsage(AttributeTargets.All)]
public class ApiAuthorize(params RoleEnum[] roles) : Attribute, IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
string? authorizeHeader = context.HttpContext.Request.Headers["Authorization"];
const string errorMessage = "user not authorized to perform this action";
if (authorizeHeader.IsNullOrEmpty())
{
throw new UnauthorizedException(errorMessage);
}

string? token = authorizeHeader?.Replace("Bearer ", "");
var config = context.HttpContext.RequestServices.GetService<IConfiguration>();
var key = config?.GetValue<string>("Jwt:Secret");

var jwtInfo = JwtUtil.ValidateToken(token, key) ?? throw new UnauthorizedException(errorMessage);

var authService = context.HttpContext.RequestServices.GetService<IAuthService>();
var hasRole = await authService.ValidateUserRole(jwtInfo.Username, roles);

if (!hasRole)
throw new UnauthorizedException(errorMessage);

context.HttpContext.Items.Add("userId", jwtInfo.UserId);
await next();
}
}

The above code is not relevant to the topic, but I have added it to make the topic clearer. I will write a separate blog post on ‘how to create authentication and authorization form scratch’.

Now let’s identify why it is bad to access HttpContext from DbContext.

The Principle of Separation of Concerns

1. Clear Boundaries

The primary reason not to mix HttpContext with DbContext is to maintain a clear separation of concerns:

  • HttpContext is part of the presentation or API layer that deals with HTTP requests and responses.
  • DbContext is part of the data access layer that interacts with the database.

By keeping these two layers separate, you ensure that each component remains focused on its own responsibility. This separation makes the code easier to understand, test, and maintain.

2. Single Responsibility Principle (SRP)

Each class or component in your application should have one reason to change. If you embed HTTP-specific logic in your data access layer, any change to the way HTTP requests are handled may force you to modify your DbContext, violating the Single Responsibility Principle.The Pitfalls of Accessing HttpContext in DbContext

The Pitfalls of Accessing HttpContext in DbContext

1. Testing and Maintainability

  • Complicates Unit Testing: Incorporating HttpContext into your DbContext ties your data access logic to web-specific dependencies. This coupling makes it harder to write unit tests because you now have to simulate an HTTP context, even when testing purely database-related functionality.
  • Reduced Reusability: If your DbContext is aware of HTTP-specific data, it becomes less portable. You might find it challenging to reuse your data access logic in non-web contexts, such as background services or desktop applications.

2. Thread Safety and Concurrency Issues

  • Lifecycle Mismatch: The lifecycle of HttpContext is typically tied to the individual HTTP request, whereas a DbContext may be used in scenarios where the lifetime is not strictly bound to a single request. This discrepancy can lead to unexpected behavior, especially in multi-threaded environments.
  • Potential Data Leakage: Sharing information from the HTTP context across different requests or threads might inadvertently expose sensitive data or cause concurrency issues.

3. Violation of Architectural Pattern

  • Tight Coupling: Accessing HttpContext within DbContext creates a tight coupling between layers that should remain independent. This interdependency can lead to a fragile codebase where changes in one layer propagate unintended side effects in another.
  • Compromised Clean Architecture: Modern application architectures (such as Clean Architecture or Onion Architecture) advocate for a clear separation between the domain, application, and infrastructure layers. Mixing HTTP context logic into the data access layer disrupts this architecture, making the system harder to evolve.

4. Incorrect Assumptions About Scope

The HttpContext is scoped to a single HTTP request, while the DbContext is typically registered as scoped (per request) in ASP.NET Core. However, accessing HttpContext inside DbContext assumes they’ll always align perfectly. In reality:

  • Long-running DbContext instances (e.g., in singleton services) will reference a disposed HttpContext .
  • Async operations might inadvertently switch contexts, leading to NullReferenceException errors.

5. Service Location Anti-Pattern

Accessing HttpContext via IHttpContextAccessor inside DbContext often leads to the service locator anti-pattern, where components fetch dependencies directly instead of relying on constructor injection.

6. Performance Overhead

HttpContext is a complex object with significant overhead. Accessing it unnecessarily—especially in high-traffic scenarios—can degrade performance. Your database layer should remain lean and focused.

Best Practices to Keep Your Layers Clean

1. Pass Contextual Data Explicitly

If you need to access user-specific or request-specific data within your data access logic, consider passing this information explicitly through method parameters or by using a dedicated service. This way, you maintain clear boundaries and dependencies.

2. Use Domain Events or Interceptors

If certain actions within your DbContext need to trigger behavior that depends on the HTTP context (for example, auditing changes based on the current user), implement domain events or interceptors that are managed outside of your DbContext. This decouples the HTTP context from your core data access logic.

3. Leverage Dependency Injection Carefully

Configure your dependency injection container to provide services in the appropriate scope. For instance, ensure that your services interacting with HttpCont

4. Adhere to the Repository Pattern

Implement the repository pattern to mediate between the domain and data mapping layers. This pattern can help isolate HTTP context dependencies in a higher layer, keeping the DbContext free of any web-specific logic.

The Correct Way to Access HttpContext

We can always utilize the Controller classes to access HttpContext. Then retrieve userId and pass it to the service layer.

Refer the following code:

[Route("api/[controller]")]
[ApiController]
public class TransactionController(ILogger<TransactionController> logger,
ITransactionService transactionService,
IMapper mapper, IHttpContextAccessor httpContextAccessor) : ControllerBase
{
private readonly int _userId =
AuthUtil.GetUserIdFromContext(httpContextAccessor.HttpContext);

[HttpGet("id")]
public async Task<ActionResult<TransactionDto>> Get(int id)
{
var transaction = await transactionService.FindById(id, _userId);
logger.LogInformation("Transaction : {Id} retrieved for user: {UserId}",
id, _userId);
return Ok(mapper.Map<TransactionDto>(transaction));
}

}

Accessing HttpContext directly from DbContext might seem convenient for passing contextual information to your database operations, but it introduces several risks—ranging from testing difficulties and thread safety issues to violations of clean architectural principles. By keeping your concerns separated, explicitly passing contextual data when needed, and adhering to best practices like domain events and the repository pattern, you maintain a robust and scalable codebase.

Remember, clean architecture isn’t just a buzzword — it’s a commitment to making your code maintainable, testable, and future-proof. By resisting the temptation to mix concerns, you ensure that each component of your application can evolve independently and reliably.

Happy coding!

--

--

Sasanga Edirisinghe
Sasanga Edirisinghe

No responses yet