Introduction
Aspect Oriented Programming (AOP) is a very powerful approach to avoid boilerplate code and achieve better modularity. The main idea is to add behavior (advice) to the existing code without making any changes in the code itself. In Java, this idea is implemented in AspectJ and Spring frameworks. There are PostSharp (not free), NConcern, and some other frameworks (not very popular and easy to use) to do almost the same in .NET.
It is also possible to use RealProxy class to implement AOP. You can find some examples on how to do it here,
Example1: Aspect-Oriented Programming : Aspect-Oriented Programming with the RealProxy Class
Example2: MSDN.
I tried to fix first example.
Before continue reading
This article is how to fix some problems in solution, provided by Bruno Sonnino (Example1). That article has great explanation how code is supposed to work and what kind of problems it solves. Please read Aspect-Oriented Programming : Aspect-Oriented Programming with the RealProxy Class first.
Solution
This solution is an example of logging implementation. Code could be found here.
Differences with original code:
- Extension method GetDescription was added to log Exception data (Extensions.cs).
- DynamicProxy class was renamed to LoggingAdvice.
- Constructor was made private. Static function Create creates class instance and returns TransparentProxy (LoggingAdvice.cs - lines 35-41). It makes impossible to create instance of LoggingAdvice class, because only proxy, created by this class, is going to be used.
- LoggingAdvice receives actions to log function calls and errors, and function to serialize complex type values as parameters (LoggingAdvice.cs - lines 19-20).
- TaskScheduler was added as an optional parameter to support task results logging using different task scheduler. TaskScheduler.FromCurrentSynchronizationContext() will be used by default (LoggingAdvice.cs - line 36).
- Functions LogException, LogBefore and LogAfrer were added to log corresponding data (LoggingAdvice.cs - lines 150-209).
- Try/catch blocks were added to handle situation when logInfo function throws an exception (LoggingAdvice.cs - lines 53-61, 99-107).
- If result value of the function is Task, execution result will be logged after task completion (LoggingAdvice.cs - lines 69-96).
- Added output parameters support (LoggingAdvice.cs - lines 63, 110-111).
- using System;
- using System.Text;
- namespace AOP
- {
- public static class Extensions
- {
- public static string GetDescription(this Exception e)
- {
- var builder = new StringBuilder();
- AddException(builder, e);
- return builder.ToString();
- }
- private static void AddException(StringBuilder builder, Exception e)
- {
- builder.AppendLine($"Message: {e.Message}");
- builder.AppendLine($"Stack Trace: {e.StackTrace}");
- if (e.InnerException != null)
- {
- builder.AppendLine("Inner Exception");
- AddException(builder, e.InnerException);
- }
- }
- }
- }
- using System;
- using System.Linq;
- using System.Reflection;
- using System.Runtime.Remoting.Messaging;
- using System.Runtime.Remoting.Proxies;
- using System.Text;
- using System.Threading.Tasks;
- namespace AOP
- {
- public class LoggingAdvice<T> : RealProxy
- {
- private readonly T _decorated;
- private readonly Action<string> _logInfo;
- private readonly Action<string> _logError;
- private readonly Func<object, string> _serializeFunction;
- private readonly TaskScheduler _loggingScheduler;
- private LoggingAdvice(T decorated, Action<string> logInfo, Action<string> logError,
- Func<object, string> serializeFunction, TaskScheduler loggingScheduler)
- : base(typeof(T))
- {
- if (decorated == null)
- {
- throw new ArgumentNullException(nameof(decorated));
- }
- _decorated = decorated;
- _logInfo = logInfo;
- _logError = logError;
- _serializeFunction = serializeFunction;
- _loggingScheduler = loggingScheduler ?? TaskScheduler.FromCurrentSynchronizationContext();
- }
- public static T Create(T decorated, Action<string> logInfo, Action<string> logError,
- Func<object, string> serializeFunction, TaskScheduler loggingScheduler = null)
- {
- var advice = new LoggingAdvice<T>(decorated, logInfo, logError, serializeFunction, loggingScheduler);
- return (T)advice.GetTransparentProxy();
- }
- public override IMessage Invoke(IMessage msg)
- {
- var methodCall = msg as IMethodCallMessage;
- if (methodCall != null)
- {
- var methodInfo = methodCall.MethodBase as MethodInfo;
- if (methodInfo != null)
- {
- try
- {
- try
- {
- LogBefore(methodCall, methodInfo);
- }
- catch (Exception ex)
- {
- //Do not stop method execution if exception
- LogException(ex);
- }
- var args = methodCall.Args;
- var result = typeof(T).InvokeMember(
- methodCall.MethodName,
- BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, _decorated, args);
- if (result is Task)
- {
- ((Task)result).ContinueWith(task =>
- {
- if (task.Exception != null)
- {
- LogException(task.Exception.InnerException ?? task.Exception, methodCall);
- }
- else
- {
- object taskResult = null;
- if (task.GetType().IsGenericType && task.GetType().GetGenericTypeDefinition() == typeof(Task<>))
- {
- var property = task.GetType().GetProperties()
- .FirstOrDefault(p => p.Name == "Result");
- if (property != null)
- {
- taskResult = property.GetValue(task);
- }
- }
- LogAfter(methodCall, methodCall.Args, methodInfo, taskResult);
- }
- },
- _loggingScheduler);
- }
- else
- {
- try
- {
- LogAfter(methodCall, args, methodInfo, result);
- }
- catch (Exception ex)
- {
- //Do not stop method execution if exception
- LogException(ex);
- }
- }
- return new ReturnMessage(result, args, args.Length,
- methodCall.LogicalCallContext, methodCall);
- }
- catch (Exception ex)
- {
- if (ex is TargetInvocationException)
- {
- LogException(ex.InnerException ?? ex, methodCall);
- return new ReturnMessage(ex.InnerException ?? ex, methodCall);
- }
- }
- }
- }
- throw new ArgumentException(nameof(msg));
- }
- private string GetStringValue(object obj)
- {
- if (obj == null)
- {
- return "null";
- }
- if (obj.GetType().IsPrimitive || obj.GetType().IsEnum || obj is string)
- {
- return obj.ToString();
- }
- try
- {
- return _serializeFunction?.Invoke(obj) ?? obj.ToString();
- }
- catch
- {
- return obj.ToString();
- }
- }
- private void LogException(Exception exception, IMethodCallMessage methodCall = null)
- {
- try
- {
- var errorMessage = new StringBuilder();
- errorMessage.AppendLine($"Class {_decorated.GetType().FullName}");
- errorMessage.AppendLine($"Method {methodCall?.MethodName} threw exception");
- errorMessage.AppendLine(exception.GetDescription());
- _logError?.Invoke(errorMessage.ToString());
- }
- catch (Exception)
- {
- // ignored
- //Method should return original exception
- }
- }
- private void LogAfter(IMethodCallMessage methodCall, object[] args, MethodInfo methodInfo, object result)
- {
- var afterMessage = new StringBuilder();
- afterMessage.AppendLine($"Class {_decorated.GetType().FullName}");
- afterMessage.AppendLine($"Method {methodCall.MethodName} executed");
- afterMessage.AppendLine("Output:");
- afterMessage.AppendLine(GetStringValue(result));
- var parameters = methodInfo.GetParameters();
- if (parameters.Any())
- {
- afterMessage.AppendLine("Parameters:");
- for (var i = 0; i < parameters.Length; i++)
- {
- var parameter = parameters[i];
- var arg = args[i];
- afterMessage.AppendLine($"{parameter.Name}:{GetStringValue(arg)}");
- }
- }
- _logInfo?.Invoke(afterMessage.ToString());
- }
- private void LogBefore(IMethodCallMessage methodCall, MethodInfo methodInfo)
- {
- var beforeMessage = new StringBuilder();
- beforeMessage.AppendLine($"Class {_decorated.GetType().FullName}");
- beforeMessage.AppendLine($"Method {methodCall.MethodName} executing");
- var parameters = methodInfo.GetParameters();
- if (parameters.Any())
- {
- beforeMessage.AppendLine("Parameters:");
- for (var i = 0; i < parameters.Length; i++)
- {
- var parameter = parameters[i];
- var arg = methodCall.Args[i];
- beforeMessage.AppendLine($"{parameter.Name}:{GetStringValue(arg)}");
- }
- }
- _logInfo?.Invoke(beforeMessage.ToString());
- }
- }
- }
How to use
- var decoratedInstance = LoggingAdvice<IInstanceInteface>.Create(
- instance,
- s => Console.WriteLine(“Info:” + s),
- s => Console.WriteLine(“Error:” + s),
- o => o?.ToString());
Example
- namespace AOP.Example
- {
- public interface ICalculator
- {
- int Add(int a, int b);
- int Subtract(int a, int b);
- }
- }
- namespace AOP.Example
- {
- public class Calculator : ICalculator
- {
- public int Add(int a, int b)
- {
- return a + b;
- }
- public int Subtract(int a, int b)
- {
- return a - b;
- }
- }
- }
Is is easy. Each method has only one responsiblility.
One day some users start complaining that sometimes Add(2, 2) returns 4. You don't understand what's going on and decide to add logging.
- namespace AOP.Example
- {
- public class CalculatorWithoutAop: ICalculator
- {
- private readonly ILogger _logger;
- public CalculatorWithoutAop(ILogger logger)
- {
- _logger = logger;
- }
- public int Add(int a, int b)
- {
- _logger.Log($"Adding {a} + {b}");
- var result = a + b;
- _logger.Log($"Result is {result}");
- return result;
- }
- public int Subtract(int a, int b)
- {
- _logger.Log($"Subtracting {a} - {b}");
- var result = a - b;
- _logger.Log($"Result is {result}");
- return result;
- }
- }
- }
There are 3 problems with this solution.
- Calculator class coupled with logging. Loosely coupled (because ILoger it is an interface), but coupled. Every time you make changes in this interface it affects Calculator.
- Code become more complex.
- It breaks Single Responsibility principle. Add function don't just add numbers. It logs input values, add values and logs result. The same for Subtract.
- Code in this article allows you don't touch Calculator class at all.
You just need to change creation of the class.
- namespace AOP.Example
- {
- public class CalculatorFactory
- {
- private readonly ILogger _logger;
- public CalculatorFactory(ILogger logger)
- {
- _logger = logger;
- }
- public ICalculator CreateCalculator()
- {
- return LoggingAdvice <ICalculator >.Create(
- new Calculator(),
- s => _logger.Log("Info:" + s),
- s => _logger.Log("Error:" + s),
- o => o?.ToString());
- }
- }
- }
Tests
NUnit and NSubstitute are used to write Unit Tests.
- using System;
- using System.Collections.Generic;
- using System.Threading;
- using System.Threading.Tasks;
- using NSubstitute;
- using NUnit.Framework;
- namespace AOP.Tests
- {
- public interface ITestClass2
- {
- void Method();
- void MethodWithException();
- Task AsyncMethodWithException();
- }
- public class TestClass2 : ITestClass2
- {
- public void Method()
- {
- }
- public void MethodWithException()
- {
- throw new Exception();
- }
- public Task AsyncMethodWithException()
- {
- return Task.Factory.StartNew(() => { throw new Exception(); });
- }
- }
- [TestFixture]
- public class LoggingAdviceTests
- {
- private TaskScheduler _taskScheduler;
- public class Data
- {
- public string Prop { get; set; }
- }
- public interface ITestClass
- {
- string Property { get; set; }
- void MethodWithVoidResult();
- string MethodWithStringResultAndIntParameter(int intParam);
- Data MethodWithClassResultAndClassParameter(Data dataParam);
- int MethodWithOutParameter(out string stringParam);
- int MethodWithRefParameter(ref string stringParam);
- int MethodWithOutParameter(out int intParam);
- int MethodWithRefParameter(ref int intParam);
- int MethodWithMixedParameter(ref int refParam, out string outParam, bool param);
- Task MethodWithTaskResult();
- Task<string> MethodWithTaskStringResult();
- }
- [SetUp]
- public void SetUp()
- {
- SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
- _taskScheduler = new CurrentThreadTaskScheduler();
- }
- [Test]
- public void ThrowException_WhenDecoratedClassNull()
- {
- Assert.Throws<ArgumentNullException>(() =>
- {
- var decorated = LoggingAdvice<ITestClass>.Create(
- null,
- s => { },
- s => { },
- o => o?.ToString());
- });
- }
- [Test]
- public void LogInfo()
- {
- var testClass = Substitute.For<ITestClass>();
- var errorMassages = new List<string>();
- var infoMassages = new List<string>();
- var decorated = LoggingAdvice<ITestClass>.Create(
- testClass,
- s => infoMassages.Add(s),
- s => errorMassages.Add(s),
- o => o?.ToString());
- decorated.MethodWithVoidResult();
- testClass.Received().MethodWithVoidResult();
- Assert.AreEqual(2, infoMassages.Count);
- Assert.IsTrue(infoMassages[0].Contains("MethodWithVoidResult"));
- Assert.IsTrue(infoMassages[1].Contains("MethodWithVoidResult"));
- Assert.AreEqual(0, errorMassages.Count);
- }
- [Test]
- public void DoNotStop_WhenLogInfoThrowsException()
- {
- var testClass = Substitute.For<ITestClass>();
- var errorMassages = new List<string>();
- var decorated = LoggingAdvice<ITestClass>.Create(
- testClass,
- s => { throw new Exception(); },
- s => errorMassages.Add(s),
- o => o?.ToString());
- decorated.MethodWithVoidResult();
- testClass.Received().MethodWithVoidResult();
- Assert.AreEqual(2, errorMassages.Count);
- }
- [Test]
- public void LogInfo_WhenClassResultAndClassParameter()
- {
- var testClass = Substitute.For<ITestClass>();
- testClass.MethodWithClassResultAndClassParameter(Arg.Any<Data>()).Returns(new Data { Prop = "Result12345" });
- var errorMassages = new List<string>();
- var infoMassages = new List<string>();
- var decorated = LoggingAdvice<ITestClass>.Create(
- testClass,
- s => infoMassages.Add(s),
- s => errorMassages.Add(s),
- o => ((Data)o).Prop);
- var result = decorated.MethodWithClassResultAndClassParameter(new Data { Prop = "Parameter12345"});
- testClass.Received().MethodWithClassResultAndClassParameter(Arg.Any<Data>());
- Assert.AreEqual("Result12345", result.Prop);
- Assert.AreEqual(2, infoMassages.Count);
- Assert.IsTrue(infoMassages[0].Contains("dataParam:Parameter12345"));
- Assert.IsTrue(infoMassages[1].Contains("dataParam:Parameter12345"));
- Assert.IsTrue(infoMassages[1].Contains("Result12345"));
- Assert.AreEqual(0, errorMassages.Count);
- }
- [Test]
- public void LogInfo_WhenParameters()
- {
- var testClass = Substitute.For<ITestClass>();
- testClass.MethodWithStringResultAndIntParameter(Arg.Any<int>()).Returns("Result12345");
- var errorMassages = new List<string>();
- var infoMassages = new List<string>();
- var decorated = LoggingAdvice<ITestClass>.Create(
- testClass,
- s => infoMassages.Add(s),
- s => errorMassages.Add(s),
- o => o?.ToString());
- var result = decorated.MethodWithStringResultAndIntParameter(12345);
- testClass.Received().MethodWithStringResultAndIntParameter(Arg.Any<int>());
- Assert.AreEqual("Result12345", result);
- Assert.AreEqual(2, infoMassages.Count);
- Assert.IsTrue(infoMassages[0].Contains("intParam:12345"));
- Assert.IsTrue(infoMassages[1].Contains("Result12345"));
- Assert.IsTrue(infoMassages[1].Contains("intParam:12345"));
- Assert.AreEqual(0, errorMassages.Count);
- }
- [Test]
- public void DoNotStop_WhenSerializerThrowsException()
- {
- var testClass = Substitute.For<ITestClass>();
- testClass.MethodWithClassResultAndClassParameter(Arg.Any<Data>()).Returns(new Data { Prop = "Result12345" });
- var errorMassages = new List<string>();
- var infoMassages = new List<string>();
- var decorated = LoggingAdvice<ITestClass>.Create(
- testClass,
- s => infoMassages.Add(s),
- s => errorMassages.Add(s),
- o => { throw new Exception();});
- Data result = null;
- Assert.DoesNotThrow(() => result = decorated.MethodWithClassResultAndClassParameter(new Data { Prop = "Parameter12345"}));
- testClass.Received().MethodWithClassResultAndClassParameter(Arg.Any<Data>());
- Assert.AreEqual("Result12345", result?.Prop);
- Assert.AreEqual(2, infoMassages.Count);
- Assert.IsTrue(infoMassages[0].Contains("Data"));
- Assert.IsTrue(infoMassages[1].Contains("Data"));
- Assert.AreEqual(0, errorMassages.Count);
- }
- [Test]
- public void LogError()
- {
- var testClass = Substitute.For<ITestClass>();
- testClass.When(t => t.MethodWithVoidResult()).Do(info => { throw new Exception(); });
- var errorMassages = new List<string>();
- var infoMassages = new List<string>();
- var decorated = LoggingAdvice<ITestClass>.Create(
- testClass,
- s => infoMassages.Add(s),
- s => errorMassages.Add(s),
- o => o?.ToString());
- Assert.Throws<Exception>(() => decorated.MethodWithVoidResult());
- testClass.Received().MethodWithVoidResult();
- Assert.AreEqual(1, infoMassages.Count);
- Assert.IsTrue(infoMassages[0].Contains("MethodWithVoidResult"));
- Assert.AreEqual(1, errorMassages.Count);
- Assert.IsTrue(errorMassages[0].Contains("MethodWithVoidResult"));
- }
- [Test]
- public void DoNotStop_WhenLogErrorThrowsException()
- {
- var testClass = Substitute.For<ITestClass>();
- testClass.When(t => t.MethodWithVoidResult()).Do(info => { throw new Exception(); });
- var infoMassages = new List<string>();
- var decorated = LoggingAdvice<ITestClass>.Create(
- testClass,
- s => infoMassages.Add(s),
- s => { throw new ArgumentNullException(); },
- o => o?.ToString());
- Assert.Throws<Exception>(() => decorated.MethodWithVoidResult());
- testClass.Received().MethodWithVoidResult();
- Assert.AreEqual(1, infoMassages.Count);
- }
- [Test]
- public void LogInfo_WhenTaskMethodCall()
- {
- var testClass = Substitute.For<ITestClass>();
- testClass.MethodWithTaskResult().Returns(Task.CompletedTask);
- var errorMassages = new List<string>();
- var infoMassages = new List<string>();
- var decorated = LoggingAdvice<ITestClass>.Create(
- testClass,
- s => infoMassages.Add(s),
- s => errorMassages.Add(s),
- o => o?.ToString(),
- _taskScheduler);
- decorated.MethodWithTaskResult().Wait();
- ReleaseContext();
- testClass.Received().MethodWithTaskResult();
- Assert.AreEqual(2, infoMassages.Count);
- Assert.AreEqual(0, errorMassages.Count);
- }
- [Test]
- public void LogInfo_WhenTaskMethodCallWithException()
- {
- var testClass = Substitute.For<ITestClass>();
- testClass.MethodWithTaskResult().Returns(Task.FromException(new Exception("ERROR!!!!")));
- var errorMassages = new List<string>();
- var infoMassages = new List<string>();
- var decorated = LoggingAdvice<ITestClass>.Create(
- testClass,
- s => infoMassages.Add(s),
- s => errorMassages.Add(s),
- o => o?.ToString(),
- _taskScheduler);
- Assert.Throws<AggregateException>(() => decorated.MethodWithTaskResult().Wait());
- ReleaseContext();
- testClass.Received().MethodWithTaskResult();
- Assert.AreEqual(1, infoMassages.Count);
- Assert.AreEqual(1, errorMassages.Count);
- Assert.IsTrue(errorMassages[0].Contains("ERROR!!!!"));
- }
- [Test]
- public void LogInfo_WhenTaskHasResult()
- {
- var testClass = Substitute.For<ITestClass>();
- testClass.MethodWithTaskStringResult().Returns("String result");
- var errorMassages = new List<string>();
- var infoMassages = new List<string>();
- var decorated = LoggingAdvice<ITestClass>.Create(
- testClass,
- s => infoMassages.Add(s),
- s => errorMassages.Add(s),
- o => o?.ToString(),
- _taskScheduler);
- var result = decorated.MethodWithTaskStringResult().Result;
- ReleaseContext();
- Assert.AreEqual("String result", result);
- testClass.Received().MethodWithTaskStringResult();
- Assert.AreEqual(2, infoMassages.Count);
- Assert.IsTrue(infoMassages[1].Contains("String result"));
- Assert.AreEqual(0, errorMassages.Count);
- }
- [Test]
- public void LogInfo_WhenTaskWithResultThrowException()
- {
- var testClass = Substitute.For<ITestClass>();
- testClass.MethodWithTaskStringResult().Returns(Task.FromException<string>(new Exception("ERROR!!!!")));
- var errorMassages = new List<string>();
- var infoMassages = new List<string>();
- var decorated = LoggingAdvice<ITestClass>.Create(
- testClass,
- s => infoMassages.Add(s),
- s => errorMassages.Add(s),
- o => o?.ToString(),
- _taskScheduler);
- Assert.Throws<AggregateException>(() => decorated.MethodWithTaskStringResult().Wait());
- ReleaseContext();
- testClass.Received().MethodWithTaskStringResult();
- Assert.AreEqual(1, infoMassages.Count);
- Assert.AreEqual(1, errorMassages.Count);
- Assert.IsTrue(errorMassages[0].Contains("ERROR!!!!"));
- }
- [Test]
- public void LogInfo_WhenOutParameter()
- {
- var testClass = Substitute.For<ITestClass>();
- var val = "s2";
- testClass.MethodWithOutParameter(out val)
- .Returns(x =>
- {
- x[0] = "s5";
- return 25;
- });
- var errorMassages = new List<string>();
- var infoMassages = new List<string>();
- var decorated = LoggingAdvice<ITestClass>.Create(
- testClass,
- s => infoMassages.Add(s),
- s => errorMassages.Add(s),
- o => o?.ToString());
- var result = decorated.MethodWithOutParameter(out val);
- var val2 = "s2";
- testClass.Received().MethodWithOutParameter(out val2);
- Assert.AreEqual(0, errorMassages.Count);
- Assert.AreEqual(2, infoMassages.Count);
- Assert.AreEqual(25, result);
- Assert.AreEqual("s5", val);
- Assert.IsTrue(infoMassages[1].Contains("25"));
- Assert.IsTrue(infoMassages[1].Contains("stringParam:s5"));
- }
- [Test]
- public void LogInfo_WhenRefParameter()
- {
- var testClass = Substitute.For<ITestClass>();
- var val = "s2";
- testClass.MethodWithRefParameter(ref val)
- .Returns(x =>
- {
- x[0] = "s5";
- return 25;
- });
- var errorMassages = new List<string>();
- var infoMassages = new List<string>();
- var decorated = LoggingAdvice<ITestClass>.Create(
- testClass,
- s => infoMassages.Add(s),
- s => errorMassages.Add(s),
- o => o?.ToString());
- var result = decorated.MethodWithRefParameter(ref val);
- var val2 = "s2";
- testClass.Received().MethodWithRefParameter(ref val2);
- Assert.AreEqual(0, errorMassages.Count);
- Assert.AreEqual(2, infoMassages.Count);
- Assert.AreEqual(25, result);
- Assert.AreEqual("s5", val);
- Assert.IsTrue(infoMassages[0].Contains("stringParam:s2"));
- Assert.IsTrue(infoMassages[1].Contains("25"));
- Assert.IsTrue(infoMassages[1].Contains("stringParam:s5"));
- }
- [Test]
- public void LogInfo_WhenOutParameterOfValueType()
- {
- var testClass = Substitute.For<ITestClass>();
- var val = 2;
- testClass.MethodWithOutParameter(out val)
- .Returns(x =>
- {
- x[0] = 5;
- return 25;
- });
- var errorMassages = new List<string>();
- var infoMassages = new List<string>();
- var decorated = LoggingAdvice<ITestClass>.Create(
- testClass,
- s => infoMassages.Add(s),
- s => errorMassages.Add(s),
- o => o?.ToString());
- var result = decorated.MethodWithOutParameter(out val);
- var val2 = 2;
- testClass.Received().MethodWithOutParameter(out val2);
- Assert.AreEqual(0, errorMassages.Count);
- Assert.AreEqual(2, infoMassages.Count);
- Assert.AreEqual(25, result);
- Assert.AreEqual(5, val);
- Assert.IsTrue(infoMassages[1].Contains("25"));
- Assert.IsTrue(infoMassages[1].Contains("intParam:5"));
- }
- [Test]
- public void LogInfo_WhenRefParameterOfValueType()
- {
- var testClass = Substitute.For<ITestClass>();
- var val = 2;
- testClass.MethodWithRefParameter(ref val)
- .Returns(x =>
- {
- x[0] = 5;
- return 25;
- });
- var errorMassages = new List<string>();
- var infoMassages = new List<string>();
- var decorated = LoggingAdvice<ITestClass>.Create(
- testClass,
- s => infoMassages.Add(s),
- s => errorMassages.Add(s),
- o => o?.ToString());
- var result = decorated.MethodWithRefParameter(ref val);
- var val2 = 2;
- testClass.Received().MethodWithRefParameter(ref val2);
- Assert.AreEqual(0, errorMassages.Count);
- Assert.AreEqual(2, infoMassages.Count);
- Assert.AreEqual(25, result);
- Assert.AreEqual(5, val);
- Assert.IsTrue(infoMassages[0].Contains("intParam:2"));
- Assert.IsTrue(infoMassages[1].Contains("25"));
- Assert.IsTrue(infoMassages[1].Contains("intParam:5"));
- }
- [Test]
- public void LogInfo_WhenMixedParameters()
- {
- var testClass = Substitute.For<ITestClass>();
- var refParam = 0;
- var outParam = "s2";
- testClass.MethodWithMixedParameter(ref refParam, out outParam, true)
- .Returns(x =>
- {
- x[0] = 5;
- x[1] = "s5";
- return 25;
- });
- var errorMassages = new List<string>();
- var infoMassages = new List<string>();
- var decorated = LoggingAdvice<ITestClass>.Create(
- testClass,
- s => infoMassages.Add(s),
- s => errorMassages.Add(s),
- o => o?.ToString());
- var result = decorated.MethodWithMixedParameter(ref refParam, out outParam, true);
- var refParam2 = 0;
- var outParam2 = "s2";
- testClass.Received().MethodWithMixedParameter(ref refParam2, out outParam2, true);
- Assert.AreEqual(0, errorMassages.Count);
- Assert.AreEqual(2, infoMassages.Count);
- Assert.AreEqual(25, result);
- Assert.AreEqual(5, refParam);
- Assert.AreEqual("s5", outParam);
- Assert.IsTrue(infoMassages[0].Contains("refParam:0"));
- Assert.IsTrue(infoMassages[0].Contains("outParam:s2"));
- Assert.IsTrue(infoMassages[0].Contains("param:True"));
- Assert.IsTrue(infoMassages[1].Contains("25"));
- Assert.IsTrue(infoMassages[1].Contains("refParam:5"));
- Assert.IsTrue(infoMassages[1].Contains("outParam:s5"));
- Assert.IsTrue(infoMassages[1].Contains("param:True"));
- }
- [Test]
- public void LogInfoWithProperClassName()
- {
- var testClass = new TestClass2();
- var errorMassages = new List<string>();
- var infoMassages = new List<string>();
- var decorated = LoggingAdvice<ITestClass2>.Create(
- testClass,
- s => infoMassages.Add(s),
- s => errorMassages.Add(s),
- o => o?.ToString());
- decorated.Method();
- Assert.AreEqual(2, infoMassages.Count);
- Assert.IsTrue(infoMassages[0].Contains("Class AOP.Tests.TestClass2"));
- Assert.IsTrue(infoMassages[1].Contains("Class AOP.Tests.TestClass2"));
- Assert.AreEqual(0, errorMassages.Count);
- }
- [Test]
- public void LogInfoWithProperStackTrace()
- {
- var testClass = new TestClass2();
- var errorMassages = new List<string>();
- var infoMassages = new List<string>();
- var decorated = LoggingAdvice<ITestClass2>.Create(
- testClass,
- s => infoMassages.Add(s),
- s => errorMassages.Add(s),
- o => o?.ToString());
- Assert.Throws<Exception>(() => decorated.MethodWithException());
- Assert.AreEqual(1, infoMassages.Count);
- Assert.IsTrue(infoMassages[0].Contains("Class AOP.Tests.TestClass2"));
- Assert.AreEqual(1, errorMassages.Count);
- Assert.IsTrue(errorMassages[0].Contains("Class AOP.Tests.TestClass2"));
- //Only original Exception should be logged
- Assert.IsFalse(errorMassages[0].Contains("LoggingAdvice.cs"));
- Assert.IsTrue(errorMassages[0].Contains("LoggingAdviceTests.cs"));
- }
- [Test]
- public void LogInfoWithProperStackTrace_WhenTask()
- {
- var testClass = new TestClass2();
- var errorMassages = new List<string>();
- var infoMassages = new List<string>();
- var decorated = LoggingAdvice<ITestClass2>.Create(
- testClass,
- s => infoMassages.Add(s),
- s => errorMassages.Add(s),
- o => o?.ToString(),
- _taskScheduler);
- Assert.Throws<AggregateException>(() => decorated.AsyncMethodWithException().Wait());
- Assert.AreEqual(1, infoMassages.Count);
- Assert.IsTrue(infoMassages[0].Contains("Class AOP.Tests.TestClass2"));
- Assert.AreEqual(1, errorMassages.Count);
- Assert.IsTrue(errorMassages[0].Contains("Class AOP.Tests.TestClass2"));
- //Only original Exception should be logged
- Assert.IsFalse(errorMassages[0].Contains("LoggingAdvice.cs"));
- Assert.IsTrue(errorMassages[0].Contains("LoggingAdviceTests.cs"));
- }
- [Test]
- public void LogInfo_WhenPropertySet()
- {
- var testClass = Substitute.For<ITestClass>();
- var errorMassages = new List<string>();
- var infoMassages = new List<string>();
- var decorated = LoggingAdvice<ITestClass>.Create(
- testClass,
- s => infoMassages.Add(s),
- s => errorMassages.Add(s),
- o => o?.ToString());
- decorated.Property = "Str1";
- testClass.Received().Property = "Str1";
- Assert.AreEqual(2, infoMassages.Count);
- Assert.IsTrue(infoMassages[0].Contains("set_Property"));
- Assert.IsTrue(infoMassages[1].Contains("set_Property"));
- Assert.AreEqual(0, errorMassages.Count);
- }
- [Test]
- public void LogInfo_WhenPropertyGet()
- {
- var testClass = Substitute.For<ITestClass>();
- testClass.Property.Returns("Str1");
- var errorMassages = new List<string>();
- var infoMassages = new List<string>();
- var decorated = LoggingAdvice<ITestClass>.Create(
- testClass,
- s => infoMassages.Add(s),
- s => errorMassages.Add(s),
- o => o?.ToString());
- var value = decorated.Property;
- var tmp = testClass.Received().Property;
- Assert.AreEqual("Str1", value);
- Assert.AreEqual(2, infoMassages.Count);
- Assert.IsTrue(infoMassages[0].Contains("get_Property"));
- Assert.IsTrue(infoMassages[1].Contains("get_Property"));
- Assert.AreEqual(0, errorMassages.Count);
- }
- /// <summary>
- /// Call to process scheduled tasks
- /// </summary>
- protected void ReleaseContext()
- {
- Task.Delay(1).Wait();
- }
- }
- }

Conclusion
This code works for my cases. If you have any examples about when this code does not work or how this code could be improved, please feel free to contact me.
That's it — enjoy!

Tridip BhattacharjeePosted Sep 19, 2017, 6:08 AM
If possible please come with a small article on AOP where you will develop a small calculator with addition functionality and later you will show how to incorporate more feature into calculator like subtract,division with AOP very loosely coupled way. i want to see a nice example of APO which come with small example code just to show how we can remove dependency with APO approach. please do the best. thanks sir.
Tridip BhattacharjeePosted Sep 18, 2017, 8:21 AM
Can you please develop a small calculator program with AOP approach and highlight those area which show the real power of developing anything with AOP approach.
Tridip BhattacharjeePosted Sep 14, 2017, 8:49 AM
I have one request that it would be better if you explain APO with small example like what you said calculator. can you do it please. thanks
Tridip BhattacharjeePosted Sep 14, 2017, 8:48 AM
Your two answer is awesome.......thanks for help.
Tridip BhattacharjeePosted Sep 14, 2017, 8:45 AM
You said "one of the ways to separate different parts (aspects) of software. AOP allows to avoid tight coupling. In this particular case LoggerAspect does not know anything about class he is decorating. Class does not know anything about logging." this same kind of separation we can do by dependency injection and DI is small learning curve then why some one invest time to learn AOP?
Tridip BhattacharjeePosted Sep 12, 2017, 7:55 AM
When and why people use aspect-oriented-programming ? is it any design pattern or something else? tell us advantage of aspect-oriented-programming ?