Skip to main content

RA.Utilities.Integrations

NuGet version NuGet Downloads Codecov Publish NuGet GitHub license

RA.Utilities.Integrations simplifies and standardizes how you make HTTP calls to external APIs in .NET applications. It builds on Microsoft's recommended IHttpClientFactory pattern and provides a reusable BaseHttpClient with all four HTTP verbs, strongly-typed query string and header parameters backed by a compile-time source generator, and a fluent registration pipeline.

🎯 Purpose​

The package solves the problems that come up in every external API integration:

  • Standardizes Configuration — centralize base URL, timeouts, and API keys in appsettings.json via BaseApiSettings<T>, keeping configuration out of your code.
  • Simplifies Registration — one extension method, AddHttpClientIntegration, binds and validates configuration and registers a typed HttpClient.
  • Provides Compile-Time Safety — [QueryParameters] and [HeaderParameters] classes are mapped to request parameters by a source generator, so typos in keys, wrong types, and missing mappings fail at build time instead of runtime.
  • Covers the Full CRUD Surface — GetAsync, PostAsync, PutAsync, and DeleteAsync share one consistent request pipeline with JSON serialization built in.
  • Improves Resilience — typed clients via IHttpClientFactory plus optional logging and header-forwarding handlers for robust, observable integrations.

✨ Key Features​

  • Strongly-typed query strings — [QueryParameters] + [QueryParameterName] attributes with generated IQueryStringRequest implementations
  • Strongly-typed headers — [HeaderParameters] + [HeaderParameterName] attributes with generated IHeaderRequest implementations
  • Null-safe requests — query/header parameters are optional; null never throws
  • JSON in, JSON out — bodies serialized as application/json; charset=utf-8, responses deserialized with camel-case naming
  • Incremental source generator — shipped inside the package as an analyzer, zero reflection at runtime

🛠️ Getting Started​

Prerequisites​

  • .NET 10.0 or later
  • An ASP.NET Core application (the package references the Microsoft.AspNetCore.App shared framework)

Installation​

.NET CLI

dotnet add package RA.Utilities.Integrations

Package Manager Console

Install-Package RA.Utilities.Integrations

PackageReference (csproj)

<PackageReference Include="RA.Utilities.Integrations" Version="10.2.0" />

Hello World​

Define your parameter classes, build a client, and call an API:

using RA.Utilities.Integrations;
using RA.Utilities.Integrations.Attributes;

[QueryParameters]
public partial class GetProductsQuery
{
public int? CategoryId { get; init; }
public string? Search { get; init; }
}

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

// Configured and injected via IHttpClientFactory; see ⚙️ Configuration below.
public class ProductsClient : BaseHttpClient
{
public ProductsClient(HttpClient httpClient, IOptions<IIntegrationSettings> settings)
: base(httpClient, settings)
{
}

public Task<Product?> GetProductsAsync(GetProductsQuery query) =>
GetAsync<Product>("products", query, new RequestHeaders { XCorrelationId = Guid.NewGuid().ToString() });

public Task<Product?> CreateProductAsync(Product product) =>
PostAsync<Product, Product>("products", product);

public Task<Product?> UpdateProductAsync(Product product) =>
PutAsync<Product, Product>($"products/{product.Id}", product);

public Task DeleteProductAsync(int id) =>
DeleteAsync($"products/{id}");
}

🧠 Core Concepts​

The package is built from a few cooperating pieces:

ComponentRoleExample
BaseHttpClientOne pipeline for GET, POST, PUT, and DELETE with JSON serializationGetAsync<Product>("products", query)
[QueryParameters]Marker for strongly-typed query string classes (generator implements IQueryStringRequest)?CategoryId=5&Search=widgets
[HeaderParameters]Marker for strongly-typed header classes (generator implements IHeaderRequest)x-request-id: trace-1
QueryUtilitiesURL-encodes a QueryParams collection (skips empty values, ? prefix)"?page=2"
AddHttpClientIntegrationRegisters a typed client, binds + validates settingsservices.AddHttpClientIntegration<...>(section)
BaseApiSettings<T>Validated settings base class with strongly-typed endpoint namesBaseUrl, Timeout, Actions

How they work together​

appsettings.json ──► BaseApiSettings<T> ──► AddHttpClientIntegration ──► typed HttpClient
│
[QueryParameters] class ──(source generator)──► IQueryStringRequest ──────┤
[HeaderParameters] class ──(source generator)──► IHeaderRequest ──────────┤
▼
BaseHttpClient.GetAsync/PostAsync/PutAsync/DeleteAsync
│
┌─ QueryUtilities (URL encoding)
└─ JsonConverterUtilities (JSON body)

Terminology​

  • Strongly-typed parameters: Query/header values described as C# classes instead of Dictionary<string, string> — property names are checked at compile time and surfaced by IntelliSense.
  • Source generator: A Roslyn incremental generator shipped inside the NuGet package (under analyzers/) that emits partial class implementations at compile time.
  • Typed client: A class that receives a configured HttpClient from IHttpClientFactory and exposes strongly-typed methods.

📖 How-To Guides​

Call all four HTTP verbs​

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

Product? created = await client.PostAsync<Product, Product>("products", product);

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

await client.DeleteAsync("products/1");

Use repeated query keys and dictionaries​

[QueryParameters]
public partial class FilterQuery
{
public int[] Ids { get; init; } = []; // ?Ids=1&Ids=2
public Dictionary<string, string> Filters { get; init; } // key of each entry becomes the query key
}

var query = new FilterQuery
{
Ids = [1, 2],
Filters = new() { ["brand"] = "acme" }
};
// GET /products?Ids=1&Ids=2&brand=acme

Override emitted keys and header names​

[QueryParameters]
public partial class PagedQuery
{
[QueryParameterName("page_size")]
public int PageSize { get; init; }
}

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

Register a typed client with validation​

See the full walkthrough in AddHttpClientIntegration.

Add logging and header forwarding​

services.AddHttpClientIntegration<IMyApiClient, MyApiClient, MyApiSettings>(configSection)
.AddLoggingHandler() // structured request/response logging
.AddInternalHeadersForwardingHandler(); // forwards Authorization + x-request-id per call

⚙️ Configuration​

Settings live in appsettings.json and are bound + validated by AddHttpClientIntegration:

{
"Integrations": {
"MyApi": {
"BaseUrl": "https://api.example.com/v1/",
"Timeout": 60,
"Actions": {
"GetProduct": "products/{0}"
}
}
}
}
using RA.Utilities.Integrations.Extensions;
using RA.Utilities.Integrations.Options;

public class MyApiSettings : BaseApiSettings<MyApiActions> { }
public class MyApiActions
{
public string GetProduct { get; set; } = string.Empty;
}

services.AddHttpClientIntegration<IMyApiClient, MyApiClient, MyApiSettings>(
builder.Configuration.GetSection("Integrations:MyApi"));

BaseUrl and Actions are required and validated at startup (BaseApiSettings<T> details).


🔗 Dependencies​


✨ Available Components​


🙌 Contributing & Support​

Found a bug or have a suggestion?​

Contributing​

Contributions are welcome! Please follow the standard workflow:

  1. Fork the RA.Utilities repository
  2. Create a branch from main with a descriptive name (e.g., feature/new-integration-handler)
  3. Make your changes — ensure XML doc comments are included for any new public API
  4. Submit a pull request to the main branch with a clear description
ResourceURL
NuGetRA.Utilities.Integrations
SourceGitHub — RedonAlla/RA.Utilities
LicenseMIT
Build StatusGitHub Actions