After updating NServiceBus to the latest version released last week (which also has a new implementation), I'm seeing weird errors when sending json from the client.
I will send a message from the client and the receiver displays this message:
2016-10-18 22:16:33.612 INFO MFG.Receiver.DeviceHandler Got message with id: a222b136-6a4e-474e-8012-cc1c24e5e539
I have a breakpoint in my handler, below, and it shows the message object is baked and there should not be any issues.
public class DeviceHandler : IHandleMessages<DeviceRequest>
{
private readonly IDeviceProvider _provider = new DeviceProvider();
private static readonly ILog Log = LogManager.GetLogger<DeviceHandler>();
public Task Handle(DeviceRequest message, IMessageHandlerContext context)
{
Log.Info($"Got message with id: {context.MessageId}");
...
return context.SendLocal($"Message with Id {context.MessageId} received.");
}
}
When it hits the reply method at the end, it throws the below errors:
2016-10-18 22:16:33.666 INFO NServiceBus.RecoverabilityExecutor Immediate Retry is going to retry message 'a222b136-6a4e-474e-8012-cc1c24e5e539' because of an exception:
System.Exception: Could not find metadata for 'System.String'.
Ensure the following:
1. 'System.String' is included in initial scanning.
2. 'System.String' implements either 'IMessage', 'IEvent' or 'ICommand' or alternatively, if you don't want to implement an interface, you can use 'Unobtrusive Mode'.
at NServiceBus.Unicast.Messages.MessageMetadataRegistry.GetMessageMetadata(Type messageType) in C:\Build\src\NServiceBus.Core\Unicast\Messages\MessageMetadataRegistry.cs:line 39
I'm not sure why it would throw the System.String error, after it already received the message from the handler and the properties are populated...
The json sent looks like this:
{
"$type": "DeviceRequest, MFG.Domain",
"Id": "devices-65",
"DeviceId": 1,
"Location": "Orlando",
"DeviceType": "test"
}
My Sender (client) looks like this:
static void Main()
{
...
using (var channel = connection.CreateModel())
{
var messageId = Guid.NewGuid().ToString();
var properties = channel.CreateBasicProperties();
properties.MessageId = messageId;
var payload = GenerateJsonPayload();
channel.BasicPublish(string.Empty, ServerEndpointName, false, properties, Encoding.UTF8.GetBytes(payload));
Console.WriteLine($"Message with id {messageId} sent to queue.");
}
...
}
public static string GenerateJsonPayload()
{
var obj = new DeviceRequest
{
DeviceId = 1,
DeviceType = "test",
Location = "Orlando"
};
var settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
};
var result = JsonConvert.SerializeObject(obj, Formatting.Indented, settings);
return result;
}
I've had the "could not find metadata" issue before, and it was due to malformed json or not having type. If I remove the JsonSerializerSettings, and just passed a serialized object, I instead get errors:
2016-10-18 22:31:27.698 ERROR NServiceBus.RecoverabilityExecutor Moving message '6405179d-ea36-4264-af2a-704da19af120' to the error queue 'error' because processing failed due to an exception:
NServiceBus.MessageDeserializationException: An error occurred while attempting to extract logical messages from transport message 6405179d-ea36-4264-af2a-704da19af120 ---> System.Exception: Could not find metadata for 'Newtonsoft.Json.Linq.JObject'.
I have no idea what I'm missing here and this wasn't an issue with the previous version. Is this a bug or ... ?
Use a concrete message type for your SendLocal operation.
As part of handling the message you are doing return context.SendLocal($"Message with Id {context.MessageId} received.");. This will try to send a message of type "string" to the local queue. NServiceBus is telling you that no message metadata was registered for the message type of "string". Therefor it can not construct the message and throws an exception.
Related
When I add a new user to my IdentiyServerService the following MassTransit code is called:
var newUserCreated = new UserCreated
{
UserId = userId.ToString(),
Name = user.Name
};
await _bus.Publish(newUserCreated);
My destination is that my ProfileService receive this event by RabbitMq.
My RabbitMq configuration in my Startup.cs (IdentiyServerService)
private static void ConfigureBus(ContainerBuilder builder)
{
builder.Register(context =>
{
return Bus.Factory.CreateUsingRabbitMq(config =>
{
var host = config.Host(new Uri("rabbitmq://localhost"), h =>
{
h.Username("guest");
h.Password("guest");
});
});
}).As<IBus, IBusControl, IPublishEndpoint>().SingleInstance();
}
I start the bus like this
//Startup.cs IdentityServerService
var container = containerBuilder.Build();
var busControl = container.Resolve<IBusControl>();
busControl.Start();
The configuration in my ProfileService look quite almost the same. The difference is, that I add an consumer in my Startup.cs (ProfileService)
config.ReceiveEndpoint(host, "user_queue", ep =>
{
ep.Consumer<UserCreatedConsumer>(); // The consumer is registered explicitly this time.
});
I add an IConsumer as well
public class UserCreatedConsumer : IConsumer<UserCreated>
{
public Task Consume(ConsumeContext<UserCreated> context)
{
var user = context.Message;
Debug.WriteLine("My debug string here");
return TaskUtil.Completed;
}
}
When I create a new user the messages receive the RabbitMq (publish rates increase). But then nothing goes on. The total number of messages in the Queued messages does not change.
I have two connections (I would expect IdentityServerService and ProfileService) and I have different queues (I was only expecting one: user_queue)
When I implement the IConsumer inside my IdentityServerService I receive the message.
I have no error log, warnings or something else.
Anyway... 1) Why does ProfileService not receive the message? 2) And why do I have so much queues?
If you need some more information... please tell
Edit
When I send a message in the rabbitMq-management my ProfileService receive a message, but now I get the following error
MassTransit.Messages Error: 0 : R-FAULT
rabbitmq://localhost/user_queue_new Value cannot be null.
Parameter name: source,
System.Runtime.Serialization.SerializationException: An exception occurred while deserializing the message envelope ---> System.ArgumentNullException: Value cannot be null.
Parameter name: source
I have the following code in C#, which does not throw error if the routing key is invalid.
var connFactory = GetConnectionFactory();
using (var conn = connFactory.CreateConnection())
{
using (var channel = conn.CreateModel())
{
channel.TxSelect();
var publicationAddress = new PublicationAddress(ExchangeType.Direct, Settings.ServiceBusExchange, Settings.ServiceBusRoutingKey);
var headers = new Dictionary<String, Object>();
headers.Add("TransactionID", transactionID);
var basicProperties = new BasicProperties();
basicProperties.ContentEncoding = Encoding.UTF8.ToString();
basicProperties.ContentType = "text/xml";
basicProperties.Headers = headers;
basicProperties.DeliveryMode = 2;
var payLoad = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(publicationAddress, basicProperties, payLoad);
channel.TxCommit();
}
}
My question is, how can I make the code throw error if the routing key is invalid? Like when I Publish a message using RabbitMQ UI with invalid routing key, it gives a message "Message published, but not routed."
Thanks in advance.
it does not exist the concept of "invalid routing key", since you can bind dynamically queues to the exchanges.
Btw what you are looking for is "unroutable messages", you have to use the mandatory flag and implement the ReturnListener in the same channel, if a message does not reach any queue will be redirect to the handler.
In this in this way (the code is Java, but in c# is more or less the same):
boolean isMandatory = true; // if true the message will be handled by HandlingReturnListener
// if false the message will be dropped!
channel.addReturnListener(new ReturnListener() {
public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(replyText + ":" + replyCode);
System.out.println("******** UnHandled Message ***************");
}
});
String myExchange = "myUnroutableExchange_";
channel.exchangeDeclare(myExchange, "topic", false, false, null);
channel.basicPublish(myExchange, "NO_KEY", isMandatory, null, "".getBytes());
For this there is something called PublisherAcknoledgement. This will basically gives an Ack to the publisher about the status of the message. You will be able to also differentiate between whether the message has reached till Exchange or it has reached till the consumer. You just have to handle each case properly.
This is a good way to know the status of the message being delivered. You might not know if its happening because of the wrong routing key but with doing various checks you might be able to narrow down to the result.
I have been venturing in the ServiceStack's documentation regarding an issue with throwing Exceptions from an Action that returns a Stream.
The issue is that while all the other Actions in my service return beautiful errors like:
{
"ResponseStatus": {
"ErrorCode": "ArgumentException",
"Message": "Unable to load data",
"StackTrace": "[GetData: 7/11/2016 1:02:11 PM]:\n[REQUEST: {Token:asdf,Id:1}]\nServiceStack.HttpError: Unable to load codes from token ---> System.ArgumentException: Unable to load codes from token.............(abridged)
}
}
There is an Action with the return type as Stream from which, regardless of the type of exception returned, the http client receives the following response:
With the handler (as per the SS documentation):
Error: ArgumentNullException: As result 'ErrorResponse' is not a supported responseType, a defaultAction must be supplied
Parameter name: defaultAction
And without any handlers:
'no content'
400
Any help will be greatly appreciated.
Sample Code-->
Here is an example of the Action:
[AddHeader(ContentType = "application/pdf")]
public Stream Get(GetPdfRequest request)
{
throw new NotImplementedException("FAKE EXCEPTION");
}
and in the APPHOST's Configure() method:
this.UncaughtExceptionHandlers.Add((req, res, operationName, ex) =>
{
var logger = LogManager.GetLogger(GetType());
logger.Error("Unhandled error in API during request binding.", ex);
res.Write("Error: {0}: {1}".Fmt(ex.GetType().Name, ex.Message));
res.EndRequest(skipHeaders: true);
});
this.ServiceExceptionHandlers.Add((httpReq, request, exception) =>
{
var logger = LogManager.GetLogger(GetType());
logger.Error("Unhandled error in API.", exception);
//call default SS exception handler
return DtoUtils.CreateErrorResponse(request, exception);
});
Here is a screenshot of what I see on the Swagger Rest client when the above Action is called.
The issue is due to being unable to serialize the ErrorResponse DTO into the unregistered "application/pdf" ContentType.
I've just added a fallback to use the Config.DefaultContentType for serializing errors in unregistered Content Types in this commit, available from v4.0.61 that's now available on MyGet.
A workaround for prior versions of ServiceStack is instead of using the [AddHeader] Request Filter Attribute, to instead set the Content-Type in the Service implementation just before you serialize, so any Exceptions are thrown before Response ContentType is set, e.g:
public class ErrorStream {}
public class ErrorStreamService : Service
{
public Stream Any(ErrorStream request)
{
if (!IsValid(request))
throw new NotImplementedException("Exception in Stream Response");
base.Request.ResponseContentType = "application/pdf";
return PdfAsStream(request);
}
}
Which throws a Typed Exception when using a Service Client:
try
{
var response = client.Get<Stream>(new ErrorStream());
Assert.Fail();
}
catch (WebServiceException ex)
{
Assert.That(ex.IsAny400());
Assert.That(!ex.IsAny500());
Assert.That(ex.ErrorCode, Is.EqualTo("NotImplementedException"));
Assert.That(ex.StatusCode, Is.EqualTo((int)HttpStatusCode.MethodNotAllowed));
}
Also UncaughtExceptionHandlers is only for handling Exceptions thrown outside of a Service, exceptions that occur within a Service are instead handled by ServiceExceptionHandlers instead, but be careful when modifying the default Exception handling behavior as you can invalidate the typed Exception handling on the client.
I'm trying to use JsonSerializerSettings for custom error handling, but when I specify the object type the error stops in runtime debugging. The "json" is no valid JSON, due to remote error that is out of my hands to change/fix.
Working:
var responseData = JsonConvert.DeserializeObject(json,new JsonSerializerSettings
{
MissingMemberHandling = MissingMemberHandling.Ignore,
Error = (sender, args) =>
{
// My error handling
}
});
Breaks With:
Additional information: Error converting value "Received merchantid does not match a registered merchant" to type 'TransmitModels+errorData'. Path ...
TransmitModels.errorData responseData = JsonConvert.DeserializeObject<TransmitModels.errorData>(json,new JsonSerializerSettings
{
MissingMemberHandling = MissingMemberHandling.Ignore,
Error = (sender, args) =>
{
// My error handling
}
});
You need to call
args.ErrorContext.Handled = true;
in your callback to tell Json.NET that you handled the exception. If you don't (maybe because you just want to log the error), the exception is thrown after your callback.
Here's my scenario:
I'm sending an Azure ServiceBus Queue message from Node.js using the node azure sdk like so:
var message = {
body: JSON.stringify({ foo: 'Bar' })
};
serviceBusService.sendQueueMessage('myQueue', message, function (error) {
if (!error) {
console.log('msessage sent');
}
});
I have a c# worker role that is listening to the Queue:
QueueClient Client = QueueClient.CreateFromConnectionString(connStr, QueueName);
Client.OnMessage((receivedMessage) =>
{
var body = receivedMessage.GetBody<string>();
});
When the GetBody method gets executed, i get the following error:
There was an error deserializing the object of type System.String. The input source is not correctly formatted
After some digging around, i found THIS article that helped me get a solution:
Client.OnMessage((receivedMessage) =>
{
var bodyJson = new StreamReader(receivedMessage.GetBody<Stream>(), Encoding.UTF8).ReadToEnd();
var myMessage = JsonConvert.DeserializeObject<MyMessage>(bodyJson);
});
If anyone has faced this issue and found a better solution, please let me know!
Thanks!
To anyone who found this question if they were getting this error from sending the message using Service Bus Explorer (like me).
Make sure you specify the correct message type in the drop down:
Thanks for the update, I was doing the reverse and this helped me. I thought I'd add to your solution for completeness. The DeserializeObject method needs the "MyMessage" class defining. In your original post, your JSON is:
{ foo: 'Bar' }
If we drop that into json2csharp (json2csharp.com) we now have the class required to complete your solution:
public class MyMessage
{
public string foo { get; set; }
}
Of course, the dependency is having Newtonsoft.Json package added to your Visual Studio solution:
Install-Package Newtonsoft.Json -Pre
Using the nuget package: Microsoft.Azure.ServiceBus
The following info is contained inside as as comment:
If a message is only being sent and received using this Microsoft.Azure.ServiceBus
client library, then the below extension methods are not relevant and should not be used.
If this client library will be used to receive messages that were sent using both WindowsAzure.Messaging client library and this (Microsoft.Azure.ServiceBus) library, then the Users need to add a User property Microsoft.Azure.ServiceBus.Message.UserProperties while sending the message. On receiving the message, this property can be examined to determine if the message was from WindowsAzure.Messaging client library and if so use the message.GetBody() extension method to get the actual body associated with the message.
---------------------------------------------- Scenarios to
use the GetBody Extension method: ----------------------------------------------
If message was constructed using the WindowsAzure.Messaging client library as
follows:
var message1 = new BrokeredMessage("contoso"); // Sending a plain string var
message2 = new BrokeredMessage(sampleObject); // Sending an actual customer object
var message3 = new BrokeredMessage(Encoding.UTF8.GetBytes("contoso")); // Sending
a UTF8 encoded byte array object await messageSender.SendAsync(message1); await
messageSender.SendAsync(message2); await messageSender.SendAsync(message3);
Then retrieve the original objects using this client library as follows: (By
default Microsoft.Azure.ServiceBus.InteropExtensions.DataContractBinarySerializer
will be used to deserialize and retrieve the body. If a serializer other than
that was used, pass in the serializer explicitly.)
var message1 = await messageReceiver.ReceiveAsync(); var returnedData1 = message1.GetBody();
var message2 = await messageReceiver.ReceiveAsync(); var returnedData2 = message1.GetBody();
var message3 = await messageReceiver.ReceiveAsync(); var returnedData3Bytes =
message1.GetBody(); Console.WriteLine($"Message3 String: {Encoding.UTF8.GetString(returnedData3Bytes)}");
------------------------------------------------- Scenarios to NOT use the GetBody
Extension method: ------------------------------------------------- If message
was sent using the WindowsAzure.Messaging client library as follows: var message4
= new BrokeredMessage(new MemoryStream(Encoding.UTF8.GetBytes("contoso"))); await
messageSender.SendAsync(message4); Then retrieve the original objects using this
client library as follows: var message4 = await messageReceiver.ReceiveAsync();
string returned = Encoding.UTF8.GetString(message4.Body); // Since message was
sent as Stream, no deserialization required here.
May it help you
With the latest Service Bus client libraries (.NET, JS, Java, Python), you can send message(s) using the JS library like this:
const serviceBusClient = new ServiceBusClient("<connectionstring>");
const sender = serviceBusClient.createSender("<queuename>");
await sender.sendMessages([{
body: {
title: "hello"
}
}]);
Note that .sendMessages takes a list as an input, even if you're just sending one message.
And get the body of the received message using .NET library like this:
await using var client = new ServiceBusClient("<connectionstring>");
ServiceBusReceiver receiver = client.CreateReceiver("<queuename>");
ServiceBusReceivedMessage receivedMessage = await receiver.ReceiveMessageAsync();
string body = receivedMessage.Body.ToString();
Console.WriteLine(body); //prints {"title":"hello"}