0

Background: I have an old class (represented as OriginalComponent in the code below) which I would like to add logging to its methods. The project employs neither an IOC container nor AOP capabilities, and as such I wanted to be cleaver about how I add Logging.

My thought for solution: I feel that a good place to start is the dynamic construct that will act as a decorator class. My code is listed below.

However, my code suffers from a big flaw: If I do not call the methods in my OriginalComponent class with precise parameter types then the decorator class will not find the methods.

Question: Does anyone know how I can overcome this shortcoming and ultimately allow me to be able to call a method in a similar inexact way that the compiler allows for an inexact parameter types in a method call.

The following code is written in LinqPad 5.

void Main()
{
    var calculator = new OriginalComponent();
    var logCalc = new DecoratorLogging<IComponent, LogEnableAttribute>(calculator);
    dynamic d = logCalc;

    // Does not work
    try
    {
        var ri = d.Add(1, 2);
        Console.WriteLine($"Main: ri: {ri.GetType().Name}::{ri}");
        Console.WriteLine(new string('-', 20));
        Console.WriteLine();
    }
    catch (Exception ex)
    {
        Console.WriteLine("ri = d.Add(1, 2) failed");
        Console.WriteLine(ex.ExceptionMessages());
        Console.WriteLine(new string('=', 60));
        Console.WriteLine();
    }

    // Works...
    try
    {
        var ri = d.Add(1M, 2M);
        Console.WriteLine($"Main: ri: {ri.GetType().Name}::{ri}");
        Console.WriteLine(new string('-', 20));
        Console.WriteLine();
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ExceptionMessages());
        Console.WriteLine(new string('=', 60));
        Console.WriteLine();
    }
}

// Define other methods and classes here

/////   Common interface
public interface IComponent { }

/////   OriginalComponent
public class OriginalComponent : IComponent
{
    [LogEnable(true)]
    public decimal Add(decimal a, decimal b) => a + b;
}

/////   DecoratorLogging<TComponent, TAttr>
public class DecoratorLogging<TComponent, TAttr> : DynamicObject, IComponent
    where TComponent : class
    where TAttr : Attribute, IAttributeEnabled
{
    private TComponent _orig;

    public DecoratorLogging(TComponent orig = null) { _orig = orig; }
    public TComponent Orig => _orig;

    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
        var methNm = binder.Name;
        var parmType = args.Select(a => a.GetType()).ToArray();
        MethodInfo methodInfo = null;
        dynamic tyObj = this;
        for (int i = 0; methodInfo == null; ++i)
        {
            try { tyObj = tyObj.Orig; }
            catch (RuntimeBinderException) { throw new Exception($"Method {methNm} was not found."); }
            var ty = tyObj.GetType();
            methodInfo = ty.GetMethod(methNm, parmType);
        }

        result = null;
        Stopwatch sw = null;

        try
        {
            BeforeLoggingConcern(binder, args, methodInfo, out sw);

            if (tyObj.GetType() == _orig.GetType())
                result = methodInfo.Invoke(_orig, args);
            else
                ((dynamic)_orig).TryInvokeMember(binder, args, out result);

            AfterLoggingConcern(binder, result, methodInfo, sw);
        }
        catch (Exception ex)
        {
            ExceptionLoggingConcern(binder, ex, methodInfo, sw);
            throw;
        }

        return true;
    }

    private void BeforeLoggingConcern(InvokeMemberBinder binder, object[] args, MethodInfo mi, out Stopwatch sw)
    {
        sw = null;
        var isEnabled = IsLogEnabled(mi);
        if (!isEnabled) return;

        Console.WriteLine($"Logging Before:  {binder.Name} method was called");
        var sArgs = string.Join(", ", args.Select(a => $"({a.GetType().Name}, {a})").ToArray());
        Console.WriteLine("Logging Aguments: {0}", sArgs);
        Console.WriteLine();
        sw = new Stopwatch();
        sw.Start();
    }

    private void ExceptionLoggingConcern(InvokeMemberBinder binder, Exception ex, MethodInfo mi, Stopwatch sw)
    {
        var isEnabled = IsLogEnabled(mi);
        if (!isEnabled) return;

        if (sw != null)
        {
            sw.Stop();
            Console.WriteLine($"Logging Exception:  {binder.Name} threw an exception after {sw.ElapsedMilliseconds} milliseconds");
        }
        else
            Console.WriteLine($"Logging Exception:  {binder.Name} threw an exception");

        Console.WriteLine($"Logging Internal message:{Environment.NewLine}\t{ex.ExceptionMessages()}");
        Console.WriteLine(new string('*', 10));
        Console.WriteLine();
    }

    private void AfterLoggingConcern(InvokeMemberBinder binder, object result, MethodInfo mi, Stopwatch sw)
    {
        var isEnabled = IsLogEnabled(mi);
        if (!isEnabled) return;

        if (sw != null)
        {
            sw.Stop();
            Console.WriteLine($"Logging After:  {binder.Name} ended after {sw.ElapsedMilliseconds} milliseconds");
        }
        else
            Console.WriteLine($"Logging After:  {binder.Name} ended");

        Console.WriteLine($"Logging resulting in: type: {result.GetType().Name}, value: {result}");
        Console.WriteLine(new string('.', 10));
        Console.WriteLine();
    }

    private bool IsLogEnabled(MethodInfo method)
    {
        var logAttrs = method.GetCustomAttributes<TAttr>(true);
        if (logAttrs == null) return false;
        return logAttrs.Any(a => a.IsEnabled);
    }
}

/////   IAttributeEnabled
public interface IAttributeEnabled
{
    bool IsEnabled { get; }
}

/////   LogEnableAttribute
[AttributeUsage(AttributeTargets.Method, Inherited = true)]
public class LogEnableAttribute : Attribute, IAttributeEnabled
{
    private bool _logEnabled;
    public LogEnableAttribute(bool logEnabled = true) { _logEnabled = logEnabled; }
    public bool IsEnabled => _logEnabled;
}

/////   Ext (Used to extract all exception messages)
public static class Ext
{
    public static string ExceptionMessages(this Exception ex)
    {
        if (ex.InnerException == null) return ex.Message;
        return string.Join($"{Environment.NewLine}\t", ex.InnerExceptions().Select((a, i) => $"{i}. {a.Message}"));
    }

    public static IEnumerable<Exception> InnerExceptions(this Exception ex)
    {
        for (Exception ix = ex; ix != null; ix = ix.InnerException)
            yield return ix;
    }
}
AviFarah
  • 327
  • 1
  • 10
  • Did you try **ILweaving**? http://stackoverflow.com/questions/189359/what-is-il-weaving – Pankaj Rawat May 13 '17 at 07:24
  • Thank you Pankaj. No. I did not, the bank that I work at does not have IL weaving as part of its tools and as such I will need to petition for it. Since the software works, I doubt that my petition will work. – AviFarah May 13 '17 at 14:58

1 Answers1

0

The problem with my code above is attempting to find the method using types that do not exist in the OriginalComponent class. The line:

methodInfo = ty.GetMethod(methNm, parmType);

is the culprit.

What I needed to do is use the Type1.IsAssignableFrom(Type2) to resolve which method to call.

AviFarah
  • 327
  • 1
  • 10