Get message type from PipeContext from MassTransit - rabbitmq

I am implementing a MassTransit middleware in my receive end point to record the performance of handling the message, i want to get the message type from the PipeContext, how can i get it?
public async Task Send(T context, IPipe<T> next)
{
// I want to know the message type from here so that i can log it
using (_logger.BeginTimedOperation("Time for handling message", null, LogEventLevel.Debug))
{
await next.Send(context);
}
}

You would need to intercept at the ConsumeContext, which has a property for the message types from the serialization envelope.
Then, you can get the supported message types using:
IEnumerable<string> SupportedMessageTypes {get;}
That should get you what you need to log the message type with the duration.
So a filter along the lines of:
public class LogMessageTypeFilter :
IFilter<ConsumeContext>
{
}
Implement the send method, call next within the method, and then take action after the next pipe completes.

Related

Generic Mediatr Handler not found

I'm trying to register a generic request handler for a generic request, but I'm getting the error below when calling
await _mediator.Send(new ExchangeOrderRequest<ExchangeOrderResponseSuccessEvent>());
System.InvalidOperationException: Handler was not found for request of
type
MediatR.IRequestHandler`2[MyNamespace.ExchangeOrderRequest`1[MyNamespace.ExchangeOrderResponseSuccessEvent],MediatR.Unit]`.
Register your handlers with the container. See the samples in GitHub
for examples.
As side note, for giggles, I was able to inject an instance of IRequestHandler<ExchangeOrderRequest<ExchangeOrderResponseSuccessEvent>> into a controller just fine.
// Some assembly scanning, doesn't find my handler
services.AddMediatR(typeof(Startup), typeof(ExchangeOrderRequest), typeof(IDocumentMessage), typeof(OrderExecution.Handler));
// Try to manually add the handler. Still doesn't find it
services.AddTransient<IRequestHandler<ExchangeOrderRequest<ExchangeOrderResponseSuccessEvent>>, ExchargeOrderRequestHandler<ExchangeOrderResponseSuccessEvent, ExchangeOrderRequest<ExchangeOrderResponseSuccessEvent>>>();
public class ExchargeOrderRequestHandler<TEvent, TRequest> : IRequestHandler<TRequest>
where TRequest : ExchangeOrderRequest<TEvent>
where TEvent : ExchangeOrderEvent
{
// Stuff
}`
public class ExchangeOrderRequest<T>: DocumentMessage, IRequest where T: ExchangeOrderEvent
{
// Stuff
}
public class ExchangeOrderEvent : IEvent
{
// Stuff
}
What am I missing here?
I'm having the same issue...
However, I am able to register the handler like this..
services.AddTransient(typeof(IRequestHandler<,>), typeof(CustomHandler<,>));
This works for only one generic handler... I have many... I need to be able to do something like this:
services.AddTransient(typeof(IRequestHandler<,>), typeof(CustomHandler1<,>));
services.AddTransient(typeof(IRequestHandler<,>), typeof(CustomHandler2<,>));
//etc...
When I try and do this... no matter what command I send to MediatR it resolves the last handler I registered.. So naturally the type constraints are violoated and a runtime exception occurs.

How to properly return status/error codes from an Azure Function that was triggered by an Event Grid event

I'm trying to wrap my head around how to manage controlled and uncontrolled results/failures in Azure Functions triggered by Event Grid events. I'm sure I'm missing some fundamental aspect but I can't really make sense of the available Microsoft documentation.
Using the current Visual Studio 2019 Azure Function templates for Event Grid triggers, we get C# methods that looks similar to this:
[FunctionName("UserCreated")]
public static void UserCreated([EventGridTrigger]EventGridEvent evt, ILogger log)
{
...
}
Q1. What is the proper way of returning statuses and error codes from these methods so that the event can be either retried or passed to the dead letter blob? Lets say I want to return a Status 400 Bad Request because the custom data payload in the event that was passed in wasn't up to speed. I've tried this:
[FunctionName("UserCreated")]
public static async Task<IActionResult> UserCreated([EventGridTrigger]EventGridEvent evt, ILogger log)
{
...
return new BadRequestObjectResult("ouch");
}
... which just results in this error when running the function locally:
Cannot bind parameter '$return' to type IActionResult&. Make sure the parameter Type is supported by the binding.
I don't understand this error and have no idea how to solve it.
Q2. How do we properly catch exceptions and return them in an orderly fashion? Let's say the UserCreated method above requires the passed in event to have a few custom datapoints, and that one of those datapoints are missing. E.g. we have this:
[JsonObject(ItemRequired = Required.Always)]
private class CustomerAndContactIds
{
public int CustomerId { get; set; }
public int ContactId { get; set; }
}
... and when we convert some event data that is missing e.g. the ContactID field, like so:
private static T ExtractCustomPayloadFromEvent<T>(EventGridEvent evt)
{
return JObject.FromObject(evt.Data).ToObject<T>();
}
... we get this in the logs:
System.Private.CoreLib: Exception while executing function: UserCreated. Newtonsoft.Json: Required property 'ContactPersonId' not found in JSON. Path ''.
However, all we get in e.g. Postman is:
Exception while executing function: UserCreated
What's the proper way of making this a bit more legible for consumers that aren't privy to e.g. the Azure log stream or Azure Insights?
To Q1:
Currently version of the EventGridTrigger function doesn't have a specific exception object to handle for its wrapper back a HttpStatusCode value. In other words, any exception in the azure function will force a retry pattern when it is enabled.
As a workaround for this issue, we can use a HttpTrigger function for subscriber handler with the HttpStatusCode return value.

What happens to messages sent/published in a Handler that fails with an exception?

I've read that each message handler is wrapped in an "ambient transaction", and that database access is automatically enlisted in that transaction when possible. Does NServiceBus do anything else with that transaction? Specifically, I'm wondering if it can somehow cancel any messages that a handler sends/publishes in the case of an exception.
In the code below, does the bus Send the ArchiveMessage as soon as the Send method is called, or does it queue it up and only send it if the handler executes successfully?
public class BadHandler
{
public IBus Bus { get; set; }
public void Handle(MyMessage msg)
{
Bus.Send(new ArchiveMessage(msg.MessageId)); //does this message send?
throw new Exception("Something terrible happened, maybe my database connection failed!");
}
}
I this case the message would not be sent. MyMessage will be retried the configured number of times and them moved to the designated error queue. You can have greater control over that process if you wish, you would need to create a custom FaultManager.

Is there an easy way to subscribe to the default error queue in EasyNetQ?

In my test application I can see messages that were processed with an exception being automatically inserted into the default EasyNetQ_Default_Error_Queue, which is great. I can then successfully dump or requeue these messages using the Hosepipe, which also works fine, but requires dropping down to the command line and calling against both Hosepipe and the RabbitMQ API to purge the queue of retried messages.
So I'm thinking the easiest approach for my application is to simply subscribe to the error queue, so I can re-process them using the same infrastructure. But in EastNetQ, the error queue seems to be special. We need to subscribe using a proper type and routing ID, so I'm not sure what these values should be for the error queue:
bus.Subscribe<WhatShouldThisBe>("and-this", ReprocessErrorMessage);
Can I use the simple API to subscribe to the error queue, or do I need to dig into the advanced API?
If the type of my original message was TestMessage, then I'd like to be able to do something like this:
bus.Subscribe<ErrorMessage<TestMessage>>("???", ReprocessErrorMessage);
where ErrorMessage is a class provided by EasyNetQ to wrap all errors. Is this possible?
You can't use the simple API to subscribe to the error queue because it doesn't follow EasyNetQ queue type naming conventions - maybe that's something that should be fixed ;)
But the Advanced API works fine. You won't get the original message back, but it's easy to get the JSON representation which you could de-serialize yourself quite easily (using Newtonsoft.JSON). Here's an example of what your subscription code should look like:
[Test]
[Explicit("Requires a RabbitMQ server on localhost")]
public void Should_be_able_to_subscribe_to_error_messages()
{
var errorQueueName = new Conventions().ErrorQueueNamingConvention();
var queue = Queue.DeclareDurable(errorQueueName);
var autoResetEvent = new AutoResetEvent(false);
bus.Advanced.Subscribe<SystemMessages.Error>(queue, (message, info) =>
{
var error = message.Body;
Console.Out.WriteLine("error.DateTime = {0}", error.DateTime);
Console.Out.WriteLine("error.Exception = {0}", error.Exception);
Console.Out.WriteLine("error.Message = {0}", error.Message);
Console.Out.WriteLine("error.RoutingKey = {0}", error.RoutingKey);
autoResetEvent.Set();
return Task.Factory.StartNew(() => { });
});
autoResetEvent.WaitOne(1000);
}
I had to fix a small bug in the error message writing code in EasyNetQ before this worked, so please get a version >= 0.9.2.73 before trying it out. You can see the code example here
Code that works:
(I took a guess)
The screwyness with the 'foo' is because if I just pass that function HandleErrorMessage2 into the Consume call, it can't figure out that it returns a void and not a Task, so can't figure out which overload to use. (VS 2012)
Assigning to a var makes it happy.
You will want to catch the return value of the call to be able to unsubscribe by disposing the object.
Also note that Someone used a System Object name (Queue) instead of making it a EasyNetQueue or something, so you have to add the using clarification for the compiler, or fully specify it.
using Queue = EasyNetQ.Topology.Queue;
private const string QueueName = "EasyNetQ_Default_Error_Queue";
public static void Should_be_able_to_subscribe_to_error_messages(IBus bus)
{
Action <IMessage<Error>, MessageReceivedInfo> foo = HandleErrorMessage2;
IQueue queue = new Queue(QueueName,false);
bus.Advanced.Consume<Error>(queue, foo);
}
private static void HandleErrorMessage2(IMessage<Error> msg, MessageReceivedInfo info)
{
}

In NServiceBus, how can I handle when a message comes in without a matching saga?

If I have a saga that consists of two message types, say started by message1 and completed by message2, can I return a callback if a message2 comes in without a message1 already existing? I know it will dump it in the error queue, but I want to be able to return a status to the sending client to say there is an error state due to the first message not being there.
So I figured it out, I just needed to implement IFindSagas for the message type:
public class MySagaFinder : IFindSagas<MySagaData>.Using<Message2>
{
public ISagaPersister Persister { get; set; }
public IBus Bus { get; set; }
public MySagaFinder FindBy(Message2 message)
{
var data = Persister.Get<MySagaData>("MessageIdProperty", message.MessageIdProperty);
if (data == null)
{
Bus.Return(0);
}
return data;
}
}
I don't know if this is the right way to do it, but it works!
If you have a saga that can receive two messages, but messages can be received in any order, make sure the saga can be started by both messages. Then verify if both message have arrived by setting some state in the saga itself. If both messages have arrived, mark it as complete.
Default NServicebBus behavior is to ignore any message that has no corresponding saga. This is because you can set a timeout, for example. If nothing happens within 24 hours, the saga can send a Timeout message to itself. But if something did happen and you marked your saga as being completed, what should happen to the Timeout message? Therefor NServiceBus ignores it.