API connectors
Overview
The API connectors provide the easy way how to connect backend services using REST API on .NET platform. The AVAnative backend services should provide REST API connector NuGet package to seamless integration.
-
Token providers are providing access tokens for authentication and authorization.
- ImpersonatedTokenProvider - provides token for impersonated context
- ConsumerContextTokenProvider - provides token based on consumer context for messaging consumer
- ConnectorTokenForwardProvider - provides token for user context / user account forwarded from frontend application
- ClientTokenProvider - provides token for client context / service account
- CombinedClientTokenProvider - combines other token providers to chose the appropriate one based on runtime context
-
Header providers are providing headers for API calls.
- RequestSecurityContextHeaderProvider - provides headers based on security context
- LocalizationContextHeaderProvider - provides headers based on localization context
- TraceContextHeaderProvider - provides headers based on trace context
The core package:
ASOL.Core.ApiConnector
Usage
Registration of API clients
The registration of API clients into dependency container. You can use the local impersonation scope to configure the combined token provider dynamically via runtime context. You can also configure API client for a specific token provider during registration using extension method.
using ASOL.Core.ApiConnector.Extensions;
namespace My.App.API
{
/// <summary>
/// Dependency injection with API clients to allow communication with other services.
/// </summary>
public static class StartupApiClients
{
/// <summary>
/// Registration of API clients.
/// </summary>
/// <param name="services">Service collection</param>
/// <param name="configuration">Configuration</param>
/// <returns>Service collection</returns>
public static IServiceCollection AddApiClients(this IServiceCollection services, IConfiguration configuration)
{
//(mandatory) registration of header providers and token providers
services.AddApiConnectorInfrastructure();
//(optional) registration of ApiClientHelper
services.AddApiClientHelper();
//example1 - connector to MessageProvider service - NuGet ASOL.MessageProvider.Connector
//note: the default IConnectorTokenProvider is used (e.g. CombinedClientTokenProvider)
services.Configure<MessageProviderClientOptions>(configuration.GetSection(nameof(MessageProviderClientOptions)));
services.AddMessageProviderClient();
//example2 - connector to ContentManager service - NuGet ASOL.ContentManager.Connector
//note: the specific token provider is used (e.g. ClientTokenProvider)
services.Configure<ContentManagerClientOptions>(configuration.GetSection(nameof(ContentManagerClientOptions)));
services.AddContentManagerClient(sp => sp.GetRequiredService<ClientTokenProvider>());
return services;
}
}
}
Extending of API clients
Add API method or endpoint
You can inherit API client and extend its interface with additional methods or endpoints when standard API client doesn't provide requested functionality.
using ASOL.SubjectManager.Connector;
/// <summary>
/// Represents the custom SubjectManager client.
/// </summary>
public interface ICustomSubjectManagerClient : ISubjectManagerClient
{
/// <inheritdoc cref="ISubjectManagerClientMyFavoriteEndpoint"/>
ISubjectManagerClientMyFavoriteEndpoint MyFavorites { get; }
/// <summary>
/// Get my favorite data by code.
/// </summary>
/// <param name="code">The code identifier</param>
/// <param name="acceptNotFound">The flag if not found response (404) is accepted or not</param>
/// <param name="ct">The cancellation token</param>
/// <returns>The my favorite data or null</returns>
Task<MyFavoriteData?> GetMyFavoriteDataByCodeAsync(string code, bool acceptNotFound = false, CancellationToken ct = default);
}
/// <summary>
/// Represents the specific SubjectManager client endpoint for my favorite data.
/// </summary>
public interface ISubjectManagerClientMyFavoriteEndpoint
{
/// <summary>
/// Get my favorite data by code.
/// </summary>
/// <param name="code">The code identifier</param>
/// <param name="acceptNotFound">The flag if not found response (404) is accepted or not</param>
/// <param name="ct">The cancellation token</param>
/// <returns>The my favorite data or null</returns>
Task<MyFavoriteData?> GetDataByCodeAsync(string code, bool acceptNotFound = false, CancellationToken ct = default);
}
using ASOL.Core.ApiConnector;
using ASOL.SubjectManager.Connector;
using ASOL.SubjectManager.Connector.Options;
using ASOL.SubjectManager.Contracts;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Pathoschild.Http.Client;
/// <inheritdoc cref="ICustomSubjectManagerClient"/>
public partial class CustomSubjectManagerClient : SubjectManagerClient, ICustomSubjectManagerClient
{
public CustomSubjectManagerClient(
IOptions<SubjectManagerClientOptions> options,
ILogger<SubjectManagerClient> logger,
IConnectorTokenProvider tokenProvider,
IEnumerable<IRequestHeaderProvider> headerProviders)
: base(options, logger, tokenProvider, headerProviders)
{
}
/// <inheritdoc/>
public async Task<MyFavoriteData?> GetMyFavoriteDataByCodeAsync(string code, bool acceptNotFound = false, CancellationToken ct = default)
{
if (string.IsNullOrEmpty(code)) throw new ArgumentNullException(nameof(code));
ct.ThrowIfCancellationRequested();
var resource = $"{ApiVersionPrefix}/Persons/{Uri.EscapeDataString(code)}";
Logger.LogDebug($"Retrieving my favorite data by code from {CombineUri(resource)}");
var request = (await AddAuthenticationAndHeaders(Client.GetAsync(resource), ct))
.WithCancellationToken(ct);
var result = await GetSingleResultAsync<PersonModel>(request, acceptNotFound, ct);
return ConvertToMyFavoriteData(result);
}
}
partial class CustomSubjectManagerClient : ISubjectManagerClientMyFavoriteEndpoint
{
async Task<MyFavoriteData?> ISubjectManagerClientMyFavoriteEndpoint.GetDataByCodeAsync(string code, bool acceptNotFound, CancellationToken ct)
{
if (string.IsNullOrEmpty(code)) throw new ArgumentNullException(nameof(code));
ct.ThrowIfCancellationRequested();
var resource = $"{ApiVersionPrefix}/Persons/{Uri.EscapeDataString(code)}";
Logger.LogDebug($"Retrieving my favorite data by code from {CombineUri(resource)}");
var request = (await AddAuthenticationAndHeaders(Client.GetAsync(resource), ct))
.WithCancellationToken(ct);
var result = await GetSingleResultAsync<PersonModel>(request, acceptNotFound, ct);
return ConvertToMyFavoriteData(result);
}
}
using ASOL.Core.ApiConnector.Extensions;
namespace My.App.API
{
/// <summary>
/// Dependency injection with API clients to allow communication with other services.
/// </summary>
public static class StartupApiClients
{
/// <summary>
/// Registration of API clients.
/// </summary>
/// <param name="services">Service collection</param>
/// <param name="configuration">Configuration</param>
/// <returns>Service collection</returns>
public static IServiceCollection AddApiClients(this IServiceCollection services, IConfiguration configuration)
{
//(mandatory) registration of header providers and token providers
services.AddApiConnectorInfrastructure();
//(optional) registration of ApiClientHelper
services.AddApiClientHelper();
//example3 - custom connector to SubjectManager service - NuGet ASOL.SubjectManager.Connector
services.Configure<SubjectManagerClientOptions>(configuration.GetSection(nameof(SubjectManagerClientOptions)));
services.AddCustomSubjectManagerClient();
return services;
}
/// <summary>
/// Represents the custom SubjectManager client extension methods for service collection.
/// </summary>
/// <param name="services">The services collection.</param>
/// <param name="options">The options for SubjectManager client. (optional)</param>
/// <returns>The services collection. (fluent API)</returns>
public static IServiceCollection AddCustomSubjectManagerClient(this IServiceCollection services)
{
services.AddScoped<ISubjectManagerClient>(sp => sp.GetRequiredService<ICustomSubjectManagerClient>());
services.AddScoped<ICustomSubjectManagerClient, CustomSubjectManagerClient>();
return services;
}
}
}
Assign a token provider
You can inherit API client and assign a specific token provider directly into custom API client.
using ASOL.Core.ApiConnector;
using ASOL.SubjectManager.Connector;
using ASOL.SubjectManager.Connector.Options;
using ASOL.SubjectManager.Contracts;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Pathoschild.Http.Client;
/// <inheritdoc cref="ICustomSubjectManagerClient"/>
public class CustomSubjectManagerClient : SubjectManagerClient
{
public CustomSubjectManagerClient(
IOptions<SubjectManagerClientOptions> options,
ILogger<SubjectManagerClient> logger,
ClientTokenProvider tokenProvider, // <-- the specific token provider
IEnumerable<IRequestHeaderProvider> headerProviders)
: base(options, logger, tokenProvider, headerProviders)
{
}
}
Implementation
Extension method to register API client
using ASOL.Core.ApiConnector;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace My.App.Connector.Extensions
{
/// <summary>
/// Represents the MyApp connector extension methods for service collection.
/// </summary>
public static class MyAppConnectorServiceCollectionExtensions
{
/// <summary>
/// Represents the MyApp client extension methods for service collection.
/// </summary>
/// <param name="services">The services collection</param>
/// <param name="customTokenProvider">The custom token provider (optional)</param>
/// <param name="options">The options for MyApp client (optional)</param>
/// <returns>The services collection (fluent api)</returns>
public static IServiceCollection AddMyAppClient(this IServiceCollection services, Func<IServiceProvider, IConnectorTokenProvider> customTokenProvider = null, Action<MyAppClientOptions> options = null)
{
services.AddScoped<IMyAppClient>(sp =>
{
var logger = sp.GetRequiredService<ILogger<MyAppClient>>();
var tokenProvider = customTokenProvider?.Invoke(sp) ?? sp.GetRequiredService<IConnectorTokenProvider>();
var headerProviders = sp.GetServices<IRequestHeaderProvider>();
IOptions<MyAppClientOptions> opt;
if (options == null)
{
opt = sp.GetRequiredService<IOptions<MyAppClientOptions>>();
}
else
{
var optData = MyAppClientOptions.Default;
options(optData);
opt = Options.Create(optData);
}
return new MyAppClient(opt, logger, tokenProvider, headerProviders);
});
return services;
}
}
}