Skip to main content

Migration Guides

v10.2.0 → v10.2.1​

v10.2.1 removes the ProxySettings class. v10.2.0 had already removed the built-in proxy pipeline (ProxyMessageHandler, WithProxyFromSettings<TSettings>(...)); the ProxySettings POCO itself is now gone too.

What changed​

Aspectv10.2.0v10.2.1
ProxySettingsconfiguration-only POCO (not consumed by the package)removed — define your own options type
UseProxy flag on IIntegrationSettings/BaseApiSettings<T>unconsumedstill present, still unconsumed

Migration Steps​

Replace the ProxySettings property on your settings class with your own options POCO and wire the primary handler explicitly.

Before (v10.2.0):

public class MyApiSettings : BaseApiSettings<MyApiActions>
{
public ProxySettings? Proxy { get; set; }
}

After (v10.2.1):

public sealed class ProxyOptions
{
[Required, Url] public string Address { get; set; } = default!;
public string? Username { get; set; }
public string? Password { get; set; }
public bool BypassProxyOnLocal { get; set; }
}

public class MyApiSettings : BaseApiSettings<MyApiActions>
{
public ProxyOptions? Proxy { get; set; }
}
builder.Services.AddHttpClient("MyApi")
.ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler
{
Proxy = new WebProxy(proxyOptions.Address),
UseProxy = true
});

v10.1.1 → v10.2.0​

v10.2.0 consolidates the registration API around C# 14 extension members — one extension class per receiver type — and removes the built-in API key and proxy machinery. Extension-method call sites (services.AddHttpClientIntegration(...), builder.AddLoggingHandler(), ...) are unchanged; only class-name references and the removed members need attention.

Why this change?​

  • DependencyInjectionExtensions mixed IServiceCollection methods and IHttpClientBuilder methods in one class; the split (ServiceCollectionExtensions + HttpClientBuilderExtensions) follows the ASP.NET Core naming convention and matches how C# 14 extension blocks group members by receiver type.
  • The API key and proxy handlers (ApiKeyAuthenticationHandler<TSettings>, ProxyMessageHandler, and their With* registration methods) saw little use and are straightforward to replace with a custom DelegatingHandler when needed.

What changed​

Aspectv10.1.1v10.2.0
Registration classDependencyInjectionExtensions (all methods)ServiceCollectionExtensions (Add* methods) + HttpClientBuilderExtensions (pipeline methods)
Method styleclassic static extension methodsC# 14 extension members — call sites unchanged
Attach logging handlerWithHttpLoggingHandler()AddLoggingHandler()
Attach header forwardingWithInternalHeadersForwardingHandler()AddInternalHeadersForwardingHandler()
Logging handler typeRequestResponseLoggingHandlerLoggingHandler (same behavior)
Request options utilitiesHttpRequestMessageExtensionsHttpRequestOptionsExtensions (GetClientIpAddress() only)
API key supportIApiKeySettings, ApiKeyAuthenticationHandler<TSettings>, WithApiKey(...), WithApiKeyFromSettingsHandler<TSettings>()removed — use a custom DelegatingHandler or ConfigureHttpClient
Proxy supportIProxySettings, ProxyMessageHandler, WithProxyFromSettings<TSettings>(...), ProxySettings : IProxySettingsremoved — ProxySettings remains a configuration-only POCO; use ConfigurePrimaryHttpMessageHandler for a proxy
InternalHeadersForwardHandlercaptured headers once in the constructorreads Authorization / x-request-id per request (no code change needed)

Migration Steps​

1. Update class-name references​

Only code that references a class by name — a typeof(...) expression, a static call, or a reflection string — needs updating. Receiver-style calls such as services.AddHttpClientIntegration(...) keep working unchanged.

// ❌ v10.1.1
var t = typeof(DependencyInjectionExtensions);

// ✅ v10.2.0 — pick the class that holds the members you reference
var t = typeof(ServiceCollectionExtensions); // AddHttpClientIntegration, Add*HttpMessageHandler
var b = typeof(RA.Utilities.Integrations.Extensions.HttpClientBuilderExtensions); // AddLoggingHandler, AddInternalHeadersForwardingHandler
note

HttpClientBuilderExtensions shares its name with Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions (the built-in AddHttpMessageHandler<T>/ConfigureHttpClient class). When both namespaces are imported, reference it fully qualified in typeof(...) or static calls. Receiver-style calls such as builder.AddLoggingHandler() are unaffected.

2. Rename the pipeline attachment methods​

// ❌ v10.1.1
services.AddHttpClientIntegration<IMyApiClient, MyApiClient, MyApiSettings>(section)
.WithHttpLoggingHandler()
.WithInternalHeadersForwardingHandler();

// ✅ v10.2.0
services.AddHttpClientIntegration<IMyApiClient, MyApiClient, MyApiSettings>(section)
.AddLoggingHandler()
.AddInternalHeadersForwardingHandler();

3. Rename the logging handler type​

// ❌ v10.1.1
services.AddScopedHttpMessageHandler<RequestResponseLoggingHandler>();

// ✅ v10.2.0
services.AddScopedHttpMessageHandler<LoggingHandler>();

Manual constructions update the generic type argument: new LoggingHandler(logger) where logger is an ILogger<LoggingHandler>.

4. Replace API key and proxy usage​

The API key and proxy members were removed without built-in replacements:

// ❌ v10.1.1 — removed
builder.WithApiKey("my-secret-api-key");
builder.WithApiKeyFromSettingsHandler<MyApiSettings>();
builder.WithProxyFromSettings(settings => settings.Proxy);

// ✅ v10.2.0 — static API key via default request headers
builder.ConfigureHttpClient(client => client.DefaultRequestHeaders.Add("X-Api-Key", "my-secret-api-key"));

// ✅ v10.2.0 — proxy via the primary handler
builder.ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler
{
Proxy = new WebProxy("http://proxy.server:8080", BypassOnLocal: true)
{
Credentials = new NetworkCredential("proxy-user", "proxy-password")
}
});

For dynamic, per-request API keys, write a small DelegatingHandler that adds the header in SendAsync and attach it with AddHttpMessageHandler<T>() / AddTransientHttpMessageHandler<T>(). Settings classes no longer implement IApiKeySettings/IProxySettings — the ApiKey/Proxy properties themselves remain plain, bindable options properties.

5. Optional: adopt the request options utility rename​

request.Options.GetClientIpAddress() works identically in both versions — only class-name references change (HttpRequestMessageExtensions → HttpRequestOptionsExtensions). The ToDictionary(...) helper was removed; inline headers?.ToDictionary(h => h.Key, h => string.Join(",", h.Value)) where needed.


v10.0.1 → v10.1.0​

v10.1.0 reworks the BaseHttpClient request surface: headers are now strongly typed, all four HTTP verbs are supported, JSON is the fixed content type, and query/header parameter mappings can be generated at compile time. Most call sites need small mechanical updates; none of the changes affect how the HTTP requests themselves are sent.

Why this change?​

  • Header dictionaries (Dictionary<string, string>) offered no compile-time safety and allowed typos in header names.
  • The settings model carried MediaType/Encoding, but JSON was the only supported format anyway.
  • Query parameter models had to hand-write the IQueryStringRequest contract; a source generator now does that work.

What changed​

Aspectv10.0.1v10.1.0
HTTP verbsGetAsync, PostAsync, PutAsync+ DeleteAsync
Headers parameterDictionary<string, string>?IHeaderRequest?
Header classesn/a[HeaderParameters] + generated IHeaderRequest
Query classeshand-written IQueryStringRequest[QueryParameters] + generated IQueryStringRequest (hand-written still works)
Settings media typeIIntegrationSettings.MediaType / Encodingremoved — JSON only
Typed responsesTask<TResponse>Task<TResponse?> (null for empty bodies)
AddOptionWithValidations(...)availableremoved
Query string extensionDictionaryExtensions.ToQueryString(this QueryParams)removed — use QueryUtilities.ToQueryString(QueryParams?)

Migration Steps​

1. Replace header dictionaries with header classes​

Before (v10.0.1):

var headers = new Dictionary<string, string>
{
["x-request-id"] = "trace-1"
};

var product = await client.GetAsync<Product>("products/1", headers: headers);

After (v10.1.0):

using RA.Utilities.Integrations.Attributes;

[HeaderParameters]
public partial class RequestHeaders
{
[HeaderParameterName("x-request-id")]
public string? XCorrelationId { get; init; }
}

var product = await client.GetAsync<Product>(
"products/1",
headers: new RequestHeaders { XCorrelationId = "trace-1" });

2. Mark query parameter classes with [QueryParameters]​

Hand-written IQueryStringRequest implementations still work, but marked classes get the mapping generated:

Before (v10.0.1):

public class GetProductsQuery : IQueryStringRequest
{
public int? CategoryId { get; set; }

public QueryParams QueryStringValues()
{
var query = new QueryParams();
if (CategoryId is not null)
{
query.Add(nameof(CategoryId), CategoryId.Value.ToString());
}
return query;
}
}

After (v10.1.0):

[QueryParameters]
public partial class GetProductsQuery
{
public int? CategoryId { get; set; }
}

The class must be partial (and so must all of its containing types); the generator implements IQueryStringRequest on a generated partial part. Use [QueryParameterName("...")] to override a key.

3. Handle nullable typed responses​

Before (v10.0.1):

Product product = await client.GetAsync<Product>("products/1");

After (v10.1.0):

Product? product = await client.GetAsync<Product>("products/1");

if (product is null)
{
// The server returned a successful response with an empty body.
}

4. Remove media type / encoding from settings​

Before (v10.0.1):

public class MyApiSettings : BaseApiSettings<MyApiActions>
{
public override string MediaType { get; set; } = "application/json";
}

After (v10.1.0):

Delete the properties — request bodies are always sent as application/json; charset=utf-8 and responses are always parsed as JSON.

5. Replace AddOptionWithValidations usage​

Before (v10.0.1):

services.AddOptionWithValidations<MyFeatureSettings>();

After (v10.1.0):

Register validated integration settings through AddHttpClientIntegration, or use the built-in options API directly:

services.AddOptions<MyFeatureSettings>()
.BindConfiguration("MyFeature")
.ValidateDataAnnotations()
.ValidateOnStart();

6. Replace DictionaryExtensions.ToQueryString(...)​

Before (v10.0.1):

using RA.Utilities.Integrations.Extensions;
using RA.Utilities.Integrations.Models;

QueryParams parameters = [new("page", "1")];
string queryString = parameters.ToQueryString(); // "?page=1"

After (v10.1.0):

using RA.Utilities.Integrations.Models;
using RA.Utilities.Integrations.Utilities;

QueryParams parameters = [new("page", "1")];
string queryString = QueryUtilities.ToQueryString(parameters); // "?page=1"

Full Changelog​

See the package release notes for a complete version history.