I'm having problem with MassTransit in Request-Respond model (using MassTransit.RabbitMQ 3.0.14).
TLDR; Request-Respond does not work when response type is List/IEnumerable.
In client I'm creating instance of IRequestClient:
var RequestObjects = busControl.CreateRequestClient<MyObjectsRequest, List<MyObject>>(
new Uri(configuration["MassTransit:ServerAddress"] + "/" + configuration["MassTransit:TestQueueName"]),
TimeSpan.FromSeconds(15));
Next, I call server for a response:
RequestObjects.Request(new MyObjectsRequest{ Id = 1 });
On server side I have registered consumer:
public async Task Consume(ConsumeContext<MyObjectRequest> context)
{
var myList = new List<MyObject>
{
new MyObject { Id = 1 },
new MyObject { Id = 2 }
}
context.Respond(myList);
}
And the problem is that response go to some temporary _skipped queue (I'm tracking it via RabbitMQ's web panel) and I'm getting RequestTimeOutException.
I've tried also with IEnumerable<> - same thing.
In fact, Array<MyObject> works well - response is going back to my client.
AFAIR when I was using MassTransit 2.x subscribing on Lists worked well. Is there a possibility to do the same on MassTransit 3.x?
What's curious, the message type seems to be erased when it's getting to RabbitMQ while I'm using List/IEnumerable:
The snippet of the messageType when single object is sent:
And the snippet of the messageType when Array of the objects is sent:
Using an array is definitely preferred over a List<T> or an IEnumerable<T> - for reasons of which are different. Arrays are immutable (generally, anyway) and cannot typically be modified.
That being said, I'm pretty sure that all of the signatures you've mentioned should work if you're using JSON, BSON, or XML. The relevant line of code is at https://github.com/MassTransit/MassTransit/blob/develop/src/MassTransit/Serialization/JsonConverters/ListJsonConverter.cs#L59 -- clearly both List and IEnumerable are present.
Do you have a failing unit test? If so, can you share it?
Related
I have an ASP.NET Core application which calls a service from another library. The
service works with an external API, which requires a sessionId. We have to call a Login API method to get the sessionId. How long this sessionId lives and when it can be changed - we don't know. Rule is: sessionId can be valid for 1 request, for 10 requests, for 100 requests, can be valid 1 minute, 10 minutes, 1 day... Nobody knows it.
The service has many methods to call similar APIs:
public class BillRequest
{
private readonly HttpClient client;
public BillRequest()
{
client = new HttpClient
{
BaseAddress = new Uri("https://myapi.com/api/v2/")
};
}
public async Task<List<Dto1>> CustomerBankAccountListAsync(int start, int count)
{
List<KeyValuePair<string, string>> nvc = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("sessionId", CURRENT_SESSION_ID)
};
var customerStream = await client.PostAsync("List/CustomerBankAccount.json", new FormUrlEncodedContent(nvc));
var customerString = await customerStream.Content.ReadAsStringAsync();
//....
}
public async Task<List<Dto2>> Method2(int start, int count)
{
List<KeyValuePair<string, string>> nvc = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("sessionId", CURRENT_SESSION_ID)
};
var customerStream = await client.PostAsync("List/Method2.json", new FormUrlEncodedContent(nvc));
var customerString = await customerStream.Content.ReadAsStringAsync();
//....
}
// logic to get SessionId here
public async Task LoginAsync()
{
}
How to implement to save this sessionId inside service?
There are many options to implement:
Call Login method every time before calling a method. Easy to implement, but bad approach, because we have many unnecessary requests then and use the sessionId only once
Save the sessionId on web application level and try to catch exception, when any method gets an 'invalid sessionId' back, and then call Login method, which will return a new sessionId. In this case we have to pass sessionId to constructor of BillRequest class. It works, but I don't like to move responsibility of service to other, because it's internal responsibility of service how to work with API.
Save sessionId inside the service itself and recall Login method inside service, when old sessionId is considered invalid, rewrite it by new etc. But how to save it as "static" in memory? I don't want to save it to any external places (file system, cloud etc), but I can't save to variable of class too, because object of class can be recreated...
I'd suggest certain mental shift here towards functional programming.
Think of sessionID as of a stream of independet values rather than a single object. Then your problem can be redefined in a following (semantically equivalent) way: given a typed stream (string in your case), how to observe its flow and react on incomming changes, which your source code does not control?
Well, there is an answer, proven by an Enterpriseā¢: reactive extensions.
Techinically such a shift impliest that you're dealing with an IObservable<string> inside of your controller, which either can be injected via the standard .NET Core DI approach, or simply defined by the constructor. That's quite flexible, since rX gives your fully testable, unbelivable powerful toolset to deal with taks of this kind; rX is also compatible with native Task and hence, async/await feature. Nice fact is that it is really easy to inject required behavior from an outerworld and decorate exising observable with a more appropriate one: so, you're safe: once 3rd party's service logic changes, you can adopt your codebase almost instantly and painlessly.
What is gonna be inside that IObservable<string>? Well, I can't say, since you did not give enough information. It might be an interval asking remote server whether current sessionID is still valid and in case not - runs relogin procedure and notifies it's subscrivers about new value; it might be a timer responsible for compile-time known rule of expiration, it might be as sophisticated logic as you need: rX is flexible enough not to limit you on what can be achieved with it as long as you deal with (possible infinite) streams.
As a consequence, it means that you don't need any global value. Just subscribe to a stream of session ids and take latest - the one which is currently valid, do the job and dispose your subscription. It is not expensive and won't hit performance; neither would mess up concurency. Wrap rX into Task and await it, if you'd like to stick to a common .NET fashion.
P.S. 99% of what you would need to deliver an implementation is already there; you just need to combine it.
I have method that inserts list of objects into Mongo DB.
public class StorageService : IStorageService
{
public Task<BulkWriteResult<Option>> SaveOptions(List<Option> contracts)
{
var context = new MongoContext<Option>();
return context.SaveCollection(contracts);
}
}
var optionIds = Task
.WhenAll(storageService.SaveOptions(optionDetails.Values.ToList()))
.Result;
If list of contracts is empty, then there is no objects to insert into DB, and no tasks to complete, so Task.WhenAll keeps running indefinitely creating a deadlock.
Question
Is there a way to return empty / completed task if list is empty, or maybe there is a better solution of how to get results of insert, but at the same time, correctly handle case when there are no results?
Update #1
Approximate structure.
WebApi - MVC project
[AcceptVerbs("POST")]
public Response<int> DownloadOptions([FromBody] ContractSelector data)
{
.. some controller code
var optionIds = Task
.WhenAll(storageService.SaveOptions(optionDetails.Values.ToList()))
.Result;
// this method should gather data from multiple APIs
// so I need Task.Result of all previous operations
// I could make this method async and use await, but it's not the case here
}
Class Library project, .NET 4.6.1
public class StorageService : IStorageService
{
public Task<BulkWriteResult<Option>> SaveOptions(List<Option> contracts)
{
var context = new MongoContext<Option>();
return context.SaveCollection(contracts);
}
}
Update #2
Why there is no use for async / await. There are 2 external API calls, one is to get general info about some asset, and the second one is to get prices for this asset, I can't change this. So, if I want to get all info in one method I must request general info, then Wait for Result, and, based on general info, request relevant prices. After this, I want to save gathered info into DB and return list of saved IDs in the response to my API.
Sequence of calls
1. UI
2. WebApi MVC Controller
3. Class Library
3.1 Request asset info - wait for the result
3.2 Get asset info - request prices for selected assets - wait for the result
3.3 Get asset info and prices info - save everything to DB - return response
var contracts = Task.WhenAll(optionService.GetContracts(params)).Result;
var prices = Task.WhenAll(optionService.GetOptionDetails(contracts)).Result;
var ids = Task.WhenAll(storageService.SaveOptions(prices.Values.ToList()));
So, response depends on 3.3, 3.3 depends on 3.2, 3.2 depends on 3.1. If you know how to turn it all to a non-blocking call, I'm all ears. For now I think that 3 blocking calls in 1 HTTP request are better than 3 separate async HTTP requests.
Can't explain in details, but looks like issue was caused by incompatibility between .NET framework and Mongo DB driver. Currently I'm using .NET 4.6.1 and Mongo 2.6.1 and it works fine.
Yesterday any try to save an empty list of objects into Mongo DB caused a deadlock.
Then I tried to refactor code and install the latest version of Mongo DB, which is 2.7.0. Now any CRUD operation with Mongo DB returns this error. There is no call stack and more details about this exception.
MethodAccessException: Attempt by method 'MongoDB.Driver.ClientSession..ctor(MongoDB.Driver.IMongoClient, MongoDB.Driver.ClientSessionOptions, MongoDB.Driver.IServerSession, Boolean)' to access method 'MongoDB.Driver.Core.Clusters.ClusterClock..ctor()' failed.
Then I tried to lower version of Mongo DB to 2.6.1 and when I try to insert empty list of objects I'm getting this exception, which is correct. When I insert at least one record it works as expected.
System.AggregateException: 'One or more errors occurred.'
ArgumentException: Must contain at least 1 request.
Parameter name: requests
Testing code
var item = new Demo
{
Symbol = "SomeSymbol",
Expiration = "2019-01-01"
};
var list = new List<Demo>();
list.Add(item);
list.Add(item); // if I comment these lines, then Mongo 2.6.1 returns correct exception
var optionIds = Task.WhenAll(storageService.Save(list)).Result;
Save method is implemented as a part of Mongo repository pattern
public Task<BulkWriteResult<T>> SaveCollection(List<T> items)
{
var records = new List<ReplaceOneModel<T>>();
var processes = new List<Task<ReplaceOneResult>>();
items.ForEach(contract =>
{
var record = new ReplaceOneModel<T>(Builders<T>
.Filter
.Where(o => o.Id == contract.Id), contract)
{
IsUpsert = true
};
records.Add(record);
});
return Collection.BulkWriteAsync(records);
}
I'm working on refactoring a project that has about 5 different web services, and each web service had a ton of identical code, including adding a message inspector onto the client endpoint behaviors so we could see the request and response data.
Part of the refactoring was to come up with a cleaner model for the web services (e.g. one abstract base service model that did all the common setup, including the addition of the message inspector).
Now, when I make the service calls (invoked via reflection), the service call works perfectly fine, and if I add a breakpoint right after the response comes back, I can see that there are 3 behaviors added to the client's endpoint:
[0] Microsoft.VisualStudio.Diagnostics.ServiceModelSink.Behavior
[1] System.ServiceModel.Description.ClientCredentials
[2] MyProject.MyMessageInspector
...but the message inspector code doesn't seem to get called at all anymore. The inspector code is currently identical to the MSDN example here (except for the class name):
https://msdn.microsoft.com/en-us/library/ms733786(v=vs.110).aspx
The primary difference is that I'm now using generic methods for setting up the client, which looks like this:
...sanity checks, etc...
TClient client = Activator.CreateInstance(typeof(TClient), binding, new EndpointAddress(url)) as TClient;
ClientBase<TInterface> _clientBase = client as ClientBase<TInterface>;
...credentials, timeout, etc...
MyEndpointBehavior _inspector = new MyEndpointBehavior()
_clientBase.Endpoint.Behaviors.Add(_inspector);
Then, when I make a call, I use this code that is located in the new abstract base class (the original code did it this way, and so far the only difference is the use of generics):
ClientBase<TInterface> _clientBase = _client as ClientBase<TInterface>;
using (new OperationContextScope(_clientBase.InnerChannel))
{
// Get the method
MethodInfo mi = _client.GetType().GetMethod(APICall);
// Make the call and return the result if successful
object response = mi.Invoke(_client, APICallParameters);
return response;
}
Any ideas why this worked prior to the switchover to generic methods and not now?
I'm not sure why this made a difference, but I re-ordered the code to move the inspector addition to happen immediately after the client creation, so the code now looks like this:
...sanity checks, etc...
TClient client = Activator.CreateInstance(typeof(TClient), binding, new
EndpointAddress(url)) as TClient;
ClientBase<TInterface> _clientBase = client as ClientBase<TInterface>;
MyEndpointBehavior _inspector = new MyEndpointBehavior()
_clientBase.Endpoint.Behaviors.Add(_inspector);
...credentials, timeout, etc...
The inspector now seems to work as expected. Strange.
I am looking at using EasyNetQ for interacting with RabbitMQ and wondering if it can support following case:
Queue is declared externally with some arbitrary arguments (e.g. x-message-ttl)
Client code using EasyNetQ sends and receives messages from that queue.
Possibilities I have found are:
Simple IBus API requires that queue has default parameters
Advanced IAdvancedBus API allows to specify arguments of the declared-queue but not all (e.g. x-max-length can't be set)
The question is can I just use existing queue with custom parameters and without need to specify them?
If the queue already exists and you know its name, couldn't you use the IAdvancedBus.Consume<T> method (and not worry about IAdvancedBus.QueueDeclare)?
For example:
var queueName = "TheNameOfYourExistingQueue";
var existingQueue = new EasyNetQ.Topology.Queue(queueName, false);
// bus should be an instance of IAdvancedBus
bus.Consume<TypeOfYourMessage>(existingQueue,
(msg, info) =>
{
// Implement your handling logic here
});
Note that EasyNetQ might have trouble automatically deserializing messages into instances of TypeOfYourMessage. If that is the case, one way to solve it would be to bypass EasyNetQ's message serializer so that you can access the byte array of the message directly. Use the following overload to Consume if you wish to go that route:
void Consume(IQueue queue, Func<Byte[], MessageProperties, MessageReceivedInfo, Task> onMessage);
Even with solution 10477404, parameters like isDurable, isExclusive, isAutoDelete, and arguments must match the original Queue declaration to avoid creating a new one.
For safety, and if you have a way to know the original queue declaration parameters, use them to create the queue with IAdvancedBus.QueueDeclare() or IAdvancedBus.QueueDeclareAsync()
I've been investigating RESTful webservices hosted via a console application to solve a specific use case (ASP.NET is overkill for our current needs) and I am a bit confused.
First off I created a WCF webservice by following these MSDN directions. This works well, I am able to issue GET and POST requests and get a response.
Looking to extend this example to our use case I need to save the POST data into a collection:
so I changed the original post code from:
public string EchoWithPost(string s)
{
return "You said " + s;
}
to
public List<string> Bc = new List<string>();
public string EchoWithPost(string s)
{
this.Bc.Add(s);
return "You said " + s;
}
Expecting my list to grow with each new POST request. This is not the case however. By watching the debugger I determined that each new POST request instantiated a new list causing the old list to fall out of scope and to be lost.
Why is this?
Your WCF service is created as a new instance with each request that it handles. There are multiple things you can do, depending on your needs.
Change your list instance to a static variable (not recommended).
Change your instance-context-mode to a single instance. This means that all your wcf requests are handled by a single instance. The downside is that your service can only handle one request at a time. See here. Not recommended either if you need to handle multiple requests simultanously.
Change your instance-context-mode to per-session. Basically an instance of your service is created for each client.
I suspect the third option is most suitable to your needs.
Because a new instance of your service is created every time you perform a new call.
You should use a static list or use another type of persistence.
public static List<string> Bc = new List<string>();
public string EchoWithPost(string s)
{
this.Bc.Add(s);
return "You said " + s;
}