Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -365,4 +365,5 @@ FodyWeavers.xsd
# Ignore lib files handled by libman
samples/EF6/EF6/lib/
samples/Simple/SimpleMVC/lib/
samples/SWA/SWAFramework/lib/
samples/SWA/SWAFramework/lib/
/tests/default.runsettings
1 change: 1 addition & 0 deletions C3D.Extensions.Aspire.sln
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "SWAShared", "samples\SWA\SW
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{8AFDAAF4-2682-43BB-B576-24B96DFDEF49}"
ProjectSection(SolutionItems) = preProject
tests\default.runsettings = tests\default.runsettings
tests\Directory.Build.props = tests\Directory.Build.props
tests\Directory.Build.Targets = tests\Directory.Build.Targets
EndProjectSection
Expand Down
16 changes: 8 additions & 8 deletions build/CreateDevCert.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,17 @@ function ImportCert {
}

$oid = "1.3.6.1.4.1.311.84.1.1"
Get-ChildItem -Path Cert:\CurrentUser\My | Where-Object { $_.Extensions | Where-Object { $_.Oid.Value -eq $oid } } | Remove-Item
Get-ChildItem -Path Cert:\CurrentUser\Root | Where-Object { $_.Extensions | Where-Object { $_.Oid.Value -eq $oid } } | Remove-Item
Get-ChildItem -Path Cert:\CurrentUser\My | Where-Object { $_.Extensions | Where-Object { $_.Oid.Value -eq $oid } } | Remove-Item -Force
Get-ChildItem -Path Cert:\CurrentUser\Root | Where-Object { $_.Extensions | Where-Object { $_.Oid.Value -eq $oid } } | Remove-Item -Force

Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Extensions | Where-Object { $_.Oid.Value -eq $oid } } | Remove-Item
Get-ChildItem -Path Cert:\LocalMachine\Root | Where-Object { $_.Extensions | Where-Object { $_.Oid.Value -eq $oid } } | Remove-Item
Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Extensions | Where-Object { $_.Oid.Value -eq $oid } } | Remove-Item -Force
Get-ChildItem -Path Cert:\LocalMachine\Root | Where-Object { $_.Extensions | Where-Object { $_.Oid.Value -eq $oid } } | Remove-Item -Force

Get-ChildItem -Path Cert:\CurrentUser\My | Where-Object { $_.FriendlyName -eq "IIS Express Development Certificate" } | Remove-Item
Get-ChildItem -Path Cert:\CurrentUser\Root | Where-Object { $_.FriendlyName -eq "IIS Express Development Certificate" } | Remove-Item
Get-ChildItem -Path Cert:\CurrentUser\My | Where-Object { $_.FriendlyName -eq "IIS Express Development Certificate" } | Remove-Item -Force
Get-ChildItem -Path Cert:\CurrentUser\Root | Where-Object { $_.FriendlyName -eq "IIS Express Development Certificate" } | Remove-Item -Force

Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.FriendlyName -eq "IIS Express Development Certificate" } | Remove-Item
Get-ChildItem -Path Cert:\LocalMachine\Root | Where-Object { $_.FriendlyName -eq "IIS Express Development Certificate" } | Remove-Item
Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.FriendlyName -eq "IIS Express Development Certificate" } | Remove-Item -Force
Get-ChildItem -Path Cert:\LocalMachine\Root | Where-Object { $_.FriendlyName -eq "IIS Express Development Certificate" } | Remove-Item -Force

$oid_obj = New-Object System.Security.Cryptography.Oid($oid, "ASP.NET Core HTTPS development certificate")
$ext = New-Object System.Security.Cryptography.X509Certificates.X509Extension($oid_obj, @(2), $false)
Expand Down
4 changes: 2 additions & 2 deletions samples/SWA/SWAFramework/App_Start/BundleConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ public class BundleConfig
public static void RegisterBundles(BundleCollection bundles)
{
bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
"~/lib/jquery/jquery-{version}.js"));
"~/lib/jquery/jquery.js"));

bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/lib/jquery-validate/jquery.validate.js*").Include(
"~/lib/jquery-validate/jquery.validate.js").Include(
"~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"));

// Use the development version of Modernizr to develop with and learn from. Then, when you're
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ public class Binding
public string BindingInformation { get; set; }

public int Port => int.Parse(BindingInformation.Split(':')[1]);

public string HostName => BindingInformation.Split(':')[2];
}

[XmlRoot(ElementName = "bindings")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ void SaveConfig()
var matches = new Dictionary<EndpointAnnotation, Configuration.Binding>();
foreach (var binding in siteConfig.Bindings.Binding)
{
var endpoint = project.AddOrUpdateEndpointFromBinding(binding);
var endpoint = project.AddOrUpdateEndpointFromBinding(binding, logger);
matches[endpoint] = binding;
}

Expand Down
41 changes: 35 additions & 6 deletions src/C3D/Extensions/Aspire/IISExpress/IISExpressEntensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,19 @@ public static void MarkPortAsUsed(int port)
ap[port] = true;
}

public static bool TryMarkPortAsUsed(int port)
{
ArgumentOutOfRangeException.ThrowIfLessThan(port, 1, nameof(port));
ArgumentOutOfRangeException.ThrowIfGreaterThan(port, 65535, nameof(port));
var ap = AllocatedPorts;
if (ap[port])
{
return false; // Port is already allocated
}
ap[port] = true;
return true; // Successfully marked the port as used
}

public static int GetRandomFreePort(int minPort, int maxPort)
{
var ap = AllocatedPorts;
Expand Down Expand Up @@ -300,7 +313,7 @@ internal static Binding CreateIISConfigBinding(this EndpointAnnotation endpoint)
return new Binding()
{
Protocol = endpoint.UriScheme,
BindingInformation = $"*:{port}:localhost"
BindingInformation = $"*:{port}:{endpoint.TargetHost}"
};
}

Expand All @@ -322,11 +335,14 @@ internal static void ShowIISExpressHttpsEndpointInformation<T>(this T resource,
});
}

internal static EndpointAnnotation AddOrUpdateEndpointFromBinding(this IResourceWithEndpoints resource, Binding binding)
internal static EndpointAnnotation AddOrUpdateEndpointFromBinding(this IResourceWithEndpoints resource, Binding binding, ILogger? logger = null)
{
var hostName = string.IsNullOrEmpty(binding.HostName) ? "localhost" : binding.HostName;

var endpoint = resource.Annotations
.OfType<EndpointAnnotation>()
.Where(ea => StringComparer.OrdinalIgnoreCase.Equals(ea.Name, binding.Protocol))
.Where(ea => StringComparer.OrdinalIgnoreCase.Equals(ea.Name, binding.Protocol) &&
StringComparer.OrdinalIgnoreCase.Equals(ea.TargetHost, hostName))
.SingleOrDefault();
if (endpoint is null)
{
Expand All @@ -335,14 +351,27 @@ internal static EndpointAnnotation AddOrUpdateEndpointFromBinding(this IResource
}
else if (endpoint.TargetPort is not null && endpoint.TargetPort != binding.Port)
{
endpoint = new EndpointAnnotation(System.Net.Sockets.ProtocolType.Tcp, name: $"{binding.Protocol}-{binding.Port}");
endpoint = new EndpointAnnotation(System.Net.Sockets.ProtocolType.Tcp, name: $"{binding.Protocol}-{binding.Port}-{hostName}");
resource.Annotations.Add(endpoint);
}
MarkPortAsUsed(binding.Port);
if (StringComparer.OrdinalIgnoreCase.Equals(hostName, "localhost"))
{
MarkPortAsUsed(binding.Port);
}
else
{
if (!TryMarkPortAsUsed(binding.Port))
{
logger?.LogWarning("Port {Port} is already allocated. Ensure each binding has a unique hostName.", binding.Port);
}
logger?.LogInformation("It is recommended to use 'localhost' with unique ports for IIS Express endpoints to avoid port conflicts.");
// If the hostName is not localhost, we disable the proxy support so that SNI and host name routing works as expected.
endpoint.IsProxied = false;
}

endpoint.TargetPort = binding.Port;
endpoint.TargetHost = hostName;
endpoint.UriScheme = binding.Protocol;
//endpoint.IsProxied = false;

return endpoint;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.0">
<PrivateAssets>all</PrivateAssets>
Expand Down
3 changes: 2 additions & 1 deletion tests/SWATestProject/SWATestProject.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<UserSecretsId>83318f29-3968-4859-87c3-84d87cec02fc</UserSecretsId>
</PropertyGroup>

<ItemGroup>
Expand All @@ -14,7 +15,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.0">
<PrivateAssets>all</PrivateAssets>
Expand Down
57 changes: 35 additions & 22 deletions tests/SWATestProject/Tests/SWAIntegrationTests.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
using Microsoft.Extensions.Logging;
using System.Runtime.CompilerServices;
using Xunit.Abstractions;
using Aspire.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Runtime.CompilerServices;
using System.Threading;
using Xunit.Abstractions;

namespace SWATestProject.Tests;

public class SWAIntegrationTests(ITestOutputHelper outputHelper)
{
private void WriteFunctionName([CallerMemberName] string? caller = null) => outputHelper.WriteLine(caller);
private static readonly TimeSpan WaitForHealthyTimeout = TimeSpan.FromSeconds(90);
private const int WaitForHealthyTimeoutSeconds = 90;
private static readonly TimeSpan WaitForHealthyTimeout = TimeSpan.FromSeconds(WaitForHealthyTimeoutSeconds);

private async Task<IDistributedApplicationTestingBuilder> CreateAppHostAsync()
{
Expand All @@ -26,6 +28,7 @@ private async Task<IDistributedApplicationTestingBuilder> CreateAppHostAsync()

//ConfigureOtlpOverHttp(host, outputHelper.WriteLine);

host.Configuration!.AddUserSecrets<SWAIntegrationTests>();
//host.EnvironmentName = "Test";
//dab.AssemblyName = this.GetType().Assembly.GetName().Name;
});
Expand All @@ -43,12 +46,13 @@ private async Task<IDistributedApplicationTestingBuilder> CreateAppHostAsync()

appHost.Services.ConfigureHttpClientDefaults(clientBuilder =>
{

var timeout = TimeSpan.FromSeconds(appHost.Configuration.GetValue<double>("HttpClientTimeout", 30));
clientBuilder
.AddStandardResilienceHandler(res=> {
res.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(120);
res.CircuitBreaker.SamplingDuration = TimeSpan.FromSeconds(60);
res.AttemptTimeout.Timeout = TimeSpan.FromSeconds(30);
.AddStandardResilienceHandler(res =>
{
res.TotalRequestTimeout.Timeout = timeout * 4;
res.CircuitBreaker.SamplingDuration = timeout * 2;
res.AttemptTimeout.Timeout = timeout;
});
clientBuilder
.ConfigurePrimaryHttpMessageHandler(() =>
Expand Down Expand Up @@ -130,10 +134,11 @@ public async Task GetFrameworkRootReturnsOkStatusCode()
await using Aspire.Hosting.DistributedApplication app = await ArrangeAppHostAsync();
var resourceNotificationService = app.Services.GetRequiredService<ResourceNotificationService>();
await app.StartAsync();
var timeout = TimeSpan.FromSeconds(app.Services.GetRequiredService<IConfiguration>().GetValue<double>("WaitForHealthyTimeoutSeconds", WaitForHealthyTimeoutSeconds));

// Act
var httpClient = app.CreateHttpClient("framework", "http");
await resourceNotificationService.WaitForResourceAsync("framework", KnownResourceStates.Running).WaitAsync(WaitForHealthyTimeout);
await resourceNotificationService.WaitForResourceAsync("framework", KnownResourceStates.Running).WaitAsync(timeout);
var response = await httpClient.GetAsync("/");

// Assert
Expand All @@ -149,11 +154,13 @@ public async Task GetCoreRootReturnsOkStatusCode()
await using Aspire.Hosting.DistributedApplication app = await ArrangeAppHostAsync();
var resourceNotificationService = app.Services.GetRequiredService<ResourceNotificationService>();
await app.StartAsync();
var timeout = TimeSpan.FromSeconds(app.Services.GetRequiredService<IConfiguration>().GetValue<double>("WaitForHealthyTimeoutSeconds", WaitForHealthyTimeoutSeconds));


// Act
var httpClient = app.CreateHttpClient("core", "https");
await resourceNotificationService.WaitForResourceHealthyAsync("framework", WaitBehavior.StopOnResourceUnavailable).WaitAsync(TimeSpan.FromSeconds(90));
await resourceNotificationService.WaitForResourceHealthyAsync("core", WaitBehavior.StopOnResourceUnavailable).WaitAsync(TimeSpan.FromSeconds(90));
await resourceNotificationService.WaitForResourceHealthyAsync("framework", WaitBehavior.StopOnResourceUnavailable).WaitAsync(timeout);
await resourceNotificationService.WaitForResourceHealthyAsync("core", WaitBehavior.StopOnResourceUnavailable).WaitAsync(timeout);
var response = await httpClient.GetAsync("/");

// Assert
Expand All @@ -169,11 +176,12 @@ public async Task GetFrameworkSessionReturnsOkStatusCode()
await using Aspire.Hosting.DistributedApplication app = await ArrangeAppHostAsync();
var resourceNotificationService = app.Services.GetRequiredService<ResourceNotificationService>();
await app.StartAsync();
var timeout = TimeSpan.FromSeconds(app.Services.GetRequiredService<IConfiguration>().GetValue<double>("WaitForHealthyTimeoutSeconds", WaitForHealthyTimeoutSeconds));

// Act
var httpClient = app.CreateHttpClient("framework", "http");
await resourceNotificationService.WaitForResourceHealthyAsync("framework", WaitBehavior.StopOnResourceUnavailable).WaitAsync(WaitForHealthyTimeout);
await resourceNotificationService.WaitForResourceHealthyAsync("core", WaitBehavior.StopOnResourceUnavailable).WaitAsync(WaitForHealthyTimeout);
await resourceNotificationService.WaitForResourceHealthyAsync("framework", WaitBehavior.StopOnResourceUnavailable).WaitAsync(timeout);
await resourceNotificationService.WaitForResourceHealthyAsync("core", WaitBehavior.StopOnResourceUnavailable).WaitAsync(timeout);
var response = await httpClient.GetAsync("/framework");

// Assert
Expand All @@ -190,15 +198,16 @@ public async Task GetFrameworkHttpsWorksSometimes()
await using Aspire.Hosting.DistributedApplication app = await appHost.BuildAsync();
var resourceNotificationService = app.Services.GetRequiredService<ResourceNotificationService>();
await app.StartAsync();

var timeout = TimeSpan.FromSeconds(app.Services.GetRequiredService<IConfiguration>().GetValue<double>("WaitForHealthyTimeoutSeconds", WaitForHealthyTimeoutSeconds));

var httpClient = app.CreateHttpClient("framework", "https");
await resourceNotificationService.WaitForResourceHealthyAsync("framework", WaitBehavior.StopOnResourceUnavailable).WaitAsync(WaitForHealthyTimeout);
await resourceNotificationService.WaitForResourceHealthyAsync("framework", WaitBehavior.StopOnResourceUnavailable).WaitAsync(timeout);

try
{
// Act
var response = await httpClient.GetAsync("/");

// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
Expand All @@ -207,7 +216,7 @@ public async Task GetFrameworkHttpsWorksSometimes()
outputHelper.WriteLine(ex.Message);

// Fix
var resource = app.Services.GetRequiredService<DistributedApplicationModel>().Resources.Single(r=>r.Name=="framework") as IResourceWithEndpoints;
var resource = app.Services.GetRequiredService<DistributedApplicationModel>().Resources.Single(r => r.Name == "framework") as IResourceWithEndpoints;
Assert.NotNull(resource);
await resource.ExecuteFixHttpsCommand(app.Services);

Expand All @@ -227,11 +236,13 @@ public async Task GetProxiedFrameworkSessionReturnsOkStatusCode()
await using Aspire.Hosting.DistributedApplication app = await ArrangeAppHostAsync();
var resourceNotificationService = app.Services.GetRequiredService<ResourceNotificationService>();
await app.StartAsync();
var timeout = TimeSpan.FromSeconds(app.Services.GetRequiredService<IConfiguration>().GetValue<double>("WaitForHealthyTimeoutSeconds", WaitForHealthyTimeoutSeconds));


// Act
var httpClient = app.CreateHttpClient("core", "https");
await resourceNotificationService.WaitForResourceHealthyAsync("framework", WaitBehavior.StopOnResourceUnavailable).WaitAsync(WaitForHealthyTimeout);
await resourceNotificationService.WaitForResourceHealthyAsync("core", WaitBehavior.StopOnResourceUnavailable).WaitAsync(WaitForHealthyTimeout);
await resourceNotificationService.WaitForResourceHealthyAsync("framework", WaitBehavior.StopOnResourceUnavailable).WaitAsync(timeout);
await resourceNotificationService.WaitForResourceHealthyAsync("core", WaitBehavior.StopOnResourceUnavailable).WaitAsync(timeout);
var response = await httpClient.GetAsync("/framework");

// Assert
Expand All @@ -247,11 +258,13 @@ public async Task GetCoreSessionReturnsOkStatusCode()
await using Aspire.Hosting.DistributedApplication app = await ArrangeAppHostAsync();
var resourceNotificationService = app.Services.GetRequiredService<ResourceNotificationService>();
await app.StartAsync();
var timeout = TimeSpan.FromSeconds(app.Services.GetRequiredService<IConfiguration>().GetValue<double>("WaitForHealthyTimeoutSeconds", WaitForHealthyTimeoutSeconds));


// Act
var httpClient = app.CreateHttpClient("core", "https");
await resourceNotificationService.WaitForResourceHealthyAsync("framework", WaitBehavior.StopOnResourceUnavailable).WaitAsync(WaitForHealthyTimeout);
await resourceNotificationService.WaitForResourceHealthyAsync("core", WaitBehavior.StopOnResourceUnavailable).WaitAsync(WaitForHealthyTimeout);
await resourceNotificationService.WaitForResourceHealthyAsync("framework", WaitBehavior.StopOnResourceUnavailable).WaitAsync(timeout);
await resourceNotificationService.WaitForResourceHealthyAsync("core", WaitBehavior.StopOnResourceUnavailable).WaitAsync(timeout);
var response = await httpClient.GetAsync("/core");

// Assert
Expand Down