Connector Implementation Overview
When you need to integrate with a messaging provider that doesn't have a built-in connector, you build a custom connector. This guide explains the architecture and what the framework provides.
What is a Connector?
A connector is a class that implements the IChannelConnector interface, translating between the framework's unified IMessage model and a provider's specific API.
When to Build a Custom Connector
Build a custom connector when:
- ✅ You need to integrate with a proprietary or internal messaging system
- ✅ A provider doesn't have an existing connector in the framework
- ✅ You need custom authentication or message translation logic
- ✅ You want to wrap legacy systems with the framework's unified interface
Architecture: Template Method Pattern
The framework uses the Template Method pattern:
ChannelConnectorBase (abstract base class)
├── Defines operation skeletons (initialize, send, receive, status)
├── Handles cross-cutting concerns
└── Calls your overrides for provider-specific logic
Your Connector (concrete implementation)
├── Implements provider-specific initialization
├── Translates IMessage to provider API format
├── Makes API calls and parses responses
└── Handles connection testing
What the Base Class Provides
ChannelConnectorBase handles all cross-cutting concerns so you focus on provider-specific logic:
✅ State Management
Tracks connector lifecycle and prevents operations when not ready:
// Base class prevents operations if not in Ready state
if (State != ConnectorState.Ready)
throw new InvalidOperationException("Connector not ready");
✅ Capability Validation
Checks schema capabilities before delegating to your code:
// Base class validates capability before calling your send method
ValidateCapability(ChannelCapability.SendMessages);
✅ Message Validation
Validates messages against the schema before your send logic runs:
// Base class validates message before calling SendMessageCoreAsync
await foreach (var result in ValidateMessageAsync(message, ct))
{
if (result != ValidationResult.Success)
return OperationResult<SendResult>.ValidationFailed(...);
}
✅ Authentication
Resolves credentials via the authentication manager:
// Base class auto-authenticates during InitializeAsync()
// Credential is available via AuthenticationCredential property
var token = AuthenticationCredential?.Value;
✅ Error Wrapping
Catches exceptions from your code and wraps them in OperationResult<T>:
try
{
// Your code throws exception
throw new HttpRequestException("Network error");
}
catch (Exception ex)
{
// Base class wraps as OperationResult.Fail()
return OperationResult<SendResult>.Fail("HTTP_ERROR", ex.Message);
}
✅ Logging Scopes
Creates structured logging scopes for tracing:
using var scope = BeginConnectorLoggerScope();
// Logs include: [Provider/Type v1.0.0]
using var messageScope = BeginMessageLoggerScope(message);
// Logs include: [MessageId: msg-123]
What You Implement
You implement provider-specific translation logic in override methods:
Required Overrides (4 methods)
InitializeConnectorAsync()- Validate settings, create client, authenticateTestConnectorConnectionAsync()- Lightweight connectivity testSendMessageCoreAsync()- TranslateIMessageto provider API and sendGetConnectorStatusAsync()- Return current connector status
Optional Overrides
ReceiveMessagesCoreAsync()- Receive inbound messagesGetMessageStatusCoreAsync()- Query message delivery statusGetConnectorHealthAsync()- Health check implementationSendBatchCoreAsync()- Batch message sendingGetSendTimeout(),GetReceiveTimeout()- Custom timeout values
Minimal Example
[ChannelSchema(typeof(MySchemaFactory))]
public class MyConnector : ChannelConnectorBase
{
private HttpClient _httpClient;
public MyConnector(IChannelSchema schema, ConnectionSettings? settings = null,
ILogger? logger = null, IAuthenticationManager? authManager = null)
: base(schema, settings, logger, authManager) { }
// 1. Initialize
protected override ValueTask InitializeConnectorAsync(CancellationToken ct)
{
var apiKey = ConnectionSettings.GetParameter<string>("ApiKey");
_httpClient = new HttpClient();
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
SetState(ConnectorState.Ready);
return ValueTask.CompletedTask;
}
// 2. Test connection
protected override ValueTask TestConnectorConnectionAsync(CancellationToken ct)
{
// Throw on failure - base class wraps exception
return ValueTask.CompletedTask;
}
// 3. Send message
protected override async Task<SendResult> SendMessageCoreAsync(
IMessage message, CancellationToken ct)
{
var payload = new
{
to = message.Receiver?.Address,
text = (message.Content as TextContent)?.Text
};
var response = await _httpClient.PostAsJsonAsync("/api/send", payload, ct);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<ApiResponse>(ct);
return new SendResult(message.Id, result.Id);
}
// 4. Get status
protected override Task<StatusInfo> GetConnectorStatusAsync(CancellationToken ct)
{
return Task.FromResult(new StatusInfo("OK"));
}
}
Getting Started
- Read Minimum Implementation - Build a working connector
- Review Authentication - Configure authentication
- Study Message Validation - Add custom validation
- Explore Advanced Topics - Receive, status, batch, testing
Next Steps
- Build your first connector: Minimum Implementation Guide
- Understand authentication: Authentication in Custom Connectors
- Add validation: Message Validation
- Advanced features: Advanced Topics