CargoWise eAdapter + .NET: robust integrations for logistics systems
CargoWise eAdapter is the officially supported gateway for exchanging data with CargoWise. It moves XML messages between CargoWise and external systems using transports like file drop (shared folder/SFTP), email, HTTP/SOAP, and message queues. This post shows practical .NET patterns that ship to production.
Core concepts (quick primer)
- Message: XML payload that follows CargoWise schemas (XSD). Common objects:
Shipment
,House
,Consignment
,Organization
,Customs
,Events
. - Direction:
- Inbound (to CargoWise): place a file/message in a watched location/endpoint; eAdapter validates and loads it.
- Outbound (from CargoWise): eAdapter emits messages when data changes per your subscriptions.
- Transports: File drop (local/UNC/SFTP), HTTP/SOAP, SMTP/POP3, MQ. Choose the simplest that meets SLAs and security.
- Reliability: eAdapter assigns message IDs and generates processing reports/acks. Design for retries and idempotency.
Recommended architecture on .NET
- Edge workers: Small services (Windows Service, Azure Function, container) that translate between your domain and CW XML.
- Mappings: Keep transform logic isolated (XSLT or C# mappers). Validate against CargoWise XSDs.
- Queues: Use Azure Storage Queues/Service Bus for retry, backpressure, and observability.
- Observability: Correlation ID per message, structured logs, dead-letter queue, replay tools.
Project layout example
src/
Adapter.Host/ # Windows service or worker
Adapter.Transforms/ # XSDs, XSLT, mapping code
Adapter.Contracts/ # POCOs, DTOs
Adapter.Tests/
Inbound to CargoWise (your system → CW)
Simplest transport: SFTP drop to the eAdapter inbound folder. The worker picks new files and moves them to processing/
after upload completes.
// Adapter.Host/InboundUploader.cs
using System.Net;
using System.Text;
using Renci.SshNet; // SSH.NET
public sealed class InboundUploader
{
private readonly ConnectionInfo connection;
private readonly string remoteDropPath; // e.g. /inbound/shipment
public InboundUploader(string host, int port, string user, string password, string remoteDropPath)
{
connection = new ConnectionInfo(host, port, user, new PasswordAuthenticationMethod(user, password));
this.remoteDropPath = remoteDropPath.TrimEnd('/');
}
public void Upload(string fileName, string xmlContents)
{
using var client = new SftpClient(connection);
client.Connect();
using var ms = new MemoryStream(Encoding.UTF8.GetBytes(xmlContents));
client.UploadFile(ms, $"{remoteDropPath}/{fileName}");
client.Disconnect();
}
}
Transform your DTOs to CargoWise XML using XDocument or XSLT:
// Adapter.Transforms/ShipmentMapper.cs
using System.Xml.Linq;
public static class ShipmentMapper
{
public static string ToCargoWiseXml(Shipment dto)
{
XNamespace ns = "http://www.cargowise.com/Schemas/Universal/2011/11"; // example, confirm version
var doc = new XDocument(
new XElement(ns + "UniversalShipment",
new XElement(ns + "Shipment",
new XElement(ns + "ShipmentNumber", dto.Number),
new XElement(ns + "Direction", dto.Direction),
new XElement(ns + "TransportMode", dto.Mode)
)
)
);
return doc.ToString(SaveOptions.DisableFormatting);
}
}
Validate against XSDs (CargoWise supplies them) during tests to catch mapping regressions.
Outbound from CargoWise (CW → your system)
Subscribe to events in eAdapter and consume messages via SFTP/HTTP. In .NET, expose an authenticated HTTP endpoint or poll an inbox.
// Minimal ASP.NET endpoint receiving outbound messages
[ApiController]
[Route("/cargowise/webhook")]
public class CargoWiseWebhookController : ControllerBase
{
[HttpPost]
public async Task<IActionResult> Receive()
{
using var reader = new StreamReader(Request.Body, Encoding.UTF8);
var xml = await reader.ReadToEndAsync();
// TODO: validate signature/IP allowlist
// Parse XML → upsert into your system
return Ok();
}
}
For file-based delivery, schedule a durable fetch:
// Periodic job that downloads files and moves to archive
public async Task<int> DrainOutboundAsync()
{
var files = await sftp.ListAsync("/outbound/events");
foreach (var f in files)
{
var xml = await sftp.ReadAllTextAsync(f.FullName);
await handler.ProcessAsync(xml);
await sftp.MoveAsync(f.FullName, $"/outbound/archive/{f.Name}");
}
return files.Count;
}
Error handling & idempotency
- Idempotency key: Use CW message ID or hash of the business key; reject duplicates.
- Retries: Exponential backoff; push to DLQ after N attempts.
- Poison messages: Persist raw XML for triage with reason codes.
- Ack loops: Ensure you don’t re‑ingest your own outbound copies; separate inbound/outbound roots.
Security checklist
- SFTP with key auth; restrict to dedicated user; chroot to the drop path.
- HTTPS webhooks; IP allowlist; HMAC signature.
- Secrets in GitHub Actions / Azure Key Vault; never commit credentials.
Local & test environments
- Ask CargoWise for a test company and eAdapter test endpoints.
- Keep test XSDs and sample payloads under version control.
- Build a replay harness to feed fixtures into your inbound path.
Monitoring
- Correlation ID per message; include in all logs/metrics.
- Emit custom metrics: messages processed, failures, retry counts, end‑to‑end latency.
- Alert on DLQ growth and consecutive failures.
Deployment
- For Windows: use
WorkerService
template withsc.exe
or NSSM; set recovery to auto‑restart. - For cloud: Azure Functions or container on App Service / ECS with scheduled triggers.
- Ship configuration as code (JSON/YAML) so new lanes can be added without redeploy.
Quick start checklist
- Obtain CargoWise schemas and sample messages
- Choose transport (SFTP vs HTTP) and credentials
- Map DTOs → CW XML and validate against XSD
- Implement idempotent inbound + outbound pipelines
- Add retry/DLQ, structured logging, metrics, and alerts
- Security hardening (keys, IP allowlist, HMAC)
With a thin, well‑tested .NET adapter and disciplined observability, eAdapter integrations can be both resilient and easy to extend as lanes and partners grow.