I am new to both MassTransit and Azure Service Bus. I am attempting to use an architecture where either RabbitMq or Azure Service Bus is used in a .NET Core 3.1 API. I have the RabbitMq portion working and just started on the Azure Service Bus. I have an API that will take an incoming payload and publish it to a queue. When I attempt to publish via the Azure Service Bus approach, I get an error "SubCode=40000. Cannot operate on type Topic because the namespace 'servicehubqa' is using 'Basic' tier.
I am attempting to use a queue approach and am hoping to create the queue as messages are published. Currently, the service bus is using a Basic pricing tier as the documentation says that I can play with queues at that level. I am not sure if I need to manually create the queue (I had to do this approach using RabbitMq since no queue would be created if no consumer exists). Is topic the default approach if nothing is specified? How do I specify queue vs topic?
My code is as follows below.
Startup - ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(Configuration);
services.AddScoped<IMassTransitRabbitMqTransport, MassTransitRabbitMqTransport>();
services.AddScoped<IMassTransitAzureServiceBusTransport, MassTransitAzureServiceBusTransport>();
var messageProvider = ConfigProvider.GetConfig("MessageService", "Messaging_Service");
switch (messageProvider)
{
case "AzureServiceBus":
services.AddScoped<IMessagingService, MassTransitAzureServiceBusMessagingService>();
break;
case "RabbitMq":
services.AddScoped<IMessagingService, MassTransitRabbitMqMessagingService>();
break;
default:
throw new ArgumentException("Invalid message service");
};
services.AddControllers();
}
Controller
public class ListenerController : ControllerBase
{
readonly ILogger<ListenerController> logger;
readonly IMessagingService messenger;
public ListenerController(
ILogger<ListenerController> logger,
IMessagingService messenger)
{
this.logger = logger;
this.messenger = messenger;
}
[HttpPost]
public async Task<IActionResult> Post()
{
var payload = new
{
...
};
await messenger.Publish(payload);
return Ok();
}
}
IMessagingService
public interface IMessagingService
{
Task Publish(object payload);
}
IMassTransitTransport
public interface IMassTransitTransport
{
IBusControl BusControl { get; }
}
public interface IMassTransitRabbitMqTransport : IMassTransitTransport { }
public interface IMassTransitAzureServiceBusTransport : IMassTransitTransport { }
MassTransitAzureServiceBusTransport
public sealed class MassTransitAzureServiceBusTransport : IMassTransitAzureServiceBusTransport
{
public IBusControl BusControl { get; }
public MassTransitAzureServiceBusTransport()
{
BusControl = ConfigureBus();
BusControl.StartAsync();
}
IBusControl ConfigureBus()
{
return Bus.Factory.CreateUsingAzureServiceBus(config => {
var host = config.Host(ConfigProvider.GetConfig("AzureServiceBus", "AzureServiceBus_ConnStr"), host => { });
});
}
}
MassTransitAzureServiceBusMessagingService
public class MassTransitAzureServiceBusMessagingService : IMessagingService
{
readonly IMassTransitAzureServiceBusTransport massTransitTransport;
public MassTransitAzureServiceBusMessagingService(IMassTransitAzureServiceBusTransport massTransitTransport)
{
//transport bus config already happens in massTransitTransport constructor
this.massTransitTransport = massTransitTransport;
}
public async Task Publish(object payload)
{
var jsn = Newtonsoft.Json.JsonConvert.SerializeObject(payload);
var cmd = JObject.Parse(jsn)["Command"];
switch (cmd.ToString())
{
case "UPDATESTATUS":
//IRegisterCommandUpdateStatus is an interface specifying the properties needed
await massTransitTransport.BusControl.Publish<IRegisterCommandUpdateStatus>(payload);
break;
default: break;
}
}
}
The Azure Service Bus basic tier does not allow the use of topics. So you would not be able to use publish. That said, MassTransit doesn't really work with the basic tier, despite attempts in the past that may have been successful.
The MassTransit documentation does state that if you want to use a Topic (i.e. the ability to publish to multiple subscriptions at the same time), you use the publish.
If you want to send a message to a queue (the message is routed to a specific location), you use the send and provide the correct information.
Topics require standard pricing and Queues can use basic pricing.
With this information, the MassTransitAzureServiceBusMessagingService would be modified as follows:
Basic Pricing - Queues
public async Task Publish(object payload)
{
var jsn = Newtonsoft.Json.JsonConvert.SerializeObject(payload);
var cmd = JObject.Parse(jsn)["Command"];
switch (cmd.ToString())
{
case "UPDATESTATUS":
var queueUri = new Uri(massTransitTransport.BusControl.Address, "registration.updatestatus");
var endpoint = await massTransitTransport.BusControl.GetSendEndpoint(queueUri);
await endpoint.Send<IRegisterCommandUpdateStatus>(payload);
break;
default: break;
}
}
Standard Pricing - Topics/Subscriptions
public async Task Publish(object payload)
{
var jsn = Newtonsoft.Json.JsonConvert.SerializeObject(payload);
var cmd = JObject.Parse(jsn)["Command"];
switch (cmd.ToString())
{
case "UPDATESTATUS":
await massTransitTransport.BusControl.Publish<IRegisterCommandUpdateStatus>(payload);
break;
default: break;
}
}
Related
Our requirement is like below :-
Exchange 1 is a topic exchange and queue 1 is bound to it. It is on VHOST 1.
Application is subscribed to the queue 1. It processes the message of queue 1. After processing of queue 1 message, we want to publish next message to different exchange which is on VHOST 2 (different rabbit connection)
I have below questions :-
a) Is it possible to implement this without federation ?a
b) In a same application, Can I maintain 2 different rabbit connections ?
We are using using EasynetQ as a client to connect with rabbitmq.
Can you please share some sample on this.
Thanks in advance.
a) Yes, you can also create a shovel between vhosts, which is simpler than a federation
b) Yes I don't see a problem with creating multiple IBus instances, as long as you use different DI (sub) containers per bus instance, so there's an added complexity.
Here is how I handle multiple connections. I can't find a solution directly from EasyNetQ. I don't use the default DI adaptors for MS DI. And I only use the advance api and inject the service I need manually. So far it seems working but it needs more test for sure.
in startup.cs / ConfigureServices
services.AddBusStation(busStationBuilder =>
{
// inject IBusStation and get the bus thru name
appSettings.RabbitMQSettings.Connections.ForEach(c =>
{
var taskQueueBus = RabbitHutch.CreateBus(c.ConnectionString, CustomServiceRegister.ServiceRegisterAction());
c.Exchanges.ForEach(async e =>
{
await taskQueueBus.Advanced.ExchangeDeclareAsync(e.Name, e.Type, e.Durable, e.AutoDelete);
});
busStationBuilder.Add(c.Name, taskQueueBus.Advanced);
busStationBuilder.AddDefaultBus(taskQueueBus);
});
});
public interface IBusStation
{
IBus DefualtBus { get; }
IAdvancedBus Get(string busName);
void Add(string busName, IAdvancedBus advancedBus);
void Add(IBus bus);
}
public class BusStation : IBusStation
{
private Dictionary<string, IAdvancedBus> BusList { get; set; } = new Dictionary<string, IAdvancedBus>();
public IBus DefualtBus { get; private set; }
public IAdvancedBus Get(string busName)
{
if (BusList.TryGetValue(busName, out IAdvancedBus advancedBus))
{
return advancedBus;
}
return null;
}
public void Add(string busName, IAdvancedBus advancedBus)
{
BusList.Add(busName, advancedBus);
}
public void Add(IBus bus)
{
this.DefualtBus = bus;
}
}
public class BusStationBuilder
{
private readonly IBusStation _BusStation;
public BusStationBuilder(IServiceCollection services, IBusStation busStation)
{
this._BusStation = busStation;
services.AddSingleton(busStation);
}
public BusStationBuilder Add(string busName, IAdvancedBus advancedBus)
{
_BusStation.Add(busName, advancedBus);
return this;
}
public BusStationBuilder AddDefaultBus(IBus bus)
{
_BusStation.Add(bus);
return this;
}
}
public static class DependencyExtensions
{
public static IServiceCollection AddBusStation(this IServiceCollection services, Action<BusStationBuilder> builder)
{
var busStationBuilder = new BusStationBuilder(services, new BusStation());
builder(busStationBuilder);
return services;
}
}
appsettings.json
"RabbitMQSettings": {
"DefaultQueue": "task.main",
"Connections": [
{
"Name": "Task_Queue",
"ConnectionString": "host=192.168.123.123;virtualHost=/;username=admin;password=password123;prefetchCount=1;persistentMessages=true;publisherConfirms=true",
"Exchanges": [
{
"Name": "Direct_Task_Queue",
"Type": "direct",
"Passive": false,
"Durable": true,
"AutoDelete": false,
"Internal": false,
"AlternateExchange": null,
"Delayed": false
}
]
}
]
},
I am using rabbitmq in a "Work Queues" scenario.
I need eg. a pool of 5 consumers, (each with its own channel), so one consumer doing I/O operations, won't block other consumer of the same queue.
Eg.
If I have on my queue:
Message 1, Message 2, Message 3, Message 4. Each instance of (FistConsumerHandler) will take 1 message from the queue using Round Robin (default rabbitmq behavior)
The problem I am facing is I need to do this using Dependency Injection.
Here is what i have so far:
On Windows service start (my consumers are hosted in a windows service):
protected override void OnStart(string[] args)
{
BuildConnections();
// Register the consumers. For simplicity only showing FirstConsumerHandler.
AddConsumerHandlers<FistConsumerHandler>(ConstantesProcesos.Exchange, ConstantesProcesos.QueueForFirstHandler);
BuildStartup();
var logger = GetLogger<ServicioProcesos>();
logger.LogInformation("Windows Service Started");
Console.WriteLine("Press [enter] to exit.");
}
protected virtual void BuildConnections(
string notificationHubPath = "notificationhub_path",
string rabbitMQHostname = "rabbitmq_hostname",
string rabbitMQPort = "rabbitmq_port",
string rabbitMQUserName = "rabbitmq_username",
string rabbitMQPassword = "rabbitmq_password")
{
ContextHelpers.Setup(ConfigurationManager.ConnectionStrings[appContextConnectionString].ConnectionString);
if (_connection == null)
{
var factory = new ConnectionFactory
{
HostName = ConfigurationManager.AppSettings[rabbitMQHostname],
Port = int.Parse(ConfigurationManager.AppSettings[rabbitMQPort]),
UserName = ConfigurationManager.AppSettings[rabbitMQUserName],
Password = ConfigurationManager.AppSettings[rabbitMQPassword],
DispatchConsumersAsync = true,
};
// Create a connection
do
{
try
{
_connection = factory.CreateConnection();
}
catch (RabbitMQ.Client.Exceptions.BrokerUnreachableException e)
{
Thread.Sleep(5000);
}
} while (_connection == null);
}
_startupBuilder = new StartupBuilder(_connection);
}
protected void AddConsumerHandlers<THandler>(string exchange, string queue)
{
var consumerHandlerItem = new ConsumerHandlerItem
{
ConsumerType = typeof(THandler),
Exchange = exchange,
Queue = queue
};
_startupBuilder._consumerHandlerItems.Add(consumerHandlerItem);
}
protected void BuildStartup()
{
ServiceProvider = _startupBuilder.Build();
}
Startup Builder:
using Microsoft.Extensions.DependencyInjection;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Collections.Generic;
public class StartupBuilder
{
private static IConnection _connection;
private IModel _channel;
public List<ConsumerHandlerItem> _consumerHandlerItems;
public IServiceCollection Services { get; private set; }
public StartupBuilder(IConnection connection)
{
_connection = connection;
_consumerHandlerItems = new List<ConsumerHandlerItem>();
Services = new ServiceCollection();
}
public IServiceProvider Build()
{
_channel = _connection.CreateModel();
Services.InitSerilog();
// Add channel as singleton (this is not correct as I need 1 channel per ConsumerHandler)
Services.AddSingleton(_channel);
// Register the ConsumerHandler to DI
foreach (var item in _consumerHandlerItems)
{
// Add FirstHandler to DI
Type consumerType = item.ConsumerType;
Services.AddSingleton(consumerType);
}
// Finish DI Setup
var serviceProvider = Services.BuildServiceProvider();
// Bind the consumer handler to the channel and queue
foreach (var item in _consumerHandlerItems)
{
var consumerHandler = (AsyncEventingBasicConsumer)serviceProvider.GetRequiredService(item.ConsumerType);
_channel.AssignNewProcessor(item, consumerHandler);
}
return serviceProvider;
}
}
Helpers:
public static class QueuesHelpers
{
public static void AssignNewProcessor(this IModel channel, ConsumerHandlerItem item, AsyncEventingBasicConsumer consumerHandler)
{
channel.ExchangeDeclare(item.Exchange, ExchangeType.Topic, durable: true);
channel.QueueDeclare(item.Queue, true, false, false, null);
channel.QueueBind(item.Queue, item.Exchange, item.Queue, null);
channel.BasicConsume(item.Queue, false, consumerHandler);
}
}
Consumer handler:
public class FistConsumerHandler : AsyncEventingBasicConsumer
{
private readonly ILogger<FistConsumerHandler> _logger;
private Guid guid = Guid.NewGuid();
public FistConsumerHandler(
IModel channel,
ILogger<FistConsumerHandler> logger) : base(channel)
{
Received += ConsumeMessageAsync;
_logger = logger;
}
private async Task ConsumeMessageAsync(object sender, BasicDeliverEventArgs eventArgs)
{
try
{
// consumer logic to consume the message
}
catch (Exception ex)
{
}
finally
{
Model.Acknowledge(eventArgs);
}
}
}
The problem with this code is:
There is ony 1 instance of FistConsumerHandler (as is reigstered as singleton). I need, for instance 5.
I have only 1 channel, I need 1 channel per instance.
To sum up, the expected behavior using Microsoft.Extensions.DependencyInjection should be:
Create a connection (share this connection with all consumers)
When a message is received to the queue, it should be consumed by 1 consumer using its own channel
If another message is received to the queue, it should be consumed by another consumer
TL;DR; Create your own scope
I've done something similar in an app I'm working on, albeit not as cleanly as I would like (and thus why I came across this post). The key for me was using IServiceScopeFactory to get injected services and use them in a consumer method. In a typical HTTP request the API will automatically create/close scope for you as the request comes in / response goes out, respectively. But since this isn't an HTTP request, we need to create / close the scope for using injected services.
This is a simplified example for getting an injected DB context (but could be anything), assuming I've already set up the RabbitMQ consumer, deserialized the message as an object (FooEntity in this example):
public class RabbitMQConsumer
{
private readonly IServiceProvider _provider;
public RabbitMQConsumer(IServiceProvider serviceProvider)
{
this._serviceProvider = serviceProvider;
}
public async Task ConsumeMessageAsync()
{
// Using statement ensures we close scope when finished, helping avoid memory leaks
using (var scope = this._serviceProvider.CreateScope())
{
// Get your service(s) within the scope
var context = scope.ServiceProvider.GetRequiredService<MyDBContext>();
// Do things with dbContext
}
}
}
Be sure to register RabbitMQConsumer as a singleton and not a transient in Startup.cs also.
References:
Similar SO post
MS Docs
I have an Interface called ICompleteConsumer which has only one method and I have multiple implementations for that interface.
EX:
public class SampleClass1: ICompleteConsumer {
public async Task Complete(Model model) {
// do the work
}
}
public class SampleClass2: ICompleteConsumer {
public async Task Complete(Model model) {
// do the work
}
}
I registered these in startUp like this
services.AddScoped<ICompleteConsumer, SampleClass1>(),
services.AddScoped<ICompleteConsumer, SampleClass2>(),
I am inject these dependencies as below
public class ConsumerHandlerService(string message) {
private readonly ICompleteConsumer completeConsumer;
public ConsumerHandlerService(ICompleteConsumer completeConsumer) {
this.completeConsumer = completeConsumer
}
switch (MessageType) {
case (MessageType .1) //I want SampleClass1 implementation to here
var model = JsonConvert.DeserializeObject < Model > (message);
await completeConsumer.complete(model);
break;
case (MessageType .2) // I want SampleClass2 implementation to here
var model = JsonConvert.DeserializeObject < Model > (message);
await completeConsumer.complete(model);
break;
}
}
How do I achieve this using .net core DI?
There are actually two points of discussion in your question. One is a point related to the design of the code, the other one is a point related on how to use the .NET core DI container in order to handle the required registrations.
Both of them are important, but we need to treat them one at a time.
How to organize the code
To solve your problem in a clean and extensibile way you need to use a design pattern known as the composite design pattern. In order to do so, you need to change the definition of your interface to the following:
public interface IMessageConsumer
{
bool CanHandleMessage(Message message);
Task HandleMessage(Message message);
}
Your interface implementations are then changed as follows:
public class FooMessageConsumer: IMessageConsumer
{
public bool CanHandleMessage(Message message)
{
if (message is null) throw new ArgumentNullException(nameof(message));
return message.Type == "foo";
}
public Task HandleMessage(Message message)
{
if (message is null)
throw new ArgumentNullException(nameof(message));
if (!this.CanHandleMessage(message))
throw new InvalidOperationException($"{nameof(FooMessageConsumer)} can only handle foo messages.");
await Task.Delay(100).ConfigureAwait(false);
Console.Writeline($"Message {message.Id} handled by {nameof(FooMessageConsumer)}");
}
}
public class BarMessageConsumer: IMessageConsumer
{
public bool CanHandleMessage(Message message)
{
if (message is null) throw new ArgumentNullException(nameof(message));
return message.Type == "bar";
}
public Task HandleMessage(Message message)
{
if (message is null)
throw new ArgumentNullException(nameof(message));
if (!this.CanHandleMessage(message))
throw new InvalidOperationException($"{nameof(BarMessageConsumer)} can only handle bar messages.");
await Task.Delay(100).ConfigureAwait(false);
Console.Writeline($"Message {message.Id} handled by {nameof(BarMessageConsumer)}");
}
}
At this point you need to introduce a special message consumer, which will be used to dispatch the message to the proper consumer. This is called the composite message consumer and this is the implementation of IMessageConsumer that you will register in your DI container and that will be injected in all the classes which need a message consumer in order to do their business.
public class CompositeMessageConsumer : IMessageConsumer
{
private readonly IMessageConsumer[] _consumers;
public CompositeMessageConsumer(IEnumerable<IMessageConsumer> consumers)
{
if (consumers is null)
throw new ArgumentNullException(nameof(consumers));
this._consumers = consumers.ToArray();
}
public bool CanHandleMessage(Message message)
{
if (message is null) throw new ArgumentNullException(nameof(message));
return this._consumers.Any(c => c.CanHandleMessage(message));
}
public async Task HandleMessage(Message message)
{
if (message is null)
throw new ArgumentNullException(nameof(message));
if (!this.CanHandleMessage(message))
throw new InvalidOperationException("None of the available consumers is able to handle the provided message.");
var consumer = this._consumers.First(c => c.CanHandleMessage(message));
await consumer.HandleMessage(message).ConfigureAwait(false);
}
}
Here is an example of a class which uses the IMessageConsumer interface. At runtime, the DI container will inject an instance of CompositeMessageConsumer.
// this is an example of a class depending on the IMessageConsumer service
public class MessageProcessor
{
// at runtime this will be an instance of CompositeMessageConsumer
private readonly IMessageConsumer _consumer;
// the DI container will inject an instance of CompositeMessageConsumer here
public MessageProcessor(IMessageConsumer consumer)
{
if (consumer is null) throw new ArgumentNullException(nameof(consumer));
this._consumer = consumer;
}
public async Task ProcessIncomingMessage(Message message)
{
if (message is null) throw new ArgumentNullException(nameof(message));
// do all the pre processing here...
// handle the message
await this._consumer.HandleMessage(message).ConfigureAwait(false);
// do all the post processing here...
}
}
How to register the services on the .NET core DI container
Deciding the proper lifetime for your registrations is a problem that goes beyond the scope of this discussion.
In my example code above I have defined stateless consumer classes and the composite consumer only iterates over the array of the available consumers. The array is never modified during the iteration. This means that all the involved classes are thread safe, so we can register all of them with a singleton lifetime.
That said, the simplest registration that you can perform is the following:
// register the consumers as classes
services.AddSingleton<FooMessageConsumer>();
service.AddSingleton<BarMessageConsumer>();
// register the composite message consumer as an interface, so that when you require IMessageConsumer you get CompositeMessageConsumer
services.AddSingleton<IMessageConsumer>(container =>
{
var fooConsumer = container.GetRequiredService<FooMessageConsumer>();
var barConsumer = container.GetRequiredService<BarMessageConsumer>();
return new CompositeMessageConsumer(new IMessageConsumer[]
{
fooConsumer,
barConsumer
});
});
A great book to learn about these topics is this one. If you are a .NET developer this is definitely a must read.
Try this:
private readonly IEnumerable<ICompleteConsumer> completeConsumers;
public ConsumerHandlerService(IEnumerable<ICompleteConsumer> completeConsumers) {
this.completeConsumers = completeConsumer
}
...
//Get the service you want basing on the messagetype, I guess
var completeConsumer = this.completeConsumers.First(c => c.Type == MessageType); // adjust this to your needs
switch (MessageType) {
case (MessageType .1) //I want SampleClass1 implementation to here
var model = JsonConvert.DeserializeObject < Model > (message);
await completeConsumer.complete(model);
break;
case (MessageType .2) // I want SampleClass2 implementation to here
var model = JsonConvert.DeserializeObject < Model > (message);
await completeConsumer.complete(model);
break;
}
You can get multiple service by IEnumerable
public class ConsumerHandlerService
{
private readonly IEnumerable<ICompleteConsumer> _completeConsumers;
public ConsumerHandlerService(IEnumerable<ICompleteConsumer> completeConsumers)
{
this.completeConsumers = completeConsumers;
}
public void DoSomeWork(string message)
{
var model = JsonConvert.DeserializeObject<Model>(message);
foreach (var completeConsumer in completeConsumers)
{
completeConsumer.Complete(model);
}
}
}
I've tried using MassTransit to publish a message to a topic named events in an Azure Service Bus. I have problems configuring MassTransit to use my predefined topic events, instead it creates a new topic named by the namespace/classname for the message type. So I wonder how to specify which topic to use instead of creating a new one.
This is the code I've tested with:
using System;
using System.Threading.Tasks;
using MassTransit;
using MassTransit.AzureServiceBusTransport;
using Microsoft.ServiceBus;
namespace PublisherNameSpace
{
public class Publisher
{
public static async Task PublishMessage()
{
var topic = "events";
var bus = Bus.Factory.CreateUsingAzureServiceBus(
cfg =>
{
var azureServiceBusHost = cfg.Host(new Uri("sb://<busname>.servicebus.windows.net"), host =>
{
host.OperationTimeout = TimeSpan.FromSeconds(5);
host.TokenProvider =
TokenProvider.CreateSharedAccessSignatureTokenProvider(
"RootManageSharedAccessKey",
"<key>"
);
});
cfg.ReceiveEndpoint(azureServiceBusHost, topic, e =>
{
e.Consumer<TestConsumer>();
});
});
await bus.Publish<TestConsumer>(new TestMessage { TestString = "testing" });
}
}
public class TestConsumer : IConsumer<TestMessage>
{
public Task Consume(ConsumeContext<TestMessage> context)
{
return Console.Out.WriteAsync("Consuming message");
}
}
public class TestMessage
{
public string TestString { get; set; }
}
}
The accepted answer clears up the subscription side:
cfg.SubscriptionEndpoint(
host,
"sub-1",
"my-topic-1",
e =>
{
e.ConfigureConsumer<TestConsumer>(provider);
});
For those wondering how to get the bus configuration right on the publish side, it should look like:
cfg.Message<TestMessage>(x =>
{
x.SetEntityName("my-topic-1");
});
You can then call publish on the bus:
await bus.Publish<TestMessage>(message);
Thanks to #ChrisPatterson for pointing this out to me!
If you want to consume from a specific topic, create a subscription endpoint instead of a receive endpoint, and specify the topic and subscription name in the configuration.
The simplest form is shown in the unit tests:
https://github.com/MassTransit/MassTransit/blob/develop/tests/MassTransit.Azure.ServiceBus.Core.Tests/Subscription_Specs.cs
I was able to send to an Azure Service Bus Topic using the _sendEndpointProvider.GetSendEndpoint(new Uri("topic:shape")); where... "shape" is the topic name.
public class MassTransitController : ControllerBase
{
private readonly ILogger<MassTransitController> _logger;
private readonly ISendEndpointProvider _sendEndpointProvider;
public MassTransitController(ILogger<MassTransitController> logger, ISendEndpointProvider sendEndpointProvider)
{
_logger = logger;
_sendEndpointProvider = sendEndpointProvider;
}
[HttpGet]
public async Task<IActionResult> Get()
{
try
{
var randomType = new Random();
var randomColor = new Random();
var shape = new Shape();
shape.ShapeId = Guid.NewGuid();
shape.Color = ShapeType.ShapeColors[randomColor.Next(ShapeType.ShapeColors.Count)];
shape.Type = ShapeType.ShapeTypes[randomType.Next(ShapeType.ShapeTypes.Count)];
var endpoint = await _sendEndpointProvider.GetSendEndpoint(new Uri("topic:shape"));
await endpoint.Send(shape);
return Ok(shape);
}
catch (Exception ex)
{
throw ex;
}
}
}
I also was able to get a .NET 5 Worker Consumer working with code like this... where the subscription "sub-all" would catch all shapes.. I'm going to make a blog post / git repo of this.
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddMassTransit(x =>
{
x.UsingAzureServiceBus((context, cfg) =>
{
cfg.Host("Endpoint=sb://******");
cfg.SubscriptionEndpoint(
"sub-all",
"shape",
e =>
{
e.Handler<Shape>(async context =>
{
await Console.Out.WriteLineAsync($"Shape Received: {context.Message.Type}");
});
e.MaxDeliveryCount = 15;
});
});
});
services.AddMassTransitHostedService();
});
I want to make use of Azure notification hub service. In all their examples. The clients directly register with azure to give the service their device token.
I want to change this model slightly in order to gain central control and also due to compatibility with existing clients.
I want all my clients to register with GCM or APNS and obtain their Token. I then want to send that token off to my own api. ASP.NET Web API. The api will then fire off a request to the Azure notification service and register on behalf of the device.
Can I achieve this? and how would I go about registering a device from the asp.net api.
In their Documentation examples it contains code to send the actual push notifications from an asp.net app. But not how to register a device (being of any type) from asp.net
Unless im being daft and missing something...
You can find this in the official documentation: Registering from your App Backend.
public class RegisterController : ApiController
{
private NotificationHubClient hub;
public RegisterController()
{
hub = NotificationHubClient.CreateClientFromConnectionString("Endpoint=sb://buildhub-ns.servicebus.windows.net/;SharedAccessKeyName=DefaultFullSharedAccessSignature;SharedAccessKey=DuWV4SQ08poV6HZly8O/KQNWv3YRTZlExJxu3pNCjGU=", "build2014_2");
}
public class DeviceRegistration
{
public string Platform { get; set; }
public string Handle { get; set; }
public string[] Tags { get; set; }
}
// POST api/register
// This creates a registration id
public async Task<string> Post()
{
return await hub.CreateRegistrationIdAsync();
}
// PUT api/register/5
// This creates or updates a registration (with provided PNS handle) at the specified id
public async void Put(string id, DeviceRegistration deviceUpdate)
{
// IMPORTANT: add logic to make sure that caller is allowed to register for the provided tags
RegistrationDescription registration = null;
switch (deviceUpdate.Platform)
{
case "mpns":
registration = new MpnsRegistrationDescription(deviceUpdate.Handle);
break;
case "wns":
registration = new WindowsRegistrationDescription(deviceUpdate.Handle);
break;
case "apns":
registration = new AppleRegistrationDescription(deviceUpdate.Handle);
break;
case "gcm":
registration = new GcmRegistrationDescription(deviceUpdate.Handle);
break;
default:
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
registration.RegistrationId = id;
registration.Tags = new HashSet<string>(deviceUpdate.Tags);
try
{
await hub.CreateOrUpdateRegistrationAsync(registration);
} catch (MessagingException e) {
ReturnGoneIfHubResponseIsGone(e);
}
}
// DELETE api/register/5
public async void Delete(string id)
{
await hub.DeleteRegistrationAsync(id);
}
private static void ReturnGoneIfHubResponseIsGone(MessagingException e)
{
var webex = e.InnerException as WebException;
if (webex.Status == WebExceptionStatus.ProtocolError)
{
var response = (HttpWebResponse)webex.Response;
if (response.StatusCode == HttpStatusCode.Gone)
throw new HttpRequestException(HttpStatusCode.Gone.ToString());
}
}
}