Integration Services
Overview
The integration services layer in MepApps.Dash.Inv.Batch.MiniMrpOrderCreation provides seamless connectivity with SYSPRO ERP system and external services. This layer manages API communications, data transformations, authentication, and implements robust error handling and retry mechanisms for reliable system integration.
Key Concepts
- SYSPRO E.net Integration: Business object posting through SYSPRO sessions
- XML Document Generation: Creating structured documents for SYSPRO posts
- Session Management: SYSPRO authentication and session handling
- Error Mapping: Translating SYSPRO errors to user-friendly messages
- Retry Mechanisms: Handling transient failures gracefully
Implementation Details
SYSPRO Post Service
The core integration service for SYSPRO communications:
// Services/SysproPostService.cs (lines 8-26)
public class SysproPostService
{
private readonly ILogger<SysproPostService> _logger;
private readonly ISysproEnetSession _sysproEnetSession;
public SysproPostService(
ILogger<SysproPostService> logger,
ISysproEnetSession sysproEnetSession)
{
_logger = logger;
try
{
_sysproEnetSession = sysproEnetSession;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error initializing SysproPostService");
throw;
}
}
}
Order Creation Integration
Processing orders through SYSPRO business objects:
// Services/SysproPostService.cs (lines 28-52)
public IEnumerable<SummaryOrder> CreateOrders(IEnumerable<SummaryOrder> orders)
{
try
{
foreach (var order in orders)
{
order.PostItem = new SysproEnetPostItem("PORTOI");
var inputXml = GetInputXml(order);
var paramXml = GetParamXml();
order.PostItem.UpdatePostXmlValues(inputXml, paramXml);
var outputXml = PerformBusinessObjectPost(
order.PostItem.InputXml,
order.PostItem.ParamXml,
order.PostItem.BusinessObject);
order.PostItem.UpdateOutputXml(outputXml);
order.OrderNumber = ParseXmlOutput(order.PostItem.OutputXml);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating orders {@Orders}", orders);
throw;
}
return orders;
}
XML Document Generation
Creating structured XML for SYSPRO posts:
// Services/SysproPostService.cs
public string GetInputXml(SummaryOrder order)
{
var doc = new XDocument(
new XElement("PostPurchaseOrders",
new XElement("Item",
new XElement("Supplier", order.Supplier),
new XElement("Warehouse", order.Warehouse),
new XElement("OrderDate", DateTime.Now.ToString("yyyy-MM-dd")),
new XElement("DueDate", order.DueDate?.ToString("yyyy-MM-dd") ?? ""),
new XElement("Buyer", order.Buyer),
new XElement("StockLines",
order.OrderLines.Select(line =>
new XElement("StockLine",
new XElement("StockCode", line.StockCode),
new XElement("Warehouse", line.Warehouse),
new XElement("OrderQty", line.OrderQty),
new XElement("OrderUom", line.OrderUom),
new XElement("Price", line.Price),
new XElement("PriceUom", line.PriceUom),
new XElement("TaxCode", line.TaxCode),
new XElement("DueDate", line.DueDate?.ToString("yyyy-MM-dd") ?? "")
)
)
)
)
)
);
return doc.ToString();
}
public string GetParamXml()
{
var doc = new XDocument(
new XElement("PostPurchaseOrders",
new XElement("Parameters",
new XElement("ValidateOnly", "N"),
new XElement("IgnoreWarnings", "N"),
new XElement("ApplyIfEntireDocumentValid", "Y"),
new XElement("ActionType", "A"),
new XElement("WarnIfLocked", "Y")
)
)
);
return doc.ToString();
}
SYSPRO Session Management
Managing SYSPRO authentication and sessions:
public class SysproSessionService
{
private readonly ISysproEnetSession _sysproSession;
private readonly ILogger<SysproSessionService> _logger;
public async Task<bool> ValidateSessionAsync()
{
try
{
if (_sysproSession == null)
{
_logger.LogWarning("SYSPRO session is null");
return false;
}
// Check session validity
var isValid = await _sysproSession.IsSessionValidAsync();
if (!isValid)
{
_logger.LogWarning("SYSPRO session is invalid, attempting reconnection");
return await ReconnectSessionAsync();
}
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error validating SYSPRO session");
return false;
}
}
private async Task<bool> ReconnectSessionAsync()
{
try
{
await _sysproSession.ReconnectAsync();
return await _sysproSession.IsSessionValidAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to reconnect SYSPRO session");
return false;
}
}
}
Business Object Posting
Executing SYSPRO business object posts with error handling:
// Services/SysproPostService.cs
private string PerformBusinessObjectPost(
string inputXml,
string paramXml,
string businessObject)
{
try
{
// Validate session before posting
if (!_sysproEnetSession.IsValid)
{
throw new InvalidOperationException("SYSPRO session is not valid");
}
// Perform the post
var result = _sysproEnetSession.Post(
businessObject,
paramXml,
inputXml);
// Check for errors in response
var errorCheck = XDocument.Parse(result);
var errors = errorCheck.Descendants("Error").ToList();
if (errors.Any())
{
var errorMessages = errors.Select(e => e.Value);
_logger.LogError("SYSPRO post errors: {Errors}",
string.Join(", ", errorMessages));
throw new SysproPostException(errorMessages);
}
return result;
}
catch (Exception ex)
{
_logger.LogError(ex,
"Error performing business object post for {BusinessObject}",
businessObject);
throw;
}
}
Response Parsing
Extracting data from SYSPRO XML responses:
// Services/SysproPostService.cs
private string ParseXmlOutput(string outputXml)
{
try
{
if (string.IsNullOrWhiteSpace(outputXml))
{
_logger.LogWarning("Output XML is empty");
return "FAILED";
}
var doc = XDocument.Parse(outputXml);
// Check for errors first
var errorElements = doc.Descendants("ErrorMessage");
if (errorElements.Any())
{
var errors = errorElements.Select(e => e.Value);
_logger.LogError("SYSPRO returned errors: {Errors}",
string.Join(", ", errors));
return "FAILED";
}
// Extract purchase order number
var orderNumber = doc.Descendants("PurchaseOrder")
.FirstOrDefault()?.Value;
if (string.IsNullOrWhiteSpace(orderNumber))
{
_logger.LogWarning("No purchase order number in response");
return "FAILED";
}
return orderNumber;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error parsing XML output: {OutputXml}", outputXml);
return "FAILED";
}
}
Retry and Circuit Breaker Patterns
Implementing resilience patterns for integration:
public class ResilientSysproService
{
private readonly IRetryPolicy _retryPolicy;
private readonly ICircuitBreaker _circuitBreaker;
public ResilientSysproService()
{
_retryPolicy = new ExponentialBackoffRetry(
maxRetries: 3,
baseDelay: TimeSpan.FromSeconds(1));
_circuitBreaker = new CircuitBreaker(
threshold: 5,
timeout: TimeSpan.FromMinutes(1));
}
public async Task<T> ExecuteWithResilience<T>(
Func<Task<T>> operation,
string operationName)
{
return await _circuitBreaker.ExecuteAsync(async () =>
{
return await _retryPolicy.ExecuteAsync(async () =>
{
try
{
return await operation();
}
catch (SysproSessionException)
{
// Session errors are retryable
throw;
}
catch (SysproBusinessException)
{
// Business errors are not retryable
throw new NonRetryableException();
}
});
});
}
}
External Service Integration
Pattern for integrating with external services:
public class ExcelExportService : IExcelExportService
{
private readonly ILogger<ExcelExportService> _logger;
public async Task<byte[]> ExportToExcelAsync<T>(
IEnumerable<T> data,
string worksheetName) where T : class
{
try
{
using var package = new ExcelPackage();
var worksheet = package.Workbook.Worksheets.Add(worksheetName);
// Load data into worksheet
worksheet.Cells["A1"].LoadFromCollection(data, true);
// Format as table
var table = worksheet.Tables.Add(
worksheet.Dimension,
worksheetName.Replace(" ", ""));
table.TableStyle = TableStyles.Medium2;
// Auto-fit columns
worksheet.Cells[worksheet.Dimension.Address].AutoFitColumns();
return await package.GetAsByteArrayAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error exporting to Excel");
throw;
}
}
}
Examples
Example 1: Complete Integration Flow
public async Task<IntegrationResult> ProcessPurchaseOrderIntegration(
OrderRequest request)
{
var result = new IntegrationResult();
try
{
// Step 1: Validate session
if (!await ValidateSessionAsync())
{
throw new IntegrationException("SYSPRO session invalid");
}
// Step 2: Transform request to SYSPRO format
var sysproOrder = TransformToSysproFormat(request);
// Step 3: Generate XML documents
var inputXml = GenerateInputXml(sysproOrder);
var paramXml = GenerateParameterXml();
// Step 4: Post to SYSPRO with retry
var response = await ExecuteWithResilience(async () =>
{
return await PostToSysproAsync(inputXml, paramXml);
}, "PurchaseOrderPost");
// Step 5: Parse response
result.OrderNumber = ParseOrderNumber(response);
result.IsSuccess = result.OrderNumber != "FAILED";
// Step 6: Log integration event
await LogIntegrationEventAsync(request, result);
}
catch (Exception ex)
{
result.IsSuccess = false;
result.ErrorMessage = ex.Message;
_logger.LogError(ex, "Integration failed");
}
return result;
}
Example 2: Error Mapping
public class SysproErrorMapper
{
private readonly Dictionary<string, string> _errorMap = new()
{
["Supplier on hold"] = "The selected supplier is currently on hold and cannot receive orders",
["Stock code not stocked"] = "One or more items are not stocked in the selected warehouse",
["Insufficient privileges"] = "You don't have permission to create purchase orders",
["Locked record"] = "The record is being edited by another user. Please try again later"
};
public string MapToUserFriendlyMessage(string sysproError)
{
foreach (var mapping in _errorMap)
{
if (sysproError.Contains(mapping.Key, StringComparison.OrdinalIgnoreCase))
{
return mapping.Value;
}
}
// Default message for unmapped errors
return $"An error occurred during processing: {sysproError}";
}
}
Example 3: Batch Integration Processing
public async Task<BatchIntegrationResult> ProcessBatchIntegration(
IEnumerable<OrderRequest> requests)
{
var result = new BatchIntegrationResult();
var tasks = new List<Task<IntegrationResult>>();
// Process in parallel with concurrency limit
using var semaphore = new SemaphoreSlim(5); // Max 5 concurrent
foreach (var request in requests)
{
await semaphore.WaitAsync();
var task = Task.Run(async () =>
{
try
{
return await ProcessPurchaseOrderIntegration(request);
}
finally
{
semaphore.Release();
}
});
tasks.Add(task);
}
var results = await Task.WhenAll(tasks);
result.Successful = results.Count(r => r.IsSuccess);
result.Failed = results.Count(r => !r.IsSuccess);
result.Details = results.ToList();
return result;
}
Best Practices
Authentication and Security
- Never log sensitive credentials
- Use secure session management
- Implement token refresh mechanisms
- Validate certificates for external services
Error Handling
- Map technical errors to user-friendly messages
- Log detailed errors for debugging
- Implement retry for transient failures
- Use circuit breakers to prevent cascading failures
Performance Optimization
- Batch operations where possible
- Use async/await for I/O operations
- Implement connection pooling
- Cache frequently used reference data
Data Transformation
- Validate data before transformation
- Use strongly-typed models
- Handle null values gracefully
- Document mapping rules clearly
Common Pitfalls
Not Handling Session Expiry
// BAD: Assuming session is always valid
var result = _sysproSession.Post(businessObject, xml);
// GOOD: Check and refresh session
if (!await _sysproSession.IsValidAsync())
{
await _sysproSession.RefreshAsync();
}
var result = await _sysproSession.PostAsync(businessObject, xml);
Ignoring Partial Failures
// BAD: All or nothing approach
if (anyFailed) throw new Exception("Batch failed");
// GOOD: Handle partial success
var result = new BatchResult
{
Successful = successCount,
Failed = failedItems,
CanContinue = successCount > 0
};
Poor Error Messages
// BAD: Generic error
throw new Exception("Post failed");
// GOOD: Detailed context
throw new IntegrationException(
$"Failed to post purchase order for supplier {supplier}: {sysproError}",
innerException);
Related Documentation
Summary
The integration services layer provides robust, reliable connectivity between the MiniMrpOrderCreation dashboard and external systems, primarily SYSPRO ERP. Through proper session management, error handling, retry mechanisms, and data transformation patterns, the layer ensures smooth operation even in the face of network issues or system unavailability. The structured approach to XML generation, response parsing, and error mapping maintains system reliability while providing clear feedback to users.