I am have a windows service which will subscribe to a queue and process messages continuously. Every thing is working except except for the reconnect logic. I am using IBM.XMS.dll version 8 in my code.
I am having issues in re-establishing the subscription when IBM Websphere MQ server fail-over happens. During fail-over, I get the following exception:
IBM.XMS.IllegalStateException: XMSCC0026
XMSCC0026.explanation
XMSCC0026.useraction
at IBM.XMS.Client.Impl.State.CheckNotClosed(String message)
at IBM.XMS.Client.Impl.XmsMessageProducerImpl.Send_(Boolean inIdentifiedContext, XmsDestinationImpl dest, IMessage message, DeliveryMode deliveryMode, Int32 priority, Int64 timeToLive, Boolean explicitDlvModePriorityAndTimeToLive)
at IBM.XMS.Client.Impl.XmsMessageProducerImpl.Send(IMessage message)
Stack Trace: at IBM.XMS.Client.Impl.State.CheckNotClosed(String message)
at IBM.XMS.Client.Impl.XmsMessageProducerImpl.Send_(Boolean inIdentifiedContext, XmsDestinationImpl dest, IMessage message, DeliveryMode deliveryMode, Int32 priority, Int64 timeToLive, Boolean explicitDlvModePriorityAndTimeToLive)
at IBM.XMS.Client.Impl.XmsMessageProducerImpl.Send(IMessage message)
I need some help in re-establishing the subscription in onException event handler:
private void ReconnectToXMS_MQ()
{
if (!_cancellationToken.IsCancellationRequested)
{
LoggingService.LogDebug($"[XMSQueue] Reconnecting for queue: {ConfigQueueName} and Address: {Address}");
if(MsgQueueType == QueueType.Publisher)
{
Dispose(true);
InitializeXMSMQ();
InitialiseOutbound(ConfigQueueName, Pattern, false);
}
else if(MsgQueueType == QueueType.Subscriber)
{
//Need help here
}
else if(MsgQueueType == QueueType.Receiver)
{
Dispose(true);
InitializeXMSMQ();
InitialiseInbound(ConfigQueueName, Pattern, false);
}
}
}
Following is the full code snippet of my XMS implementation.
internal class XMSQueue : MessageQueueBase
{
#region " Local Methods/Properties "
private IConnectionFactory _connectionfactory;
private IConnection _connection;
private IDestination _destination;
private ISession _session;
private IMessageConsumer _consumer;
private IMessageProducer _producer;
private CancellationToken _cancellationTOke;
private string ConfigQueueName { get; set; }
private XMSProperties xmsConfiguration { get; set; }
private MessageFormat MsgFormat { get; set; }
private QueueType MsgQueueType { get; set; }
#endregion
#region " MessageQueueBase OVerrides "
public override string Name { get; set; }
#region " Receive/Subscribe "
public override void InitialiseInbound(string name, MessagePattern pattern, bool isTemporary)
{
try
{
ConfigQueueName = name;
Initialise(Direction.Inbound, name, pattern, isTemporary);
InitializeXMSMQ();
//Set Destination
_destination = CreateDestinationInbound();
//Create Consumer
_consumer = _session.CreateConsumer(_destination);
}
catch (XMSException xmsError)
{
LogXMSExceptionDetails(xmsError);
throw xmsError;
}
catch (Exception ex)
{
throw ex;
}
}
public override void Subscribe<TReceiveMessageType>(Action<TReceiveMessageType> onMessageReceived, CancellationToken cancellationToken, MessageFormat messageFormat = MessageFormat.XMS_IMessage)
{
try
{
MsgQueueType = QueueType.Subscriber;
_cancellationTOke = cancellationToken;
cancellationToken.Register(() =>
{
_consumer.MessageListener = null;
});
MsgFormat = messageFormat;
// Create and register the listener
MessageListener messageListener = new MessageListener((msg) =>
{
TReceiveMessageType message;
message = ProcessInboundMessage<TReceiveMessageType>(messageFormat, msg);
onMessageReceived(message);
});
_consumer.MessageListener = messageListener;
// Start the connection
_connection.Start();
}
catch (XMSException xmsError)
{
LogXMSExceptionDetails(xmsError);
ReconnectToXMS_MQ();
}
catch (Exception ex)
{
ReconnectToXMS_MQ();
}
}
public override void Receive<TReceiveMessageType>(Action<TReceiveMessageType> onMessageReceived, bool processAsync, int maximumWaitMilliseconds = 0, MessageFormat messageFormat = MessageFormat.XMS_IMessage)
{
try
{
MsgQueueType = QueueType.Receiver;
MsgFormat = messageFormat;
// Start the connection
_connection.Start();
IMessage inbound = null;
TReceiveMessageType message;
// Receive the message
inbound = _consumer.Receive();
if (inbound != null)
{
message = ProcessInboundMessage<TReceiveMessageType>(messageFormat, inbound);
if (processAsync)
{
Task.Factory.StartNew(() => onMessageReceived(message));
}
else
{
onMessageReceived(message);
}
}
else
{
throw new Exception("Message received was null.");
}
}
catch (XMSException xmsError)
{
LogXMSExceptionDetails(xmsError);
throw xmsError;
}
catch (Exception ex)
{
throw ex;
}
}
#endregion
#region " Send/Publish "
public override void InitialiseOutbound(string name, MessagePattern pattern, bool isTemporary)
{
try
{
ConfigQueueName = name; //Save the config queue name for later use in reconnection logic
Initialise(Direction.Outbound, name, pattern, isTemporary);
InitializeXMSMQ();
//Set Destination
_destination = CreateDestinationOutbound();
//Create Producer
_producer = _session.CreateProducer(_destination);
}
catch (XMSException xmsError)
{
LogXMSExceptionDetails(xmsError);
throw xmsError;
}
catch (Exception ex)
{
throw ex;
}
}
public override bool Send<T>(T message, MessageFormat messageFormat)
{
try
{
MsgQueueType = QueueType.Publisher;
MsgFormat = messageFormat;
//Start the connection
_connection.Start();
//Create Message
IMessage outbound = null;
if (messageFormat == MessageFormat.String)
{
outbound = _session.CreateTextMessage(message.ToString());
}
else if (messageFormat == MessageFormat.ByteArray)
{
outbound = _session.CreateObjectMessage();
byte[] byteText = message as byte[];
((IObjectMessage)outbound).SetObject(byteText);
}
else if (messageFormat == MessageFormat.XMS_IMessage)
{
outbound = message as IMessage;
}
else
{
throw new NotSupportedException("UnRecognized/UnSUpported message format. Please use String, ByteArray or XMS_IMessage message formats");
}
_producer.Send(outbound);
return true;
}
catch (XMSException xmsError)
{
LogXMSExceptionDetails(xmsError);
return false;
}
catch (Exception ex)
{
return false;
}
}
#endregion
protected override void Dispose(bool disposing)
{
try
{
if (_consumer != null)
{
_consumer.Close();
_consumer.Dispose();
}
//Reset Producer
if (_producer != null)
{
_producer.Close();
_producer.Dispose();
}
//Reset Destination
if (_destination != null)
_destination.Dispose();
//Reset Session
if (_session != null)
{
_session.Close();
_session.Dispose();
}
//Reset Connection
if (_connection != null)
{
_connection.Close();
_consumer.Dispose();
}
}
catch (Exception)
{
//ignore any exceptions at this point
}
}
#endregion
#region " Local Methods "
#region " Initialize and Reconnect "
private void InitializeXMSMQ()
{
xmsConfiguration = new XMSProperties(Properties);
xmsConfiguration.ConnectionType = RequirePropertyValue(ConfigurationKeys.ConnectionType);
//SetConnectionFactory Connection Factory
SetConnectionFactory();
//Set Connection
_connection = _connectionfactory.CreateConnection(null, null); //We do not use UserID and Password to connect.
_connection.ExceptionListener = new ExceptionListener(OnConnectionException);
//Set Session
_session = _connection.CreateSession(false, AcknowledgeMode.AutoAcknowledge);
}
private void ReconnectToXMS_MQ()
{
if (!_cancellationTOke.IsCancellationRequested)
{
if(MsgQueueType == QueueType.Publisher)
{
Dispose(true);
InitializeXMSMQ();
InitialiseOutbound(ConfigQueueName, Pattern, false);
}
else if(MsgQueueType == QueueType.Subscriber)
{
//Need help here
}
else if(MsgQueueType == QueueType.Receiver)
{
Dispose(true);
InitializeXMSMQ();
InitialiseInbound(ConfigQueueName, Pattern, false);
}
}
}
private void OnConnectionException(Exception ex)
{
ReconnectToXMS_MQ();
}
private static void LogXMSExceptionDetails(XMSException xmsError)
{
if (xmsError.LinkedException != null)
{
LogError($"[XMSQueue] Linked Exception: {xmsError.LinkedException.Message} ");
if (xmsError.LinkedException.InnerException != null)
LogError($"[XMSQueue] Linked Inner Exception: {xmsError.LinkedException.InnerException} ");
}
}
#endregion
#region " XMS Connection Factory "
private void SetConnectionFactory()
{
_connectionfactory = CreateConnectionFactory();
}
private IConnectionFactory CreateConnectionFactory()
{
IConnectionFactory iConnFact;
switch (xmsConfiguration.ConnectionType.ToUpper())
{
// WPM connection factory
case "WPM":
iConnFact = CreateConnectionFactoryWPM();
break;
// RTT connection factory
case "RTT":
iConnFact = CreateConnectionFactoryRTT();
break;
// WMQ connection factory
case "WMQ":
iConnFact = CreateConnectionFactoryWMQ();
break;
default:
iConnFact = null;
break;
}
return iConnFact;
}
/// <summary>
/// Create a WPM connection factory and set relevant properties.
/// </summary>
/// <returns>A connection factory</returns>
private static IConnectionFactory CreateConnectionFactoryWPM()
{
// Create the connection factories factory
XMSFactoryFactory factoryFactory = XMSFactoryFactory.GetInstance(XMSC.CT_WPM);
// Use the connection factories factory to create a connection factory
IConnectionFactory cf = factoryFactory.CreateConnectionFactory();
// WPM multicast is currently disabled
// cf.SetIntProperty(XMSC.WPM_MULTICAST, Options.MulticastModeWPM.ValueAsNumber);
return (cf);
}
/// <summary>
/// Create a RTT connection factory and set relevant properties.
/// </summary>
/// <returns>A connection factory</returns>
private static IConnectionFactory CreateConnectionFactoryRTT()
{
// Create the connection factories factory
XMSFactoryFactory factoryFactory = XMSFactoryFactory.GetInstance(XMSC.CT_RTT);
// Use the connection factories factory to create a connection factory
IConnectionFactory cf = factoryFactory.CreateConnectionFactory();
return (cf);
}
/// <summary>
/// Create a WMQ connection factory and set relevant properties.
/// </summary>
/// <returns>A connection factory</returns>
private IConnectionFactory CreateConnectionFactoryWMQ()
{
// Create the connection factories factory
XMSFactoryFactory factoryFactory = XMSFactoryFactory.GetInstance(XMSC.CT_WMQ);
// Use the connection factories factory to create a connection factory
IConnectionFactory cf = factoryFactory.CreateConnectionFactory();
// Set the properties
cf.SetStringProperty(XMSC.WMQ_HOST_NAME, xmsConfiguration.WMQProperties.Hostname);
cf.SetIntProperty(XMSC.WMQ_PORT, xmsConfiguration.WMQProperties.Port);
cf.SetStringProperty(XMSC.WMQ_CHANNEL, xmsConfiguration.WMQProperties.Channel);
cf.SetIntProperty(XMSC.WMQ_CONNECTION_MODE, xmsConfiguration.WMQProperties.ConnectionMode);
if (string.IsNullOrEmpty(xmsConfiguration.WMQProperties.QueueManager))
{
cf.SetStringProperty(XMSC.WMQ_QUEUE_MANAGER, "");
}
else
{
cf.SetStringProperty(XMSC.WMQ_QUEUE_MANAGER, xmsConfiguration.WMQProperties.QueueManager);
}
if (xmsConfiguration.WMQProperties.BrokerVersion != -1) //-1 => Not set in configuration
cf.SetIntProperty(XMSC.WMQ_BROKER_VERSION, xmsConfiguration.WMQProperties.BrokerVersion);
cf.SetStringProperty(XMSC.WMQ_CONNECTION_NAME_LIST, xmsConfiguration.WMQProperties.Hostname);
if (!string.IsNullOrWhiteSpace(xmsConfiguration.WMQProperties.ConnectionList))
{
cf.SetStringProperty(XMSC.WMQ_CONNECTION_NAME_LIST, xmsConfiguration.WMQProperties.ConnectionList);
cf.SetIntProperty(XMSC.WMQ_CLIENT_RECONNECT_TIMEOUT, xmsConfiguration.WMQProperties.ReconnectTimeout);
cf.SetIntProperty(XMSC.WMQ_CLIENT_RECONNECT_OPTIONS, xmsConfiguration.WMQProperties.ReconnectOptions);
}
if (!string.IsNullOrWhiteSpace(xmsConfiguration.WMQProperties.SSLCertRepository))
{
cf.SetStringProperty(XMSC.WMQ_SSL_KEY_REPOSITORY, xmsConfiguration.WMQProperties.SSLCertRepository);
cf.SetStringProperty(XMSC.WMQ_SSL_CIPHER_SPEC, xmsConfiguration.WMQProperties.SSLCipherSpec);
}
cf.SetStringProperty(XMSC.WMQ_PROVIDER_VERSION, XMSC.WMQ_PROVIDER_VERSION_DEFAULT);
cf.SetBooleanProperty(XMSC.WMQ_SYNCPOINT_ALL_GETS, true);
return (cf);
}
#endregion
#region " Create IDestination "
protected IDestination CreateDestinationOutbound()
{
IDestination iDest;
switch (xmsConfiguration.ConnectionType.ToUpper())
{
// WPM destination
case Literals.WPM:
case Literals.WMQ:
if (Pattern == MessagePattern.FireAndForget)
{
iDest = (Address.StartsWith("queue://")) ?
_session.CreateQueue(Address) : // Create a Queue
_session.CreateTopic(Address); // FireAndForget is defaulted to Topic for Outbound unless Address is defined with queue
}
else if (Pattern == MessagePattern.PublishSubscribe)
{
iDest = (Address.StartsWith("queue://")) ?
_session.CreateQueue(Address) : // Create a Queue
_session.CreateTopic(Address); // PublishSubscribe is defaulted to Topic for Outbound unless Address is defined with queue
}
else
{
iDest = (Address.StartsWith("queue://")) ?
_session.CreateQueue(Address) : // Create a queue
_session.CreateTopic(Address); // Otherwise, default to creating a topic
}
iDest.SetIntProperty(XMSC.DELIVERY_MODE, xmsConfiguration.WMQProperties.DeliveryMode);
iDest.SetIntProperty(XMSC.WMQ_TARGET_CLIENT, xmsConfiguration.WMQProperties.TargetClient);
break;
// RTT destination
case Literals.RTT:
iDest = _session.CreateTopic(Address); // Create a topic
break;
default:
iDest = null;
break;
}
return (iDest);
}
protected IDestination CreateDestinationInbound()
{
IDestination iDest;
switch (xmsConfiguration.ConnectionType.ToUpper())
{
// WPM destination
case Literals.WPM:
case Literals.WMQ:
if (Pattern == MessagePattern.FireAndForget)
{
iDest = (Address.StartsWith("topic://")) ?
_session.CreateTopic(Address) : // Create a Topic
_session.CreateQueue(Address); // FireAndForget is defaulted to Queues for Inbound unless Address is defined with topic
}
else if (Pattern == MessagePattern.PublishSubscribe)
{
iDest = (Address.StartsWith("topic://")) ?
_session.CreateTopic(Address) : // Create a Topic
_session.CreateQueue(Address); // PublishSubscribe is defaulted to Queue for Inbound unless Address is defined with topic
}
else
{
iDest = (Address.StartsWith("topic://")) ?
_session.CreateTopic(Address) : // Create a Topic
_session.CreateQueue(Address); // Otherwise, default to creating a Queue
}
iDest.SetIntProperty(XMSC.DELIVERY_MODE, xmsConfiguration.WMQProperties.DeliveryMode);
iDest.SetIntProperty(XMSC.WMQ_TARGET_CLIENT, xmsConfiguration.WMQProperties.TargetClient);
break;
// RTT destination
case Literals.RTT:
iDest = _session.CreateQueue(Address); // Create a Queue
break;
default:
iDest = null;
break;
}
return (iDest);
}
#endregion
private static TReceiveMessageType ProcessInboundMessage<TReceiveMessageType>(MessageFormat messageFormat, IMessage inbound)
{
TReceiveMessageType message;
if (messageFormat == MessageFormat.String)
{
ITextMessage txtMessage = (ITextMessage)inbound;
message = (TReceiveMessageType)(object)txtMessage.Text;
}
else if (messageFormat == MessageFormat.ByteArray)
{
IObjectMessage inboundBytes = (IObjectMessage)inbound;
byte[] body = inboundBytes.GetObject();
message = (TReceiveMessageType)(object)body;
}
else if (messageFormat == MessageFormat.WMQ_MQMessage)
{
throw new NotSupportedException("MessageFormat.WMQ_MQMessage is not supported in IBM.XMS implementation. Please use String, ByteArray, Stream, Object (byte[]) or XMS_IMessage message formats");
}
else if (messageFormat == MessageFormat.XMS_IMessage)
{
message = (TReceiveMessageType)inbound;
}
else if (messageFormat == MessageFormat.Object)
{
IObjectMessage inboundObject = (IObjectMessage)inbound;
byte[] body = inboundObject.GetObject();
message = (TReceiveMessageType)(object)body;
}
else if (messageFormat == MessageFormat.Stream)
{
IObjectMessage inboundObject = (IObjectMessage)inbound;
byte[] body = inboundObject.GetObject();
MemoryStream streamBody = new MemoryStream(body);
message = (TReceiveMessageType)(object)streamBody;
}
else
{
throw new NotSupportedException("UnRecognized message format. Please use String, ByteArray, Stream, Object (byte[]) or XMS_IMessage message formats");
}
return message;
}
#endregion
}
Related
I have a windows TCP service, which has many devices connecting to it, and a client can have one or more devices.
Requirement:
Separate Folder per client with separate log file for each device.
so something like this:
/MyService/25-04-2016/
Client 1/
Device1.txt
Device2.txt
Device3.txt
Client 2/
Device1.txt
Device2.txt
Device3.txt
Now I have not used a 3rd Party library like log4net or NLog, I have a class which handles this.
public class xPTLogger : IDisposable
{
private static object fileLocker = new object();
private readonly string _logFileName;
private readonly string _logFilesLocation;
private readonly int _clientId;
public xPTLogger() : this("General") { }
public xPTLogger(string logFileName)
{
_clientId = -1;
_logFileName = logFileName;
_logFilesLocation = SharedConstants.LogFilesLocation; // D:/LogFiles/
}
public xPTLogger(string logFileName, int companyId)
{
_clientId = companyId;
_logFileName = logFileName;
_logFilesLocation = SharedConstants.LogFilesLocation;
}
public void LogMessage(MessageType messageType, string message)
{
LogMessage(messageType, message, _logFileName);
}
public void LogExceptionMessage(string message, Exception innerException, string stackTrace)
{
var exceptionMessage = innerException != null
? string.Format("Exception: [{0}], Inner: [{1}], Stack Trace: [{2}]", message, innerException.Message, stackTrace)
: string.Format("Exception: [{0}], Stack Trace: [{1}]", message, stackTrace);
LogMessage(MessageType.Error, exceptionMessage, "Exceptions");
}
public void LogMessage(MessageType messageType, string message, string logFileName)
{
var dateTime = DateTime.UtcNow.ToString("dd-MMM-yyyy");
var logFilesLocation = string.Format("{0}{1}\\", _logFilesLocation, dateTime);
if (_clientId > -1) { logFilesLocation = string.Format("{0}{1}\\{2}\\", _logFilesLocation, dateTime, _clientId); }
var fullLogFile = string.IsNullOrEmpty(logFileName) ? "GeneralLog.txt" : string.Format("{0}.txt", logFileName);
var msg = string.Format("{0} | {1} | {2}\r\n", DateTime.UtcNow.ToString("dd-MMM-yyyy HH:mm:ss"), messageType, message);
fullLogFile = GenerateLogFilePath(logFilesLocation, fullLogFile);
LogToFile(fullLogFile, msg);
}
private string GenerateLogFilePath(string objectLogDirectory, string objectLogFileName)
{
if (string.IsNullOrEmpty(objectLogDirectory))
throw new ArgumentNullException(string.Format("{0} location cannot be null or empty", "objectLogDirectory"));
if (string.IsNullOrEmpty(objectLogFileName))
throw new ArgumentNullException(string.Format("{0} cannot be null or empty", "objectLogFileName"));
if (!Directory.Exists(objectLogDirectory))
Directory.CreateDirectory(objectLogDirectory);
string logFilePath = string.Format("{0}\\{1}", objectLogDirectory, objectLogFileName);
return logFilePath;
}
private void LogToFile(string logFilePath, string message)
{
if (!File.Exists(logFilePath))
{
File.WriteAllText(logFilePath, message);
}
else
{
lock (fileLocker)
{
File.AppendAllText(logFilePath, message);
}
}
}
public void Dispose()
{
fileLocker = new object();
}
}
And then I can use it like this:
var _logger = new xPTLogger("DeviceId", 12);
_logger.LogMessage(MessageType.Info, string.Format("Information Message = [{0}]", 1));
The problem with the above class is that, because the service is multi-threaded, some threads try to access the same log file at the same time causing an Exception to Throw.
25-Apr-2016 13:07:00 | Error | Exception: The process cannot access the file 'D:\LogFiles\25-Apr-2016\0\LogFile.txt' because it is being used by another process.
Which sometimes causes my service to crash.
How do I make my Logger class to work in multi-threaded services?
EDIT
Changes to the Logger Class
public class xPTLogger : IDisposable
{
private object fileLocker = new object();
private readonly string _logFileName;
private readonly string _logFilesLocation;
private readonly int _companyId;
public xPTLogger() : this("General") { }
public xPTLogger(string logFileName)
{
_companyId = -1;
_logFileName = logFileName;
_logFilesLocation = SharedConstants.LogFilesLocation; // "D:\\MyLogs";
}
public xPTLogger(string logFileName, int companyId)
{
_companyId = companyId;
_logFileName = logFileName;
_logFilesLocation = SharedConstants.LogFilesLocation;
}
public void LogMessage(MessageType messageType, string message)
{
LogMessage(messageType, message, _logFileName);
}
public void LogExceptionMessage(string message, Exception innerException, string stackTrace)
{
var exceptionMessage = innerException != null
? string.Format("Exception: [{0}], Inner: [{1}], Stack Trace: [{2}]", message, innerException.Message, stackTrace)
: string.Format("Exception: [{0}], Stack Trace: [{1}]", message, stackTrace);
LogMessage(MessageType.Error, exceptionMessage, "Exceptions");
}
public void LogMessage(MessageType messageType, string message, string logFileName)
{
if (messageType == MessageType.Debug)
{
if (!SharedConstants.EnableDebugLog)
return;
}
var dateTime = DateTime.UtcNow.ToString("dd-MMM-yyyy");
var logFilesLocation = string.Format("{0}{1}\\", _logFilesLocation, dateTime);
if (_companyId > -1) { logFilesLocation = string.Format("{0}{1}\\{2}\\", _logFilesLocation, dateTime, _companyId); }
var fullLogFile = string.IsNullOrEmpty(logFileName) ? "GeneralLog.txt" : string.Format("{0}.txt", logFileName);
var msg = string.Format("{0} | {1} | {2}\r\n", DateTime.UtcNow.ToString("dd-MMM-yyyy HH:mm:ss"), messageType, message);
fullLogFile = GenerateLogFilePath(logFilesLocation, fullLogFile);
LogToFile(fullLogFile, msg);
}
private string GenerateLogFilePath(string objectLogDirectory, string objectLogFileName)
{
if (string.IsNullOrEmpty(objectLogDirectory))
throw new ArgumentNullException(string.Format("{0} location cannot be null or empty", "objectLogDirectory"));
if (string.IsNullOrEmpty(objectLogFileName))
throw new ArgumentNullException(string.Format("{0} cannot be null or empty", "objectLogFileName"));
if (!Directory.Exists(objectLogDirectory))
Directory.CreateDirectory(objectLogDirectory);
string logFilePath = string.Format("{0}\\{1}", objectLogDirectory, objectLogFileName);
return logFilePath;
}
private void LogToFile(string logFilePath, string message)
{
lock (fileLocker)
{
try
{
if (!File.Exists(logFilePath))
{
File.WriteAllText(logFilePath, message);
}
else
{
File.AppendAllText(logFilePath, message);
}
}
catch (Exception ex)
{
var exceptionMessage = ex.InnerException != null
? string.Format("Exception: [{0}], Inner: [{1}], Stack Trace: [{2}]", ex.Message, ex.InnerException.Message, ex.StackTrace)
: string.Format("Exception: [{0}], Stack Trace: [{1}]", ex.Message, ex.StackTrace);
var logFilesLocation = string.Format("{0}{1}\\", _logFilesLocation, DateTime.UtcNow.ToString("dd-MMM-yyyy"));
var logFile = GenerateLogFilePath(logFilesLocation, "FileAccessExceptions.txt");
try
{
if (!File.Exists(logFile))
{
File.WriteAllText(logFile, exceptionMessage);
}
else
{
File.AppendAllText(logFile, exceptionMessage);
}
}
catch (Exception) { }
}
}
}
public void Dispose()
{
//fileLocker = new object();
//_logFileName = null;
//_logFilesLocation = null;
//_companyId = null;
}
}
If you don't want to use existing solutions, the reasonable approach to handle multithreaded writes in your logger is to use queue. Here is a sketch:
public class LogQueue : IDisposable {
private static readonly Lazy<LogQueue> _isntance = new Lazy<LogQueue>(CreateInstance, true);
private Thread _thread;
private readonly BlockingCollection<LogItem> _queue = new BlockingCollection<LogItem>(new ConcurrentQueue<LogItem>());
private static LogQueue CreateInstance() {
var queue = new LogQueue();
queue.Start();
return queue;
}
public static LogQueue Instance => _isntance.Value;
public void QueueItem(LogItem item) {
_queue.Add(item);
}
public void Dispose() {
_queue.CompleteAdding();
// wait here until all pending messages are written
_thread.Join();
}
private void Start() {
_thread = new Thread(ConsumeQueue) {
IsBackground = true
};
_thread.Start();
}
private void ConsumeQueue() {
foreach (var item in _queue.GetConsumingEnumerable()) {
try {
// append to your item.TargetFile here
}
catch (Exception ex) {
// do something or ignore
}
}
}
}
public class LogItem {
public string TargetFile { get; set; }
public string Message { get; set; }
public MessageType MessageType { get; set; }
}
Then in your logger class:
private void LogToFile(string logFilePath, string message) {
LogQueue.Instance.QueueItem(new LogItem() {
TargetFile = logFilePath,
Message = message
});
}
Here we delegate actual logging to separate class which writes log messages one by one, so cannot have any multithreading issues. Additional benefit of such approach is that logging happens asynchronously and as such does not slow down the real work.
Drawback is you can lose some messages in case of process crash (don't think that is really a problem but still mention it) and you consume separate thread to log asynchronously. When there is one thread it's not a problem but if you create one thread per device, that might be (though there is no need to - just use single queue, unless you really write A LOT of messages per second).
While it probably isn't the most elegant solution, you could have retry logic built in. For example:
int retries = 0;
while(retries <= 3){
try{
var _logger = new xPTLogger("DeviceId", 12);
_logger.LogMessage(MessageType.Info, string.Format("Information Message = [{0}]", 1));
break;
}
catch (Exception ex){
//Console.WriteLine(ex.Message);
retries++;
}
}
Also, I wrote that code just now without actually testing it so if there's some stupid error in it forgive me. But quite simply it'll try to write to the log as many times as you set in the "while" line. You can even add a sleep statement in the catch block if you think it'd be worth it.
I have no experience with Log4Net or NLog so no comment there. Maybe there's a sweet solution via one of those packages. Good luck!
After reading about SOLID code in a book and in an online article, I wanted to refactor an existing class so that it can be 'SOLID'-compatible.
But I think I got lost, especially with the dependency injection: when I wanted to instantiate an object of the class, I needed to 'inject' the all the dependencies, but the dependencies themselves have dependencies.. this is where I started getting lost.
The Idea is like this: I want to create a class (In my case, a simple Amazon S3 wrapper class) to di simple upload & get URL actions.
How can I correctly use interfaces and dependency injection? what went wrong?
How should the class look like?
here is my code:
public interface IConfigurationProvider
{
string GetConfigurationValue(String configurationKey);
}
public interface ILogger
{
void WriteLog(String message);
}
public interface IAWSClientProvider
{
AmazonS3Client GetAmazonS3Client();
}
public interface IAWSBucketManager
{
string GetDefaultBucketName();
}
public class AWSBucketManager : IAWSBucketManager
{
ILogger logger;
IConfigurationProvider configurationProvider;
public AWSBucketManager(ILogger Logger, IConfigurationProvider ConfigurationProvider)
{
logger = Logger;
configurationProvider = ConfigurationProvider;
}
public string GetDefaultBucketName()
{
string bucketName = string.Empty;
try
{
bucketName = configurationProvider.GetConfigurationValue("Amazon_S3_ExportAds_BucketName");
}
catch (Exception ex)
{
logger.WriteLog(String.Format("getBucketName : Unable to get bucket name from configuration.\r\n{0}", ex));
}
return bucketName;
}
}
public class AWSClientProvider : IAWSClientProvider
{
IConfigurationProvider configurationProvider;
IAWSBucketManager awsBucketManager;
ILogger logger;
private string awsS3BucketName;
private Dictionary<string, RegionEndpoint> regionEndpoints;
public AWSClientProvider(IConfigurationProvider ConfigurationProvider, IAWSBucketManager BucketManager, ILogger Logger)
{
logger = Logger;
configurationProvider = ConfigurationProvider;
awsBucketManager = BucketManager;
}
private RegionEndpoint getAWSRegion()
{
RegionEndpoint regionEndpoint = null;
// Init endpoints dictionary
try
{
IEnumerable<RegionEndpoint> regions = RegionEndpoint.EnumerableAllRegions;
regionEndpoints = regions.ToDictionary(r => r.SystemName, r => r);
}
catch (Exception Ex)
{
logger.WriteLog(String.Format("getAWSRegion() - Failed to get region list from AWS.\r\n{0}", Ex));
throw;
}
// Get configuration value
try
{
string Config = configurationProvider.GetConfigurationValue("Amazon_S3_Region");
if (String.IsNullOrEmpty(Config))
{
throw new Exception("getAWSRegion() : Amazon_S3_Region must not be null or empty string.");
}
regionEndpoint = regionEndpoints[Config];
}
catch (Exception Ex)
{
logger.WriteLog(String.Format("getAWSRegion() : Unable to get region settings from configuration.\r\n{0}", Ex));
throw Ex;
}
return regionEndpoint;
}
private AWSCredentials getAWSCredentials()
{
string accessKey, secretKey;
BasicAWSCredentials awsCredentials;
try
{
accessKey = configurationProvider.GetConfigurationValue("Amazon_S3_AccessKey");
secretKey = configurationProvider.GetConfigurationValue("Amazon_S3_SecretKey");
}
catch (Exception Ex)
{
logger.WriteLog(String.Format("getAWSCredentials() - Unable to get access key and secrey key values from configuration.\r\n", Ex.Message));
throw;
}
try
{
awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
}
catch (Exception Ex)
{
logger.WriteLog(String.Format("getAWSCredentials() - Unable to create basic AWS credentials object.\r\n{0}", Ex.Message));
awsCredentials = null;
throw;
}
return awsCredentials;
}
public AmazonS3Client GetAmazonS3Client()
{
AmazonS3Client client = null;
RegionEndpoint region = getAWSRegion();
AWSCredentials credentials = getAWSCredentials();
awsS3BucketName = awsBucketManager.GetDefaultBucketName();
if (credentials != null)
{
client = new AmazonS3Client(credentials, region);
}
return client;
}
}
public class AWSS3Actions
{
IConfigurationProvider configurationProvider; // decoupling getting configuration
ILogger logger; // decoupling logger
IAWSClientProvider awsClientProvider;
private const int defaultExpirationDays = 14;
public AWSS3Actions(IConfigurationProvider ConfigurationProvider, ILogger Logger, IAWSClientProvider ClientProvider)
{
configurationProvider = ConfigurationProvider;
logger = Logger;
awsClientProvider = ClientProvider;
}
#region Private Mmethods
private string getFileUrl(string fileName, int expirationDaysPeriod, string awsS3BucketName)
{
GetPreSignedUrlRequest request = new GetPreSignedUrlRequest();
string URL = "";
DateTime dtBase = new DateTime();
dtBase = DateTime.Now;
dtBase = dtBase.AddDays(expirationDaysPeriod);
request.BucketName = awsS3BucketName;
request.Key = fileName;
request.Expires = dtBase;
try
{
URL = awsClientProvider.GetAmazonS3Client().GetPreSignedURL(request);
}
catch (AmazonS3Exception ex)
{
// log
logger.WriteLog(String.Format("getFileUrl() : Could not get presigned URL for the provided request.\r\n{0}", ex));
throw ex;
}
return URL;
}
private int getDefaultURLExpiration()
{
int expirationDays = 0;
try
{
// set the time span in days
int.TryParse(configurationProvider.GetConfigurationValue("getDefaultURLExpiration() : Amazon_S3_ExportAds_ExpirationDaysOfURL"), out expirationDays); // get from configuration util
}
catch
{
// in case of exception, set the min 14 days time space exiration
expirationDays = defaultExpirationDays;
}
return expirationDays;
}
private void validateUpload(string fileName, Stream fileStream)
{
if (fileName == null || fileName.Equals(string.Empty) || fileStream.Length < 1)
{
throw new Exception("fileName : File name must not be an empty string.");
}
if (fileStream == null)
{
throw new Exception("fileStream : Input memory stream (file stream) must not be null.");
}
}
#endregion
#region Public methods
public bool IsFileExists(string fileName, string awsS3BucketName)
{
bool fileExists = false;
try
{
S3FileInfo fileInfo = new S3FileInfo(awsClientProvider.GetAmazonS3Client(), awsS3BucketName, fileName);
fileExists = fileInfo.Exists;
}
catch (AmazonS3Exception Ex)
{
// log
logger.WriteLog(String.Format("isFileExists() : Could not determine if file (key) exists in S3 Bucket.\r\n", Ex.Message));
throw;
}
return fileExists;
}
public bool UploadObject(string fileName, Stream fileStream, string awsS3BucketName)
{
bool uploadResult = true;
// Validate input parameters
validateUpload(fileName, fileStream);
if (awsClientProvider.GetAmazonS3Client() != null)
{
try
{
PutObjectRequest request = new PutObjectRequest
{
BucketName = awsS3BucketName,
Key = fileName,
InputStream = fileStream
};
PutObjectResponse response = awsClientProvider.GetAmazonS3Client().PutObject(request);
if (response != null)
{
if (response.HttpStatusCode != System.Net.HttpStatusCode.OK)
{
var meta = response.ResponseMetadata.Metadata.Keys.
Select(k => k.ToString() + " : " + response.ResponseMetadata.Metadata[k]).
ToList().Aggregate((current, next) => current + "\r\n" + next);
// log error
logger.WriteLog(String.Format("Status Code: {0}\r\nETag : {1}\r\nResponse metadata : {1}",
(int)response.HttpStatusCode, response.ETag, meta));
// set the return value
uploadResult = false;
}
}
}
catch (AmazonS3Exception ex)
{
uploadResult = false;
if (ex.ErrorCode != null && (ex.ErrorCode.Equals("InvalidAccessKeyId") || ex.ErrorCode.Equals("InvalidSecurity")))
{
// LOG
logger.WriteLog(String.Format("UploadObject() : invalied credentials"));
throw ex;
}
else
{
// LOG
logger.WriteLog(String.Format("UploadObject() : Error occurred. Message:'{0}' when writing an object", ex.Message));
throw ex;
}
}
}
else
{
throw new Exception("UploadObject() : Could not start object upload because Amazon client is null.");
}
return uploadResult;
}
public bool UploadObject(string subFolderInBucket, string FileName, Stream fileStream, string awsS3BucketName)
{
return UploadObject(subFolderInBucket + #"/" + FileName, fileStream, awsS3BucketName);
}
public string GetURL(string fileName, string bucket)
{
string url = string.Empty;
try
{
if (IsFileExists(fileName, bucket))
{
url = getFileUrl(fileName, getDefaultURLExpiration(), bucket);
}
}
catch (Exception Ex)
{
// log
logger.WriteLog(String.Format("getURL : Failed in isFileExists() method. \r\n{0}", Ex.Message));
}
return url;
}
#endregion
}
With your current class structure, the Composition Root might look like this:
var logger = new FileLogger("c:\\temp\\log.txt");
var configurationProvider = new ConfigurationProvider();
var actions = new AWSS3Actions(
configurationProvider,
logger,
new AWSClientProvider(
configurationProvider,
new AWSBucketManagerlogger(
logger,
configurationProvider),
logger));
The above example shows a hand-wired object graph (a.k.a. Pure DI). My advice is to start off by applying Pure DI and switch to a DI library (such as Simple Injector, Autofac or StructureMap) when building up object graphs by hand becomes cumbersome and maintenance heavy.
From perspective of Dependency Injection, what you're doing seems sane, although your code smells. Here are some references to look at:
Logging and the SOLID principles
How to design a logger abstraction
Side note: In general it's better to load configuration values up front (at application start-up), instead of reading it at runtime. Reading those values at runtime, causes these values to be read in delayed fashion, which prevents the application from failing fast, and it spreads the use of the configuration abstraction throughout the application. If possible, inject those primitive configuration values directly into the constructor of the type that requires that value.
I have several objects in my solution that use the following pattern:
#region Repositories
private RepositoryAccount _account;
private RepositoryInquiry _inquiry;
private RepositoryLoan _loan;
public RepositoryAccount Account { get { return _account ?? (_account = new RepositoryAccount(this)); } }
public RepositoryInquiry Inquiry { get { return _inquiry ?? (_inquiry = new RepositoryInquiry(this)); } }
public RepositoryLoan Loan { get { return _loan ?? (_loan = new RepositoryLoan(this)); } }
#endregion
I created a Generic Class to try to make the Property handling a little cleaner but ran into a snag. The Property Class looks like:
public class Property<T> where T : class, new()
{
private T _value;
public T Value
{
get { return _value ?? (_value = new T()); }
set
{
// insert desired logic here
_value = value;
}
}
public static implicit operator T(Property<T> value)
{
return value.Value;
}
public static implicit operator Property<T>(T value)
{
return new Property<T> { Value = value };
}
}
I replace my property (with backing objects) with the new Generic Property like:
public Property<RepositoryAccount> Account { get; set; }
public Property<RepositoryInquiry> Inquiry { get; set; }
public Property<RepositoryLoan> Loan { get; set; }
However, if you noticed in the original public getter, I need to instantiate each object with another object: this is required and there is no paramaterless constructor for these repository objects.
Any suggestions?
The whole object looks like:
public class UnitOfWorkSymitar : IUnitOfWork
{
#region Properties
internal IPHostEntry IpHostInfo;
internal IPAddress IpAddress;
internal IPEndPoint RemotEndPoint;
internal System.Net.Sockets.Socket Sender;
internal byte[] Bytes = new byte[1024];
internal bool IsAllowedToRun = false;
internal string SymServerIp;
internal int SymPort;
public string State { get; set; }
#endregion
#region Constructor
public UnitOfWorkSymitar() { OpenSymitarConnection(); }
protected UnitOfWorkSymitar(string serverIp, int? port) { OpenSymitarConnection(serverIp, port); }
#endregion
#region Implement IUnitOfWork
public void Commit() { /* No commit on this socket connection */ }
public void Rollback() { /* No rollback on this socket connection */ }
#endregion
#region Implement IDisposable
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
public void Dispose()
{
CloseSymitarConnection();
}
#endregion
#region Private Helpers
/// <summary>
/// Connect to a remote device.
/// </summary>
/// <returns>Success/failure message</returns>
private string OpenSymitarConnection(string serverIp = null, int? port = null)
{
var config = ConfigInfoSymitar.Instance;
SymServerIp = serverIp ?? config.SymServerIp;
SymPort = port ?? config.SymPort;
try
{
// Establish the remote endpoint for the socket.
//IpHostInfo = Dns.GetHostEntry(SymServerIp);
//IpAddress = IpHostInfo.AddressList[0];
IpAddress = Dns.GetHostAddresses(SymServerIp)[0];
RemotEndPoint = new IPEndPoint(IpAddress, SymPort);
// Create a TCP/IP socket.
Sender = new System.Net.Sockets.Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// Connect the socket to the remote endpoint. Catch any errors.
Sender.Connect(RemotEndPoint);
}
catch (Exception ex)
{
State = DetermineError(ex);
return State;
//throw;
}
IsAllowedToRun = true;
State = "Success";
return State;
}
/// <summary>
/// Setup and send the request
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public string SendMessage(string request)
{
try
{
// Encode the data string into a byte array and add the carriage return for SymConnect.
var msg = Encoding.ASCII.GetBytes(request);
if(!request.EndsWith("\n"))
msg = Encoding.ASCII.GetBytes(request + "\n");
// Send the data through the socket.
var bytesSent = Sender.Send(msg);
// Receive the response from the remote device.
var bytesRec = Sender.Receive(Bytes);
var response = Encoding.ASCII.GetString(Bytes, 0, bytesRec);
response = response.Replace("\n", "");
return response;
}
catch (Exception ex)
{
return DetermineError(ex);
}
}
/// <summary>
/// Close the connection to a remote device.
/// </summary>
/// <returns></returns>
private string CloseSymitarConnection()
{
try
{
// Release the socket.
Sender.Shutdown(SocketShutdown.Both);
Sender.Close();
}
catch (Exception ex)
{
return DetermineError(ex);
}
finally
{
IsAllowedToRun = false;
}
return "Success!";
}
/// <summary>
/// Determine if this is an Arguments, Socket or some other exception
/// </summary>
/// <param name="ex">The exception to be checked</param>
/// <returns>String message</returns>
private static string DetermineError(Exception ex)
{
if (ex.GetType() == typeof(ArgumentNullException))
return "Missing Arguments: " + Environment.NewLine + ex.GetFullMessage();
if (ex.GetType() == typeof(SocketException))
return "Socket Error: " + Environment.NewLine + ex.GetFullMessage();
return "Unexpected Error: " + Environment.NewLine + ex.GetFullMessage();
}
#endregion
#region Symitar Samples
private static string ExecSymConnectRequest(string symServerIP, int symPort, string request)
{
// Data buffer for incoming data.
var bytes = new byte[1024];
// Connect to a remote device.
try
{
// Establish the remote endpoint for the socket.
// This example uses port 11000 on the local computer.
var ipHostInfo = Dns.GetHostEntry(symServerIP);
var ipAddress = ipHostInfo.AddressList[0];
var remoteEP = new IPEndPoint(ipAddress, symPort);
// Create a TCP/IP socket.
var sender = new System.Net.Sockets.Socket(AddressFamily.InterNetwork
, SocketType.Stream
, ProtocolType.Tcp);
// Connect the socket to the remote endpoint. Catch any errors.
try
{
sender.Connect(remoteEP);
// Encode the data string into a byte array and add the carriage return for SymConnect.
var msg = Encoding.ASCII.GetBytes(request + "\n");
// Send the data through the socket.
var bytesSent = sender.Send(msg);
// Receive the response from the remote device.
var bytesRec = sender.Receive(bytes);
var response = Encoding.ASCII.GetString(bytes, 0, bytesRec);
// Release the socket.
sender.Shutdown(SocketShutdown.Both);
sender.Close();
response.Replace("\n", "");
return response;
}
catch (ArgumentNullException ane)
{
return "Missing Arguments: " + ane.Message;
}
catch (SocketException se)
{
return "Socket Error: " + se.Message;
}
catch (Exception e)
{
return "Unexpected Error: " + e.Message;
}
}
catch (Exception e)
{
return e.Message;
}
}
#endregion
#region Repositories
private RepositoryAccount _account;
private RepositoryInquiry _inquiry;
private RepositoryLoan _loan;
public RepositoryAccount Account { get { return _account ?? (_account = new RepositoryAccount(this)); } }
public RepositoryInquiry Inquiry { get { return _inquiry ?? (_inquiry = new RepositoryInquiry(this)); } }
public RepositoryLoan Loan { get { return _loan ?? (_loan = new RepositoryLoan(this)); } }
#endregion
}
Might try something like:
class Program {
Property<string> Foo = new Property<string>(() => "FOO!");
}
public class Property<T> where T : class {
private Lazy<T> instance;
public Property(Func<T> generator) {
instance = new Lazy<T>(generator);
}
public T Value {
get { return instance.Value; }
}
public static implicit operator T(Property<T> value) {
return value.Value;
}
public static implicit operator Property<T>(T value) {
return new Property<T>(() => value);
}
}
There is a solution using reflection although it might not be the cleanest OOP one. A method of the FormatterService class in the System.Runtime namespace FormatterServices.GetUninitializedObject() will create an instance without calling the constructor. There is a similar answer to this problem here.
In order to make it work with your solution you have to make some changes to your code. First of all remove the new() from your generic class Property declaration otherwise you will always get an error from the compiler if you try to use a type T with no parameter-less constructor. Second add this method to your Property class:
private T GetInstance()
{
return (T)FormatterServices.GetUninitializedObject(typeof(T)); //does not call ctor
}
It will return an unitialized object but it won't call the constructor.
Here is the full code:
public class Property<T> where T : class
{
private T _value;
public T Value
{
get
{
return _value ?? (_value = GetInstance());
}
set
{
// insert desired logic here
_value = value;
}
}
private T GetInstance()
{
return (T)FormatterServices.GetUninitializedObject(typeof(T)); //does not call ctor
}
public static implicit operator T(Property<T> value)
{
return value.Value;
}
public static implicit operator Property<T>(T value)
{
return new Property<T> { Value = value };
}
}
I created a gist here, you can try the code into LinqPad and see the result.
hope it helps.
I have made a WCF publisher Subscriber service which handles 70 to 80 connections per day
If a connection breaks in between ( due to internet connection failure , System shutdown etc.)
the service itself will try to set up a connection itself when the connectivity resumes.
sometime back I am noticed my service is throwing exceptions (operation Timed out)
This request operation sent to net.tcp://localhost:2001/sub did not receive a reply within the configured timeout (00:00:59.9799877). The time allotted to this operation may have been a portion of a longer timeout. This may be because the service is still processing the operation or because the service was unable to send a reply message. Please consider increasing the operation timeout (by casting the channel/proxy to IContextChannel and setting the OperationTimeout property) and ensure that the service is able to connect to the client
and then after some of these Exceptions RM destination time out exceptions are coming up which faults the line for further communication with the service
The request to create a reliable session has been refused by the RM Destination. Server
'net.tcp://localhost:2001/Sub' is too busy to process this request. Try again later. The channel could not be opened.
During this time the the clients already subscribed are responding successfully to the pub sub requests
No new Channels are subscribing to the service
I am using net Tcp binding in the Service
client code
public class Subscriber : IPublishing
{
static int Counter = 0;
ISubscription _proxy;
string _endpoint = string.Empty;
public Subscriber()
{
_endpoint = "net.tcp://localhost:2001/sub";
MakeProxy(_endpoint, this);
}
void MakeProxy(string EndpoindAddress, object callbackinstance)
{
try
{
NetTcpBinding netTcpbinding = new NetTcpBinding(SecurityMode.None);
EndpointAddress endpointAddress = new EndpointAddress(EndpoindAddress);
InstanceContext context = new InstanceContext(callbackinstance);
// Added for Reliable communication
netTcpbinding.ReliableSession.Enabled = true;
netTcpbinding.ReliableSession.Ordered = true;
netTcpbinding.MaxBufferSize = 2147483647;
netTcpbinding.MaxBufferPoolSize = 2147483647;
netTcpbinding.MaxReceivedMessageSize = 2147483647;
netTcpbinding.ReliableSession.InactivityTimeout = TimeSpan.MaxValue;
netTcpbinding.ReceiveTimeout = TimeSpan.MaxValue;
DuplexChannelFactory<ISubscription> channelFactory = new DuplexChannelFactory<ISubscription>(new InstanceContext(this), netTcpbinding, endpointAddress);
_proxy = channelFactory.CreateChannel();
Counter++;
}
catch (Exception ex)
{
//
}
}
public void Subscribe()
{
try
{
_proxy.Subscribe("500");
Counter = 0;
}
catch (Exception ex)
{
((IContextChannel)_proxy).Abort();
}
}
public void Publish(Message e, String ID)
{
}
public void _Subscribe()
{
try
{
//OnUnSubscribe();
Subscribe();
}
catch (Exception ex)
{
}
}
}
Server Code
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
class Subscription : ISubscription
{
#region ISubscription Members
public void Subscribe(string ID)
{
String s = DateTime.Now.ToString(
IPublishing subscriber = OperationContext.Current.GetCallbackChannel<IPublishing>();
Filter.AddSubscriber(KioskID, subscriber);
}
public void UnSubscribe(string ID)
{
IPublishing subscriber = OperationContext.Current.GetCallbackChannel<IPublishing>();
Filter.RemoveSubscriber(KioskID, subscriber);
}
#endregion
}
class Filter
{
static Dictionary<string, List<IPublishing>> _subscribersList = new Dictionary<string, List<IPublishing>>();
static public Dictionary<string, List<IPublishing>> SubscribersList
{
get
{
lock (typeof(Filter))
{
return _subscribersList;
}
}
}
static public List<IPublishing> GetSubscribers(String topicName)
{
lock (typeof(Filter))
{
if (SubscribersList.ContainsKey(topicName))
{
return SubscribersList[topicName];
}
else
return null;
}
}
static public void AddSubscriber(String topicName, IPublishing subscriberCallbackReference)
{
DebugLog.WriteLog("C:\\DevPubSubServiceLogs", topicName + "came for Sub");
lock (typeof(Filter))
{
DebugLog.WriteLog("C:\\DevPubSubServiceLogs",topicName + "inside lock");
if (SubscribersList.ContainsKey(topicName))
{
// Removing any stray subscribers for same topic name
// because only 1 subscriber for 1 topic name should exist
SubscribersList.Remove(topicName);
}
List<IPublishing> newSubscribersList = new List<IPublishing>();
newSubscribersList.Add(subscriberCallbackReference);
SubscribersList.Add(topicName, newSubscribersList);
}
DebugLog.WriteLog("C:\\DevPubSubServiceLogs", topicName + "subscribed");
}
static public void RemoveSubscriber(String topicName, IPublishing subscriberCallbackReference)
{
lock (typeof(Filter))
{
if (SubscribersList.ContainsKey(topicName))
{
//if (SubscribersList[topicName].Contains(subscriberCallbackReference))
//{
// SubscribersList[topicName].Remove(subscriberCallbackReference);
//}
SubscribersList.Remove(topicName);
}
}
}
}
Contracts
[ServiceContract]
public interface IPublishing
{
[OperationContract(IsOneWay = true)]
void Publish(Message e, string ID);
}
[ServiceContract(CallbackContract = typeof(IPublishing))]
public interface ISubscription
{
[OperationContract]
void Subscribe(string topicName);
[OperationContract]
void UnSubscribe(string topicName);
}
Please assist..
I am writing an n-tiered application that uses the Sync Framework v2.1 in combination with a WCF web service. My code is based on this example: Database-Sync
Syncing seems to work without a problem. But, when I add the batching functionality I get an error that is proving difficult to debug.
In the client portion of the solution (a WPF app), I have a virtual class that inherits from KnowledgeSyncProvider. It also implements the IDisposable interface. I use this class to call the WCF service I wrote and use the sync functionality there. When the sync code runs (orchestrator.Synchronize() is called), everything seems to work correctly and the EndSession function override that I have written runs without error. But, after execution leaves that function, a System.Error occurs from within Microsoft.Synchronization.CoreInterop.ISyncSession.Start (as far as I can tell).
The functions I have written to provide the IDisposable functionality are never called so, something is happening after EndSession, but before my application's KnowledgeSyncProvider virtual class can be disposed.
Here is the error information:
_message: "System error."
Stack Trace: at Microsoft.Synchronization.CoreInterop.ISyncSession.Start(CONFLICT_RESOLUTION_POLICY resolutionPolicy, _SYNC_SESSION_STATISTICS& pSyncSessionStatistics)
at Microsoft.Synchronization.KnowledgeSyncOrchestrator.DoOneWaySyncHelper(SyncIdFormatGroup sourceIdFormats, SyncIdFormatGroup destinationIdFormats, KnowledgeSyncProviderConfiguration destinationConfiguration, SyncCallbacks DestinationCallbacks, ISyncProvider sourceProxy, ISyncProvider destinationProxy, ChangeDataAdapter callbackChangeDataAdapter, SyncDataConverter conflictDataConverter, Int32& changesApplied, Int32& changesFailed)
at Microsoft.Synchronization.KnowledgeSyncOrchestrator.DoOneWayKnowledgeSync(SyncDataConverter sourceConverter, SyncDataConverter destinationConverter, SyncProvider sourceProvider, SyncProvider destinationProvider, Int32& changesApplied, Int32& changesFailed)
at Microsoft.Synchronization.KnowledgeSyncOrchestrator.Synchronize()
at Microsoft.Synchronization.SyncOrchestrator.Synchronize()
at MyApplication.Library.SynchronizationHelper.SynchronizeProviders() in C:\My Documents\Visual Studio 2010\Projects\MyApplication\MyApplication\Library\SynchronizationHelper.cs:line 43
at MyApplication.ViewModels.MainViewModel.SynchronizeDatabases() in C:\My Documents\Visual Studio 2010\Projects\MyApplication\MyApplication\ViewModels\MainViewModel.cs:line 46
I have enabled trace info but, that doesn't seem to provide any helpful information in this case.
Can anyone make a suggestion as to how I might be able to figure out what is causing this error?
Thanks in advance for any help you can provide.
Here is the code that handles syncing:
public void SynchronizeProviders()
{
CeDatabase localDb = new CeDatabase();
localDb.Location = Directory.GetCurrentDirectory() + "\Data\SYNCTESTING5.sdf";
RelationalSyncProvider localProvider = ConfigureCeSyncProvider(localDb.Connection);
MyAppKnowledgeSyncProvider srcProvider = new MyAppKnowledgeSyncProvider();
localProvider.MemoryDataCacheSize = 5000;
localProvider.BatchingDirectory = "c:\batchingdir";
srcProvider.BatchingDirectory = "c:\batchingdir";
SyncOrchestrator orchestrator = new SyncOrchestrator();
orchestrator.LocalProvider = localProvider;
orchestrator.RemoteProvider = srcProvider;
orchestrator.Direction = SyncDirectionOrder.UploadAndDownload;
CheckIfProviderNeedsSchema(localProvider as SqlCeSyncProvider, srcProvider);
SyncOperationStatistics stats = orchestrator.Synchronize();
}
And here is the KnowledgeSyncProviderClass that is used to create srcProvider:
class MyAppKnowledgeSyncProvider : KnowledgeSyncProvider, IDisposable
{
protected MyAppSqlSync.IMyAppSqlSync proxy;
protected SyncIdFormatGroup idFormatGroup;
protected DirectoryInfo localBatchingDirectory;
protected bool disposed = false;
private string batchingDirectory = Environment.ExpandEnvironmentVariables("%TEMP%");
public string BatchingDirectory
{
get { return batchingDirectory; }
set
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentException("value cannot be null or empty");
}
try
{
Uri uri = new Uri(value);
if (!uri.IsFile || uri.IsUnc)
{
throw new ArgumentException("value must be a local directory");
}
batchingDirectory = value;
}
catch (Exception e)
{
throw new ArgumentException("Invalid batching directory.", e);
}
}
}
public MyAppKnowledgeSyncProvider()
{
this.proxy = new MyAppSqlSync.MyAppSqlSyncClient();
this.proxy.Initialize();
}
public override void BeginSession(SyncProviderPosition position, SyncSessionContext syncSessionContext)
{
this.proxy.BeginSession(position);
}
public DbSyncScopeDescription GetScopeDescription()
{
return this.proxy.GetScopeDescription();
}
public override void EndSession(SyncSessionContext syncSessionContext)
{
this.proxy.EndSession();
if (this.localBatchingDirectory != null)
{
this.localBatchingDirectory.Refresh();
if (this.localBatchingDirectory.Exists)
{
this.localBatchingDirectory.Delete(true);
}
}
}
public override ChangeBatch GetChangeBatch(uint batchSize, SyncKnowledge destinationKnowledge, out object changeDataRetriever)
{
MyAppSqlSync.GetChangesParameters changesWrapper = proxy.GetChanges(batchSize, destinationKnowledge);
changeDataRetriever = changesWrapper.DataRetriever;
DbSyncContext context = changeDataRetriever as DbSyncContext;
if (context != null && context.IsDataBatched)
{
if (this.localBatchingDirectory == null)
{
string remotePeerId = context.MadeWithKnowledge.ReplicaId.ToString();
string sessionDir = Path.Combine(this.batchingDirectory, "MyAppSync_" + remotePeerId);
this.localBatchingDirectory = new DirectoryInfo(sessionDir);
if (!this.localBatchingDirectory.Exists)
{
this.localBatchingDirectory.Create();
}
}
string localFileName = Path.Combine(this.localBatchingDirectory.FullName, context.BatchFileName);
FileInfo localFileInfo = new FileInfo(localFileName);
if (!localFileInfo.Exists)
{
byte[] remoteFileContents = this.proxy.DownloadBatchFile(context.BatchFileName);
using (FileStream localFileStream = new FileStream(localFileName, FileMode.Create, FileAccess.Write))
{
localFileStream.Write(remoteFileContents, 0, remoteFileContents.Length);
}
}
context.BatchFileName = localFileName;
}
return changesWrapper.ChangeBatch;
}
public override FullEnumerationChangeBatch GetFullEnumerationChangeBatch(uint batchSize, SyncId lowerEnumerationBound, SyncKnowledge knowledgeForDataRetrieval, out object changeDataRetriever)
{
throw new NotImplementedException();
}
public override void GetSyncBatchParameters(out uint batchSize, out SyncKnowledge knowledge)
{
MyAppSqlSync.SyncBatchParameters wrapper = proxy.GetKnowledge();
batchSize = wrapper.BatchSize;
knowledge = wrapper.DestinationKnowledge;
}
public override SyncIdFormatGroup IdFormats
{
get
{
if (idFormatGroup == null)
{
idFormatGroup = new SyncIdFormatGroup();
idFormatGroup.ChangeUnitIdFormat.IsVariableLength = false;
idFormatGroup.ChangeUnitIdFormat.Length = 1;
idFormatGroup.ReplicaIdFormat.IsVariableLength = false;
idFormatGroup.ReplicaIdFormat.Length = 16;
idFormatGroup.ItemIdFormat.IsVariableLength = true;
idFormatGroup.ItemIdFormat.Length = 10 * 1024;
}
return idFormatGroup;
}
}
public override void ProcessChangeBatch(ConflictResolutionPolicy resolutionPolicy, ChangeBatch sourceChanges, object changeDataRetriever, SyncCallbacks syncCallbacks, SyncSessionStatistics sessionStatistics)
{
DbSyncContext context = changeDataRetriever as DbSyncContext;
if (context != null && context.IsDataBatched)
{
string fileName = new FileInfo(context.BatchFileName).Name;
string peerId = context.MadeWithKnowledge.ReplicaId.ToString();
if (!this.proxy.HasUploadedBatchFile(fileName, peerId))
{
FileStream stream = new FileStream(context.BatchFileName, FileMode.Open, FileAccess.Read);
byte[] contents = new byte[stream.Length];
using (stream)
{
stream.Read(contents, 0, contents.Length);
}
this.proxy.UploadBatchFile(fileName, contents, peerId);
}
context.BatchFileName = fileName;
}
SyncSessionStatistics stats = this.proxy.ApplyChanges(resolutionPolicy, sourceChanges, changeDataRetriever);
sessionStatistics.ChangesApplied += stats.ChangesApplied;
sessionStatistics.ChangesFailed += stats.ChangesFailed;
}
public override void ProcessFullEnumerationChangeBatch(ConflictResolutionPolicy resolutionPolicy, FullEnumerationChangeBatch sourceChanges, object changeDataRetriever, SyncCallbacks syncCallbacks, SyncSessionStatistics sessionStatistics)
{
throw new NotImplementedException();
}
~MyAppKnowledgeSyncProvider()
{
Dispose(false);
}
#region IDisposable Members
public virtual void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
if (this.proxy != null)
{
CloseProxy();
}
}
disposed = true;
}
}
public virtual void CloseProxy()
{
if (this.proxy != null)
{
this.proxy.Cleanup();
ICommunicationObject channel = proxy as ICommunicationObject;
if (channel != null)
{
try
{
channel.Close();
}
catch (TimeoutException)
{
channel.Abort();
}
catch (CommunicationException)
{
channel.Abort();
}
}
this.proxy = null;
}
}
#endregion
}
Bizarrely, it has just started working.
I got a notification within Visual Studio 2010 that I should "update components" or "install components". It installed some web components.
Now, my code runs without error.
While I am very glad to be past this error, I am getting past it in a very unsatisfying way.
Well. I'll take what I can get.