Skip to content

Commit 71717d4

Browse files
authored
Merge pull request #30 from CZEMacLeod/fix-iis-customdomain-29
Fixes #29
2 parents 2ae111f + 7ffd58c commit 71717d4

File tree

9 files changed

+87
-41
lines changed

9 files changed

+87
-41
lines changed

C3D.Extensions.Aspire.sln

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "SWAShared", "samples\SWA\SW
9090
EndProject
9191
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{8AFDAAF4-2682-43BB-B576-24B96DFDEF49}"
9292
ProjectSection(SolutionItems) = preProject
93+
tests\default.runsettings = tests\default.runsettings
9394
tests\Directory.Build.props = tests\Directory.Build.props
9495
tests\Directory.Build.Targets = tests\Directory.Build.Targets
9596
EndProjectSection

build/CreateDevCert.ps1

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,17 @@ function ImportCert {
2828
}
2929

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

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

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

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

4343
$oid_obj = New-Object System.Security.Cryptography.Oid($oid, "ASP.NET Core HTTPS development certificate")
4444
$ext = New-Object System.Security.Cryptography.X509Certificates.X509Extension($oid_obj, @(2), $false)

samples/SWA/SWAFramework/App_Start/BundleConfig.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ public class BundleConfig
88
public static void RegisterBundles(BundleCollection bundles)
99
{
1010
bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
11-
"~/lib/jquery/jquery-{version}.js"));
11+
"~/lib/jquery/jquery.js"));
1212

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

1717
// Use the development version of Modernizr to develop with and learn from. Then, when you're

src/C3D/Extensions/Aspire/IISExpress/ApplicationHostConfiguration/ApplicationHostConfiguration.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,8 @@ public class Binding
197197
public string BindingInformation { get; set; }
198198

199199
public int Port => int.Parse(BindingInformation.Split(':')[1]);
200+
201+
public string HostName => BindingInformation.Split(':')[2];
200202
}
201203

202204
[XmlRoot(ElementName = "bindings")]

src/C3D/Extensions/Aspire/IISExpress/IISEndPointConfigurator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ void SaveConfig()
113113
var matches = new Dictionary<EndpointAnnotation, Configuration.Binding>();
114114
foreach (var binding in siteConfig.Bindings.Binding)
115115
{
116-
var endpoint = project.AddOrUpdateEndpointFromBinding(binding);
116+
var endpoint = project.AddOrUpdateEndpointFromBinding(binding, logger);
117117
matches[endpoint] = binding;
118118
}
119119

src/C3D/Extensions/Aspire/IISExpress/IISExpressEntensions.cs

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,19 @@ public static void MarkPortAsUsed(int port)
207207
ap[port] = true;
208208
}
209209

210+
public static bool TryMarkPortAsUsed(int port)
211+
{
212+
ArgumentOutOfRangeException.ThrowIfLessThan(port, 1, nameof(port));
213+
ArgumentOutOfRangeException.ThrowIfGreaterThan(port, 65535, nameof(port));
214+
var ap = AllocatedPorts;
215+
if (ap[port])
216+
{
217+
return false; // Port is already allocated
218+
}
219+
ap[port] = true;
220+
return true; // Successfully marked the port as used
221+
}
222+
210223
public static int GetRandomFreePort(int minPort, int maxPort)
211224
{
212225
var ap = AllocatedPorts;
@@ -300,7 +313,7 @@ internal static Binding CreateIISConfigBinding(this EndpointAnnotation endpoint)
300313
return new Binding()
301314
{
302315
Protocol = endpoint.UriScheme,
303-
BindingInformation = $"*:{port}:localhost"
316+
BindingInformation = $"*:{port}:{endpoint.TargetHost}"
304317
};
305318
}
306319

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

325-
internal static EndpointAnnotation AddOrUpdateEndpointFromBinding(this IResourceWithEndpoints resource, Binding binding)
338+
internal static EndpointAnnotation AddOrUpdateEndpointFromBinding(this IResourceWithEndpoints resource, Binding binding, ILogger? logger = null)
326339
{
340+
var hostName = string.IsNullOrEmpty(binding.HostName) ? "localhost" : binding.HostName;
341+
327342
var endpoint = resource.Annotations
328343
.OfType<EndpointAnnotation>()
329-
.Where(ea => StringComparer.OrdinalIgnoreCase.Equals(ea.Name, binding.Protocol))
344+
.Where(ea => StringComparer.OrdinalIgnoreCase.Equals(ea.Name, binding.Protocol) &&
345+
StringComparer.OrdinalIgnoreCase.Equals(ea.TargetHost, hostName))
330346
.SingleOrDefault();
331347
if (endpoint is null)
332348
{
@@ -335,14 +351,27 @@ internal static EndpointAnnotation AddOrUpdateEndpointFromBinding(this IResource
335351
}
336352
else if (endpoint.TargetPort is not null && endpoint.TargetPort != binding.Port)
337353
{
338-
endpoint = new EndpointAnnotation(System.Net.Sockets.ProtocolType.Tcp, name: $"{binding.Protocol}-{binding.Port}");
354+
endpoint = new EndpointAnnotation(System.Net.Sockets.ProtocolType.Tcp, name: $"{binding.Protocol}-{binding.Port}-{hostName}");
339355
resource.Annotations.Add(endpoint);
340356
}
341-
MarkPortAsUsed(binding.Port);
357+
if (StringComparer.OrdinalIgnoreCase.Equals(hostName, "localhost"))
358+
{
359+
MarkPortAsUsed(binding.Port);
360+
}
361+
else
362+
{
363+
if (!TryMarkPortAsUsed(binding.Port))
364+
{
365+
logger?.LogWarning("Port {Port} is already allocated. Ensure each binding has a unique hostName.", binding.Port);
366+
}
367+
logger?.LogInformation("It is recommended to use 'localhost' with unique ports for IIS Express endpoints to avoid port conflicts.");
368+
// If the hostName is not localhost, we disable the proxy support so that SNI and host name routing works as expected.
369+
endpoint.IsProxied = false;
370+
}
342371

343372
endpoint.TargetPort = binding.Port;
373+
endpoint.TargetHost = hostName;
344374
endpoint.UriScheme = binding.Protocol;
345-
//endpoint.IsProxied = false;
346375

347376
return endpoint;
348377
}

tests/OutputWatcherTestProject/OutputWatcherTestProject.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<PrivateAssets>all</PrivateAssets>
1616
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1717
</PackageReference>
18-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
18+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
1919
<PackageReference Include="xunit" Version="2.9.3" />
2020
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.0">
2121
<PrivateAssets>all</PrivateAssets>

tests/SWATestProject/SWATestProject.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
<Nullable>enable</Nullable>
77
<IsPackable>false</IsPackable>
88
<IsTestProject>true</IsTestProject>
9+
<UserSecretsId>83318f29-3968-4859-87c3-84d87cec02fc</UserSecretsId>
910
</PropertyGroup>
1011

1112
<ItemGroup>
@@ -14,7 +15,7 @@
1415
<PrivateAssets>all</PrivateAssets>
1516
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1617
</PackageReference>
17-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
18+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
1819
<PackageReference Include="xunit" Version="2.9.3" />
1920
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.0">
2021
<PrivateAssets>all</PrivateAssets>

tests/SWATestProject/Tests/SWAIntegrationTests.cs

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1-
using Microsoft.Extensions.Logging;
2-
using System.Runtime.CompilerServices;
3-
using Xunit.Abstractions;
41
using Aspire.Hosting;
52
using Microsoft.Extensions.Configuration;
63
using Microsoft.Extensions.Hosting;
4+
using Microsoft.Extensions.Logging;
5+
using System.Runtime.CompilerServices;
6+
using System.Threading;
7+
using Xunit.Abstractions;
78

89
namespace SWATestProject.Tests;
910

1011
public class SWAIntegrationTests(ITestOutputHelper outputHelper)
1112
{
1213
private void WriteFunctionName([CallerMemberName] string? caller = null) => outputHelper.WriteLine(caller);
13-
private static readonly TimeSpan WaitForHealthyTimeout = TimeSpan.FromSeconds(90);
14+
private const int WaitForHealthyTimeoutSeconds = 90;
15+
private static readonly TimeSpan WaitForHealthyTimeout = TimeSpan.FromSeconds(WaitForHealthyTimeoutSeconds);
1416

1517
private async Task<IDistributedApplicationTestingBuilder> CreateAppHostAsync()
1618
{
@@ -26,6 +28,7 @@ private async Task<IDistributedApplicationTestingBuilder> CreateAppHostAsync()
2628

2729
//ConfigureOtlpOverHttp(host, outputHelper.WriteLine);
2830

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

4447
appHost.Services.ConfigureHttpClientDefaults(clientBuilder =>
4548
{
46-
49+
var timeout = TimeSpan.FromSeconds(appHost.Configuration.GetValue<double>("HttpClientTimeout", 30));
4750
clientBuilder
48-
.AddStandardResilienceHandler(res=> {
49-
res.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(120);
50-
res.CircuitBreaker.SamplingDuration = TimeSpan.FromSeconds(60);
51-
res.AttemptTimeout.Timeout = TimeSpan.FromSeconds(30);
51+
.AddStandardResilienceHandler(res =>
52+
{
53+
res.TotalRequestTimeout.Timeout = timeout * 4;
54+
res.CircuitBreaker.SamplingDuration = timeout * 2;
55+
res.AttemptTimeout.Timeout = timeout;
5256
});
5357
clientBuilder
5458
.ConfigurePrimaryHttpMessageHandler(() =>
@@ -130,10 +134,11 @@ public async Task GetFrameworkRootReturnsOkStatusCode()
130134
await using Aspire.Hosting.DistributedApplication app = await ArrangeAppHostAsync();
131135
var resourceNotificationService = app.Services.GetRequiredService<ResourceNotificationService>();
132136
await app.StartAsync();
137+
var timeout = TimeSpan.FromSeconds(app.Services.GetRequiredService<IConfiguration>().GetValue<double>("WaitForHealthyTimeoutSeconds", WaitForHealthyTimeoutSeconds));
133138

134139
// Act
135140
var httpClient = app.CreateHttpClient("framework", "http");
136-
await resourceNotificationService.WaitForResourceAsync("framework", KnownResourceStates.Running).WaitAsync(WaitForHealthyTimeout);
141+
await resourceNotificationService.WaitForResourceAsync("framework", KnownResourceStates.Running).WaitAsync(timeout);
137142
var response = await httpClient.GetAsync("/");
138143

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

153160
// Act
154161
var httpClient = app.CreateHttpClient("core", "https");
155-
await resourceNotificationService.WaitForResourceHealthyAsync("framework", WaitBehavior.StopOnResourceUnavailable).WaitAsync(TimeSpan.FromSeconds(90));
156-
await resourceNotificationService.WaitForResourceHealthyAsync("core", WaitBehavior.StopOnResourceUnavailable).WaitAsync(TimeSpan.FromSeconds(90));
162+
await resourceNotificationService.WaitForResourceHealthyAsync("framework", WaitBehavior.StopOnResourceUnavailable).WaitAsync(timeout);
163+
await resourceNotificationService.WaitForResourceHealthyAsync("core", WaitBehavior.StopOnResourceUnavailable).WaitAsync(timeout);
157164
var response = await httpClient.GetAsync("/");
158165

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

173181
// Act
174182
var httpClient = app.CreateHttpClient("framework", "http");
175-
await resourceNotificationService.WaitForResourceHealthyAsync("framework", WaitBehavior.StopOnResourceUnavailable).WaitAsync(WaitForHealthyTimeout);
176-
await resourceNotificationService.WaitForResourceHealthyAsync("core", WaitBehavior.StopOnResourceUnavailable).WaitAsync(WaitForHealthyTimeout);
183+
await resourceNotificationService.WaitForResourceHealthyAsync("framework", WaitBehavior.StopOnResourceUnavailable).WaitAsync(timeout);
184+
await resourceNotificationService.WaitForResourceHealthyAsync("core", WaitBehavior.StopOnResourceUnavailable).WaitAsync(timeout);
177185
var response = await httpClient.GetAsync("/framework");
178186

179187
// Assert
@@ -190,15 +198,16 @@ public async Task GetFrameworkHttpsWorksSometimes()
190198
await using Aspire.Hosting.DistributedApplication app = await appHost.BuildAsync();
191199
var resourceNotificationService = app.Services.GetRequiredService<ResourceNotificationService>();
192200
await app.StartAsync();
193-
201+
var timeout = TimeSpan.FromSeconds(app.Services.GetRequiredService<IConfiguration>().GetValue<double>("WaitForHealthyTimeoutSeconds", WaitForHealthyTimeoutSeconds));
202+
194203
var httpClient = app.CreateHttpClient("framework", "https");
195-
await resourceNotificationService.WaitForResourceHealthyAsync("framework", WaitBehavior.StopOnResourceUnavailable).WaitAsync(WaitForHealthyTimeout);
204+
await resourceNotificationService.WaitForResourceHealthyAsync("framework", WaitBehavior.StopOnResourceUnavailable).WaitAsync(timeout);
196205

197206
try
198207
{
199208
// Act
200209
var response = await httpClient.GetAsync("/");
201-
210+
202211
// Assert
203212
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
204213
}
@@ -207,7 +216,7 @@ public async Task GetFrameworkHttpsWorksSometimes()
207216
outputHelper.WriteLine(ex.Message);
208217

209218
// Fix
210-
var resource = app.Services.GetRequiredService<DistributedApplicationModel>().Resources.Single(r=>r.Name=="framework") as IResourceWithEndpoints;
219+
var resource = app.Services.GetRequiredService<DistributedApplicationModel>().Resources.Single(r => r.Name == "framework") as IResourceWithEndpoints;
211220
Assert.NotNull(resource);
212221
await resource.ExecuteFixHttpsCommand(app.Services);
213222

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

231242
// Act
232243
var httpClient = app.CreateHttpClient("core", "https");
233-
await resourceNotificationService.WaitForResourceHealthyAsync("framework", WaitBehavior.StopOnResourceUnavailable).WaitAsync(WaitForHealthyTimeout);
234-
await resourceNotificationService.WaitForResourceHealthyAsync("core", WaitBehavior.StopOnResourceUnavailable).WaitAsync(WaitForHealthyTimeout);
244+
await resourceNotificationService.WaitForResourceHealthyAsync("framework", WaitBehavior.StopOnResourceUnavailable).WaitAsync(timeout);
245+
await resourceNotificationService.WaitForResourceHealthyAsync("core", WaitBehavior.StopOnResourceUnavailable).WaitAsync(timeout);
235246
var response = await httpClient.GetAsync("/framework");
236247

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

251264
// Act
252265
var httpClient = app.CreateHttpClient("core", "https");
253-
await resourceNotificationService.WaitForResourceHealthyAsync("framework", WaitBehavior.StopOnResourceUnavailable).WaitAsync(WaitForHealthyTimeout);
254-
await resourceNotificationService.WaitForResourceHealthyAsync("core", WaitBehavior.StopOnResourceUnavailable).WaitAsync(WaitForHealthyTimeout);
266+
await resourceNotificationService.WaitForResourceHealthyAsync("framework", WaitBehavior.StopOnResourceUnavailable).WaitAsync(timeout);
267+
await resourceNotificationService.WaitForResourceHealthyAsync("core", WaitBehavior.StopOnResourceUnavailable).WaitAsync(timeout);
255268
var response = await httpClient.GetAsync("/core");
256269

257270
// Assert

0 commit comments

Comments
 (0)