Skip to content

Latest commit

 

History

History
217 lines (159 loc) · 7.37 KB

File metadata and controls

217 lines (159 loc) · 7.37 KB

? ClientUpdateService Dependency Injection Error Fixed

Problem

The application was failing at startup with the following error:

System.InvalidOperationException: A suitable constructor for type 'SecureBootWatcher.Client.Services.ClientUpdateService' could not be located.
Ensure the type is concrete and all parameters of a public constructor are either registered as services or passed as arguments.

Root Cause

In Program.cs, the ClientUpdateService was being registered twice in conflicting ways:

// Line 215 - Correct registration
services.AddSingleton<IClientUpdateService, ClientUpdateService>();

// Line 216 - INCORRECT registration
services.AddHttpClient<IClientUpdateService, ClientUpdateService>();

Why This Failed

The AddHttpClient<TClient, TImplementation>() method is used when you want the DI container to create an HTTP client that implements the interface. However, ClientUpdateService is not an HTTP client itself - it uses IHttpClientFactory to create HTTP clients.

The correct pattern is:

  1. Register IHttpClientFactory via AddHttpClient() (already done on line 205)
  2. Register ClientUpdateService as a singleton that consumes IHttpClientFactory

Solution

Removed the duplicate/incorrect registration:

Before (Broken)

services.AddHttpClient("SecureBootIngestion");

services.AddSecureBootWatcherOptions(configuration);

services.AddSingleton<IRegistrySnapshotProvider, RegistrySnapshotProvider>();
services.AddSingleton<IEventLogReader, EventLogReader>();
services.AddSingleton<IEventCheckpointStore, FileEventCheckpointStore>();
services.AddSingleton<ISecureBootCertificateEnumerator, PowerShellSecureBootCertificateEnumerator>();

// Register Client Update Service
services.AddSingleton<IClientUpdateService, ClientUpdateService>();  // ? Correct
services.AddHttpClient<IClientUpdateService, ClientUpdateService>(); // ? WRONG - Causes error

services.AddSingleton<IReportBuilder, ReportBuilder>();
services.AddSingleton<SecureBootWatcherService>();

After (Fixed)

services.AddHttpClient("SecureBootIngestion");

services.AddSecureBootWatcherOptions(configuration);

services.AddSingleton<IRegistrySnapshotProvider, RegistrySnapshotProvider>();
services.AddSingleton<IEventLogReader, EventLogReader>();
services.AddSingleton<IEventCheckpointStore, FileEventCheckpointStore>();
services.AddSingleton<ISecureBootCertificateEnumerator, PowerShellSecureBootCertificateEnumerator>();

// Register Client Update Service (needs IHttpClientFactory, so register after AddHttpClient)
services.AddSingleton<IClientUpdateService, ClientUpdateService>();  // ? Only registration needed

services.AddSingleton<IReportBuilder, ReportBuilder>();
services.AddSingleton<SecureBootWatcherService>();

ClientUpdateService Dependencies

The ClientUpdateService constructor requires three dependencies:

public ClientUpdateService(
    ILogger<ClientUpdateService> logger,           // ? Registered via AddLogging()
    IHttpClientFactory httpClientFactory,          // ? Registered via AddHttpClient()
    IOptions<SecureBootWatcherOptions> options)   // ? Registered via AddSecureBootWatcherOptions()
{
    _logger = logger;
    _httpClientFactory = httpClientFactory;
    _options = options;
}

All three dependencies are properly registered in the DI container, so the service can now be constructed successfully.

Verification

Build Status

Build succeeded.
    5 Warning(s)
    0 Error(s)

Runtime Test

[16:56:01 INF] ========================================
[16:56:01 INF] SecureBootWatcher Client Starting
[16:56:01 INF] ========================================
[16:56:01 INF] Version: 1.3.39+d1ccd76aa1
[16:56:01 INF] Base Directory: C:\Users\...\SecureBootWatcher.Client\bin\Debug\net48\
[16:56:01 INF] Log File Path: ...\logs\client-.log
[16:56:01 INF] Log Format: CMTrace
[16:56:01 INF] Rolling Interval: Day
[16:56:01 INF] Fleet ID: mslabs
[16:56:01 INF] Active Sinks: AzureQueue, WebApi

? Client starts successfully without DI errors!

Key Takeaways

? DO

  1. Use AddHttpClient() to register IHttpClientFactory
  2. Use AddSingleton<TInterface, TImplementation>() for services that consume IHttpClientFactory
  3. Inject IHttpClientFactory into your service constructor
  4. Create HTTP clients using _httpClientFactory.CreateClient() or _httpClientFactory.CreateClient("name")

? DON'T

  1. Don't use AddHttpClient<TClient, TImplementation>() for services that need IHttpClientFactory
  2. Don't register the same service twice with different methods
  3. Don't confuse "typed HTTP clients" with "services that use HTTP clients"

When to Use AddHttpClient<TClient, TImplementation>

Use AddHttpClient<TClient, TImplementation>() only when:

  • The client IS the service (not just uses HTTP)
  • The client's constructor only needs HttpClient (no other dependencies)

Example (not our case):

public class GitHubApiClient
{
    private readonly HttpClient _httpClient;
    
    public GitHubApiClient(HttpClient httpClient)  // Only HttpClient!
    {
        _httpClient = httpClient;
    }
}

// Registration
services.AddHttpClient<GitHubApiClient>(client =>
{
    client.BaseAddress = new Uri("https://api.github.com");
});

Our Pattern (Using IHttpClientFactory)

Our ClientUpdateService needs multiple dependencies, so we use the factory pattern:

public class ClientUpdateService : IClientUpdateService
{
    private readonly ILogger<ClientUpdateService> _logger;
    private readonly IHttpClientFactory _httpClientFactory;
    private readonly IOptions<SecureBootWatcherOptions> _options;
    
    public ClientUpdateService(
        ILogger<ClientUpdateService> logger,
        IHttpClientFactory httpClientFactory,  // Factory injected
        IOptions<SecureBootWatcherOptions> options)
    {
        _logger = logger;
        _httpClientFactory = httpClientFactory;
        _options = options;
    }
    
    public async Task<UpdateCheckResult> CheckForUpdateAsync(CancellationToken cancellationToken)
    {
        // Create HTTP client when needed
        var client = _httpClientFactory.CreateClient();
        client.BaseAddress = _options.Value.Sinks.WebApi.BaseAddress;
        
        // Use client...
    }
}

// Registration
services.AddHttpClient();  // Register factory
services.AddSingleton<IClientUpdateService, ClientUpdateService>();  // Register service

This pattern allows:

  • ? Multiple HTTP clients in the same service
  • ? Other dependencies (logger, options)
  • ? Full control over HTTP client configuration
  • ? Named HTTP clients if needed

Related Documentation


Fix Summary:

  • ? Removed duplicate AddHttpClient<IClientUpdateService, ClientUpdateService>() registration
  • ? Kept single AddSingleton<IClientUpdateService, ClientUpdateService>() registration
  • ? Client now starts without DI errors
  • ? ClientUpdateService properly constructed with all dependencies

Status: FIXED AND VERIFIED ?