CORS (Cross-Origin Resource Sharing) is the mechanism that controls which external origins are permitted to access your server's resources. The browser's same-origin policy blocks cross-origin HTTP requests made from JavaScript by default — CORS lets you relax that restriction in a controlled way.
Preflight
A preflight request is sent by the browser to check whether the server understands CORS before dispatching the actual request. Simple requests skip the preflight if they meet all of these conditions:
- Methods: GET, HEAD, POST
- Headers: Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-Width, Width
- Content-Type: application/x-www-form-urlencoded, multipart/form-data, text/plain
Anything outside those constraints triggers a preflight — an automatic OPTIONS request the browser sends first to check whether the server allows the real request.
API Without CORS Headers
A standard controller response with no CORS headers is rejected by the browser when called from a different origin — the request reaches the server but the browser refuses to let JavaScript read the response:
// Without CORS headers — browser blocks cross-origin calls
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(i => new WeatherForecast
{
Date = DateTime.Now.AddDays(i),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
}).ToArray();
}Allowing Simple Requests
For a simple request, adding the Access-Control-Allow-Origin response header is enough. The browser checks this header before exposing the response to the client:
// Fix for simple requests — add the Allow-Origin header manually
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
Response.Headers.Add("Access-Control-Allow-Origin", "*");
// ...
}Triggering a Preflight
Adding any custom header to the request moves it outside the "simple request" category and forces a preflight:
// Blazor client — adding a custom header triggers a preflight
var client = new HttpClient { BaseAddress = new Uri("https://api.example.com") };
client.DefaultRequestHeaders.Add("x-custom-header", "some-value");
var result = await client.GetAsync("WeatherForecast");Handling the OPTIONS Preflight
The browser sends an OPTIONS request first. The server must respond with the allowed origin, methods, and headers. The actual request follows only if the preflight receives a 200 with the appropriate headers:
// Handle OPTIONS preflight + actual request with custom header support
[Route("[controller]")]
[ApiController]
public class WeatherForecastController : ControllerBase
{
[HttpOptions]
public IActionResult Preflight()
{
Response.Headers.Add("Access-Control-Allow-Origin", "*");
Response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
Response.Headers.Add("Access-Control-Allow-Headers", "x-custom-header, Content-Type");
return Ok();
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
Response.Headers.Add("Access-Control-Allow-Origin", "*");
// ...
}
}Production: ASP.NET Core CORS Middleware
Manually adding headers to every action is error-prone. The built-in CORS middleware handles preflight responses automatically:
// Production approach: use the built-in ASP.NET Core CORS middleware
// Startup.ConfigureServices
services.AddCors(options =>
{
options.AddPolicy("AllowBlazorApp", policy =>
{
policy.WithOrigins("https://blazor.example.com")
.WithHeaders("x-custom-header", "Content-Type")
.WithMethods("GET", "POST");
});
});
// Startup.Configure — must come before UseRouting
app.UseCors("AllowBlazorApp");