From 7b4e913e303a19d469251fe9dc8d258f13646334 Mon Sep 17 00:00:00 2001 From: Vladimir Pecanac Date: Sun, 11 Oct 2026 00:25:01 +0200 Subject: [PATCH] Enabling CORS in ASP.NET Core: migrate the sample into CodeMazeGuides on .NET 10 Moves the sample from the standalone cors-aspnetcore-example repository into aspnetcore-webapi/EnablingCorsInAspNetCore as one solution with Api, Client and Tests projects, all on net10.0. - UseCors now runs before UseAuthentication and UseAuthorization - Swashbuckle removed; Microsoft.AspNetCore.OpenApi 10.0.12 with AddOpenApi and MapOpenApi; WithOpenApi removed (ASPDEPR002) - one WeatherForecast record shared by the controller and the minimal API endpoint (the duplicate class is gone) - client reduced to the FetchData page, its layout and Program.cs; PreferExactMatches removed (obsolete, no effect); nullable forecasts field - launchSettings dotnetRunMessages is a boolean (a string makes the .NET 10 SDK skip the profile) - new xunit integration tests for the allowed origin, a refused origin, the preflight and the exposed X-Pagination header --- .../EnablingCorsInAspNetCore/Api/Api.csproj | 13 ++++ .../Controllers/WeatherForecastController.cs | 35 +++++++++ .../EnablingCorsInAspNetCore/Api/Program.cs | 78 +++++++++++++++++++ .../Api/Properties/launchSettings.json | 14 ++++ .../Api/appsettings.Development.json | 9 +++ .../Api/appsettings.json | 10 +++ .../EnablingCorsInAspNetCore/Client/App.razor | 10 +++ .../Client/Client.csproj | 14 ++++ .../Client/Pages/FetchData.razor | 55 +++++++++++++ .../Client/Program.cs | 9 +++ .../Client/Properties/launchSettings.json | 14 ++++ .../Client/Shared/MainLayout.razor | 11 +++ .../Client/_Imports.razor | 6 ++ .../Client/wwwroot/css/app.css | 36 +++++++++ .../Client/wwwroot/index.html | 23 ++++++ .../EnablingCorsInAspNetCore.sln | 62 +++++++++++++++ .../Tests/ApiApplicationFactory.cs | 5 ++ .../Tests/CorsIntegrationTest.cs | 70 +++++++++++++++++ .../Tests/GlobalUsings.cs | 3 + .../Tests/Tests.csproj | 30 +++++++ 20 files changed, 507 insertions(+) create mode 100644 aspnetcore-webapi/EnablingCorsInAspNetCore/Api/Api.csproj create mode 100644 aspnetcore-webapi/EnablingCorsInAspNetCore/Api/Controllers/WeatherForecastController.cs create mode 100644 aspnetcore-webapi/EnablingCorsInAspNetCore/Api/Program.cs create mode 100644 aspnetcore-webapi/EnablingCorsInAspNetCore/Api/Properties/launchSettings.json create mode 100644 aspnetcore-webapi/EnablingCorsInAspNetCore/Api/appsettings.Development.json create mode 100644 aspnetcore-webapi/EnablingCorsInAspNetCore/Api/appsettings.json create mode 100644 aspnetcore-webapi/EnablingCorsInAspNetCore/Client/App.razor create mode 100644 aspnetcore-webapi/EnablingCorsInAspNetCore/Client/Client.csproj create mode 100644 aspnetcore-webapi/EnablingCorsInAspNetCore/Client/Pages/FetchData.razor create mode 100644 aspnetcore-webapi/EnablingCorsInAspNetCore/Client/Program.cs create mode 100644 aspnetcore-webapi/EnablingCorsInAspNetCore/Client/Properties/launchSettings.json create mode 100644 aspnetcore-webapi/EnablingCorsInAspNetCore/Client/Shared/MainLayout.razor create mode 100644 aspnetcore-webapi/EnablingCorsInAspNetCore/Client/_Imports.razor create mode 100644 aspnetcore-webapi/EnablingCorsInAspNetCore/Client/wwwroot/css/app.css create mode 100644 aspnetcore-webapi/EnablingCorsInAspNetCore/Client/wwwroot/index.html create mode 100644 aspnetcore-webapi/EnablingCorsInAspNetCore/EnablingCorsInAspNetCore.sln create mode 100644 aspnetcore-webapi/EnablingCorsInAspNetCore/Tests/ApiApplicationFactory.cs create mode 100644 aspnetcore-webapi/EnablingCorsInAspNetCore/Tests/CorsIntegrationTest.cs create mode 100644 aspnetcore-webapi/EnablingCorsInAspNetCore/Tests/GlobalUsings.cs create mode 100644 aspnetcore-webapi/EnablingCorsInAspNetCore/Tests/Tests.csproj diff --git a/aspnetcore-webapi/EnablingCorsInAspNetCore/Api/Api.csproj b/aspnetcore-webapi/EnablingCorsInAspNetCore/Api/Api.csproj new file mode 100644 index 0000000000..0268d75ddb --- /dev/null +++ b/aspnetcore-webapi/EnablingCorsInAspNetCore/Api/Api.csproj @@ -0,0 +1,13 @@ + + + + net10.0 + enable + enable + + + + + + + diff --git a/aspnetcore-webapi/EnablingCorsInAspNetCore/Api/Controllers/WeatherForecastController.cs b/aspnetcore-webapi/EnablingCorsInAspNetCore/Api/Controllers/WeatherForecastController.cs new file mode 100644 index 0000000000..8cc0275a68 --- /dev/null +++ b/aspnetcore-webapi/EnablingCorsInAspNetCore/Api/Controllers/WeatherForecastController.cs @@ -0,0 +1,35 @@ +using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.Mvc; + +namespace Api.Controllers; + +[ApiController] +[Route("[controller]")] +public class WeatherForecastController : ControllerBase +{ + private static readonly string[] Summaries = + [ + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + ]; + + [EnableCors("CorsPolicy")] + [HttpGet] + public IEnumerable Get() + { + var rng = new Random(); + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + ( + DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + rng.Next(-20, 55), + Summaries[rng.Next(Summaries.Length)] + )) + .ToArray(); + } + + [EnableCors("AnotherCorsPolicy")] + [HttpGet("anotherPolicyExample")] + public IActionResult GetTempAboveLimit() + { + return Ok("This action is protected with another named CORS policy"); + } +} diff --git a/aspnetcore-webapi/EnablingCorsInAspNetCore/Api/Program.cs b/aspnetcore-webapi/EnablingCorsInAspNetCore/Api/Program.cs new file mode 100644 index 0000000000..f7677d9ba7 --- /dev/null +++ b/aspnetcore-webapi/EnablingCorsInAspNetCore/Api/Program.cs @@ -0,0 +1,78 @@ +using Microsoft.Net.Http.Headers; + +var builder = WebApplication.CreateBuilder(args); + +const string policyName = "CorsPolicy"; +const string anotherPolicyName = "AnotherCorsPolicy"; + +builder.Services.AddCors(options => +{ + options.AddPolicy(name: policyName, builder => + { + builder.WithOrigins("https://localhost:5011") + .AllowAnyHeader() + .WithMethods("GET"); + }); + options.AddPolicy(name: anotherPolicyName, builder => + { + builder.WithOrigins("https://localhost:5021") + .WithHeaders(HeaderNames.ContentType, HeaderNames.Accept) + .WithMethods("GET") + .WithExposedHeaders("X-Pagination"); + }); +}); + +builder.Services.AddControllers(); + +builder.Services.AddOpenApi(); +builder.Services.AddProblemDetails(); + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); + app.UseDeveloperExceptionPage(); +} +else +{ + app.UseExceptionHandler(); + app.UseHsts(); + app.UseStatusCodePages(); +} + +app.UseHttpsRedirection(); + +app.UseCors(); + +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapControllers(); + +var summaries = new[] +{ + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" +}; + +app.MapGet("/minimal/weatherforecast", () => + { + var forecast = Enumerable.Range(1, 5).Select(index => + new WeatherForecast + ( + DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + Random.Shared.Next(-20, 55), + summaries[Random.Shared.Next(summaries.Length)] + )) + .ToArray(); + return forecast; + }) + .WithName("GetWeatherForecast") + .RequireCors(policyName); + +app.Run(); + +public record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) +{ + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); +} diff --git a/aspnetcore-webapi/EnablingCorsInAspNetCore/Api/Properties/launchSettings.json b/aspnetcore-webapi/EnablingCorsInAspNetCore/Api/Properties/launchSettings.json new file mode 100644 index 0000000000..910892bd02 --- /dev/null +++ b/aspnetcore-webapi/EnablingCorsInAspNetCore/Api/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "profiles": { + "Api": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "launchUrl": "weatherforecast", + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/aspnetcore-webapi/EnablingCorsInAspNetCore/Api/appsettings.Development.json b/aspnetcore-webapi/EnablingCorsInAspNetCore/Api/appsettings.Development.json new file mode 100644 index 0000000000..8983e0fc1c --- /dev/null +++ b/aspnetcore-webapi/EnablingCorsInAspNetCore/Api/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/aspnetcore-webapi/EnablingCorsInAspNetCore/Api/appsettings.json b/aspnetcore-webapi/EnablingCorsInAspNetCore/Api/appsettings.json new file mode 100644 index 0000000000..d9d9a9bff6 --- /dev/null +++ b/aspnetcore-webapi/EnablingCorsInAspNetCore/Api/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/aspnetcore-webapi/EnablingCorsInAspNetCore/Client/App.razor b/aspnetcore-webapi/EnablingCorsInAspNetCore/Client/App.razor new file mode 100644 index 0000000000..6f67a6ea61 --- /dev/null +++ b/aspnetcore-webapi/EnablingCorsInAspNetCore/Client/App.razor @@ -0,0 +1,10 @@ + + + + + + +

Sorry, there's nothing at this address.

+
+
+
diff --git a/aspnetcore-webapi/EnablingCorsInAspNetCore/Client/Client.csproj b/aspnetcore-webapi/EnablingCorsInAspNetCore/Client/Client.csproj new file mode 100644 index 0000000000..ff32908680 --- /dev/null +++ b/aspnetcore-webapi/EnablingCorsInAspNetCore/Client/Client.csproj @@ -0,0 +1,14 @@ + + + + net10.0 + enable + enable + + + + + + + + diff --git a/aspnetcore-webapi/EnablingCorsInAspNetCore/Client/Pages/FetchData.razor b/aspnetcore-webapi/EnablingCorsInAspNetCore/Client/Pages/FetchData.razor new file mode 100644 index 0000000000..60a3cfb007 --- /dev/null +++ b/aspnetcore-webapi/EnablingCorsInAspNetCore/Client/Pages/FetchData.razor @@ -0,0 +1,55 @@ +@page "/fetchdata" +@inject HttpClient Http + +

Weather forecast

+ +

This component demonstrates fetching data from the server.

+ +@if (forecasts == null) +{ +

Loading...

+} +else +{ + + + + + + + + + + + @foreach (var forecast in forecasts) + { + + + + + + + } + +
DateTemp. (C)Temp. (F)Summary
@forecast.Date.ToShortDateString()@forecast.TemperatureC@forecast.TemperatureF@forecast.Summary
+} + +@code { + private WeatherForecast[]? forecasts; + + protected override async Task OnInitializedAsync() + { + forecasts = await Http.GetFromJsonAsync("/weatherforecast"); + } + + public class WeatherForecast + { + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public string? Summary { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + } +} diff --git a/aspnetcore-webapi/EnablingCorsInAspNetCore/Client/Program.cs b/aspnetcore-webapi/EnablingCorsInAspNetCore/Client/Program.cs new file mode 100644 index 0000000000..fbe28e2c4e --- /dev/null +++ b/aspnetcore-webapi/EnablingCorsInAspNetCore/Client/Program.cs @@ -0,0 +1,9 @@ +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Client; + +var builder = WebAssemblyHostBuilder.CreateDefault(args); +builder.RootComponents.Add("#app"); + +builder.Services.AddScoped(_ => new HttpClient { BaseAddress = new Uri("https://localhost:5001") }); + +await builder.Build().RunAsync(); diff --git a/aspnetcore-webapi/EnablingCorsInAspNetCore/Client/Properties/launchSettings.json b/aspnetcore-webapi/EnablingCorsInAspNetCore/Client/Properties/launchSettings.json new file mode 100644 index 0000000000..759c8462c5 --- /dev/null +++ b/aspnetcore-webapi/EnablingCorsInAspNetCore/Client/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "profiles": { + "Client": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "applicationUrl": "https://localhost:5011;http://localhost:5010", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/aspnetcore-webapi/EnablingCorsInAspNetCore/Client/Shared/MainLayout.razor b/aspnetcore-webapi/EnablingCorsInAspNetCore/Client/Shared/MainLayout.razor new file mode 100644 index 0000000000..7bb971954f --- /dev/null +++ b/aspnetcore-webapi/EnablingCorsInAspNetCore/Client/Shared/MainLayout.razor @@ -0,0 +1,11 @@ +@inherits LayoutComponentBase + +
+ + +
+ @Body +
+
diff --git a/aspnetcore-webapi/EnablingCorsInAspNetCore/Client/_Imports.razor b/aspnetcore-webapi/EnablingCorsInAspNetCore/Client/_Imports.razor new file mode 100644 index 0000000000..87e06ca174 --- /dev/null +++ b/aspnetcore-webapi/EnablingCorsInAspNetCore/Client/_Imports.razor @@ -0,0 +1,6 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Client +@using Client.Shared diff --git a/aspnetcore-webapi/EnablingCorsInAspNetCore/Client/wwwroot/css/app.css b/aspnetcore-webapi/EnablingCorsInAspNetCore/Client/wwwroot/css/app.css new file mode 100644 index 0000000000..d88d4a33a6 --- /dev/null +++ b/aspnetcore-webapi/EnablingCorsInAspNetCore/Client/wwwroot/css/app.css @@ -0,0 +1,36 @@ +html, body { + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +.top-row { + padding: 0.5rem 1.5rem; + border-bottom: 1px solid #d6d5d5; +} + +.content { + padding: 1.1rem 1.5rem; +} + +.table th, .table td { + padding: 0.25rem 0.75rem; + text-align: left; +} + +#blazor-error-ui { + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; +} + + #blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; + } diff --git a/aspnetcore-webapi/EnablingCorsInAspNetCore/Client/wwwroot/index.html b/aspnetcore-webapi/EnablingCorsInAspNetCore/Client/wwwroot/index.html new file mode 100644 index 0000000000..ba0cf1fc8d --- /dev/null +++ b/aspnetcore-webapi/EnablingCorsInAspNetCore/Client/wwwroot/index.html @@ -0,0 +1,23 @@ + + + + + + + Client + + + + + +
Loading...
+ +
+ An unhandled error has occurred. + Reload + X +
+ + + + diff --git a/aspnetcore-webapi/EnablingCorsInAspNetCore/EnablingCorsInAspNetCore.sln b/aspnetcore-webapi/EnablingCorsInAspNetCore/EnablingCorsInAspNetCore.sln new file mode 100644 index 0000000000..5b769bfa09 --- /dev/null +++ b/aspnetcore-webapi/EnablingCorsInAspNetCore/EnablingCorsInAspNetCore.sln @@ -0,0 +1,62 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Api", "Api\Api.csproj", "{A10D17C9-E47B-473F-8C6B-76E981A2748D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client", "Client\Client.csproj", "{9F981D85-267A-46F6-8B0E-C8D91442F024}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{FDBF3625-CB5D-4102-B8E2-0DCC74C7B5D3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A10D17C9-E47B-473F-8C6B-76E981A2748D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A10D17C9-E47B-473F-8C6B-76E981A2748D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A10D17C9-E47B-473F-8C6B-76E981A2748D}.Debug|x64.ActiveCfg = Debug|Any CPU + {A10D17C9-E47B-473F-8C6B-76E981A2748D}.Debug|x64.Build.0 = Debug|Any CPU + {A10D17C9-E47B-473F-8C6B-76E981A2748D}.Debug|x86.ActiveCfg = Debug|Any CPU + {A10D17C9-E47B-473F-8C6B-76E981A2748D}.Debug|x86.Build.0 = Debug|Any CPU + {A10D17C9-E47B-473F-8C6B-76E981A2748D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A10D17C9-E47B-473F-8C6B-76E981A2748D}.Release|Any CPU.Build.0 = Release|Any CPU + {A10D17C9-E47B-473F-8C6B-76E981A2748D}.Release|x64.ActiveCfg = Release|Any CPU + {A10D17C9-E47B-473F-8C6B-76E981A2748D}.Release|x64.Build.0 = Release|Any CPU + {A10D17C9-E47B-473F-8C6B-76E981A2748D}.Release|x86.ActiveCfg = Release|Any CPU + {A10D17C9-E47B-473F-8C6B-76E981A2748D}.Release|x86.Build.0 = Release|Any CPU + {9F981D85-267A-46F6-8B0E-C8D91442F024}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9F981D85-267A-46F6-8B0E-C8D91442F024}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9F981D85-267A-46F6-8B0E-C8D91442F024}.Debug|x64.ActiveCfg = Debug|Any CPU + {9F981D85-267A-46F6-8B0E-C8D91442F024}.Debug|x64.Build.0 = Debug|Any CPU + {9F981D85-267A-46F6-8B0E-C8D91442F024}.Debug|x86.ActiveCfg = Debug|Any CPU + {9F981D85-267A-46F6-8B0E-C8D91442F024}.Debug|x86.Build.0 = Debug|Any CPU + {9F981D85-267A-46F6-8B0E-C8D91442F024}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9F981D85-267A-46F6-8B0E-C8D91442F024}.Release|Any CPU.Build.0 = Release|Any CPU + {9F981D85-267A-46F6-8B0E-C8D91442F024}.Release|x64.ActiveCfg = Release|Any CPU + {9F981D85-267A-46F6-8B0E-C8D91442F024}.Release|x64.Build.0 = Release|Any CPU + {9F981D85-267A-46F6-8B0E-C8D91442F024}.Release|x86.ActiveCfg = Release|Any CPU + {9F981D85-267A-46F6-8B0E-C8D91442F024}.Release|x86.Build.0 = Release|Any CPU + {FDBF3625-CB5D-4102-B8E2-0DCC74C7B5D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FDBF3625-CB5D-4102-B8E2-0DCC74C7B5D3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FDBF3625-CB5D-4102-B8E2-0DCC74C7B5D3}.Debug|x64.ActiveCfg = Debug|Any CPU + {FDBF3625-CB5D-4102-B8E2-0DCC74C7B5D3}.Debug|x64.Build.0 = Debug|Any CPU + {FDBF3625-CB5D-4102-B8E2-0DCC74C7B5D3}.Debug|x86.ActiveCfg = Debug|Any CPU + {FDBF3625-CB5D-4102-B8E2-0DCC74C7B5D3}.Debug|x86.Build.0 = Debug|Any CPU + {FDBF3625-CB5D-4102-B8E2-0DCC74C7B5D3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FDBF3625-CB5D-4102-B8E2-0DCC74C7B5D3}.Release|Any CPU.Build.0 = Release|Any CPU + {FDBF3625-CB5D-4102-B8E2-0DCC74C7B5D3}.Release|x64.ActiveCfg = Release|Any CPU + {FDBF3625-CB5D-4102-B8E2-0DCC74C7B5D3}.Release|x64.Build.0 = Release|Any CPU + {FDBF3625-CB5D-4102-B8E2-0DCC74C7B5D3}.Release|x86.ActiveCfg = Release|Any CPU + {FDBF3625-CB5D-4102-B8E2-0DCC74C7B5D3}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/aspnetcore-webapi/EnablingCorsInAspNetCore/Tests/ApiApplicationFactory.cs b/aspnetcore-webapi/EnablingCorsInAspNetCore/Tests/ApiApplicationFactory.cs new file mode 100644 index 0000000000..ef9b9c75c6 --- /dev/null +++ b/aspnetcore-webapi/EnablingCorsInAspNetCore/Tests/ApiApplicationFactory.cs @@ -0,0 +1,5 @@ +namespace Tests; + +public class ApiApplicationFactory : WebApplicationFactory +{ +} diff --git a/aspnetcore-webapi/EnablingCorsInAspNetCore/Tests/CorsIntegrationTest.cs b/aspnetcore-webapi/EnablingCorsInAspNetCore/Tests/CorsIntegrationTest.cs new file mode 100644 index 0000000000..6965a90d52 --- /dev/null +++ b/aspnetcore-webapi/EnablingCorsInAspNetCore/Tests/CorsIntegrationTest.cs @@ -0,0 +1,70 @@ +namespace Tests; + +public class CorsIntegrationTest : IClassFixture +{ + private const string ClientOrigin = "https://localhost:5011"; + private const string OtherClientOrigin = "https://localhost:5021"; + + private readonly HttpClient _client; + + public CorsIntegrationTest(ApiApplicationFactory factory) + { + _client = factory.CreateClient(); + } + + [Theory] + [InlineData("/weatherforecast")] + [InlineData("/minimal/weatherforecast")] + public async Task GivenAllowedOrigin_WhenGetIsSent_ThenAllowOriginHeaderEchoesTheOrigin(string url) + { + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.Add(HeaderNames.Origin, ClientOrigin); + + var response = await _client.SendAsync(request); + + response.EnsureSuccessStatusCode(); + Assert.Equal(ClientOrigin, Assert.Single(response.Headers.GetValues(HeaderNames.AccessControlAllowOrigin))); + } + + [Theory] + [InlineData("/weatherforecast", "https://unknown.com")] + [InlineData("/weatherforecast", OtherClientOrigin)] + [InlineData("/minimal/weatherforecast", "https://unknown.com")] + public async Task GivenDisallowedOrigin_WhenGetIsSent_ThenNoAllowOriginHeaderIsReturned(string url, string origin) + { + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.Add(HeaderNames.Origin, origin); + + var response = await _client.SendAsync(request); + + response.EnsureSuccessStatusCode(); + Assert.False(response.Headers.Contains(HeaderNames.AccessControlAllowOrigin)); + } + + [Fact] + public async Task GivenAllowedOrigin_WhenPreflightIsSent_ThenAllowMethodsHeaderListsGet() + { + var request = new HttpRequestMessage(HttpMethod.Options, "/weatherforecast"); + request.Headers.Add(HeaderNames.Origin, ClientOrigin); + request.Headers.Add(HeaderNames.AccessControlRequestMethod, "GET"); + + var response = await _client.SendAsync(request); + + Assert.Equal(System.Net.HttpStatusCode.NoContent, response.StatusCode); + Assert.Equal(ClientOrigin, Assert.Single(response.Headers.GetValues(HeaderNames.AccessControlAllowOrigin))); + Assert.Equal("GET", Assert.Single(response.Headers.GetValues(HeaderNames.AccessControlAllowMethods))); + } + + [Fact] + public async Task GivenAnotherPolicyOrigin_WhenGetIsSent_ThenPaginationHeaderIsExposed() + { + var request = new HttpRequestMessage(HttpMethod.Get, "/weatherforecast/anotherPolicyExample"); + request.Headers.Add(HeaderNames.Origin, OtherClientOrigin); + + var response = await _client.SendAsync(request); + + response.EnsureSuccessStatusCode(); + Assert.Equal(OtherClientOrigin, Assert.Single(response.Headers.GetValues(HeaderNames.AccessControlAllowOrigin))); + Assert.Equal("X-Pagination", Assert.Single(response.Headers.GetValues(HeaderNames.AccessControlExposeHeaders))); + } +} diff --git a/aspnetcore-webapi/EnablingCorsInAspNetCore/Tests/GlobalUsings.cs b/aspnetcore-webapi/EnablingCorsInAspNetCore/Tests/GlobalUsings.cs new file mode 100644 index 0000000000..45eb9f6c6c --- /dev/null +++ b/aspnetcore-webapi/EnablingCorsInAspNetCore/Tests/GlobalUsings.cs @@ -0,0 +1,3 @@ +global using Xunit; +global using Microsoft.AspNetCore.Mvc.Testing; +global using Microsoft.Net.Http.Headers; diff --git a/aspnetcore-webapi/EnablingCorsInAspNetCore/Tests/Tests.csproj b/aspnetcore-webapi/EnablingCorsInAspNetCore/Tests/Tests.csproj new file mode 100644 index 0000000000..eb86167659 --- /dev/null +++ b/aspnetcore-webapi/EnablingCorsInAspNetCore/Tests/Tests.csproj @@ -0,0 +1,30 @@ + + + + net10.0 + enable + enable + + false + true + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + +