Creating a C# Amazon SQS Client in ServiceStack - servicestack

There is some documentation on using Amazon SQS as an MQ Server forServiceStack Messaging API
But the message publisher is frontend web code and when you dig into the Email Contacts demo app, it is using Rabbit MQ.
There is a ServiceStack Email Contacts AWS App demo but it doesn't use the Messaging API.
Trying to use the Rabbit MQ Integration Test in Email Contacts as an example:
[Test]
public void Can_Send_Email_via_MqClient()
{
var mqFactory = new RabbitMqMessageFactory();
using (var mqClient = mqFactory.CreateMessageQueueClient())
{
mqClient.Publish(new EmailContact { ContactId = 1, Subject = "UnitTest MQ Email #1", Body = "Body 1" });
mqClient.Publish(new EmailContact { ContactId = 1, Subject = "UnitTest MQ Email #2", Body = "Body 2" });
}
}
I quickly confused and lead astray and the ServiceStack API for Sqs seems very different than RabbitMQ. I cannot even seem to be able to use a strongly type POCO as a Message:
[Fact(DisplayName = "Tests that a successful message is published and received")]
public async void TestMessage()
{
var mqFactory = new SqsConnectionFactory("awskey", "awssecret", RegionEndpoint.USWest1);
using (IAmazonSQS mqClient = mqFactory.GetClient())
{
var req = new SendMessageRequest("query", "hello");
await mqClient.SendMessageAsync(req);
//mqClient.Publish(new Hello { Name = "World" });
//var rec = new ReceiveMessageRequest();
//await mqClient.Re
//var responseMsg = mqClient.Get<HelloResponse>(QueueNames<HelloResponse>.In);
//mqClient.Ack(responseMsg);
//responseMsg.GetBody().Result //= Hello, World!
}
}
Is there an example app using the ServiceStack Messaging API with SQS as the MQ Server?

There is a ServiceStack Email Contacts AWS App demo but it doesn't use the Messaging API.
Note AWS Apps does register the AWS SqsMqServer:
//EmailContacts
ConfigureSqsMqServer(container);
//..
private void ConfigureSqsMqServer(Container container)
{
container.Register<IMessageService>(c => new SqsMqServer(
AwsConfig.AwsAccessKey, AwsConfig.AwsSecretKey, RegionEndpoint.USEast1) {
DisableBuffering = true,
});
var mqServer = container.Resolve<IMessageService>();
mqServer.RegisterHandler<EmailContacts.EmailContact>(ExecuteMessage);
mqServer.Start();
}
There's also a number of examples in SqsMqServerTests.cs.
If you want to use ServiceStack MQ's high-level APIs, you'd need to use ServiceStack's MQ classes instead of AWS's SQS classes directly.
Basically it works like every other MQ Server, you can fetch an MQ Client from the IMessageFactory or IMessageService (registered in your AppHost) and use it to publish DTOs:
var mqFactory = HostContext.TryResolve<IMessageFactory>(); //or
//var mqFactory = HostContext.TryResolve<IMessageService>().MessageFactory;
using (var mqClient = mqFactory.CreateMessageQueueClient())
{
mqClient.Publish(new Hello { Name = "World" });
}
Although the preferred API within a ServiceStack Service is to use PublishMessage():
PublishMessage(new Hello { Name = "World" });
Note requests to the /oneway pre-defined endpoint are automatically published to the registered MQ Server.
In client Apps without a registered SqsMqServer you'd create a SqsMqMessageFactory:
var mqFactory = new SqsMqMessageFactory(new SqsQueueManager(...));
using (var mqClient = mqFactory.CreateMessageQueueClient())
{
mqClient.Publish(new Hello { Name = "World" });
}

Related

.net 6 Blazor WebAssembly Deployed to Azure 400 Error when trying to send email via SendGrid

I have a basic Blazor WebAssembly project that is using SendGrid to send form data via email. Locally it works fine. I have deployed to Azure App Service and setup API Management as well.
Locally I set an EnvironmentVariable to hold my SendGrid Api key.
This is the EmailService code.
public async Task<ServiceResponse<Contact>> SendEmail(Contact info)
{
var apiKey = Environment.GetEnvironmentVariable("SENDGRID_API_KEY");
var toEmail = _config.GetSection("SendGrid:EmailTo").Value;
var toName = _config.GetSection("SendGrid:EmailName").Value;
var _subject = _config.GetSection("SendGrid:EmailSubject").Value;
var client = new SendGridClient(apiKey);
var from = new EmailAddress($"{info.Email}", $"{info.Name}");
var subject = _subject;
var to = new EmailAddress(toEmail, toName);
var plainTextContent = info.Message;
var htmlContent = info.Message;
var msg = MailHelper.CreateSingleEmail(from, to, subject, plainTextContent, htmlContent);
var response = await client.SendEmailAsync(msg);
if (response.IsSuccessStatusCode)
{
return new ServiceResponse<Contact>
{
Data = info,
Success = true,
Message = "Message has been sent."
};
}
else
{
return new ServiceResponse<Contact> { Success = false };
}
}
In Azure App Service I created an Application setting named "SENDGRID_API_KEY" and it holds the SendGrid API key. I also tried modifying the request before it is sent and added the Authorization header there as well.
When the form submits, it returns a 400 (Bad Request) error.
I do not have a secure certificate on this site yet.
Any ideas? Please let me know if you need more info.

Configuring Twilio SMS from Azure Functions v2

I have some code where I'm reading messages off of an Azure Event Hub that I want to either send an email or send an SMS.
The email is working through send grid, but I'm not sure how to configure the SMS part though.
I think I'd want to use Twilio and here's a sample of what my code's like. The "messageCollector" works for sending Email since there's some configuration for SendGrid in the local json. How do I configure Twilio?
[FunctionName("SendAlert")]
public static async Task Run(
[EventHubTrigger("v1-email-hub", Connection = "EventHubConnection")] EventData[] events,
[SendGrid] IAsyncCollector<SendGridMessage> messageCollector,
[TwilioSms] IAsyncCollector<CreateMessageOptions> smsCollector,
[Inject] NotificationEventLogic eventLogic,
ILogger log)
{
foreach (EventData eventData in events)
{
string messageBody = Encoding.UTF8.GetString(eventData.Body.Array, eventData.Body.Offset, eventData.Body.Count);
var notificationEvents = JsonConvert.DeserializeObject<List<NotificationEvent>>(messageBody);
foreach (var ev in notificationEvents)
{
if (ev.NotificationEventType == NotificationEventType.Email)
{
var message = new SendGridMessage();
// ... ... make message and add it
await messageCollector.AddAsync(message);
}
else if (ev.NotificationEventType == NotificationEventType.SMS)
{
// Not sure how to get this to work
var mobileMessage = new CreateMessageOptions(new PhoneNumber(ev.Data))
{
Body = $"Notification {ev.NotificationId}"
};
await smsCollector.AddAsync(mobileMessage);
}
// await smsCollector.AddAsync()
await eventLogic.CreateEventAsync(ev);
}
}
}
You will need to configure it in attribute
[TwilioSms(AccountSidSetting = "TwilioAccountSid", AuthTokenSetting = "TwilioAuthToken", From = "+1425XXXXXXX")]
as it mentioned in documentation
TwilioAccountSid This value must be set
to the name of an app setting that holds your Twilio Account Sid e.g.
TwilioAccountSid. If not set, the default app setting name is
"AzureWebJobsTwilioAccountSid".
TwilioAuthToken This value must be set to
the name of an app setting that holds your Twilio authentication token
e.g. TwilioAccountAuthToken. If not set, the default app setting name
is "AzureWebJobsTwilioAuthToken".

Using Asp.net MVC 5 with RabbitMQ and SignalR

I have an asp.net MVC 5 application. Now I want to have a new feature allow me to receive GPS signal from devices and update devices real time in web client side.
My plan is to use RabbitMQ to handle message queue and SignalR to notify clients for postion update
My code in Rabbit consumer console application like this
var client = GlobalHost.ConnectionManager.GetHubContext<MyHub>().Clients;
using (var connection = factory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
var queueName = channel.QueueDeclare().QueueName;
channel.ExchangeDeclare("mychannel", "fanout");
channel.QueueBind(queueName, "mychannel", "");
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body;
var message = Encoding.UTF8.GetString(body);
Console.WriteLine("[x] Receive {0}", message);
client.All.hello(message);
};
channel.BasicConsume(queue: queueName,
autoAck: true,
consumer: consumer);
Console.WriteLine("Press Enter to quit");
Console.ReadLine();
}
}
But the code is not fired when I produce a message to RabbitMQ. If I remove code of signalR client:
var client = GlobalHost.ConnectionManager.GetHubContext<MyHub>().Clients;
things work as expected and I can receive message as usual
My question is:
Is my approach correct
if the approach correct then how to make it work?
Many thanks

ServiceStack minimum configuration to get Redis Pub/Sub working between multiple Web sites/services

Let's say for sake of argument I have 3 web service hosts running, and only one of them has registered any handlers (which I think equates to subscribing to the channel/topic) e.g.
var mqService = new RedisMqServer(container.Resolve<IRedisClientsManager>())
{
DisablePriorityQueues = true
};
container.Register<IMessageService>(mqService);
container.Register(mqService.MessageFactory);
mqService.RegisterHandler<OutboundInitiateCallInfo>(ServiceController.ExecuteMessage);
mqService.RegisterHandler<DirectMailAssignmentInfo>(ServiceController.ExecuteMessage);
mqService.Start();
Now my question is, "Do I need to construct the other app hosts in the same fashion if they only publish??" e.g.
var mqService = new RedisMqServer(container.Resolve<IRedisClientsManager>())
{
DisablePriorityQueues = true
};
container.Register<IMessageService>(mqService);
container.Register(mqService.MessageFactory);
mqService.Start(); <=== Do I need to start the service, or is the MessageFactory registration enough?
Thank you,
Stephen
The minimum code for a publisher is just:
var redisManager = container.Resolve<IRedisClientsManager>();
using (var mqProducer = new RedisMessageProducer(redisManager))
{
mqProducer.Publish(new Msg { ... });
}
You could also use a MessageFactory:
var msgFactory = new RedisMessageFactory(redisMangager);
using (var mqClient = msgFactory.CreateMessageQueueClient())
{
mqClient.Publish(new Msg { ... });
}

Servicestack-RabbitMq: Return response type in message headers

Is there any way to add the type of the response dto to the rabbitmq response message's headers collection?
(My consumer is using spring's rabbitmq handler which seems to depend on explicit type information inside the mq header when deserializing.)
Currently servicestack's mq producer already returns serveral headers, such as "content_type='application/json".
I am in need of an additional header, e.g. "typeId"="HelloResponse", so that the consuming web app knows how to deserialize the message, even in RPC cases where the response queue name is some kind of GUID.
Is there some kind of configuration which would enable me to archieve such an behaviour? Or some hook before the message gets published so that I can add the header myself?
I've added support for automatically populating the Message Body Type in RabbitMQ's IBasicProperties.Type as well as adding support for both Publish and GetMessage Filters in this commit.
Here's an example of configuring a RabbitMqServer with custom handlers where you can modify the message and its metadata properties when its published and received:
string receivedMsgApp = null;
string receivedMsgType = null;
var mqServer = new RabbitMqServer("localhost")
{
PublishMessageFilter = (queueName, properties, msg) => {
properties.AppId = "app:{0}".Fmt(queueName);
},
GetMessageFilter = (queueName, basicMsg) => {
var props = basicMsg.BasicProperties;
receivedMsgType = props.Type; //automatically added by RabbitMqProducer
receivedMsgApp = props.AppId;
}
};
mqServer.RegisterHandler<Hello>(m =>
new HelloResponse { Result = "Hello, {0}!".Fmt(m.GetBody().Name) });
mqServer.Start();
Once Configured any message published or received will go through the above handlers, e.g:
using (var mqClient = mqServer.CreateMessageQueueClient())
{
mqClient.Publish(new Hello { Name = "Bugs Bunny" });
}
receivedMsgApp.Print(); // app:mq:Hello.In
receivedMsgType.Print(); // Hello
using (IConnection connection = mqServer.ConnectionFactory.CreateConnection())
using (IModel channel = connection.CreateModel())
{
var queueName = QueueNames<HelloResponse>.In;
channel.RegisterQueue(queueName);
var basicMsg = channel.BasicGet(queueName, noAck: true);
var props = basicMsg.BasicProperties;
props.Type.Print(); // HelloResponse
props.AppId.Print(); // app:mq:HelloResponse.Inq
var msg = basicMsg.ToMessage<HelloResponse>();
msg.GetBody().Result.Print(); // Hello, Bugs Bunny!
}
This change is available from ServiceStack v4.0.33+ that's now available on MyGet.

Resources