Skip to content

Commit 1bea07e

Browse files
authored
improving idempotency of registration methods for AspNet and App Insights (#3273)
1 parent 51bc63e commit 1bea07e

File tree

6 files changed

+72
-9
lines changed

6 files changed

+72
-9
lines changed

extensions/Worker.Extensions.Http.AspNetCore/src/FunctionsHostBuilderExtensions.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

44
using System;
@@ -18,6 +18,8 @@ namespace Microsoft.Extensions.Hosting
1818
/// </summary>
1919
public static class FunctionsHostBuilderExtensions
2020
{
21+
private const string AspNetCoreIntegrationConfiguredKey = "__FunctionsAspNetCoreConfigured";
22+
2123
/// <summary>
2224
/// Configures the worker to use the ASP.NET Core integration, enabling advanced HTTP features.
2325
/// </summary>
@@ -63,6 +65,16 @@ public static IHostBuilder ConfigureFunctionsWebApplication(this IHostBuilder bu
6365

6466
internal static IHostBuilder ConfigureAspNetCoreIntegration(this IHostBuilder builder)
6567
{
68+
if (builder.Properties.TryGetValue(AspNetCoreIntegrationConfiguredKey, out var alreadyConfiguredObj) &&
69+
alreadyConfiguredObj is bool alreadyConfigured &&
70+
alreadyConfigured)
71+
{
72+
// Already configured, don't do it twice
73+
return builder;
74+
}
75+
76+
builder.Properties[AspNetCoreIntegrationConfiguredKey] = true;
77+
6678
builder.ConfigureServices(services =>
6779
{
6880
services.AddSingleton<FunctionsEndpointDataSource>();

extensions/Worker.Extensions.Http.AspNetCore/src/WorkerBuilderExtensions.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Linq;
56
using Microsoft.AspNetCore.Builder;
67
using Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore;
78
using Microsoft.Extensions.DependencyInjection;
@@ -22,9 +23,12 @@ internal static class WorkerBuilderExtensions
2223
/// <exception cref="ArgumentNullException"></exception>
2324
internal static IFunctionsWorkerApplicationBuilder UseAspNetCoreIntegration(this IFunctionsWorkerApplicationBuilder builder)
2425
{
25-
if (builder is null)
26+
ArgumentNullException.ThrowIfNull(builder, nameof(builder));
27+
28+
// Check if already configured by looking for our middleware
29+
if (builder.Services.Any(d => d.ImplementationType == typeof(FunctionsHttpProxyingMiddleware)))
2630
{
27-
throw new ArgumentNullException(nameof(builder));
31+
return builder;
2832
}
2933

3034
builder.UseMiddleware<FunctionsHttpProxyingMiddleware>();
@@ -36,7 +40,7 @@ internal static IFunctionsWorkerApplicationBuilder UseAspNetCoreIntegration(this
3640
builder.Services.Configure<WorkerOptions>((workerOption) =>
3741
{
3842
workerOption.InputConverters.RegisterAt<HttpContextConverter>(0);
39-
workerOption.Capabilities.Add(Constants.HttpUriCapability, HttpUriProvider.HttpUriString);
43+
workerOption.Capabilities[Constants.HttpUriCapability] = HttpUriProvider.HttpUriString;
4044
});
4145

4246
return builder;

src/DotNetWorker.ApplicationInsights/FunctionsApplicationInsightsExtensions.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

44
using System;
@@ -26,6 +26,12 @@ public static IServiceCollection ConfigureFunctionsApplicationInsights(this ISer
2626
throw new ArgumentNullException(nameof(services));
2727
}
2828

29+
// Check if already configured by looking for our validation service
30+
if (services.Any(d => d.ImplementationType == typeof(ApplicationInsightsValidationService)))
31+
{
32+
return services;
33+
}
34+
2935
services.ConfigureOptions<TelemetryConfigurationSetup>();
3036
services.AddSingleton<IConfigureOptions<AppServiceOptions>, AppServiceOptionsInitializer>();
3137
services.AddSingleton<AppServiceEnvironmentVariableMonitor>();

src/DotNetWorker.ApplicationInsights/release_notes.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
### Microsoft.Azure.Functions.Worker.ApplicationInsights <version>
44

55
- Updating `Azure.Identity` from 1.12.0 to 1.17.0
6-
- Updating `Microsoft.ApplicationInsights.PerfCounterCollector` from 2.22.0 to 2.23.0
6+
- Updating `Microsoft.ApplicationInsights.PerfCounterCollector` from 2.22.0 to 2.23.0
7+
- Improved idempotency of service registration calls (#3273)

test/DotNetWorker.Tests/ApplicationInsights/ApplicationInsightsConfigurationTests.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33
using System.Linq;
44
using Microsoft.ApplicationInsights.Extensibility;
@@ -62,6 +62,8 @@ private static void Verify(IHostBuilder builder)
6262

6363
builder.Build();
6464
Assert.Contains(typeof(FunctionsTelemetryInitializer), initializers);
65+
Assert.Equal(6, initializers.Count());
6566
Assert.Contains(typeof(FunctionsTelemetryModule), modules);
67+
Assert.Equal(8, modules.Count());
6668
}
6769
}

test/extensions/Worker.Extensions.Http.AspNetCore.Tests/FunctionsHostBuilderExtensionsTests.cs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

44
using Microsoft.Azure.Functions.Worker;
@@ -58,6 +58,44 @@ public void ConfigureFunctionsWebApplication_ShouldConfigureFunctionsWebApplicat
5858
VerifyRegistrationOfAspNetCoreIntegrationServices(host);
5959
}
6060

61+
[Fact]
62+
public void ConfigureFunctionsWebApplication_CalledTwice_ShouldBeIdempotent()
63+
{
64+
var builder = new HostBuilder();
65+
66+
int callbackCount = 0;
67+
68+
// Call ConfigureFunctionsWebApplication twice but should call callbacks each time
69+
builder.ConfigureFunctionsWebApplication((_, _) => callbackCount++);
70+
builder.ConfigureFunctionsWebApplication((_, _) => callbackCount++);
71+
72+
IServiceCollection serviceCollection = default!;
73+
builder.ConfigureServices(services =>
74+
{
75+
serviceCollection = services;
76+
});
77+
78+
var host = builder.Build();
79+
80+
// Verify services are registered
81+
VerifyRegistrationOfAspNetCoreIntegrationServices(host);
82+
83+
Assert.Equal(2, callbackCount);
84+
85+
ValidateSingleServiceType(serviceCollection, typeof(FunctionsHttpProxyingMiddleware));
86+
ValidateSingleServiceType(serviceCollection, typeof(IHttpCoordinator));
87+
ValidateSingleServiceType(serviceCollection, typeof(FunctionsEndpointDataSource));
88+
89+
static void ValidateSingleServiceType(IServiceCollection services, Type type)
90+
{
91+
Assert.NotNull(services);
92+
var coordinatorDescriptors = services
93+
.Where(d => d.ServiceType == type)
94+
.ToList();
95+
Assert.Single(coordinatorDescriptors);
96+
}
97+
}
98+
6199
private static void VerifyRegistrationOfCustomMiddleware(IHost host)
62100
{
63101
Assert.NotNull(host.Services.GetService<TestMiddleware>());

0 commit comments

Comments
 (0)