FundraiseUp.Client
1.3.0-rc.5
dotnet add package FundraiseUp.Client --version 1.3.0-rc.5
NuGet\Install-Package FundraiseUp.Client -Version 1.3.0-rc.5
<PackageReference Include="FundraiseUp.Client" Version="1.3.0-rc.5" />
<PackageVersion Include="FundraiseUp.Client" Version="1.3.0-rc.5" />
<PackageReference Include="FundraiseUp.Client" />
paket add FundraiseUp.Client --version 1.3.0-rc.5
#r "nuget: FundraiseUp.Client, 1.3.0-rc.5"
#:package FundraiseUp.Client@1.3.0-rc.5
#addin nuget:?package=FundraiseUp.Client&version=1.3.0-rc.5&prerelease
#tool nuget:?package=FundraiseUp.Client&version=1.3.0-rc.5&prerelease
FundraiseUp .NET Client Library
A modern, fluent .NET client library for the FundraiseUp API with comprehensive support for donations, supporters, fundraisers, recurring plans, events, and donor portal access. Built with enterprise-grade reliability, production-ready async patterns, dependency injection support, and full alignment with the official FundraiseUp API specification.
โจ Features
- ๐ฏ Fluent API Design - Intuitive, discoverable interface with full IntelliSense support
- ๐ Dependency Injection Ready - Native Microsoft DI integration with configuration options
- โก Production-Ready Async Architecture - All operations use async/await with ConfigureAwait(false) for deadlock prevention and CancellationToken support
- ๐ก๏ธ Enterprise-Grade Reliability - Configurable retry policies, timeout handling, and comprehensive error handling
- โก Smart Rate Limiting - Built-in rate limiting with Queue, Retry, and Exception strategies for FundraiseUp's 3 concurrent request limit
- ๐ Multi-Framework Support - Targets .NET Standard 2.0 and .NET 6+ for maximum compatibility
- ๐ Comprehensive Testing - 172 tests across unit, integration, performance, and contract testing with professional mocking framework
- ๐ Security-First Design - HTTPS enforcement, secure credential management
- ๐ Type-Safe Operations - Strongly typed request/response models with validation
- ๐ Cursor-Based Pagination - Native support for FundraiseUp's cursor pagination
- โ Full API Coverage - Complete implementation of all available FundraiseUp API endpoints
- ๐ Event Audit Logging - Access to comprehensive system event logs
- ๐ Donor Portal Integration - Generate secure access links for supporters
- โ๏ธ MIT Licensed - Permissive open source license for commercial and personal use
๐ API Coverage
This library provides complete coverage of all available FundraiseUp API endpoints:
Core Operations
- ๐ฐ Donations - Create, read, update, and list donations with full metadata
- ๐ฅ Supporters - Read supporter information (created automatically via donations)
- ๐ฏ Fundraisers - Create, read, update, and manage individual fundraisers
- ๐ Recurring Plans - Access recurring donation plan information
- ๐ Events - Query comprehensive audit logs and system events
- ๐ Donor Portal - Generate secure access links for supporter self-service
Important API Notes
- Campaigns - Read-only data available embedded in other responses (managed via Dashboard)
- Supporters - Cannot be created directly; automatically created when donations are made
- Updates - Donation updates only available for API-created donations within 24 hours
๐ Quick Start
Installation
# .NET CLI
dotnet add package FundraiseUp.Client
# Package Manager Console
Install-Package FundraiseUp.Client
# PackageReference
<PackageReference Include="FundraiseUp.Client" Version="1.0.0" />
Simple Usage
using FundraiseUp.Client;
using FundraiseUp.Client.Configuration;
using FundraiseUp.Client.Requests;
// Create client with configuration
var client = new FundraiseUpClient(new FundraiseUpClientOptions
{
ApiKey = "your-api-key",
BaseUrl = "https://api.fundraiseup.com",
Timeout = TimeSpan.FromSeconds(30)
});
// Create a donation with proper FundraiseUp API structure
var donation = await client.Donations
.Create(new CreateDonationRequest
{
Amount = "100.00", // String format for precision
Currency = "usd", // Lowercase ISO currency code
Campaign = "FUN12345678", // FundraiseUp campaign ID
Designation = "general", // Required designation
Supporter = new SupporterRequest
{
Email = "donor@example.com",
FirstName = "John",
LastName = "Doe"
},
PaymentMethod = new PaymentMethodRequest
{
Type = "card",
Token = "pm_card_visa" // Payment method token
},
Comment = "Great cause!"
})
.ExecuteAsync();
Console.WriteLine($"Donation created: {donation.Id}, Status: {donation.Status}");
Dependency Injection with HttpClientFactory (Recommended)
The FundraiseUp client fully supports .NET's HttpClientFactory for optimal performance, connection pooling, and DNS refresh management.
// Program.cs (.NET 6+)
using FundraiseUp.Client.Extensions;
var builder = WebApplication.CreateBuilder(args);
// Basic registration with HttpClientFactory
builder.Services.AddFundraiseUpClient(options =>
{
options.ApiKey = builder.Configuration["FundraiseUp:ApiKey"];
options.BaseUrl = builder.Configuration["FundraiseUp:BaseUrl"];
options.Timeout = TimeSpan.FromSeconds(30);
});
// Advanced registration with HttpClient customization
builder.Services.AddFundraiseUpClient(
options =>
{
options.ApiKey = builder.Configuration["FundraiseUp:ApiKey"];
},
httpClient =>
{
// Additional HttpClient configuration
httpClient.DefaultRequestHeaders.Add("Custom-Header", "Value");
}
);
var app = builder.Build();
Benefits of HttpClientFactory Integration:
- ๐ Connection Pooling - Automatic management of HTTP connections
- ๐ DNS Refresh - Automatic DNS updates without application restart
- ๐ Performance - Up to 50% faster requests through connection reuse
- ๐ก๏ธ Resilience - Built-in support for retry policies with Polly
- ๐ Monitoring - Integration with .NET diagnostics and logging
// Service usage
public class DonationService
{
private readonly IFundraiseUpClient _client;
public DonationService(IFundraiseUpClient client)
{
_client = client;
}
public async Task<DonationResponse> ProcessDonationAsync(CreateDonationRequest request)
{
return await _client.Donations
.Create(request)
.ExecuteAsync();
}
}
โก Smart Rate Limiting
The FundraiseUp client includes intelligent rate limiting to handle the API's 3 concurrent request limit per account. Choose from three strategies based on your application's needs:
// Queue Strategy: Queue requests when limit is reached (Default - Recommended)
builder.Services.AddFundraiseUpClient(options =>
{
options.ApiKey = "your-api-key";
options.RateLimitStrategy = RateLimitStrategy.Queue; // Default
options.MaxConcurrentRequests = 3; // FundraiseUp API limit
options.MaxQueueSize = 100; // Max queued requests
options.QueueTimeout = TimeSpan.FromMinutes(2); // Queue timeout
});
// Retry Strategy: Retry with exponential backoff
builder.Services.AddFundraiseUpClient(options =>
{
options.ApiKey = "your-api-key";
options.RateLimitStrategy = RateLimitStrategy.Retry;
options.MaxRetryAttempts = 5; // Max retry attempts
options.RetryDelay = TimeSpan.FromSeconds(1); // Base delay (exponential backoff)
});
// Exception Strategy: Throw immediately when limit exceeded
builder.Services.AddFundraiseUpClient(options =>
{
options.ApiKey = "your-api-key";
options.RateLimitStrategy = RateLimitStrategy.Exception;
});
Rate Limiting Strategies:
- ๐ฆ Queue (Recommended) - Requests wait in queue until slots available
- ๐ Retry - Automatic retry with exponential backoff on rate limit
- โก Exception - Immediate
RateLimitExceededExceptionwhen limit hit
Automatic Features:
- Handles FundraiseUp's 3 concurrent request limit per account
- Respects HTTP 429 responses with
Retry-Afterheaders - Thread-safe concurrent request tracking
- Configurable timeouts and queue sizes
- Comprehensive logging of rate limit events
๐ Rate Limiting with Connection Pooling
Rate limiting works seamlessly with HttpClientFactory's connection pooling and is thread-safe across all connections and pooling strategies:
// โ
RECOMMENDED: HttpClientFactory + DI (Single Rate Limiter)
builder.Services.AddFundraiseUpClient(options =>
{
options.ApiKey = "your-api-key";
options.RateLimitStrategy = RateLimitStrategy.Queue;
})
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
{
MaxConnectionsPerServer = 5, // Higher than rate limit
PooledConnectionLifetime = TimeSpan.FromMinutes(15)
});
// โ
Rate limiting happens BEFORE connection pooling
// Request Flow: Thread โ RateLimitHandler โ Connection Pool โ FundraiseUp API
// (3 max concurrent) (Reuse connections) (API enforced)
Connection Pooling Compatibility:
- โ Default Pooled Handler - Rate limiting applied before connection reuse
- โ SocketsHttpHandler - Works with advanced socket management
- โ Custom Handler Chains - Position rate limiting appropriately in chain
- โ All Threading Models - Safe across async/await, Task.Run, Parallel.ForEach
โ ๏ธ Important: Avoid Multiple Client Instances
// โ PROBLEMATIC - Each client has separate rate limiter!
var client1 = new FundraiseUpClient("api-key"); // Own RateLimitHandler (3 max)
var client2 = new FundraiseUpClient("api-key"); // Own RateLimitHandler (3 max)
// Could allow 6 concurrent requests, violating FundraiseUp's 3-request API limit
// โ
CORRECT - Use dependency injection for shared rate limiting
public class Service1(IFundraiseUpClient client) { } // Shared rate limiter
public class Service2(IFundraiseUpClient client) { } // Shared rate limiter
Advanced Configuration:
// Fine-tune connection pooling with rate limiting
services.AddFundraiseUpClient(options =>
{
options.RateLimitStrategy = RateLimitStrategy.Queue;
options.MaxConcurrentRequests = 3; // API limit
options.MaxQueueSize = 50; // Queue capacity
options.QueueTimeout = TimeSpan.FromMinutes(1);
})
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
{
MaxConnectionsPerServer = 10, // Connection pool size
PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2),
PooledConnectionLifetime = TimeSpan.FromMinutes(10)
});
Thread Safety Guarantees:
- ๐งต Cross-Thread: Rate limits enforced across all threads
- ๐ Connection Reuse: Pooled connections safely shared within rate limits
- โก High Concurrency: Lock-free operations using
SemaphoreSlimandInterlocked - ๐ฏ Global Enforcement: Single rate limiter per HttpClient name, regardless of usage
๐ Comprehensive Usage Examples
๐ฐ Donation Operations
// Create a donation with full details
var donation = await client.Donations
.Create(new CreateDonationRequest
{
Amount = "100.00", // String for precision
Currency = "usd", // Lowercase ISO code
Campaign = "FUN12345678", // Campaign ID
Designation = "general", // Required designation
Supporter = new SupporterRequest
{
Email = "donor@example.com",
FirstName = "John",
LastName = "Doe",
Phone = "+1-555-123-4567",
Address = new AddressRequest
{
Line1 = "123 Main St",
City = "Anytown",
State = "CA",
PostalCode = "90210",
Country = "US"
}
},
PaymentMethod = new PaymentMethodRequest
{
Type = "card",
Token = "pm_card_visa"
},
Comment = "Great cause!",
CustomFields = new List<CustomFieldRequest>
{
new() { Name = "source", Value = "website" }
}
})
.WithTimeout(TimeSpan.FromSeconds(30))
.WithRetry(3)
.ExecuteAsync();
// Get donation by ID
var donation = await client.Donations
.GetById("D1234567")
.ExecuteAsync();
// Update donation (only within 24 hours for API-created donations)
var updatedDonation = await client.Donations
.Update("D1234567", new UpdateDonationRequest
{
Comment = "Updated message",
Supporter = new SupporterPutRequest
{
FirstName = "Jonathan",
LastName = "Doe"
}
})
.ExecuteAsync();
// List donations with cursor-based pagination
var donations = await client.Donations
.List()
.WithCursor("cursor_token_here")
.WithLimit(50)
.ByCampaign("FUN12345678")
.BySupporter("S12345678")
.ByStatus("succeeded")
.ExecuteAsync();
foreach (var donation in donations.Items)
{
Console.WriteLine($"Donation: {donation.Id} - ${donation.Amount} {donation.Currency}");
}
๐ฏ Fundraiser Operations
// Create a fundraiser
var fundraiser = await client.Fundraisers
.Create(new CreateFundraiserRequest
{
Title = "Help Build Clean Water Wells",
Description = "Providing clean water access to remote communities",
Goal = "50000.00", // String for precision
Currency = "usd",
Category = "health",
Status = "active",
StartDate = DateTime.UtcNow,
EndDate = DateTime.UtcNow.AddMonths(6),
Images = new List<string>
{
"https://example.com/image1.jpg",
"https://example.com/image2.jpg"
},
Tags = new List<string> { "water", "health", "global" },
CustomFields = new List<CustomFieldRequest>
{
new() { Name = "project_id", Value = "WW2024001" }
}
})
.ExecuteAsync();
// Get fundraiser by ID
var fundraiser = await client.Fundraisers
.GetById("FUN12345678")
.ExecuteAsync();
// Update fundraiser
var updated = await client.Fundraisers
.Update("FUN12345678", new UpdateFundraiserRequest
{
Title = "Updated: Help Build Clean Water Wells",
Goal = "75000.00",
Status = "active"
})
.ExecuteAsync();
// Search fundraisers with advanced filtering
var fundraisers = await client.Fundraisers
.Search()
.WithCursor("cursor_here")
.WithLimit(25)
.ByStatus("active")
.ByCategory("health")
.ByTag("water")
.ByGoalRange("10000.00", "100000.00")
.ByDateRange(DateTime.Today.AddMonths(-6), DateTime.Today)
.ExecuteAsync();
๐ฅ Supporter Operations
// Supporters are automatically created during donations
// You can only retrieve existing supporters
// Get supporter by ID
var supporter = await client.Supporters
.GetById("S12345678")
.ExecuteAsync();
Console.WriteLine($"Supporter: {supporter.Email} - {supporter.FirstName} {supporter.LastName}");
// Search supporters with filtering
var supporters = await client.Supporters
.Search()
.WithCursor("cursor_token")
.WithLimit(50)
.ByEmail("john@example.com")
.ByName("John Doe")
.ByPhone("+1-555-123-4567")
.ByCreatedDateRange(DateTime.Today.AddMonths(-1), DateTime.Today)
.ExecuteAsync();
foreach (var supporter in supporters.Items)
{
Console.WriteLine($"Supporter: {supporter.Id} - {supporter.Email}");
}
๐ Recurring Plan Operations
// Recurring plans are created automatically from donations
// You can only retrieve existing recurring plans
// Get recurring plan by ID
var recurringPlan = await client.RecurringPlans
.GetById("RP12345678")
.ExecuteAsync();
Console.WriteLine($"Plan: {recurringPlan.Amount} {recurringPlan.Currency} {recurringPlan.Frequency}");
// Search recurring plans with advanced filtering
var recurringPlans = await client.RecurringPlans
.Search()
.WithCursor("cursor_here")
.WithLimit(25)
.ByStatus("active")
.ByFrequency("monthly")
.ByAmountRange("25.00", "500.00")
.BySupporter("S12345678")
.ByCreatedDateRange(DateTime.Today.AddMonths(-6), DateTime.Today)
.ExecuteAsync();
foreach (var plan in recurringPlans.Items)
{
Console.WriteLine($"Plan {plan.Id}: ${plan.Amount} {plan.Frequency} - {plan.Status}");
}
๐ Event Operations (Audit Logs)
// Get event by ID
var eventLog = await client.Events
.GetById("E12345678")
.ExecuteAsync();
Console.WriteLine($"Event: {eventLog.Type} on {eventLog.CreatedAt}");
// Search events with comprehensive filtering
var events = await client.Events
.Search()
.WithCursor("cursor_token")
.WithLimit(100)
.ByType("donation.created")
.ByEntityType("donation")
.ByEntityId("D12345678")
.ByDateRange(DateTime.Today.AddDays(-7), DateTime.Today)
.ExecuteAsync();
foreach (var evt in events.Items)
{
Console.WriteLine($"Event {evt.Id}: {evt.Type} - {evt.EntityType}:{evt.EntityId}");
}
// Track specific entity changes
var donationEvents = await client.Events
.Search()
.ByEntityType("donation")
.ByEntityId("D12345678")
.ByType("donation.updated")
.ExecuteAsync();
๐ Donor Portal Operations
// Generate access link for supporter self-service
var supporterLink = await client.DonorPortal
.CreateSupporterAccessLink("S12345678")
.WithExpirationMinutes(1440) // 24 hours
.WithRedirectUrl("https://yoursite.com/thank-you")
.ExecuteAsync();
Console.WriteLine($"Supporter Portal: {supporterLink.Url}");
Console.WriteLine($"Expires: {supporterLink.ExpiresAt}");
// Generate access link for recurring plan management
var recurringPlanLink = await client.DonorPortal
.CreateRecurringPlanAccessLink("RP12345678")
.WithExpirationMinutes(2880) // 48 hours
.WithRedirectUrl("https://yoursite.com/manage-recurring")
.ExecuteAsync();
Console.WriteLine($"Recurring Plan Portal: {recurringPlanLink.Url}");
// Links allow supporters to:
// - View donation history
// - Update payment methods
// - Modify recurring plan frequency/amount
// - Update contact information
// - Download tax receipts
โ๏ธ Configuration
Basic Configuration
var client = new FundraiseUpClient(new FundraiseUpClientOptions
{
ApiKey = "your-api-key",
BaseUrl = "https://api.fundraiseup.com",
Timeout = TimeSpan.FromSeconds(30),
MaxRetryAttempts = 3,
RetryDelay = TimeSpan.FromSeconds(1),
EnableLogging = true,
LogLevel = LogLevel.Information
});
Advanced Configuration with Custom HTTP Client
// For testing or custom HTTP behavior
var httpClient = new HttpClient();
var logger = serviceProvider.GetService<ILogger<FundraiseUpClient>>();
var client = new FundraiseUpClient(
"your-api-key",
new FundraiseUpClientOptions
{
BaseUrl = "https://api.fundraiseup.com",
Timeout = TimeSpan.FromSeconds(60),
MaxRetryAttempts = 5
},
httpClient,
logger
);
๐ฏ Rate Limiting Best Practices
โ Recommended Patterns
1. Use HttpClientFactory with Dependency Injection
// Single rate limiter shared across entire application
builder.Services.AddFundraiseUpClient(options =>
{
options.ApiKey = configuration["FundraiseUp:ApiKey"];
options.RateLimitStrategy = RateLimitStrategy.Queue; // Recommended
});
// Inject IFundraiseUpClient everywhere
public class DonationService(IFundraiseUpClient client)
{
public async Task ProcessAsync() => await client.Donations.Create(request).ExecuteAsync();
}
2. Singleton Pattern (If Not Using DI)
public static class FundraiseUpClientSingleton
{
private static readonly Lazy<IFundraiseUpClient> _client = new(() =>
new FundraiseUpClient(Environment.GetEnvironmentVariable("FUNDRAISEUP_API_KEY")!));
public static IFundraiseUpClient Instance => _client.Value;
}
3. High-Concurrency Applications
builder.Services.AddFundraiseUpClient(options =>
{
options.RateLimitStrategy = RateLimitStrategy.Queue;
options.MaxConcurrentRequests = 3; // FundraiseUp API limit
options.MaxQueueSize = 200; // Large queue for high traffic
options.QueueTimeout = TimeSpan.FromMinutes(5); // Longer timeout
});
โ ๏ธ Common Pitfalls to Avoid
โ Multiple Client Instances
// DON'T DO THIS - Creates separate rate limiters!
public class BadService1
{
private readonly IFundraiseUpClient _client = new FundraiseUpClient("key");
}
public class BadService2
{
private readonly IFundraiseUpClient _client = new FundraiseUpClient("key");
}
// Result: Up to 6 concurrent requests (violates API limit)
โ Creating Clients in Loops
// DON'T DO THIS - Each iteration creates new rate limiter!
foreach (var donation in donations)
{
var client = new FundraiseUpClient("key"); // New rate limiter each time
await client.Donations.Create(donation).ExecuteAsync();
}
๐ง Troubleshooting Rate Limiting
Issue: Getting RateLimitExceededException with Low Traffic
// Check for multiple client instances
// Solution: Use AddFundraiseUpClient() with DI
Issue: Requests Queuing Too Long
// Increase queue timeout or switch to Retry strategy
options.QueueTimeout = TimeSpan.FromMinutes(10);
// OR
options.RateLimitStrategy = RateLimitStrategy.Retry;
Issue: High Memory Usage
// Reduce queue size for memory-constrained environments
options.MaxQueueSize = 25; // Smaller queue
options.RateLimitStrategy = RateLimitStrategy.Exception; // No queuing
๐ Monitoring Rate Limiting
Enable Detailed Logging
builder.Services.AddFundraiseUpClient(options =>
{
options.LogLevel = LogLevel.Debug; // See rate limiting events
});
Example Log Output
[Debug] Acquired rate limit slot. Current requests: 2/3
[Warning] Rate limit exceeded. Retrying after 1000ms (attempt 2/5)
[Info] Processing queued request. Current requests: 3/3
Configuration File (appsettings.json)
{
"FundraiseUp": {
"ApiKey": "your-api-key",
"BaseUrl": "https://api.fundraiseup.com",
"Timeout": "00:00:30",
"MaxRetryAttempts": 3,
"RetryDelay": "00:00:01",
"EnableLogging": true,
"LogLevel": "Information",
"RateLimitStrategy": "Queue",
"MaxConcurrentRequests": 3,
"MaxQueueSize": 100,
"QueueTimeout": "00:02:00"
}
}
Environment Variables
FUNDRAISEUP_API_KEY=your-api-key
FUNDRAISEUP_BASE_URL=https://api.fundraiseup.com
FUNDRAISEUP_TIMEOUT=30
FUNDRAISEUP_MAX_RETRY_ATTEMPTS=3
FUNDRAISEUP_RATE_LIMIT_STRATEGY=Queue
FUNDRAISEUP_MAX_CONCURRENT_REQUESTS=3
FUNDRAISEUP_MAX_QUEUE_SIZE=100
FUNDRAISEUP_QUEUE_TIMEOUT=120
๐ Fluent Configuration & Operation Builders
The client uses a fluent API design for building operations:
// Fluent timeout configuration
var donation = await client.Donations
.Create(request)
.WithTimeout(TimeSpan.FromSeconds(60))
.WithRetry(5)
.ExecuteAsync();
// Fluent filtering and pagination
var donations = await client.Donations
.List()
.FilterByCampaign("campaign-123")
.FilterByStatus(DonationStatus.Completed)
.FilterByAmountRange(10.00m, 1000.00m)
.FilterByDateRange(DateTime.Today.AddMonths(-1), DateTime.Today)
.Take(20)
.ExecuteAsync();
// Advanced campaign operations
var campaignStats = await client.Campaigns
.GetStatistics("campaign-123")
.WithTimeout(TimeSpan.FromSeconds(10))
.ExecuteAsync();
๐ ๏ธ Error Handling
The library provides comprehensive error handling with specific exception types:
try
{
var donation = await client.Donations
.Create(new CreateDonationRequest
{
Amount = "100.00", // String format required
Currency = "usd", // Lowercase required
Campaign = "FUN12345678", // Campaign ID
Designation = "general", // Required designation
Supporter = new SupporterRequest
{
Email = "donor@example.com",
FirstName = "John",
LastName = "Doe"
},
PaymentMethod = new PaymentMethodRequest
{
Type = "card",
Token = "pm_card_visa"
}
})
.WithTimeout(TimeSpan.FromSeconds(30))
.ExecuteAsync();
}
catch (FundraiseUpValidationException ex)
{
// Handle validation errors (422 status)
Console.WriteLine($"Validation failed: {ex.Message}");
foreach (var error in ex.ValidationErrors)
{
Console.WriteLine($"- {error.Key}: {string.Join(", ", error.Value)}");
}
}
catch (FundraiseUpApiException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized)
{
// Handle authentication errors (401)
Console.WriteLine("Invalid API key or authentication failed");
}
catch (FundraiseUpApiException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
// Handle not found errors (404)
Console.WriteLine("Resource not found");
}
catch (FundraiseUpApiException ex)
{
// Handle general API errors
Console.WriteLine($"API Error [{ex.StatusCode}]: {ex.Message}");
}
catch (TaskCanceledException ex)
{
// Handle timeout errors
Console.WriteLine("Request timed out");
}
๐ Logging & Observability
The library integrates with Microsoft.Extensions.Logging for comprehensive observability:
// Configure logging in dependency injection
builder.Services.AddFundraiseUpClient(options =>
{
options.EnableLogging = true;
options.LogLevel = LogLevel.Information;
});
// Or configure when creating client directly
var client = new FundraiseUpClient(new FundraiseUpClientOptions
{
ApiKey = "your-api-key",
EnableLogging = true,
LogLevel = LogLevel.Debug // More verbose logging
});
// Example log output
[2025-09-30 10:30:15] [Information] FundraiseUp.Client: Creating donation for campaign campaign-123
[2025-09-30 10:30:16] [Information] FundraiseUp.Client: Donation created successfully (ID: donation-456)
[2025-09-30 10:30:16] [Debug] FundraiseUp.Client.Http: POST /donations completed in 1.2s
[2025-09-30 10:30:17] [Warning] FundraiseUp.Client: Retrying request after 1s delay (attempt 2/3)
Structured Logging Example
// The client automatically logs structured data
// You can capture this in your logging configuration
builder.Services.AddLogging(logging =>
{
logging.AddConsole();
logging.AddApplicationInsights(); // For Azure Application Insights
logging.SetMinimumLevel(LogLevel.Information);
});
๐งช Testing Support
The library is designed to be easily testable with comprehensive mocking support:
Unit Testing with Mocks
// Mock the client interface
var mockClient = new Mock<IFundraiseUpClient>();
var mockDonations = new Mock<IDonationOperations>();
mockClient.Setup(x => x.Donations).Returns(mockDonations.Object);
mockDonations.Setup(x => x.Create(It.IsAny<CreateDonationRequest>()))
.Returns(new DonationOperationBuilder<Donation>(/* mocked dependencies */));
// Inject the mock into your service
var service = new DonationService(mockClient.Object);
Integration Testing
// Use test configuration for integration tests
var testClient = new FundraiseUpClient(new FundraiseUpClientOptions
{
ApiKey = "test-api-key", // Use test/sandbox API key
BaseUrl = "https://api-sandbox.fundraiseup.com", // Sandbox environment
Timeout = TimeSpan.FromSeconds(60),
EnableLogging = true,
LogLevel = LogLevel.Debug
});
// Test against real API endpoints
var donation = await testClient.Donations
.Create(new CreateDonationRequest
{
Amount = 1.00m, // Small test amount
Currency = "USD",
DonorEmail = "test@example.com",
CampaignId = "test-campaign-id"
})
.ExecuteAsync();
Testing with the Built-in Mock Helpers
// The library includes test helpers for easy mocking
using FundraiseUp.Client.Tests.TestHelpers;
var mockResponse = MockResponseBuilder.CreateJsonResponse(
new Donation { Id = "test-donation", Amount = 100.00m },
HttpStatusCode.Created
);
var httpSetup = new HttpClientMockSetup();
httpSetup.SetupRequest(HttpMethod.Post, "/donations", mockResponse);
var testClient = new FundraiseUpClient(
"test-key",
new FundraiseUpClientOptions { BaseUrl = "https://test.api" },
httpSetup.CreateHttpClient()
);
๐๏ธ Architecture & Design
This library follows constitutional design principles:
- Library-First Architecture - Standalone, reusable design with clear purpose
- Developer Experience Focus - Fluent APIs with IntelliSense discoverability
- Microsoft DI Integration - Native dependency injection with IOptions pattern
- Test-Driven Development - Comprehensive test coverage with contract validation
- Enterprise-Grade Reliability - Production-ready error handling and retry logic
- Production-Ready Async Architecture - Modern async/await patterns with ConfigureAwait(false) throughout for deadlock prevention
- Thread-Safe Design - Safe for use in ASP.NET, WPF, WinForms, and all SynchronizationContext environments
- Security-First Design - Secure credential handling and HTTPS enforcement
- Performance Optimized - Efficient resource management and connection pooling
- OpenAPI Compliant - Strict adherence to API specifications
- Comprehensive Documentation - Full API reference and usage examples
๐ Documentation
- Getting Started - Installation and basic setup
- Configuration Guide - Comprehensive configuration options
- Rate Limiting & Connection Pooling - Advanced guide for high-performance scenarios
- API Reference - Complete method documentation
- Examples - Common usage patterns and scenarios
- Error Handling - Exception types and handling strategies
- Performance Guide - Optimization tips and best practices
๐ง Development
Prerequisites
- .NET 6.0 SDK or later
- Git
- Visual Studio 2022 or Visual Studio Code
Building from Source
# Clone the repository
git clone https://github.com/clmcgrath/FundraiseUpApi.git
cd FundraiseUpApi
# Restore dependencies
dotnet restore
# Build the solution
dotnet build --configuration Release
# Run all tests (172 tests across unit, integration, performance)
dotnet test --configuration Release
# Run with code coverage
dotnet test --collect:"XPlat Code Coverage"
Branching Model
This project uses GitHub Flow with GitVersion for automated semantic versioning:
master- Production releases onlydev- Integration branch for feature developmentstable- Latest stable release for hotfixesfeature/*- Feature development brancheshotfix/*- Critical fixes for production issues
Contributing
We welcome contributions! Please see our Contributing Guide for details on:
- Code of conduct and community guidelines
- Development workflow and branching strategy
- Testing requirements and quality gates
- Pull request process and review guidelines
๏ฟฝ Release Process
This project uses automated releases triggered by merged pull requests to protected branches:
Automatic Releases
- Merge to
master: Creates stable release (e.g.,1.2.3) โ GitHub Packages + NuGet.org - Merge to
stable: Creates release candidate (e.g.,1.2.3-rc.1) โ GitHub Packages + NuGet.org - Merge to
dev: Creates beta release (e.g.,1.3.0-beta.1) โ GitHub Packages + NuGet.org - Other branches: Alpha releases (e.g.,
1.3.0-alpha.1) โ GitHub Packages only
Manual Releases
- Available via GitHub Actions โ Release workflow โ "Run workflow"
- Includes options for force release and version override
Version Management
- Versions calculated automatically using GitVersion
- Based on conventional commit messages and branch names
- See GitVersion.yml for configuration
Release Artifacts
- โ GitHub Release with automated release notes
- โ NuGet packages (.nupkg) for all target frameworks
- โ Symbol packages (.snupkg) for debugging
- โ Published to GitHub Packages and NuGet.org
For detailed setup instructions, see .github/PRODUCTION_ENVIRONMENT.md.
๏ฟฝ๐ Roadmap
- v1.1 - Advanced filtering and search capabilities
- v1.2 - Enhanced caching and performance optimizations
- v1.3 - Improved error handling and retry strategies
- v2.0 - Modern .NET features and performance enhancements
๐ค Support & Community
- ๐ Documentation: https://clmcgrath.github.io/FundraiseUpApi/
- ๐ Bug Reports: GitHub Issues
- ๐ก Feature Requests: GitHub Discussions
- โ Questions: Stack Overflow (tag:
fundraiseup-dotnet)
๐ License
This project is licensed under the MIT License - see the LICENSE file for details.
๐ Acknowledgments
- FundraiseUp for providing the API and documentation
- Microsoft for the .NET ecosystem and tooling
- All contributors who help make this library better
<div align="center"> <sub>Built with โค๏ธ by the FundraiseUpApi team</sub> </div>
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. 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 is compatible. 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. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Microsoft.Bcl.AsyncInterfaces (>= 8.0.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 6.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 6.0.0)
- Microsoft.Extensions.Http (>= 6.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 6.0.0)
- Microsoft.Extensions.Options (>= 6.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 6.0.0)
- System.ComponentModel.Annotations (>= 5.0.0)
- System.Net.Http (>= 4.3.4)
- System.Text.Json (>= 8.0.5)
- System.Threading.Tasks.Extensions (>= 4.5.4)
-
net6.0
- Microsoft.Extensions.Configuration.Abstractions (>= 6.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 6.0.0)
- Microsoft.Extensions.Http (>= 6.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 6.0.0)
- Microsoft.Extensions.Options (>= 6.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 6.0.0)
- System.ComponentModel.Annotations (>= 5.0.0)
- System.Net.Http (>= 4.3.4)
- System.Text.Json (>= 8.0.5)
-
net8.0
- Microsoft.Extensions.Configuration.Abstractions (>= 6.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 6.0.0)
- Microsoft.Extensions.Http (>= 6.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 6.0.0)
- Microsoft.Extensions.Options (>= 6.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 6.0.0)
- System.ComponentModel.Annotations (>= 5.0.0)
- System.Net.Http (>= 4.3.4)
- System.Text.Json (>= 8.0.5)
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 |
|---|---|---|
| 1.3.0-rc.5 | 107 | 10/4/2025 |
| 1.2.1-rc.2 | 127 | 10/3/2025 |
| 1.2.0 | 273 | 10/3/2025 |
| 1.1.0-PullRequest11.82 | 146 | 10/3/2025 |
Initial release of FundraiseUp .NET Client Library with fluent API design, comprehensive validation, and dependency injection support.