ASP.NET Core for Beginners: HttpContext, IHttpContextAccessor, IConfiguration & IOptions<T>
A hands-on guide using a real ProductAPI project — with every concept explained line by line.
What You'll Learn
| Concept | What It Does |
|---|---|
HttpContext |
Access request, response, headers, user & IP inside a controller |
IHttpContextAccessor |
Access HttpContext from services outside a controller |
IConfiguration |
Read raw values from appsettings.json by key path |
IOptions<T> |
Bind a config section to a strongly-typed C# class |
All examples come from a single working project — a ProductController with a GetNetworkService helper. By the end you'll understand every line of both files.
📁 Project Structure We're Working With
ProductAPI/
├── Controllers/
│ └── ProductController.cs
├── Services/
│ └── GetNetworkService.cs
├── Options/
│ └── OpenApiOptions.cs
├── appsettings.json
└── Program.cs
Chapter 1 — HttpContext
What Is HttpContext?
Every HTTP request that arrives at your API is wrapped in an object called HttpContext. Think of it as a box that holds everything about the current web request and response — headers, cookies, the user's identity, the IP address, and more.
💡 Simple Analogy: Imagine you run a post office. Every parcel arrives with a label (headers), a sender address (IP), recipient info (URL), the parcel contents (request body), and a reply envelope (response).
HttpContextis that entire parcel + reply envelope combined.
Where Does HttpContext Come From?
In ASP.NET Core, the framework creates a new HttpContext for every incoming request. Because your controller inherits from ControllerBase, it already has a property called HttpContext — no injection needed.
// ProductController.cs
[ApiController]
[Route("api/[controller]")]
public class ProductController : ControllerBase // <-- inherits ControllerBase
{
// ControllerBase gives you 'HttpContext' for free.
// No constructor injection needed for HttpContext itself.
}
Reading Request Headers
Headers are key-value pairs sent by the client (browser, Postman, mobile app, etc.). Common ones include Content-Type, Authorization, and custom ones like X-Custom-Header.
// Inside Get() in ProductController.cs
var headers = HttpContext.Request.Headers;
foreach (var header in headers)
{
Console.WriteLine($"{header.Key}: {header.Value}");
// Check for a specific header
if (header.Key == "X-Custom-Header")
{
Console.WriteLine($"Custom Header Value: {header.Value}");
}
}
📝 Key Points:
HttpContext.Request.Headersis a dictionary of all incoming headers.Keys are case-insensitive (
X-Custom-Header=x-custom-header).Always check if a header exists before reading its value.
Writing Response Headers
You can add headers to your response — for example, a timestamp showing when the response was generated.
// Add a custom header to the HTTP response
HttpContext.Response.Headers.Add("X-Response-Time", DateTime.UtcNow.ToString("o"));
// 'o' = ISO 8601 format, e.g. 2025-06-01T12:00:00.000Z
// Log all response headers
foreach (var header in HttpContext.Response.Headers)
{
Console.WriteLine($"{header.Key}: {header.Value}");
}
Getting the Logged-In User
HttpContext.User holds the identity of the currently authenticated user. If no authentication is configured, the user will be "Anonymous".
// Get the username of the logged-in user (or 'Anonymous' if not logged in)
var user = HttpContext.User.Identity?.Name ?? "Anonymous";
// The ?? operator means: "if null, use this fallback value"
// Identity?.Name uses ?. to safely handle the case where Identity is null
Console.WriteLine($"Session User: {user}");
Quick Reference — HttpContext Properties
| Property | Type | What You Get |
|---|---|---|
HttpContext.Request.Headers |
IHeaderDictionary |
All incoming request headers |
HttpContext.Response.Headers |
IHeaderDictionary |
Response headers you can add to |
HttpContext.User.Identity?.Name |
string? |
Logged-in username (or null) |
HttpContext.Connection.RemoteIpAddress |
IPAddress? |
Client's IP address |
HttpContext.Request.Path |
PathString |
URL path, e.g. /api/product |
HttpContext.Request.Method |
string |
HTTP verb: GET, POST, etc. |
Chapter 2 — IHttpContextAccessor
The Problem: Services Don't Have HttpContext
Controllers inherit ControllerBase and get HttpContext automatically. But what about a helper service class like GetNetworkService? Service classes do NOT inherit ControllerBase, so they have no direct access to HttpContext.
// ❌ This will NOT compile
public class GetNetworkService
{
public string GetUserIP()
{
// ERROR: 'HttpContext' does not exist in this context
return HttpContext.Connection.RemoteIpAddress?.ToString();
}
}
The solution is to inject IHttpContextAccessor — an interface provided by ASP.NET Core that lets any class access the current HttpContext.
The GetNetworkService — Full Walkthrough
public class GetNetworkService
{
// Step 1: Declare a field to store the accessor
public readonly IHttpContextAccessor _httpContextAccessor;
// Step 2: Receive IHttpContextAccessor via constructor injection
public GetNetworkService(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
// Step 3: Use it — access HttpContext through the accessor
public string GetUserIP()
{
return _httpContextAccessor.HttpContext
?.Connection.RemoteIpAddress
?.ToString() ?? "Unknown";
}
}
Breaking Down the IP Address Line
| Code Part | Meaning |
|---|---|
_httpContextAccessor.HttpContext |
Get the current HTTP context (may be null outside a request) |
?.Connection |
Safe navigation: only proceed if HttpContext is not null |
?.RemoteIpAddress |
Safe navigation: get IP (null if not available) |
.ToString() |
Convert IPAddress object to a readable string like 127.0.0.1 |
?? "Unknown" |
Fallback value if anything above returned null |
Registering the Service — Program.cs
For dependency injection to work, you must register both IHttpContextAccessor and your service in Program.cs:
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// REQUIRED: Register IHttpContextAccessor with the DI container
builder.Services.AddHttpContextAccessor();
// Register your custom service
builder.Services.AddScoped<GetNetworkService>();
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
app.Run();
⚠️ Common Mistake — Forgetting to Register
If you forget
builder.Services.AddHttpContextAccessor(), you will get:InvalidOperationException: Unable to resolve service for type 'Microsoft.AspNetCore.Http.IHttpContextAccessor'Always register it before building the app.
Using the Service in the Controller
// ProductController.cs — constructor receives GetNetworkService
public class ProductController : ControllerBase
{
private readonly GetNetworkService _myService;
public ProductController(GetNetworkService myService)
{
_myService = myService;
}
[HttpGet("getall")]
public ActionResult<List<Product>> Get()
{
// Call the service — it internally uses IHttpContextAccessor
Console.WriteLine($"Client IP: {_myService.GetUserIP()}");
return Ok(products);
}
}
HttpContext vs IHttpContextAccessor — Side by Side
| HttpContext (in Controller) | IHttpContextAccessor (in Service) |
|---|---|
Available via ControllerBase |
Injected via constructor |
Use directly: HttpContext.X |
Use via: _accessor.HttpContext?.X |
| Always available in controller | May be null outside a request |
| No registration needed | Requires AddHttpContextAccessor() |
Chapter 3 — IConfiguration
What Is IConfiguration?
IConfiguration is ASP.NET Core's built-in way to read settings from appsettings.json (and environment variables, command-line args, etc.). You inject it into your controller or service and then read values using key paths separated by colons.
The appsettings.json File
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"AppSettings": {
"AppName": "Products API"
},
"OpenApi": {
"Title": "OpenApi",
"Version": "v1",
"BaseUrl": "https://api.openai.com/v1",
"Advanced": {
"IncludeRequestingHost": true,
"IncludeRequestingUser": true,
"IncludeRequestingUserAgent": true
}
}
}
Injecting IConfiguration
// ProductController.cs
public class ProductController : ControllerBase
{
private readonly IConfiguration _configuration; // declare field
public ProductController(IConfiguration configuration) // inject
{
_configuration = configuration;
}
}
✅ No Registration Needed
Unlike
GetNetworkService,IConfigurationis registered automatically byWebApplication.CreateBuilder(args)— you do NOT need to add anything toProgram.cs. Just inject it and use it.
Reading Values — Key Paths with Colons
To navigate nested JSON, separate levels with a colon (:). Think of it like a file path, but with colons instead of slashes.
// Reading individual values from IConfiguration
// Reads "Products API" from AppSettings > AppName
var appName = _configuration["AppSettings:AppName"];
// Reads "OpenApi" from OpenApi > Title
var title = _configuration["OpenApi:Title"];
// Reads "v1" from OpenApi > Version
var version = _configuration["OpenApi:Version"];
// Reads "true" (as a string!) from OpenApi > Advanced > IncludeRequestingHost
var includeHost = _configuration["OpenApi:Advanced:IncludeRequestingHost"];
Iterating Over a Section
You can read an entire section at once using GetSection(). The code in our ProductController loops over all keys in the OpenApisection:
// Get all key-value pairs under the "OpenApi" section
foreach (var setting in _configuration.GetSection("OpenApi").AsEnumerable())
{
if (!string.IsNullOrWhiteSpace(setting.Value))
{
Console.WriteLine($"{setting.Key}: {setting.Value}");
}
}
// Console output:
// OpenApi:Title: OpenApi
// OpenApi:Version: v1
// OpenApi:BaseUrl: https://api.openai.com/v1
// OpenApi:Advanced:IncludeRequestingHost: True
// OpenApi:Advanced:IncludeRequestingUser: True
// OpenApi:Advanced:IncludeRequestingUserAgent: True
IConfiguration Key Path Cheatsheet
| JSON Path | IConfiguration Key | Returns |
|---|---|---|
AppSettings > AppName |
"AppSettings:AppName" |
"Products API" |
OpenApi > Title |
"OpenApi:Title" |
"OpenApi" |
OpenApi > Version |
"OpenApi:Version" |
"v1" |
OpenApi > Advanced > IncludeRequestingHost |
"OpenApi:Advanced:IncludeRequestingHost" |
"True" |
⚠️ IConfiguration Always Returns Strings
Even if your JSON value is
true(boolean) or42(number),IConfigurationreturns it as a string. To get a boolean, use:bool flag = _configuration.GetValue<bool>("OpenApi:Advanced:IncludeRequestingHost");This is one of the reasons
IOptions<T>(Chapter 4) is often preferred.
Chapter 4 — IOptions
The Problem With Raw IConfiguration
Using IConfiguration works, but has drawbacks: string keys can have typos, values are always strings so you must manually convert types, and there is no IntelliSense. IOptions<T> solves all of these.
| IConfiguration (raw) | IOptions (strongly typed) |
|---|---|
_config["OpenApi:Title"] |
_options.Value.Title |
Returns string — must convert |
Returns correct type (bool, int…) |
| Typos only fail at runtime | Typos caught at compile time |
| No IntelliSense for keys | Full IntelliSense in IDE |
| No registration needed | Requires 2 lines in Program.cs |
Step 1 — Create the Options Classes
Create a plain C# class that mirrors the shape of your JSON section. Each property name must match the JSON key exactly (case-insensitive).
// OpenApiOptions.cs
namespace ProductAPI;
public class OpenApiOptions
{
// SectionName is a constant holding the JSON section key.
// This avoids 'magic strings' scattered around your code.
public const string SectionName = "OpenApi";
// These properties map to OpenApi > Title, Version, BaseUrl in JSON
public string Title { get; set; } = string.Empty;
public string Version { get; set; } = string.Empty;
public string BaseUrl { get; set; } = string.Empty;
// Nested class maps to the 'Advanced' sub-object
public OpenApiAdvancedOptions Advanced { get; set; } = new();
}
public class OpenApiAdvancedOptions
{
// These are booleans — IOptions handles the conversion automatically!
public bool IncludeRequestingHost { get; set; }
public bool IncludeRequestingUser { get; set; }
public bool IncludeRequestingUserAgent { get; set; }
}
Step 2 — Register in Program.cs
Tell ASP.NET Core to bind the "OpenApi" section of appsettings.json to your OpenApiOptions class:
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// Bind the 'OpenApi' JSON section to OpenApiOptions class
builder.Services.Configure<OpenApiOptions>(
builder.Configuration.GetSection(OpenApiOptions.SectionName)
);
// Note: OpenApiOptions.SectionName is just the string "OpenApi"
// Using the constant prevents typos.
builder.Services.AddControllers();
var app = builder.Build();
app.Run();
Step 3 — Inject IOptions into the Controller
// ProductController.cs
using Microsoft.Extensions.Options; // <-- required using statement
public class ProductController : ControllerBase
{
private readonly OpenApiOptions _openApiOptions;
public ProductController(
IOptions<OpenApiOptions> openApiOptions) // <-- inject
{
_openApiOptions = openApiOptions.Value; // .Value unwraps the options
}
}
💡 Why
.Value?
IOptions<T>is a wrapper. The actualOpenApiOptionsobject lives inside.Value. You can either call.Valueeach time, or store it once in the constructor (as shown above). Storing it is the recommended pattern.
Step 4 — Read the Values
// Inside Get(int id) in ProductController.cs
Console.WriteLine($"Title: {_openApiOptions.Title}");
Console.WriteLine($"Version: {_openApiOptions.Version}");
Console.WriteLine($"BaseUrl: {_openApiOptions.BaseUrl}");
// Nested advanced options — full dot-notation access
Console.WriteLine($"IncludeRequestingHost: {_openApiOptions.Advanced.IncludeRequestingHost}");
Console.WriteLine($"IncludeRequestingUser: {_openApiOptions.Advanced.IncludeRequestingUser}");
Console.WriteLine($"IncludeRequestingUserAgent: {_openApiOptions.Advanced.IncludeRequestingUserAgent}");
// Console output:
// Title: OpenApi
// Version: v1
// BaseUrl: https://api.openai.com/v1
// IncludeRequestingHost: True <-- already a bool, no conversion!
// IncludeRequestingUser: True
// IncludeRequestingUserAgent: True
How JSON Maps to C# Class
| appsettings.json | C# Property | Type |
|---|---|---|
OpenApi > Title |
OpenApiOptions.Title |
string |
OpenApi > Version |
OpenApiOptions.Version |
string |
OpenApi > BaseUrl |
OpenApiOptions.BaseUrl |
string |
OpenApi > Advanced > IncludeRequestingHost |
OpenApiAdvancedOptions.IncludeRequestingHost |
bool |
OpenApi > Advanced > IncludeRequestingUser |
OpenApiAdvancedOptions.IncludeRequestingUser |
bool |
OpenApi > Advanced > IncludeRequestingUserAgent |
OpenApiAdvancedOptions.IncludeRequestingUserAgent |
bool |
Chapter 5 — Putting It All Together
Complete Program.cs
// Program.cs — Complete setup
using ProductAPI;
var builder = WebApplication.CreateBuilder(args);
// ── 1. IHttpContextAccessor ──────────────────────────────────────────
// Required so GetNetworkService can access HttpContext outside a controller
builder.Services.AddHttpContextAccessor();
// ── 2. Custom Services ───────────────────────────────────────────────
// Register GetNetworkService with Scoped lifetime
// (new instance per HTTP request)
builder.Services.AddScoped<GetNetworkService>();
// ── 3. IOptions<T> ───────────────────────────────────────────────────
// Bind the 'OpenApi' section of appsettings.json to OpenApiOptions
builder.Services.Configure<OpenApiOptions>(
builder.Configuration.GetSection(OpenApiOptions.SectionName)
);
// ── 4. Standard MVC ──────────────────────────────────────────────────
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Complete Controller Constructor
// ProductController.cs — constructor receives all four dependencies
public class ProductController : ControllerBase
{
private readonly GetNetworkService _myService; // uses IHttpContextAccessor internally
private readonly IConfiguration _configuration; // raw config access
private readonly OpenApiOptions _openApiOptions; // strongly-typed config
public ProductController(
GetNetworkService myService,
IConfiguration configuration,
IOptions<OpenApiOptions> openApiOptions)
{
_myService = myService;
_configuration = configuration;
_openApiOptions = openApiOptions.Value;
}
// HttpContext is available directly — no constructor injection needed.
}
How Everything Connects
HTTP Request arrives
│
▼
ASP.NET Core creates HttpContext
│
▼
DI Container injects:
├── IConfiguration (reads appsettings.json)
├── IOptions<OpenApiOptions> (strongly-typed config)
└── GetNetworkService (uses IHttpContextAccessor internally)
│
▼
ProductController runs
├── Uses HttpContext directly (headers, user, response)
└── Calls _myService.GetUserIP()
│
▼
IHttpContextAccessor → HttpContext.Connection.RemoteIpAddress
Final Checklist
[ ] Create
OpenApiOptionsclass — match property names to JSON keys exactly[ ] Register
IHttpContextAccessor—builder.Services.AddHttpContextAccessor()[ ] Register
GetNetworkService—builder.Services.AddScoped<GetNetworkService>()[ ] Register
IOptions<T>—builder.Services.Configure<OpenApiOptions>(...)[ ] Inject in constructor — receive
IConfiguration,IOptions<T>,GetNetworkService[ ] Use
HttpContextdirectly — in controller:.Request.Headers,.User.Identity, etc.[ ] Call
.ValueonIOptions<T>— storeopenApiOptions.Valuein a field
Appendix — Quick Reference Cards
All Four Concepts at a Glance
| Concept | Inject Via | Registration in Program.cs | Use Case |
|---|---|---|---|
HttpContext |
Inherited (ControllerBase) |
None needed | Headers, user, IP inside controller |
IHttpContextAccessor |
Constructor injection | AddHttpContextAccessor() |
HttpContext access in services |
IConfiguration |
Constructor injection | None (auto) | Read raw string values from JSON |
IOptions<T> |
IOptions<MyClass> |
Configure<T>(section) |
Strongly-typed config binding |
Service Lifetime Cheatsheet
| Lifetime | Method | When to Use |
|---|---|---|
| Transient | AddTransient<T>() |
New instance every time the service is requested |
| Scoped | AddScoped<T>() |
One instance per HTTP request (most common for web APIs) |
| Singleton | AddSingleton<T>() |
One instance for the entire application lifetime |
📌 Rule of Thumb: Use
AddScopedfor most web API services (likeGetNetworkService). UseAddSingletononly for stateless, thread-safe services (e.g. caches). UseAddTransientfor lightweight, stateless utilities.