A comprehensive .NET client library for the Dify Chat Application API.
âś… Complete API Coverage
- Chat operations (blocking & streaming)
- File upload
- Conversation management
- Message feedback
- Audio operations (speech-to-text, text-to-audio)
- Application information
- Annotations management
- Feedbacks retrieval
âś… Modern C# Implementation
- Async/await pattern with ConfigureAwait(false)
- IAsyncEnumerable for streaming
- Nullable reference types
- Strong typing with models
âś… Production Ready
- Proper error handling with DifyApiException
- Cancellation token support
- IDisposable implementation
- Comprehensive unit tests
- Built-in logging support (Microsoft.Extensions.Logging)
- ConfigureAwait(false) on all async calls (prevents deadlocks)
- Per-request timeout override (fine-grained timeout control)
âś… Observability
- OpenTelemetry integration (distributed tracing & metrics)
- Activity tracking for all HTTP operations
- Metrics for request count, duration, errors, and active requests
- Streaming operation and chunk metrics
- File upload size tracking
- Structured logging for all operations
- HTTP request/response logging
- Error logging with detailed context
- Optional and backward compatible
âś… Dependency Injection
- IHttpClientFactory integration (prevents socket exhaustion)
- Extension methods for easy DI setup
- Scoped/Transient lifetime support
- Compatible with ASP.NET Core
âś… Resilience & Reliability
- Polly integration for retry policies
- Circuit breaker pattern
- Exponential backoff
- Transient fault handling
- Rate limit handling (429 responses)
âś… Multi-Targeting
- .NET 8.0
- .NET 9.0
âś… Quality
- XML documentation
- Deterministic builds
- Source Link support
- Strong typing
dotnet add package DifyApiClientOr via Package Manager Console in Visual Studio:
Install-Package DifyApiClient# Clone the repository
git clone https://github.com/HK-Zhang/DifyApiClient.git
# Build the project
dotnet build
# Run tests
dotnet testusing DifyApiClient;
using DifyApiClient.Models;
// Initialize the client
var options = new DifyApiClientOptions
{
BaseUrl = "http://localhost/v1",
ApiKey = "your-api-key"
};
using var client = new DifyApiClient(options);
// Send a chat message
var request = new ChatMessageRequest
{
Query = "Hello, how are you?",
User = "user-123"
};
var response = await client.SendChatMessageAsync(request);
Console.WriteLine(response.Answer);using DifyApiClient.Extensions;
// In Program.cs or Startup.cs
builder.Services.AddDifyApiClientWithResilience(options =>
{
options.BaseUrl = builder.Configuration["Dify:BaseUrl"]!;
options.ApiKey = builder.Configuration["Dify:ApiKey"]!;
});
// In your service or controller
public class ChatService
{
private readonly IDifyApiClient _difyClient;
public ChatService(IDifyApiClient difyClient)
{
_difyClient = difyClient;
}
public async Task<string> SendMessage(string message)
{
var response = await _difyClient.SendChatMessageAsync(new ChatMessageRequest
{
Query = message,
User = "user-123"
});
return response.Answer;
}
}Benefits of DI approach:
- âś… Uses IHttpClientFactory (prevents socket exhaustion)
- âś… Automatic retry and circuit breaker policies
- âś… Built-in logging
- âś… Easier testing
- OpenTelemetry Guide - Distributed tracing and metrics instrumentation
- Timeout Configuration - Global and per-request timeout configuration
- Dependency Injection Guide - Complete DI setup with IHttpClientFactory
- Resilience Guide - Retry policies, circuit breakers, and fault handling
- Logging Guide - Comprehensive logging documentation
- Changelog - Version history and release notes
- Setup Summary - Project setup information
- GitHub Actions Summary - CI/CD pipeline details
- Publication Checklist - Release checklist
- Documentation Index - Complete documentation index
var request = new ChatMessageRequest
{
Query = "Tell me a story",
User = "user-123"
};
await foreach (var chunk in client.SendChatMessageStreamAsync(request))
{
if (chunk.Event == "message" && chunk.Answer != null)
{
Console.Write(chunk.Answer);
}
else if (chunk.Event == "message_end")
{
Console.WriteLine("\n\nDone!");
break;
}
}using var fileStream = File.OpenRead("document.pdf");
var fileInfo = await client.UploadFileAsync(
fileStream,
"document.pdf",
"user-123"
);
Console.WriteLine($"Uploaded file: {fileInfo.Id}");Enable comprehensive logging for observability and debugging:
using Microsoft.Extensions.Logging;
// Create a logger factory
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder
.SetMinimumLevel(LogLevel.Information)
.AddConsole()
.AddFilter("DifyApiClient", LogLevel.Debug);
});
var logger = loggerFactory.CreateLogger<DifyApiClient>();
// Create client with logger
var options = new DifyApiClientOptions
{
BaseUrl = "http://localhost/v1",
ApiKey = "your-api-key"
};
using var client = new DifyApiClient(options, logger);
// All operations are now logged
var response = await client.SendChatMessageAsync(request);See LOGGING.md for detailed logging documentation.
DifyApiClient includes built-in OpenTelemetry support for distributed tracing and metrics:
using OpenTelemetry.Trace;
using OpenTelemetry.Metrics;
using DifyApiClient.Extensions;
// In Program.cs
builder.Services.AddOpenTelemetry()
.WithTracing(tracerBuilder =>
{
tracerBuilder
.AddDifyApiClientInstrumentation()
.AddConsoleExporter();
})
.WithMetrics(meterBuilder =>
{
meterBuilder
.AddDifyApiClientInstrumentation()
.AddConsoleExporter();
});Available Metrics:
difyapiclient.requests.count- Total request countdifyapiclient.requests.duration- Request duration (ms)difyapiclient.requests.errors- Failed requestsdifyapiclient.requests.active- Concurrent requestsdifyapiclient.streaming.operations- Streaming operationsdifyapiclient.streaming.chunks- Chunks receiveddifyapiclient.files.upload_size- File upload sizes (bytes)
See OPENTELEMETRY.md for complete documentation.
Configure different timeouts for different operations:
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
// Quick operation - 30 second timeout
var response = await client.SendChatMessageAsync(request, cts.Token);Or extend the client with timeout parameters:
public static async Task<ChatCompletionResponse> SendChatMessageWithTimeoutAsync(
this IDifyApiClient client,
ChatMessageRequest request,
TimeSpan timeout,
CancellationToken cancellationToken = default)
{
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
cts.CancelAfter(timeout);
return await client.SendChatMessageAsync(request, cts.Token);
}
// Usage
var response = await client.SendChatMessageWithTimeoutAsync(
request,
TimeSpan.FromSeconds(15));See TIMEOUT_CONFIGURATION.md for detailed timeout configuration.
// Get conversations
var conversations = await client.GetConversationsAsync("user-123");
foreach (var conv in conversations.Data)
{
Console.WriteLine($"{conv.Id}: {conv.Name}");
}
// Get messages in a conversation
var messages = await client.GetConversationMessagesAsync(
conversationId: "conv-123",
user: "user-123"
);
// Delete a conversation
await client.DeleteConversationAsync("conv-123", "user-123");// Create an annotation
var annotation = await client.CreateAnnotationAsync(new AnnotationRequest
{
Question = "What is your name?",
Answer = "I am Dify"
});
// Get all annotations
var annotations = await client.GetAnnotationsAsync(page: 1, limit: 20);
// Update an annotation
await client.UpdateAnnotationAsync(
annotation.Id!,
new AnnotationRequest
{
Question = "What is your name?",
Answer = "I am Dify AI Assistant"
}
);
// Delete an annotation
await client.DeleteAnnotationAsync(annotation.Id!);// Get basic info
var info = await client.GetApplicationInfoAsync();
Console.WriteLine($"App: {info.Name}");
// Get parameters
var parameters = await client.GetApplicationParametersAsync();
Console.WriteLine($"Opening: {parameters.OpeningStatement}");
// Get meta information
var meta = await client.GetApplicationMetaAsync();
// Get WebApp settings
var site = await client.GetApplicationSiteAsync();
Console.WriteLine($"Theme: {site.ChatColorTheme}");// Speech to text
using var audioStream = File.OpenRead("audio.wav");
var text = await client.SpeechToTextAsync(audioStream, "audio.wav", "user-123");
Console.WriteLine($"Transcribed: {text}");
// Text to audio
var request = new TextToAudioRequest
{
MessageId = "msg-123",
Text = "Hello world",
User = "user-123"
};
using var audioResponse = await client.TextToAudioAsync(request);
using var output = File.Create("output.wav");
await audioResponse.CopyToAsync(output);SendChatMessageAsync(request)- Send message in blocking modeSendChatMessageStreamAsync(request)- Send message in streaming modeStopGenerationAsync(taskId, user)- Stop message generation
UploadFileAsync(stream, fileName, user)- Upload a file
GetConversationsAsync(user, lastId, limit, pinned)- List conversationsGetConversationMessagesAsync(conversationId, user, firstId, limit)- Get messagesDeleteConversationAsync(conversationId, user)- Delete conversationRenameConversationAsync(conversationId, request)- Rename conversationGetConversationVariablesAsync(conversationId, user)- Get variables
SubmitMessageFeedbackAsync(messageId, request)- Submit feedbackGetSuggestedQuestionsAsync(messageId, user)- Get suggestions
SpeechToTextAsync(audioStream, fileName, user)- Convert speech to textTextToAudioAsync(request)- Convert text to audio
GetApplicationInfoAsync()- Get basic informationGetApplicationParametersAsync()- Get parametersGetApplicationMetaAsync()- Get meta informationGetApplicationSiteAsync()- Get WebApp settings
GetAnnotationsAsync(page, limit)- List annotationsCreateAnnotationAsync(request)- Create annotationUpdateAnnotationAsync(annotationId, request)- Update annotationDeleteAnnotationAsync(annotationId)- Delete annotationSetAnnotationReplyAsync(action, request)- Enable/disable annotation replyGetAnnotationReplyStatusAsync(action, jobId)- Check job status
GetFeedbacksAsync(page, limit)- Get application feedbacks
All request and response models are located in the DifyApiClient.Models namespace:
- Chat:
ChatMessageRequest,ChatCompletionResponse,ChunkChatCompletionResponse - Conversation:
Conversation,Message,ConversationListResponse,MessageListResponse - Application:
ApplicationInfo,ApplicationParameters,ApplicationMeta,ApplicationSite - Annotation:
Annotation,AnnotationRequest,AnnotationListResponse - Audio:
SpeechToTextRequest,TextToAudioRequest - File:
FileUploadResponse,FileInfo - Feedback:
AppFeedback,FeedbackListResponse
var options = new DifyApiClientOptions
{
BaseUrl = "http://your-dify-instance/v1",
ApiKey = "your-api-key",
Timeout = TimeSpan.FromSeconds(100) // Optional, default is 100 seconds
};The client throws HttpRequestException for HTTP errors. Always wrap calls in try-catch:
try
{
var response = await client.SendChatMessageAsync(request);
}
catch (HttpRequestException ex)
{
Console.WriteLine($"API Error: {ex.Message}");
}Run the unit tests:
dotnet testThe test project includes comprehensive tests for all major functionality with mocked HTTP responses.
This project is licensed under the MIT License.
Contributions are welcome! Please feel free to submit a Pull Request.
For issues and questions, please open an issue on the GitHub repository.