Jds.DependencyInjection.AsyncExtras 0.1.0

dotnet add package Jds.DependencyInjection.AsyncExtras --version 0.1.0                
NuGet\Install-Package Jds.DependencyInjection.AsyncExtras -Version 0.1.0                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Jds.DependencyInjection.AsyncExtras" Version="0.1.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Jds.DependencyInjection.AsyncExtras --version 0.1.0                
#r "nuget: Jds.DependencyInjection.AsyncExtras, 0.1.0"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Jds.DependencyInjection.AsyncExtras as a Cake Addin
#addin nuget:?package=Jds.DependencyInjection.AsyncExtras&version=0.1.0

// Install Jds.DependencyInjection.AsyncExtras as a Cake Tool
#tool nuget:?package=Jds.DependencyInjection.AsyncExtras&version=0.1.0                

Dependency Injection Async Extras

Extensions to Microsoft.Extensions.DependencyInjection.Abstractions supporting resolving services asynchronously.

This project is not affiliated with Microsoft and asserts no claims upon its intellectual property.

Example Use Cases

Retrieving Remote Configuration

Configuration and options are not always applied by environment variables and command line arguments. Some applications need runtime configuration which is only available asynchronously, e.g., in a database or from a web server.

By registering an async service resolver this configuration can be retrieved reliably.

Asynchronous Initialization

Some objects are only partially ready for use when their constructor completes. These types often expose a method like public Task InitializeAsync() which must be executed and awaited before the object is ready to use. A common example is xUnit test classes, which often use IAsyncLifetime to provide an async initialization callback.

By registering an async service resolver you can ensure that all resolved instances of a dependency have already been asynchronously initialized before they're used.

Asynchronous Authentication/Authorization

Many web APIs require authentication to access. The authentication details are often provided as a token or cookie which must be sent with each request. However, authentication tokens and cookies are often not static; they must often be requested from an authorization server shortly before being presented to the web API.

By registering an async service resolver you can perform async requests for authorization tokens while creating "clients" for web APIs.

How to Use

Prerequisite: Install NuGet Package

Begin by installing the Dependency Injection Async Extras NuGet package in the target .NET project. Example using dotnet CLI:

dotnet add package Jds.DependencyInjection.AsyncExtras

Registering Async Services (e.g., in Startup.RegisterServices())

Async services are registered using the IServiceCollection extension method, TryAddAsyncResolver<T>(Func<IServiceProvider, Task<T>>, ServiceLifetime). This adds an IAsyncResolver<T> service registration.

Example Registering a Remote JSON Data Transfer Object

In this example, the following data transfer object is available, as JSON, from a web server.

public record SharedApplicationConfiguration { public bool EnableExperimentalFeatures { get; init; } }

In the code below we are working in an ASP.NET Core application. Specifically, we're updating its Startup class, implementing ConfigureServices(IServiceCollection).

The URL of the JSON data transfer object is provided in application configuration (via IConfiguration, from Microsoft.Extensions.Configuration.Abstractions). (For example, in ASP.NET Core applications, environment variables, command line arguments, and appsettings.json files provide the configuration, by default.)

// This `IServiceCollection` is assumed to have an `HttpClient` and `IConfiguration` registered prior to this code (but not shown).
IServiceCollection serviceCollection;

const string RemoteServerUrlConfigurationKey = "RemoteServerUrl";

serviceCollection.TryAddAsyncResolver<SharedApplicationConfiguration>(async serviceProvider =>
{
  // Get the remote server URL which will return the needed shared application configuration.
  string remoteUrl = serviceProvider.GetRequiredService<IConfiguration>().GetValue<string>(RemoteServerUrlConfigurationKey);
  // Get an HttpClient and retrieve the remote configuration JSON.
  SharedApplicationConfiguration? sharedConfiguration = await serviceProvider.GetRequiredService<HttpClient>()
    .GetFromJsonAsync<SharedApplicationConfiguration>(remoteUrl);
  // Return the remote configuration.
  return sharedConfiguration ?? new SharedApplicationConfiguration();
});

Using Async Services in Constructors

Async services are accessed in constructors by accepting an IAsyncResolver<T>, where T is the async service.

Example Resolving an Async Service in an ASP.NET Core Controller Constructor

In the example below, continuing the remote SharedApplicationConfiguration example above, an API Controller uses the async service we registered above, SharedApplicationConfiguration. The asynchronous value may be accessed by depending upon an IAsyncResolver<> of the async service, e.g., IAsyncResolver<SharedApplicationConfiguration>. That resolver provides the asynchronous service using its GetValueAsync() method.

[ApiController]
[Route("[controller]")]
public class ExperimentalController : ControllerBase
{
    private readonly IAsyncResolver<SharedApplicationConfiguration> _sharedConfigurationResolver;

    public ExperimentalController(IAsyncResolver<SharedApplicationConfiguration> sharedConfigurationResolver)
    {
        _sharedConfigurationResolver = sharedConfigurationResolver;
    }

    [HttpGet(Name = "SecretFeature")]
    public async Task<IActionResult> GetSecretFeature()
    {
        SharedApplicationConfiguration sharedConfiguration = await _sharedConfigurationResolver.GetValueAsync();
        return sharedConfiguration.EnableExperimentalFeatures
            ? Ok()
            : NotFound();
    }
}

Using Async Services with an IServiceProvider

Async services are accessed from IServiceProvider instances by extension method: GetServiceAsync<T>() and GetRequiredServiceAsync<T>(). (The IAsyncResolver<T> can be resolved directly from the IServiceProvider, like constructor example above. The extension methods allow direct access.)

Example Resolving an Async Service in an ASP.NET Core IHostedService

In the example below, continuing the remote SharedApplicationConfiguration example above, an IHostedService uses the async service we registered above, SharedApplicationConfiguration. The asynchronous value may be accessed directly from an IServiceProvider using an extension method, GetServiceAsync<>().

Use of an IServiceProvider dependency, rather than an explicit dependency, is often used when custom service resolution "scopes" are required. E.g., to resolve scoped services.

public class ExperimentalFeaturesInitializer : BackgroundService
{
    private readonly IServiceProvider _serviceProvider;

    public ExperimentalFeaturesInitializer(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        var logger = _serviceProvider.GetRequiredService<ILogger<ExperimentalFeaturesInitializer>>();

        var sharedConfiguration = await _serviceProvider.GetRequiredServiceAsync<SharedApplicationConfiguration>();

        if (sharedConfiguration.EnableExperimentalFeatures)
        {
            logger.LogInformation("Experimental features are enabled.");
        }
    }
}
Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.1.0 280 2/7/2023
0.1.0-build-20230128-220820... 177 1/28/2023