Azure, SubscriptionClient.OnMessage, and Sessions - session

Does the Azure Service Bus Subscription client support the ability to use OnMessage Action when the subscription requires a session?
I have a subscription, called "TestSubscription". It requires a sessionId and contains multipart data that is tied together by a SessionId.
if (!namespaceManager.SubscriptionExists("TestTopic", "Export"))
{
var testRule = new RuleDescription
{
Filter = new SqlFilter(#"(Action='Export')"),
Name = "Export"
};
var subDesc = new SubscriptionDescription("DataCollectionTopic", "Export")
{
RequiresSession = true
};
namespaceManager.CreateSubscription(sub`enter code here`Desc, testRule);
}
In a seperate project, I have a Service Bus Monitor and WorkerRole, and in the Worker Role, I have a SubscriptionClient, called "testSubscriptionClient":
testSubscriptionClient = SubscriptionClient.CreateFromConnectionString(connectionString, _topicName, CloudConfigurationManager.GetSetting("testSubscription"), ReceiveMode.PeekLock);
I would then like to have OnMessage triggered when new items are placed in the service bus queue:
testSubscriptionClient.OnMessage(PersistData);
However I get the following message when I run the code:
InvalidOperationException: It is not possible for an entity that requires sessions to create a non-sessionful message receiver
I am using Azure SDK v2.8.
Is what I am looking to do possible? Are there specific settings that I need to make in my service bus monitor, subscription client, or elsewhere that would let me retrieve messages from the subscription in this manner. As a side note, this approach works perfectly in other cases that I have in which I am not using sessioned data.

Can you try this code:
var messageSession=testSubscriptionClient.AcceptMessageSession();
messageSession.OnMessage(PersistData);
beside of this:
testSubscriptionClient.OnMessage(PersistData);
Edit:
Also, you can register your handler to handle sessions (RegisterSessionHandler). It will fire your handle every new action.
I think this is more suitable for your problem.
He shows both way, in this article. It's for queue, but I think you can apply this to topic also.

Related

Masstransit EndpointConvention Azure Service Bus

I'm wondering if I'm doing something wrong, I expected MassTransit would automatically register ReceiveEndpoints in the EndpointConvention.
Sample code:
services.AddMassTransit(x =>
{
x.AddServiceBusMessageScheduler();
x.AddConsumersFromNamespaceContaining<MyNamespace.MyRequestConsumer>();
x.UsingAzureServiceBus((context, cfg) =>
{
// Load the connection string from the configuration.
cfg.Host(context.GetRequiredService<IConfiguration>().GetValue<string>("ServiceBus:ConnectionString"));
cfg.UseServiceBusMessageScheduler();
// Without this line I'm getting an error complaining about no endpoint convention for x could be found.
EndpointConvention.Map<MyRequest>(new Uri("queue:queue-name"));
cfg.ReceiveEndpoint("queue-name", e =>
{
e.MaxConcurrentCalls = 1;
e.ConfigureConsumer<MyRequestConsumer>(context);
});
cfg.ConfigureEndpoints(context);
});
});
I thought this line EndpointConvention.Map<MyRequest>(new Uri("queue:queue-name")); wouldn't be necessary to allow sending to the bus without specifing the queue name, or am I missing something?
await bus.Send<MyRequest>(new { ...});
The EndpointConvention is a convenience method that allows the use of Send without specifying the endpoint address. There is nothing in MassTransit that will automatically configured this because, frankly, I don't use it. And I don't think anyone else should either. That stated, people do use it for whatever reason.
First, think about the ramifications - if every message type was registered as an endpoint convention, what about messages that are published and consumed on multiple endpoints? That wouldn't work.
So, if you want to route messages by message type, MassTransit has a feature for that. It's called Publish and it works great.
But wait, it's a command, and commands should be Sent.
That is true, however, if you are in control of the application and you know that there is only one consumer in your code base that consumes the KickTheTiresAndLightTheFires message contract, publish is as good as send and you don't need to know the address!
No, seriously dude, I want to use Send!
Okay, fine, here are the details. When using ConfigureEndpoints(), MassTransit uses the IEndpointNameFormatter to generate the receive endpoint queue names based upon the types registered via AddConsumer, AddSagaStateMachine, etc. and that same interface can be used to register your own endpoint conventions if you want to use Send without specifying a destination address.
You are, of course, coupling the knowledge of your consumer and message types, but that's your call. You're already dealing with magic (by using Send without an explicit destination) so why not right?
string queueName = formatter.Consumer<T>()
Use that string for the message types in that consumer as a $"queue:{queueName}" address and register it on the EndpointConvention.
Or, you know, just use Publish.

Redis keyspace notifications with StackExchange.Redis For Delete operation

I've been searching to find out how to perform a subscription to key space notifications on Redis using ServiceStack.Redis library for removal of Key.
Checking available tests on the git-hub and other websites I've found IRedisSubscription can be used for subscribing to specific Redis key events, For set operation it is working absolutely fine but when it comes to Delete operation the action is not invoked.
Is it possible to take advantage of this Redis feature using ServiceStack.Redis and get event on delete operation too?
In the configuration file I have added this line:
notify-keyspace-events KEAg
I am using the following code.
var channels = new[] { "__keyevent#0__:set" , "__keyevent#0__:del" };
using (var redisConsumer = new RedisClient("localhost:6379"))
using (var subscription = redisConsumer.CreateSubscription()) {
subscription.OnMessage = onKeyChange;
subscription.SubscribeToChannelsMatching(channels );
}
From the surface, it looks like what you got should work.
Try setting notify-keyspace-events to AKE, the g is redundant, as noted in Notifications Config:
A Alias for g$lshztxe, so that the "AKE" string means all the
events.
Try using SubscribeToChannels instead of SubscribeToChannelsMatching. The latter is for pattern subscription.
You can test how many subscribers you have with the PUBSUB NUMSUB __keyevent#0__:del command from redis-cli.
Try testing your events are being triggered with SUBSCRIBE __keyevent#0__:del from redis-cli. This will help you determine if the problem is on redis-server or the app code.
Please update the question with results if you can't get it to work after trying the above.

Disconnect consumer while bus is connected

We have the following use case:
We have two busses (internal and external). The idea is that our own services use the internal bus and all third-party services use the external bus. We have a created a service that acts as a message router (aptly named MessageRouter).
When a message is published on the internal bus, the MessageRouter can pick up that message and place it on the external bus, and vice-versa. Through configuration we can tell which messages are allowed to pass from internal to external of from external to internal. This in itself works fine.
We have grouped our messages, we have Events, Commands and Requests. Each service has three ReceiveEnpoints, one for each message type. This means that all events are queued on the Events queue etc. Each message to which a service 'subscribes' gets it's own consumer.
var queues = new Dictionary<string, IEnumerable<Type>>
{
// ReSharper disable once PossibleMultipleEnumeration
{ "Events", consumerTypes.Where(ct => ct.GetGenericArguments().Any(ga => typeof(IEvent).IsAssignableFrom(ga))) },
// ReSharper disable once PossibleMultipleEnumeration
{ "Commands", consumerTypes.Where(ct => ct.GetGenericArguments().Any(ga => typeof(ICommand).IsAssignableFrom(ga))) },
// ReSharper disable once PossibleMultipleEnumeration
{ "Queries", consumerTypes.Where(ct => ct.GetGenericArguments().Any(ga => typeof(IQuery).IsAssignableFrom(ga))) }
};
foreach (var queue in queues)
{
config.ReceiveEndpoint(GetConsumerQueueName(queue.Key), cfg =>
{
foreach (var consumerType in queue.Value)
{
cfg.Consumer(consumerType, consumerContainer.Resolve);
}
configurator?.Invoke((T)cfg);
});
}
Where config is a IBusFactoryConfigurator. This code called when the service starts.
What we would like to be able to do in our MessageRouter is to 'dynamically' add and, more importantly, remove consumers from the ReceiveEndpoint.
So far, we haven't had any luck. We have tried to add the Consumer through the use of the ConnectConsumer method on the BusControl instance. This gives us a ConnectHandle which has a Disconnect method. However, when using this approach, messages are not picked up by our consumers. Looking at the handle shows us that it is a MultipleConnectHandle, however, the handle has no 'internal' handles.
Is there any way to use the Consumer method to register the different consumers and to get their ConnectHandles, So that we can Disconnect the consumer if needed?
As stated, ideally we would like to be able to dynamically add and remove consumers to a ReceiveEndpoint.
You can't add/remove consumers on a receive endpoint while the bus is started, that isn't supported in any way, shape, or form.
You can, however, connect new receive endpoints on separate queues with one or more consumers. In your case above, it seems like you are not getting your consumers registered before connecting the receive endpoint, which is why you aren't seeing anything in the handle collection.

Notifying clients from (boxed) Syncfusion Ajax call

I'm trying to integrate the schedule component from Syncfusion. The component has a URL adaptor to connect to the controller; GetData() and Batch() for Crud Operations. Batch has a payload indicating what actions to perform. At the end, the Batch method would requery the database and send data identical to GetData() back.
Unfortunately, there is no built-in method to notify clients of anything going wrong - whether there is an exception, server-side validation kicks in or similar.
What I'd like to do is to add a placeholder outside the compentent to receive and display server messages (be it a notification popup, a or whatever.
Since I can't influence the Ajax call itself, I was wondering if I had to get started with SignalR (still in beta for .Net Core 2 as far as I know), or if I may have missed something more obvious? I have read a lot about push notifications etc - but these are not quite what I'm after, it'd be slightly over the top I think.
To summarise, let's say I have
<div id="messages"></div>
<div id="component">HereGoesTheScheduleWhichICantDoMuchWith</div>
Now in the Batch() method, it would be great to call a SendMessage("Sorry,you can't do this") - the text of which would ideally then appear in the messages-div.
How would you go about this?
I have now solved this, using SignalR (currently 1.0.0-alpha2-final) and for a nice view on the Client, PNotify.
Presently, it only works if the client is authenticated, if it needs to work anonymously you'd need to figure out a way to track SignalR's connection id.
On the page with the Syncfusion Schedule component, I connect to SignalR.
let connection = new signalR.HubConnection("/signalr", { transport: signalR.TransportType.ServerSentEvents });
connection.on("Notify",
(title, message) => {
new PNotify({
title: title,
text: message
});
});
connection.start();
The Hub (SignalRHub : Hub) creates a notification group for the user connecting:
public override Task OnConnectedAsync()
{
Groups.AddAsync(Context.ConnectionId, Context.User.Identity.Name);
return base.OnConnectedAsync();
}
The associated controller gets IHubContext<SignalRHub> signalRHub injected.
Now in the Batch-Method for the Syncfusion component, which returns Json and can't itself carry messages or notifications, you can notify the user:
_signalRHub.Clients.Group(User.Identity.Name).InvokeAsync("Notify", "A title", "A message");
In my particular case, I'm sending over an object to control layout, animation and popup duration for PNotify (e.g. longer for an exception to allow copy/paste etc) - as you please. Returning an object could be done using:
_signalRHub.Clients.Group(User.Identity.Name).InvokeAsync("Notify", JsonConvert.SerializeObject(new { title = "Some Title", message = "notification", type = "notice"}););
Obviously, connection.on("Notify"... needs to be changed accordingly.
I hope this is clear enough and might help someone else.

Using Sessions in synchronous Request-Response patterns

I am trying to get sessions to work in the following architecture.
Multiple heterogenous worker roles that monitor and process requests from queue1, and send their responses to queue2.
One front web role, which receives requests from outside via REST or SOAP, submits them into queue1, and waits for a response from queue2. Once it's received, the response is returned to the caller.
The web role is there to leverage scalability and allow the worker roles to be created dynamically when the load is too high (which is why the entire Ruth Goldberg machine, there is no way without the service bus).
I am using a call to:
MessageSession sess = myQueueClient.AcceptMessageSession(mySessionId, TimeSpan.FromSeconds(timeoutPerSec));
which is followed by:
BrokeredMessage bm = sess.Receive();
and the call to AcceptMessageSession crashes and burns with the exception:
BR0012A sessionful message receiver cannot be created on an entity that does not require sessions. Ensure RequiresSession is set to true when creating a Queue or Subscription to enable sessionful behavior.
Now I do set RequiresSession to true:
if (!_queueManager.QueueExists(clientID))
_queueManager.CreateQueue(clientID).RequiresSession = true;
else
_queueManager.GetQueue(clientID).RequiresSession = true;
but it does not help.
What am I doing wrong?
You have to create a queue with RequiresSession set to true in QueueDescription when you create a queue, not in a QueueDescription of already created queue.
So you in your case queue creation should look similar to this:
if (!_queueManager.QueueExists(clientID))
{
QueueDescription queueDescription = new QueueDescription(clientID)
{
RequiresSession = true
};
_queueManager.CreateQueue(queueDescription);
}

Resources