dotnet / dotnet/extensions

ServiceDescriptor Properties Are Null

Open
#6,375 1 comment 0 reactions 0 assignees View on GitHub
area-resilience bug untriaged
Dominant language
C#
Stars
3.2k
Forks
894
Avg merge
1d 12h
Merged PRs (30d)
23

Description

### Description

ServiceDescriptor properties are null when adding service. This can create null reference exceptions when other services check service descriptor properties if a service needs to be added.

For example the services.AddApplicationInsightsTelemetryWorkerService(); will throw this exception if adding the httpClientCustomSettings before adding the app insights telemetry worker service.

This bug however does NOT happen when using the default function

Null Reference Exception happens here in the Microsoft.Extrensions.DependencyInjection.ApplicationInsightsExtensions

![Image](https://github.com/user-attachments/assets/f90aeabd-160c-4d6e-916b-6b85d84fa20a)

` services.AddHttpClient();
services.ConfigureHttpClientDefaults(builder =>
{
var appSettings = context.Configuration.GetSection($"{nameof(AppSettings)}:{nameof(AppSettings.Resilience)}");
builder.AddStandardResilienceHandler(options =>
{
var timeoutString = appSettings[nameof(AppSettings.Resilience.Timeout)] ?? "60";
var timeout = timeoutString.ParseOrDefault(60);

options.TotalRequestTimeout = new HttpTimeoutStrategyOptions
{
Timeout = TimeSpan.FromSeconds(timeout)
};

options.AttemptTimeout = new HttpTimeoutStrategyOptions
{
Timeout = TimeSpan.FromSeconds(timeout)
};

var maxRetry = appSettings[nameof(AppSettings.Resilience.MaxRetry)] ?? "5";
var maxDelay = appSettings[nameof(AppSettings.Resilience.MaxDelay)] ?? "10";
var delay = appSettings[nameof(AppSettings.Resilience.Delay)] ?? "3";

options.Retry = new HttpRetryStrategyOptions
{
MaxRetryAttempts = maxRetry.ParseOrDefault(5),
Delay = TimeSpan.FromSeconds(delay.ParseOrDefault(3)),
MaxDelay = TimeSpan.FromSeconds(maxDelay.ParseOrDefault(10)),
UseJitter = true
};

options.CircuitBreaker = new HttpCircuitBreakerStrategyOptions()
{
// needs to be at least double the timeout interval
SamplingDuration = TimeSpan.FromSeconds(120)
};
});
});
services.AddApplicationInsightsTelemetryWorkerService();
services.ConfigureFunctionsApplicationInsights();`

### Reproduction Steps

`using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Http.Resilience;

var host = new HostBuilder()
.ConfigureFunctionsWebApplication()
.ConfigureAppConfiguration((_, config) =>
{
config.AddJsonFile(Path.Combine(Directory.GetCurrentDirectory(), "local.settings.json"), true, true);
})
.ConfigureServices((context, services) =>
{
services.AddHttpClient();
services.ConfigureHttpClientDefaults(builder =>
{
var appSettings = context.Configuration.GetSection($"{nameof(AppSettings)}:{nameof(AppSettings.Resilience)}");
builder.AddStandardResilienceHandler(options =>
{
var timeoutString = appSettings[nameof(AppSettings.Resilience.Timeout)] ?? "60";
var timeout = timeoutString.ParseOrDefault(60);

options.TotalRequestTimeout = new HttpTimeoutStrategyOptions
{
Timeout = TimeSpan.FromSeconds(timeout)
};

options.AttemptTimeout = new HttpTimeoutStrategyOptions
{
Timeout = TimeSpan.FromSeconds(timeout)
};

var maxRetry = appSettings[nameof(AppSettings.Resilience.MaxRetry)] ?? "5";
var maxDelay = appSettings[nameof(AppSettings.Resilience.MaxDelay)] ?? "10";
var delay = appSettings[nameof(AppSettings.Resilience.Delay)] ?? "3";

options.Retry = new HttpRetryStrategyOptions
{
MaxRetryAttempts = maxRetry.ParseOrDefault(5),
Delay = TimeSpan.FromSeconds(delay.ParseOrDefault(3)),
MaxDelay = TimeSpan.FromSeconds(maxDelay.ParseOrDefault(10)),
UseJitter = true
};

options.CircuitBreaker = new HttpCircuitBreakerStrategyOptions()
{
// needs to be at least double the timeout interval
SamplingDuration = TimeSpan.FromSeconds(120)
};
});
});
services.AddApplicationInsightsTelemetryWorkerService();
services.ConfigureFunctionsApplicationInsights();
})
.Build();

host.Run();
`

### Expected behavior

Should be able to add services regardless of order and be able to configure the resilience service however as intended without service descriptor properties ever being null

### Actual behavior

Service Descriptor properties are null and create null reference exceptions when injecting other services...
Please populate values for the following properties:
ServiceDescriptor.ImplementationFactory
ServiceDescriptor.ImplementationType
ServiceDescriptor.ImplementationInstance

### Regression?

_No response_

### Known Workarounds

Change order of adding services

### Configuration

.NET 8 in Isolated Azure Function

### Other information

_No response_

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.