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.
Related
Objects are rendered as strings, (name of the object), in Application Insights custom dimensions when passed as arguments to ilogger. The actual values are not shown.
Register Application Insights
services.AddApplicationInsightsTelemetry();
New log
public class HealthController : ControllerBase
{
private readonly ILogger<HealthController> _logger;
public HealthController(ILogger<HealthController> logger)
{
_logger = logger;
}
[HttpGet]
public IActionResult Get()
{
var health = new HealthViewModel()
{
ok = false
};
_logger.LogInformation("Hlep me pls {health}", health);
return Ok(health);
}
}
Result
I do not want to this this for every log:
var health = new HealthViewModel()
{
ok = false
};
_logger.LogInformation("Hlep me pls {health}", JsonConvert.SerializeObject(health));
I tried creating a middleware for application insights but the value is still the name of the object..
Why are arguments not rendered as json?
Edit
It seems like
var health = new
{
ok = false
};
_logger.LogInformation("HEJ2 {health}", health);
works but not
var health = new HealthViewModel
{
ok = false
};
_logger.LogInformation("HEJ2 {health}", health);
Not supported
Quote from https://github.com/microsoft/ApplicationInsights-dotnet/issues/1722
I think you're expecting too much of the logger. It doesn't know about JSON format, it just calls Convert.ToString on properties
Convert.ToString typically calls ToString() and the default ToString implementation for new classes is simply to return the type name
What you can do
Use ToJson() on objects logged to ILogger and create a middleware for application insights and modify the name of the log and the custom dimensions.
Middleware
public class ProcessApiTraceFilter : ITelemetryProcessor
{
private ITelemetryProcessor Next { get; set; }
private readonly IIdentity _identity;
private readonly IHostEnvironment _hostEnvironment;
public ProcessApiTraceFilter(ITelemetryProcessor next, IHostEnvironment hostEnvironment, IIdentity identity)
{
Next = next;
_identity = identity;
_hostEnvironment = hostEnvironment;
}
public void Process(ITelemetry item)
{
item.Process(_hostEnvironment, _identity);
Next.Process(item);
}
}
Implementation
public static class ApplicationInsightsExtensions
{
public static void Process(this ITelemetry item, IHostEnvironment hostEnvironment, IIdentity identity)
{
if (item is TraceTelemetry)
{
var traceTelemetry = item as TraceTelemetry;
var originalMessage = traceTelemetry.Properties.FirstOrDefault(x => x.Key == "{OriginalFormat}");
if (!string.IsNullOrEmpty(originalMessage.Key))
{
var reg = new Regex("{([A-z]*)*}", RegexOptions.Compiled);
var match = reg.Matches(originalMessage.Value);
var formattedMessage = originalMessage.Value;
foreach (Match arg in match)
{
var parameterName = arg.Value.Replace("{", "").Replace("}", "");
var parameterValue = traceTelemetry.Properties.FirstOrDefault(x => x.Key == parameterName);
formattedMessage = formattedMessage.Replace(arg.Value, "");
}
traceTelemetry.Message = formattedMessage.Trim();
}
if (identity != null)
{
var isAuthenticated = identity.IsAuthenticated();
const string customerKey = "customer";
if (isAuthenticated && !traceTelemetry.Properties.ContainsKey(customerKey))
{
var customer = identity.Customer();
if (customer != null)
{
traceTelemetry.Properties.Add(customerKey, customer.ToJson());
}
}
var request = identity.Request();
const string requestKey = "request";
if (request != null && !traceTelemetry.Properties.ContainsKey(requestKey))
{
traceTelemetry.Properties.Add(requestKey, request.ToJson());
}
}
var applicationNameKey = "applicationName";
if (hostEnvironment != null && !string.IsNullOrEmpty(hostEnvironment.ApplicationName) && !traceTelemetry.Properties.ContainsKey(applicationNameKey))
{
traceTelemetry.Properties.Add(applicationNameKey, hostEnvironment.ApplicationName);
}
}
}
}
Register application insights and middleware in startup
services.AddApplicationInsightsTelemetry();
services.AddApplicationInsightsTelemetryProcessor<ProcessApiTraceFilter>();
ToJson
public static class ObjectExtensions
{
private static readonly string Null = "null";
private static readonly string Exception = "Could not serialize object to json";
public static string ToJson(this object value, Formatting formatting = Formatting.None)
{
if (value == null) return Null;
try
{
string json = JsonConvert.SerializeObject(value, formatting);
return json;
}
catch (Exception ex)
{
return $"{Exception} - {ex?.Message}";
}
}
}
Log
//Log object? _smtpAppSettings.ToJson()
_logger.LogInformation("Email sent {to} {from} {subject}", to, _smtpAppSettings.From, subject)
Result
from your custom dimensions i can see that it`s not considering the health obj param as an extra data
_logger.LogInformation("Hlep me pls {health}", health);
trying using the jsonConverter within the string itself.
_logger.LogInformation($"Hlep me pls {JsonConvert.SerializeObject(health)}");
I wrote a Dotnetty server and used Nlog as the logging framework. Every connection has its own ID and I want to log this ID alongside other data(message, Datetime, etc...) to Sql. As Dotnetty handles multiple connections by single thread, using mdlc/ndlc to log ID caused incorrect data to log (By incorrect I mean one connection ID logged with another connection data). I can use logevent properties but I have to pass the connection ID to each class/method I want to log inside. Any suggestion ?
this is Custom Logger
public void SetFlowmeterSerial(long? flowmeterSerial)
{
MappedDiagnosticsLogicalContext.Set("Serial",flowmeterSerial);
}
public long GetSerialNumber()
{
return Convert.ToInt64(MappedDiagnosticsLogicalContext.Get("Serial"));
}
NLogConfig for database
public static void DataBaseTarget(LogLevel min, LogLevel max, LoggingConfiguration config, string
name,string connectionString,string databaseName)
{
var greDateTime = TimeCodec.Trim(DateTime.Now,TimeSpan.TicksPerSecond);
var persianDateTime = TimeCodec.GreToPersianDateTime(greDateTime);
var sqlFormattedDateTime = greDateTime.ToString("yyyy-MM-dd HH:mm:ss.fff");
var databaseTarget = new DatabaseTarget
{
ConnectionString = connectionString,//"server=.;database=FlowMeterSimulatorLOG;integrated security=sspi",
DBProvider = "System.Data.SqlClient",
DBDatabase = databaseName,//"FlowMeterSimulatorLOG",
DBHost = ".",
CommandText =
$"insert into SystemLog (FlowMeterSerial,GreDateTime,PersianDateTime,Level,CallSite,Message,ExceptionType,ExceptionMessage,StackTrace)values(#FlowMeterSerial,'{sqlFormattedDateTime}','{persianDateTime}',#Level,#CallSite,#Message,#ExceptionType,#ExceptionMessage,#StackTrace);",
};
var param = new DatabaseParameterInfo { Name = "#Level", Layout = "${level}" };
databaseTarget.Parameters.Add(param);
param = new DatabaseParameterInfo { Name = "#CallSite", Layout = "${callsite}" };
databaseTarget.Parameters.Add(param);
param = new DatabaseParameterInfo { Name = "#Message", Layout = "${message}" };
databaseTarget.Parameters.Add(param);
param = new DatabaseParameterInfo { Name = "#ExceptionType", Layout = "${exception:format=type}" };
databaseTarget.Parameters.Add(param);
param = new DatabaseParameterInfo { Name = "#ExceptionMessage", Layout = "${exception:format=message}" };
databaseTarget.Parameters.Add(param);
param = new DatabaseParameterInfo { Name = "#StackTrace", Layout = "${exception:format=stackTrace}" };
databaseTarget.Parameters.Add(param);
param = new DatabaseParameterInfo { Name = "#FlowMeterSerial", Layout = "${mdlc:item=Serial}" };
databaseTarget.Parameters.Add(param);
config.AddRule(min, max, databaseTarget);
LogManager.Configuration = config;
}
Dotnetty Handler
public class LogHandler : ChannelHandlerAdapter
{
private readonly CustomLogger _logger = (CustomLogger) LogManager.GetCurrentClassLogger(typeof(CustomLogger));
//private static readonly ILogger _logger = LogManager.GetCurrentClassLogger();
private long _serialNumber;
public override void ChannelRead(IChannelHandlerContext context, object message)
{
if (!(message is IByteBuffer buffer))
throw new ArgumentException();
var deviceInfo = context.Channel.GetAttribute(ProcessHandler.DeviceInfo).Get();
if (deviceInfo != null)
_serialNumber = deviceInfo.Serial;
_logger.SetFlowmeterSerial(_serialNumber);
_logger?.Debug(
$"Received: {ByteBufferUtil.HexDump(buffer)}");
base.ChannelRead(context, message);
}
public override async Task WriteAsync(IChannelHandlerContext context, object message)
{
try
{
if (!(message is IByteBuffer buffer)) throw new ArgumentException();
try
{
//_logger.SetFlowmeterSerial(_serialNumber);
_logger?.Debug(
$"Sent: {ByteBufferUtil.HexDump(buffer)}");
await base.WriteAsync(context, message).ConfigureAwait(false);
}
catch
{
if (buffer.ReferenceCount != 0) buffer.Release();
throw;
}
}
catch (Exception e)
{
context.FireExceptionCaught(e);
//await Task.FromException(e);
}
}
}
I have Other Handlers Like This For Processing Data and Log them.
I have never used Netty, so just trying to read the documentation:
https://netty.io/5.0/api/io/netty/channel/ChannelHandlerContext.html
Then you should use the IChannelHandlerContext for handling context information (Like CorrelationId, RequestId, etc.).
Constantly trying to keep the NLog MDLC in sync with the active netty-context will probably be fragile. Maybe just implement helper logger-methods that gets netty-context as input? Ex.:
public override void ChannelRead(IChannelHandlerContext context, object message)
{
LogDebug(context, "Read");
base.ChannelRead(context, message);
}
public override async Task WriteAsync(IChannelHandlerContext context, object message)
{
LogDebug(context, "Write");
await base.WriteAsync(context, message).ConfigureAwait(false);
}
private void LogDebug(IChannelHandlerContext context, string mesage)
{
var deviceInfo = context.Channel.GetAttribute(ProcessHandler.DeviceInfo).Get();
if (deviceInfo != null)
_logger.WithProperty("FlowMeterSerial", deviceInfo.Serial).Debug(message);
else
_logger.Debug(mesage);
}
Anyway just a random suggestion. Know nothing about Java-Netty or Dot-Netty.
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
}
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!
The user of my Visual Studio application may click a button to restore the application to the factory defaults. At the factory we configure the application and then click another button to set that configuration as the factory defaults to the present configuration settings.
However, if we save the configuration settings (XML format) to settings.settings, they are stored in our own user folder (not in the Visual Studio project folder), and the user doesn't receive them.
(settings.settings uses the default values stored at design time.)
We need to store the factory defaults in a file that is included in the executable, or distributed with it. We can write the factory defaults in a factorydefaultconfig.xml file included with the distribution, but I thought you may know of a better way of doing that.
Right now I am studying Application configuration files to see if that's what I should be using.
Yep App Settings or rolling your own is the way we've handled this in the past. Add a reference to System.Configuration to your project, and then use the following:
ConfigurationManager.AppSettings.Set("lang", "English"); //Set
string getLang = ConfigurationManager.AppSettings["lang"]; //Get
For the App.config:
<configuration>
<appSettings>
<add key="lang" value="English"/>
</appSettings>
</configuration>
Just in case you want to try this out. Here is a class I wrote called ConfigHub for doing just what you are talking about. It makes use of locks as well, to ensure you don't end up with file in use errors:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
public static class ConfigHub
{
#region State
private static string WorkingDirectoryVar = null;
private static string ConfigFileNameVar = null;
private static bool AutoRefreshVar = true;
private static bool VerboseVar = true;
private static bool SetupExecutedVar = false;
private static XmlDocument ConfigDocVar = new XmlDocument();
private static Dictionary<string, string> ConfigLookupPair = new Dictionary<string, string>();
private static Object ConfigHubLock = new Object();
private const string CommentNameVar = "#comment";
#endregion
#region Property
public static bool Verbose
{
get { return VerboseVar; }
set { VerboseVar = value; }
}
public static bool AutoRefresh
{
get { return AutoRefreshVar; }
set { AutoRefreshVar = value; }
}
public static bool SetupExecuted
{
get { return SetupExecutedVar; }
}
public static string ConfigFilePath
{
get { return WorkingDirectoryVar + #"\" + ConfigFileNameVar; }
}
public static string ConfigFileName
{
get { return ConfigFileNameVar; }
}
public static string WorkingDirectory
{
get { return WorkingDirectoryVar; }
}
#endregion
#region Setup
public static void Setup()
{
lock (ConfigHubLock)
{
//Initialize config with default
WorkingDirectoryVar = Environment.CurrentDirectory;
ConfigFileNameVar = "SCW.Config.xml";
SetupExecutedVar = true;
RefreshConfiguration();
}
}
public static void Setup(string configFileName)
{
lock (ConfigHubLock)
{
//Initialize config with specified file
WorkingDirectoryVar = Environment.CurrentDirectory;
ConfigFileNameVar = configFileName.Trim().ToLower().Replace(".xml", "") + ".xml";
SetupExecutedVar = true;
RefreshConfiguration();
}
}
#endregion
#region Merchant
public static void SetValue(string key, string value)
{
//Fail if setup hasn't been called
if (!SetupExecutedVar) throw ConfigHubException.BuildException(ConfigHubExceptionType.NotSetup, "Setup must be called before using the ConfigHub", null);
try
{
lock (ConfigHubLock)
{
//Set the value
bool foundNode = false;
foreach (XmlNode configNode in ConfigDocVar.ChildNodes[0].ChildNodes)
{
if (configNode.Name.Trim().ToLower() == key.Trim().ToLower())
{
configNode.InnerXml = value.Trim();
foundNode = true;
}
}
if (!foundNode)
{
XmlNode newNode = ConfigDocVar.CreateNode("element", key.Trim(), "");
newNode.InnerXml = value.Trim();
ConfigDocVar.ChildNodes[0].AppendChild(newNode);
}
//Save the config file
ConfigDocVar.Save(WorkingDirectoryVar + #"\" + ConfigFileNameVar);
RefreshConfiguration();
}
}
catch (Exception err)
{
throw ConfigHubException.BuildException(ConfigHubExceptionType.SetValue, "Set value failed", err);
}
}
public static string GetValue(string key)
{
//Fail if setup hasn't been called
if (!SetupExecutedVar) throw ConfigHubException.BuildException(ConfigHubExceptionType.NotSetup, "Setup must be called before using the ConfigHub", null);
if (AutoRefreshVar) RefreshConfiguration();
try
{
lock (ConfigHubLock)
{
//Get and return the value
if (AutoRefreshVar) RefreshConfiguration();
if (ConfigLookupPair.ContainsKey(key.Trim().ToLower()))
{
return ConfigLookupPair[key.Trim().ToLower()];
}
else
{
throw ConfigHubException.BuildException(ConfigHubExceptionType.NoKeyFound, "The key " + key + " was not found", null);
}
}
}
catch (Exception err)
{
throw ConfigHubException.BuildException(ConfigHubExceptionType.GetValue, "Get value failed", err);
}
}
public static void RefreshConfiguration()
{
//Fail if setup hasn't been called
if (!SetupExecutedVar) throw ConfigHubException.BuildException(ConfigHubExceptionType.NotSetup, "Setup must be called before using the ConfigHub", null);
try
{
//Load configuration from file
ConfigDocVar.Load(WorkingDirectoryVar + #"\" + ConfigFileNameVar);
List<string> duplicateCheck = new List<string>();
foreach (XmlNode configNode in ConfigDocVar.ChildNodes[0].ChildNodes)
{
if (configNode.Name.Trim().ToLower() == CommentNameVar)
{
//Ignore the Comment
}
else
{
if (duplicateCheck.Contains(configNode.Name.Trim().ToLower()))
{
//Duplicate key failure
throw ConfigHubException.BuildException(ConfigHubExceptionType.DuplicateKey, "The key " + configNode.Name.Trim() + " appears multiple times", null);
}
else
{
//Add configuration key value pair
duplicateCheck.Add(configNode.Name.Trim().ToLower());
if (!ConfigLookupPair.ContainsKey(configNode.Name.Trim().ToLower()))
{
ConfigLookupPair.Add(configNode.Name.Trim().ToLower(), configNode.InnerXml.Trim());
}
else
{
ConfigLookupPair[configNode.Name.Trim().ToLower()] = configNode.InnerXml.Trim();
}
}
}
}
}
catch (Exception err)
{
//Look form root missing and multiple roots
if (err.ToString().ToLower().Contains("root element is missing"))
{
throw ConfigHubException.BuildException(ConfigHubExceptionType.NoRootFound, "No configuration root found", err);
}
else if (err.ToString().ToLower().Contains("multiple root elements"))
{
throw ConfigHubException.BuildException(ConfigHubExceptionType.MultipleRoots, "Multiple configuration roots found", err);
}
else
{
throw ConfigHubException.BuildException(ConfigHubExceptionType.Refresh, "Refresh failed", err);
}
}
}
#endregion
}
#region Exception
public enum ConfigHubExceptionType { NotSetup, Setup, Refresh, DuplicateKey, NoKeyFound, SetValue, GetValue, NoRootFound, MultipleRoots }
public class ConfigHubException : Exception
{
public ConfigHubException(ConfigHubExceptionType errType, string message) : base("#" + errType.ToString() + "-" + message + (ConfigHub.ConfigFilePath != #"\" ? " (" + ConfigHub.ConfigFilePath + ")" : "")) { }
public ConfigHubException(ConfigHubExceptionType errType, string message, Exception innerException) : base("#" + errType.ToString() + "-" + message + (ConfigHub.ConfigFilePath != #"\" ? " (" + ConfigHub.ConfigFilePath + ")" : ""), innerException) { }
public static ConfigHubException BuildException(ConfigHubExceptionType exceptionType, string message, Exception innerException)
{
if (!ConfigHub.Verbose || innerException == null) return new ConfigHubException(exceptionType, message);
else return new ConfigHubException(exceptionType, message, innerException);
}
}
#endregion
Note. You need to build the file in your project first and remove the XML header. Like the following (Ex: MyConfigFile.xml) - NO xml tag at the top like (). Finally, make sure you set your xml config file to copy always:
<Config>
<ValueName>Value</ValueName>
</Config>