How to get all configured receive endpoints in Masstransit - masstransit

I connect the receive endpoint at runtime using below code, but it will raise an error if a receive endpoint with the same key was already added.
var handle = _bus.ConnectReceiveEndpoint($"some-name", x =>
{
x.ConfigureConsumeTopology = false;
x.Consumer<TestConsumer>();
var rabbitmqConfigurator = (IRabbitMqReceiveEndpointConfigurator)x;
rabbitmqConfigurator.Bind<QueuedWorkflowItem2>(e =>
{
e.RoutingKey = "direct."+"somename";
e.ExchangeType = ExchangeType.Direct;
});
});
Is there any method I can use to check if the receive endpoint is already added before above code?

Officially, no, there isn't a specific way to return details on all of the configured endpoints at runtime. I would be highly suspect of an application that just randomly connected endpoints to the bus in the hope that it wasn't a duplicate. Seems more like an application implementation problem than anything related to MassTransit.
However, as a hack, you could use the bus health check, which returns all endpoints, and check if the name already exists.
var health = _busControl.CheckHealth();
return health.Endpoints.Any(e => e.Key == 'some-name');

Related

MassTransit - GetSendEndpoint for using In Memory

I am beginning with MassTransit, for a publisher/consumer scenario. In production we will be using SQS, however i would like to be able to use "In Memory" for development locally.
I am having trouble with forming the correct Uri for the call to ISendEndpointProvider.GetSendEnpoint(), as per:
//THE SET UP CODE:
x.AddConsumer<MTConsumer, MTMessageConsumerDefinition>()
.Endpoint(e =>
{
// override the default endpoint name
e.Name = "process-input-item";
//... more configurations as per docs here...
})
;
x.UsingInMemory((context, cfg) =>
{
cfg.ConfigureEndpoints(context);
});
});
//The Publish Code:
var endpoint = await SendEndpointProvider.GetSendEndpoint(new Uri("/ProcessInputItem"));
await endpoint.Send(new MTMessage { InputItemId = item.Id});
Note
I have tried the various cases for the endpoint string.
I do not want to capture the instance of IBus to call Send as that is not the 'closest' instance to the consumer, which according to the docs is important to consider.
Mass Transit document reference: https://masstransit-project.com/usage/configuration.html#receive-endpoints
Thank you for any guidance with this,
Dylan
As explained in the documentation, there are short endpoint addresses which can be used. In your case:
await SendEndpointProvider.GetSendEndpoint(new Uri("queue:process-input-item"));

MassTransit And Service Fabric Stateful Service?

I've been trying to come up with a demo of a website that uses MassTransit with RabbitMQ to post messages to a service running on Service Fabric as a Stateful service.
Everything was going fine, my client would post a message:
IBusControl bus = BusConfigurator.ConfigureBus();
Uri sendToUri = new Uri($"{RabbitMqConstants.RabbitMqUri}" + $"{RabbitMqConstants.PeopleServiceQueue}");
ISendEndpoint endPoint = await bus.GetSendEndpoint(sendToUri);
await endPoint.Send<ICompanyRequest>(new {CompanyId = id });
My consumer in my service fabric service was defined like:
IBusControl busControl = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
IRabbitMqHost host = cfg.Host(new Uri(RabbitMqConstants.RabbitMqUri), h =>
{
h.Username(RabbitMqConstants.UserName);
h.Password(RabbitMqConstants.Password);
});
cfg.ReceiveEndpoint(host, RabbitMqConstants.PeopleServiceQueue, e =>
{
e.Consumer<PersonInformationConsumer>();
});
});
busControl.Start();
This does allow me to consume the message in my class and I can process it fine. The problem comes when we want to use IReliableDictonary or IReliableQueue or anything that needs to reference the context that is run from the RunAsync function in the service fabric service.
So my question is, how can I configure (is it possible) MassTransit to work within a Stateful Service Fabric Service which knowledge of the service context itself?
Many thanks in advance.
Mike
Update
Ok, I've made some progress on this, if I point the register routines to my message consumer class (eg):
ServiceRuntime.RegisterServiceAsync("ServiceType", context => new PersonInformationConsumer(context)).GetAwaiter().GetResult();
ServiceEventSource.Current.ServiceTypeRegistered(Process.GetCurrentProcess().Id, typeof(PersonInformationConsumer).Name);
Then in my consumer class for my messages I can do the following:
internal sealed class PersonInformationConsumer : StatefulService, IConsumer<ICompanyRequest>
{
private static StatefulServiceContext _currentContext;
#region Constructors
public PersonInformationConsumer(StatefulServiceContext serviceContext) : base(serviceContext)
{
_currentContext = serviceContext;
}
public PersonInformationConsumer() : base(_currentContext)
{
}
I can now successfully call the service message:
ServiceEventSource.Current.ServiceMessage(this.Context, "Message has been consumed, request Id: {0}", context.Message.CompanyId);
The problem I have now is trying to store something on the IReliableDictionary, doing this causes as "Object reference not set to an instance of an object" error :( ... any ideas would be appreciated (although may not read until new year now!)
public async Task Consume(ConsumeContext<ICompanyRequest> context)
{
ServiceEventSource.Current.ServiceMessage(this.Context, "Message has been consumed, request Id: {0}", context.Message.CompanyId);
using (ITransaction tx = StateManager.CreateTransaction())
{
try
{
var myDictionary = await StateManager.GetOrAddAsync<IReliableDictionary<string, long>>("myDictionary");
This is causing the error.... HELP! :)
You'll need to do a bit more to get MassTransit and stateful services working together, there's a few issues to concern yourself here.
Only the master within a stateful partition (n masters within n partitions) will be able to write/update to the stateful service, all replicas will throw exceptions when trying to write back any state. So you'll need to deal with this issue, on the surface it sounds easy until you take in to consideration the master can move around the cluster due to re-balancing the cluster, the default for general service fabric applications is to just turn off the processing on the replicas and only run the work on the master. This is all done by the RunAsync method (try it out, run 3 stateful services with something noddy in the RunAsync method, then terminate the master).
There is also partitioning of your data to consider, due to stateful services scale with partitions, you'll need to create a way to distributing data to separate endpoint on your service bus, maybe have a separate queue that only listens to a given partition range? Say you have a UserCreated message, you might split this on country UK goes to partition 1, US goes to partition 2 etc...
If you just want to get something basic up and running, I'd limit it to one partition and just try putting your bus creation within the the RunAsync and shutdown the bus once a cancelation is requested on the cancelation token.
protected override async Task RunAsync(CancellationToken cancellationToken)
{
var busControl = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
IRabbitMqHost host = cfg.Host(new Uri(RabbitMqConstants.RabbitMqUri), h =>
{
h.Username(RabbitMqConstants.UserName);
h.Password(RabbitMqConstants.Password);
});
cfg.ReceiveEndpoint(host, RabbitMqConstants.PeopleServiceQueue, e =>
{
// Pass in the stateful service context
e.Consumer(c => new PersonInformationConsumer(Context));
});
});
busControl.Start();
while (true)
{
if(cancellationToken.CancellationRequested)
{
//Service Fabric wants us to stop
busControl.Stop();
cancellationToken.ThrowIfCancellationRequested();
}
await Task.Delay(TimeSpan.FromSeconds(1));
}
}

Why does GetSendEndpoint require absolute uri instead of just a name of the queue?

IBusControl.GetSendEndpoint() requires absolute uri, whereas it should already have all sufficient information because bus is already configured:
var bus = Bus.Factory.CreateUsingRabbitMq(sbc =>
{
sbc.Host(new Uri("rabbitmq://localhost/"), h =>
{
h.Username("guest");
h.Password("guest");
});
});
The short answer is that it allows you to send to any host or vhost. Publish uses the configured endpoint, and is what we expect you to be using most of the time. Direct sends are needed sometimes but introduce coupling - one of the things messaging is supposed to reduce.

What is the correct way to call patch from an OData client in Web Api 2

Following the OData samples created by the web api team, my controller has the following for supporting Patch:
public HttpResponseMessage Patch([FromODataUri] int key, Delta<Foo> item)
{
var dbVersion = myDb.GetById(key);
if(dbVersion == null)
throw Request.EntityNotFound();
item.Patch(dbVersion);
myDb.Update(dbVersion);
return Request.CreateResponse(HttpStatusCode.NoContent);
}
and using the auto-generated client (derived from DataServiceContext), I submit a patch request like this:
var foo = svcContainer.Foos.Where (f => f.Id == 1).SingleOrDefault();
foo.Description = "Updated Description";
svcContainer.UpdateObject(foo);
svcContainer.SaveChanges(SaveChangesOptions.PatchOnUpdate);
However, tracing the call in fiddler, I see that all other properties of Foo are serialized and sent to the service. Is that the correct behavior? I expected only the Id and Description to be sent over the wire. Also, if I debug the service method and call
GetChangedPropertyNames on item, all its property names are returned.
Should I be creating some sort of Delta instance on the client?
I understand the disconnected nature of the service and thus the service side does not have a context for tracking changes, but it seems to me the api team added support for patch for a reason, so I'd like to know if the client ought to be invoking the update in a different manner.
Update
The link YiDing provided explains how to create a true PATCH request from the client (using the Microsoft.OData.Client.DataServiceContext created by the Microsoft.OData.Client 6.2.0 and above.
For convenience, here is the code snippet:
var svcContainer = new Default.Container(<svcUri>);
var changeTracker = new DataServiceCollection<Foo>(svcContainer.Foos.Where(f => f.Id == 1));
changeTracker[0].Description = "Patched Description";
svcContainer.SaveChanges();
The DataServiceCollection implements property tracking, and using this pattern, only the updated properties are sent to the service.
Without using DataServiceCollection and simply using
svcContainer.UpdateObject(foo);
svcContainer.SaveChanges();
all properties are still sent over the wire despite documentation to the contrary, at least as of Microsoft.OData.Client 6.7.0
The client side property tracking is now supported from Microsoft.OData.Client version 6.2.0. It will detect only the modified properties of an entity and send the update request as PATCH instead of PUT to meet the requirement of your scenario. Please refer to this blog post for more details:
https://devblogs.microsoft.com/odata/tutorial-sample-client-property-tracking-for-patch/

Outbound calls with Twilio Rest Api are not executed

I'm building an application and one of the features is integrated with Twilio.
I have all the IVR flow done with Asp.Net Mvc 3 and everything is working correctly so far.
However, one of the features is to have the user input a phone number and have Twilio call that number and play something once the other user answers.
I'm using the Twilio REST API to make the call, but the call is not being done and I don't have any error on the application or on Twilio.
What I'm doing is this: I have an Action that receive the data from twilio
public ActionResult Dial(Call request, int opt)
{
var twilio = new TwilioRestClient(Configuration.TwilioAccKey, Configuration.TwilioAuthKey);
twilio.InitiateOutboundCall(Configuration.TwilioPhoneNumber,
"+" + request.Digits,
string.Format("{0}/Calls/Endorsement/Play?opt={1}", Configuration.BaseUrl, opt));
var response = new TwilioResponse();
response.Redirect("/Calls/Endorsement/Play?opt=" + opt, "GET");
return TwiML(response);
}
The response after the REST call is being executed and the outbound call doesn't throw any error.
What am I doing wrong?
Thanks!
Your code to initiate the outbound call looks correct.
Its possible that an exception is being returned from the REST API. I've changed your code to use the InitiateOutboundCall methods callback parameter to check if the RestException property is not null:
var twilio = new TwilioRestClient(Configuration.TwilioAccKey,
Configuration.TwilioAuthKey);
twilio.InitiateOutboundCall(Configuration.TwilioPhoneNumber,
"+" + request.Digits,
string.Format("{0}/Calls/Endorsement/Play?opt={1}", Configuration.BaseUrl, opt),
call =>
{
if (call.RestException != null)
{
//handle the rest error
}
}
);
If RestException is null and nothing is being logged in the Twilio debugger log, then your best option might be to break out Fiddler and see whats happening during the actual request to the API.
I had a similar problem and want to post here in case someone else finds this issue like I did. (At the time this is the only thing that shows up in a search for "InitiateOutboundCall".)
In my case no exceptions were thrown either by my app or by Twilio. The call to InitiateOutboundCall simply was not doing anything.
The docs make it clear that the URL must be absolute and I had left off the "http://". After adding this everything worked as expected.

Resources