Type-safe HTTP client for the RSSVibe API with automatic bearer token authentication.
Add the client to your service collection:
// With base address
services.AddRSSVibeApiClient("https://api.rssvibe.local");
// Or with configuration
services.AddRSSVibeApiClient(client =>
{
client.BaseAddress = new Uri("https://api.rssvibe.local");
client.Timeout = TimeSpan.FromSeconds(30);
});
// With resilience (requires Microsoft.Extensions.Http.Resilience)
services.AddRSSVibeApiClient("https://api.rssvibe.local")
.AddStandardResilienceHandler();The client automatically injects bearer tokens if IAccessTokenProvider is registered:
using Microsoft.AspNetCore.Components.Authorization;
using RSSVibe.Contracts;
public sealed class ClientAccessTokenProvider(AuthenticationStateProvider authStateProvider)
: IAccessTokenProvider
{
public async Task<string?> GetAccessTokenAsync()
{
var authState = await authStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
if (user?.Identity?.IsAuthenticated != true)
return null;
// Get token from claims (adjust claim type based on your auth implementation)
return user.FindFirst("access_token")?.Value;
}
}
// In Program.cs
builder.Services.AddScoped<IAccessTokenProvider, ClientAccessTokenProvider>();
builder.Services.AddRSSVibeApiClient("https+http://apiservice");using Microsoft.AspNetCore.Components.Authorization;
using RSSVibe.Contracts;
public sealed class ServerAccessTokenProvider(
AuthenticationStateProvider authStateProvider,
IHttpContextAccessor? httpContextAccessor = null)
: IAccessTokenProvider
{
public async Task<string?> GetAccessTokenAsync()
{
// Try HTTP context first (for server-side calls)
if (httpContextAccessor?.HttpContext?.User?.Identity?.IsAuthenticated == true)
{
var token = httpContextAccessor.HttpContext.User.FindFirst("access_token")?.Value;
if (!string.IsNullOrWhiteSpace(token))
return token;
}
// Fallback to AuthenticationStateProvider
var authState = await authStateProvider.GetAuthenticationStateAsync();
return authState.User.FindFirst("access_token")?.Value;
}
}
// In Program.cs
builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<IAccessTokenProvider, ServerAccessTokenProvider>();
builder.Services.AddRSSVibeApiClient("https+http://apiservice");Register the client and token provider in both projects:
RSSVibe.Web (Server):
builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<IAccessTokenProvider, ServerAccessTokenProvider>();
builder.Services.AddRSSVibeApiClient("https+http://apiservice");RSSVibe.Web.Client (WASM):
builder.Services.AddScoped<IAccessTokenProvider, ClientAccessTokenProvider>();
builder.Services.AddRSSVibeApiClient("https+http://apiservice");public class AuthExampleService(IRSSVibeApiClient apiClient)
{
public async Task<string?> LoginAsync()
{
var loginRequest = new LoginRequest(
Email: "[email protected]",
Password: "password123",
RememberMe: false
);
var result = await apiClient.Auth.LoginAsync(loginRequest);
if (result.IsSuccess)
{
return result.Data!.AccessToken;
}
Console.WriteLine($"Login failed: {result.ErrorTitle} - {result.ErrorDetail}");
return null;
}
public async Task<ProfileResponse?> GetProfileAsync()
{
// Automatically includes Authorization header from IAccessTokenProvider
var result = await apiClient.Auth.GetProfileAsync();
return result.IsSuccess ? result.Data : null;
}
}public class FeedAnalysisExampleService(IRSSVibeApiClient apiClient)
{
public async Task<Guid?> CreateAnalysisAsync()
{
var request = new CreateFeedAnalysisRequest(
TargetUrl: "https://example.com/feed",
AiModel: "gpt-4",
ForceReanalysis: false
);
// Automatically includes Authorization header from IAccessTokenProvider
var result = await apiClient.FeedAnalyses.CreateAsync(request);
if (result.IsSuccess)
{
return result.Data!.AnalysisId;
}
return null;
}
public async Task<FeedAnalysisDetailResponse?> GetAnalysisAsync(Guid analysisId)
{
var result = await apiClient.FeedAnalyses.GetAsync(analysisId);
return result.IsSuccess ? result.Data : null;
}
public async Task<ListFeedAnalysesResponse?> ListAnalysesAsync()
{
var request = new ListFeedAnalysesRequest(
Status: FeedAnalysisStatus.Completed,
Sort: "createdAt:desc",
Skip: 0,
Take: 20,
Search: null
);
var result = await apiClient.FeedAnalyses.ListAsync(request);
return result.IsSuccess ? result.Data : null;
}
}All client methods return an ApiResult<T> or ApiResultNoData:
public sealed record ApiResult<TData>
{
public bool IsSuccess { get; init; }
public TData? Data { get; init; }
public int StatusCode { get; init; }
public string? ErrorTitle { get; init; }
public string? ErrorDetail { get; init; }
}- ✅ Type-safe client with strongly-typed request/response models
- ✅ Clean public interface with internal implementation
- ✅ Automatic JSON serialization/deserialization
- ✅ Automatic bearer token authentication via
IAccessTokenProvider - ✅ Problem Details (RFC 7807) error handling
- ✅ Culture-invariant query parameter encoding
- ✅ Supports dependency injection
- ✅ Compatible with
Microsoft.Extensions.Http.Resilience - ✅ Works with Blazor Server, WASM, and Auto render modes