0

I am using proxies and intercepts for logging purposes. One of the attributes I want to log is message ID from rabbit MQ.

We are using the following object:

namespace MassTransit
{
    public interface ConsumeContext<out T> : ConsumeContext, MessageContext, PipeContext, IPublishEndpoint, IPublishObserverConnector, ISendEndpointProvider where T : class
    {
        T Message { get; }

        /// <summary>Notify that the message has been consumed</summary>
        /// <param name="duration"></param>
        /// <param name="consumerType">The consumer type</param>
        Task NotifyConsumed(TimeSpan duration, string consumerType);

        /// <summary>
        /// Notify that a fault occurred during message consumption
        /// </summary>
        /// <param name="duration"></param>
        /// <param name="consumerType"></param>
        /// <param name="exception"></param>
        Task NotifyFaulted(TimeSpan duration, string consumerType, Exception exception);
    }
}

It is the generic Message that I need to get hold of within the intercept.

I can successfully cast it to an object say:

ConsumeContext<AuthenticationDataRequest>

And within visual studio once I've cast it the Message object pops up (without casting there is no MessageObject).

To cast I am using the following generic method:

public Guid? RunMessageRetrieve(dynamic obj, Type castTo)
{
    MethodInfo castMethod = GetType().GetMethod("GetMessageIdFromContext").MakeGenericMethod(castTo);
    return castMethod.Invoke(null, new object[] { obj }) as Guid?;
}

public static Guid? GetMessageIdFromContext<T>(dynamic context) where T : class
{
    Guid? messageId = null; 

    try
    {
        var contextCasted = (T)context;
        Type contextType = contextCasted.GetType();
        var message = contextCasted.GetType().GetProperty("Message");
        if (message != null)
        {
            messageId = message.GetType().GetProperty("MessageId").GetValue(message) as Guid?;
        }
    }
    catch (InvalidCastException castException)
    {
        Console.WriteLine("Could not retrieve message Id from context message as the cast failed");
    }
    catch (NullException nullException)
    {
        Console.WriteLine("Could not retrieve message Id from context as the message Id did not exist");
    }

    return messageId;
}

Here you can see in visual studio the message, and within that I can get the message ID:

enter image description here

However I have tried to get the actual message property out using reflection because of course I don't know the type at compile time and I just can't seem to work it out. The following is null because it's of course a generic type:

var message = contextCasted.GetType().GetProperty("Message");

This has to be doable because when the actual method is invoked after the intercepts it has the proper object with the message.

FaizanHussainRabbani
  • 3,256
  • 3
  • 26
  • 46
Simon Nicholls
  • 635
  • 1
  • 9
  • 31

2 Answers2

1

I went with the following in the end, works a treat, I didn't think of using the Type directly like that immediately. It was the dynamic parameter that was messing things up:

public static Guid? GetMessageIdFromContext(object context, Type contextType) 
    {
        Guid? messageId = null;

        try
        {
            var contextProp = contextType.GetProperty("Message");
            if (contextProp != null)
            {
                var message = contextProp.GetValue(context);
                if (message != null)
                {
                    messageId = message.GetType().GetProperty("UniqueId").GetValue(message) as Guid?;
                }

            }
        }
        catch (NullException nullException)
        {
            Console.WriteLine("Could not retrieve message Id from context as the message Id did not exist");
        }

        return messageId;
    }
Simon Nicholls
  • 635
  • 1
  • 9
  • 31
0

Just cast to the interface that has the property that you want, and use it. Reflection is not needed.

public static Guid? GetMessageId<T>(ConsumeContext<T> input) where T : class
{
    var casted = input as ConsumeContext<IMessage>;
    if (casted == null) return null;
    return casted.Message.MessageId;
}

Example program:

public class Program
{
    public static Guid? GetMessageId<T>(ConsumeContext<T> input) where T : class
    {
        var casted = input as ConsumeContext<IMessage>;
        if (casted == null) return null;
        return casted.Message.MessageId;
    }
    public static void Main()
    {
        var exampleObject = new MyClass<Message>();
        exampleObject.Message = new Message { MessageId = new Guid("00000001-0002-0003-0004-000000000005") };

        var messageId = GetMessageId(exampleObject);
        Console.WriteLine(messageId.Value);
    }
}

Output:

00000001-0002-0003-0004-000000000005

It also works if you have to downcast, i.e. even if T is a specific type of message, you can still cast it to the interface or base class that exposes the message.

    public static void Main()
    {
        var exampleObject = new MyClass<SpecificMessage>();
        exampleObject.Message = new SpecificMessage { MessageId = new Guid("00000001-0002-0003-0004-000000000005") };

        var messageId = GetMessageId(exampleObject);
        Console.WriteLine(messageId.Value);
    }

Full working example on DotNetFiddle

John Wu
  • 50,556
  • 8
  • 44
  • 80
  • Unfortunately this wouldn't work because of IMessage, the Message is defined in an external library (MassTransit) which we didn't want to override. Thank you for the suggestion however. – Simon Nicholls Feb 27 '18 at 09:54
  • You don't have to use an interface; you could also cast to the `Message` class itself, assuming all of your message types inherit from it, and it has the property that you need. – John Wu Feb 27 '18 at 09:56
  • The message is generic T Message { get; } – Simon Nicholls Feb 27 '18 at 11:10