Nlog Doesn't Work Correctly In Dotnetty Handler - c#

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.

Related

Application Insights - ILogger arguments rendered as name of the object in custom dimensions

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)}");

How to write repository unit tests with respect to Cosmos database(SQLApi + CosmosClient)

I have a class as below :
public class CosmosRepository: ICosmosRepository
{
private ICosmoDBSettings cosmoDbSettings;
private CosmosClient cosmosClient;
private static readonly object syncLock = new object();
// The database we will create
private Database database;
public CosmosRepository(ICosmoDBSettings cosmoDBSettings)
{
this.cosmoDbSettings = cosmoDBSettings;
this.InitializeComponents();
}
private void InitializeComponents()
{
try
{
if (cosmosClient != null)
{
return;
}
lock (syncLock)
{
if (cosmosClient != null)
{
return;
}
this.cosmosClient = new CosmosClient(
cosmoDbSettings.CosmosDbUri
, cosmoDbSettings.CosmosDbAuthKey
, new CosmosClientOptions
{
ConnectionMode = ConnectionMode.Direct
}
);
this.database = this.cosmosClient.CreateDatabaseIfNotExistsAsync(cosmoDbSettings.DocumentDbDataBaseName).Result;
}
}
catch (Exception ex)
{
throw ex;
}
}
}
I have my repository method as:
Don't bother about hardcoded values.
public async Task<Employee> GetById()
{
var container = this.database.GetContainer("Employees");
var document = await container.ReadItemAsync<Employee>("44A85B9E-2522-4BDB-891A-
9EA91F6D4CBF", new PartitionKey("PartitionKeyValue"));
return document.Response;
}
Note
How to write Unit Test(MS Unit tests) in .NET Core with respect to Cosmos Database?
How to mock CosmosClient and all its methods.
Could someone help me with this issue?
My UnitTests looks like:
public class UnitTest1
{
private Mock<ICosmoDBSettings> cosmoDbSettings;
private Mock<CosmosClient> cosmosClient;
private Mock<Database> database;
[TestMethod]
public async Task TestMethod()
{
this.CreateSubject();
var databaseResponse = new Mock<DatabaseResponse>();
var helper = new CosmosDBHelper(this.cosmoDbSettings.Object);
this.cosmosClient.Setup(d => d.CreateDatabaseIfNotExistsAsync("TestDatabase", It.IsAny<int>(), It.IsAny<RequestOptions>(), It.IsAny<CancellationToken>())).ReturnsAsync(databaseResponse.Object);
var mockContainer = new Mock<Container>();
this.database.Setup(x => x.GetContainer(It.IsAny<string>())).Returns(mockContainer.Object);
var mockItemResponse = new Mock<ItemResponse<PortalAccount>>();
mockItemResponse.Setup(x => x.StatusCode).Returns(It.IsAny<HttpStatusCode>);
var mockPortalAccount = new PortalAccount { PortalAccountGuid = Guid.NewGuid() };
mockItemResponse.Setup(x => x.Resource).Returns(mockPortalAccount);
mockContainer.Setup(c => c.ReadItemAsync<PortalAccount>(It.IsAny<string>(),It.IsAny<PartitionKey>(), It.IsAny<ItemRequestOptions>(), It.IsAny<CancellationToken>())).ReturnsAsync(mockItemResponse.Object);
var pas = helper.GetById().Result;
Assert.AreEqual(pas.PortalAccountGuid, mockPortalAccount.PortalAccountGuid);
}
public void CreateSubject(ICosmoDBSettings cosmoDBSettings = null)
{
this.cosmoDbSettings = cosmoDbSettings ?? new Mock<ICosmoDBSettings>();
this.cosmoDbSettings.Setup(x => x.CosmosDbUri).Returns("https://localhost:8085/");
this.cosmoDbSettings.Setup(x => x.CosmosDbAuthKey).Returns("C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==");
this.cosmoDbSettings.Setup(x => x.DocumentDbDataBaseName).Returns("TestDatabase");
this.database = new Mock<Database>();
this.cosmosClient = new Mock<CosmosClient>();
}
}
Note:
Exception:
Response status code does not indicate success: 404 Substatus: 0 Reason: (Microsoft.Azure.Documents.DocumentClientException: Message: {"Errors":["Resource Not Found"]}
I'm not creating a document. directly I'm fetching the document because I'm returning the mock response only. Is it correct??

How to call a method inside Execute method in Quartz.net Scheduler

I am a beginner to Quartz.Net. I am trying to call a method from my quartz.net schedule job excute method. Can anyone help if this is the right way or is there any better approach available?
[HttpPost]
public ActionResult Index(Tweet twts, HttpPostedFileBase imgFile)
{
UploadFile uploadFile = new UploadFile();
bool isPosted = uploadFile.UploadFiles(twts, imgFile);
if(isPosted)
{
twts.tweets = "";
ViewBag.Message = "Tweeted Successfully";
}
else
{
ViewBag.Message = "Tweet Post Unsuccessful";
}
return View("Tweets");
}
UploadFile.cs
public bool UploadFiles(Tweet twts, HttpPostedFileBase imgFile)
{
string key = Utils.Twitterkey;
string secret = Utils.Twittersecret;
string token = Utils.Twittertoken;
string tokenSecret = Utils.TwittertokenSecret;
string message = twts.tweets;
string filePath; string imagePath = "";
HttpPostedFileBase filebase =
new HttpPostedFileWrapper(HttpContext.Current.Request.Files["imgFile"]);
if (imgFile == null)
{
imgFile = filebase;
}
if (imgFile.FileName != "")
{
filePath = HttpContext.Current.Server.MapPath("~/Images/");
if (!Directory.Exists(filePath))
{
Directory.CreateDirectory(filePath);
}
filePath = filePath + Path.GetFileName(imgFile.FileName);
imgFile.SaveAs(filePath);
imagePath =
Path.Combine(HttpContext.Current.Server.MapPath(#"~/Images/"), filePath);
}
//Enter the Image Path if you want to upload image.
var service = new TweetSharp.TwitterService(key, secret);
service.AuthenticateWith(token, tokenSecret);
//this Condition will check weather you want to upload a image & text or only text
if (imagePath.Length > 0)
{
using (var stream = new FileStream(imagePath, FileMode.Open))
{
var result = service.SendTweetWithMedia(
new SendTweetWithMediaOptions
{
Status = message,
Images = new Dictionary<string, Stream> { { "sos", stream } }
});
}
}
else // just message
{
var result = service.SendTweet(new SendTweetOptions
{
Status = message
});
}
return true;
}
JobScheduler.cs
public class JobScheduler<IDGJob>
where IDGJob : Quartz.IJob
{
public static void Start()
{
IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler().Result;
scheduler.Start();
IJobDetail job = JobBuilder.Create<IDGJob>().Build();
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("IDGJob", "IDG")
.WithCronSchedule("0 0 12 1/1 * ? *")
.StartAt(DateTime.UtcNow)
.WithPriority(1)
.Build();
scheduler.ScheduleJob(job, trigger);
}
}
IDGJob.cs
public class IDGJob : IJob
{
Action<Tweet, HttpPostedFileBase> upFile;
public IDGJob(Action<Tweet, HttpPostedFileBase> UploadFiles)
{
if (UploadFiles == null)
{
throw new ArgumentNullException(nameof(UploadFiles));
}
Action<Tweet, HttpPostedFileBase> upFile = UploadFiles;
}
public void Execute(IJobExecutionContext context)
{
upFile(twts, imgFile); ****
}
}
Based on the below answer I updated this class.
**** Throws Error "The twts does not exist in the current context".. of course it will, but my question how to pass the value from the above flow?
Your IDGJob type should take a callback and invoke it inside Execute()
public class IDGJob : IJob
{
Action callback;
public IDGJob(Action action)
{
if(action == null)
throw new ArgumentNullException(nameof(action));
callback = action;
}
public void Execute(IJobExecutionContext context)
{
callback();
}
}
Now the code using this class can provide a callback when it constructs an object and have it do whatever is needed.
Alternatively, you can expose an event Executed and bind handlers in the client code.
public class IDGJob : IJob
{
public event EventHandler Executed;
public void Execute(IJobExecutionContext context)
{
Executed?.(this, EventArgs.Empty);
}
}
In this version, client code would += on Executed.

Call non static method in PageRenderer Xamarin

I have a simple code that make authentication in Platform-specific code:
[assembly: ExportRenderer(typeof(FacebookLoginPage), typeof(FacebookLoginRenderer))]
namespace VejoSeriesMobile.Droid.Renderers
{
public class FacebookLoginRenderer : PageRenderer
{
public static string ClientId = "";
public static string ClientSecret = "";
public static string Scope = "email, public_profile";
public static string AuthorizeUrl = "https://m.facebook.com/dialog/oauth";
public static string RedirectUrl = "https://www.facebook.com/connect/login_success.html";
public static string AccessTokenUrl = "https://m.facebook.com/dialog/oauth/token";
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
var activity = this.Context as Activity;
var auth = new OAuth2Authenticator(
clientId: "717427648360004",
scope: Scope,
authorizeUrl: new Uri("https://m.facebook.com/dialog/oauth/"),
redirectUrl: new Uri("http://www.facebook.com/connect/login_success.html"));
auth.Completed += OnAuthenticationCompleted;
activity.StartActivity(auth.GetUI(activity));
}
async void OnAuthenticationCompleted(object sender, AuthenticatorCompletedEventArgs e)
{
if (e.IsAuthenticated)
{
var request = new OAuth2Request("GET", new Uri("https://graph.facebook.com/me?fields=id,name,picture,email"), null, e.Account);
await request.GetResponseAsync().ContinueWith(t =>
{
var fbUser = JObject.Parse(t.Result.GetResponseText());
var id = fbUser["id"].ToString().Replace("\"", "");
var name = fbUser["name"].ToString().Replace("\"", "");
var email = fbUser["email"].ToString().Replace("\"", "");
var conta = new Account { Username = name };
conta.Properties.Add("email", email);
AccountStore.Create(Context).Save(conta, "VejoSeries");
App.SuccessfulLoginAction.Invoke();
});
}
}
}
}
in my App portable class I have a static method that is calling after login complete:
But in this scope I only can use Static method, I wish to call a non static method for re rendering the entire Page (with menus, etc..) after login. How can I do this?
Above my successLoginAction:
public static Action SuccessfulLoginAction
{
get
{
return new Action(() =>
{
//var masterDetailPage = Application.Current.MainPage as MasterDetailPage;
//masterDetailPage.Detail = new NavigationPage((Page)Activator.CreateInstance(typeof(Painel)));
//masterDetailPage.IsPresented = false;
_NavPage.Navigation.PopAsync();
_NavPage.Navigation.PushAsync(new MainPage());
});
}
}
The short answer is don't make it static.
If you change it to just a public property, you can access it through App.Current.
To be more specific:
public Action SuccessfulLoginAction
{
get
{
return new Action(() =>
{
//var masterDetailPage = Application.Current.MainPage as MasterDetailPage;
//masterDetailPage.Detail = new NavigationPage((Page)Activator.CreateInstance(typeof(Painel)));
//masterDetailPage.IsPresented = false;
_NavPage.Navigation.PopAsync();
_NavPage.Navigation.PushAsync(new MainPage());
});
}
}
Then call it from anywhere like this:
((App)Application.Current).SuccessfulLoginAction.Invoke();
That way you can access your _NavPage as the whole thing is bound to the instance of App.

Refactoring for SOLID principles

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.

Categories

Resources