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

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 { ... });
}

Related

Recording Bot using Skype.bot.media

We are creating a bot that can join the team meeting and it can start the recording as it joins the team meeting. But we are getting this error(Expected not null
Parameter name: client). I am attaching the code below:
when debugger goes to CreateLocalMediaSession() session method then at that method it gives the error.(Expected not null
Parameter name: client)
public async Task<ICall> JoinCallAsync()
{
// A tracking id for logging purposes. Helps identify this call in logs.
var scenarioId = Guid.NewGuid();
var (chatInfo, meetingInfo) = JoinInfo.ParseJoinURL("https://teams.microsoft.com/l/meetup-join/19:meeting_YTI5NDQ2ODQtMmNlNy00YTBhLTg2NTMtYmZmOGIyMzdhMTgw#thread.v2/0?context=%7B%22Tid%22:%22204d6395-ea6c-4e64-abea-e04cd30845e2%22,%22Oid%22:%225a95f69b-70e2-40d3-8b9a-5810ffcc6ec9%22%7D");
var tenantId = (meetingInfo as OrganizerMeetingInfo).Organizer.GetPrimaryIdentity().GetTenantId();
var mediaSession = this.CreateLocalMediaSession(scenarioId);
var joinParams = new JoinMeetingParameters(chatInfo, meetingInfo, mediaSession)
{
TenantId = tenantId,
};
if (!string.IsNullOrWhiteSpace("bot"))
{
// Teams client does not allow changing of one's display name.
// If the display name is specified, we join as an anonymous (guest) user
// with the specified display name. This will put the bot in lobby
// unless lobby bypass is disabled.
joinParams.GuestIdentity = new Identity
{
Id = Guid.NewGuid().ToString(),
DisplayName = "bot",
};
}
var statefulCall = await this.Client.Calls().AddAsync(joinParams, scenarioId).ConfigureAwait(false);
statefulCall.GraphLogger.Info($"Call creation complete: {statefulCall.Id}");
return statefulCall;
}
Code for creating local media session:
private ILocalMediaSession CreateLocalMediaSession(Guid mediaSessionId = default)
{
try
{
// create media session object, this is needed to establish call connections
return this.Client.CreateMediaSession(
new AudioSocketSettings
{
StreamDirections = StreamDirection.Recvonly,
// Note! Currently, the only audio format supported when receiving unmixed audio is Pcm16K
SupportedAudioFormat = AudioFormat.Pcm16K,
ReceiveUnmixedMeetingAudio = true //get the extra buffers for the speakers
},
new VideoSocketSettings
{
StreamDirections = StreamDirection.Inactive
},
mediaSessionId: mediaSessionId);
}
catch (Exception e)
{
_logger.Log(System.Diagnostics.TraceLevel.Error, e.Message);
throw;
}
}
We are creating a bot that can join the team meeting and it can start the recording as it joins the team meeting. But we are getting this error(Expected not null
Parameter name: client). I am attaching the code below:
error facing:
enter image description here

Is there any way to capture and save end-to-end conversation data into blob storage or cosmos DB in bot framework using SDK V4 Nodedjs

I want to store the conversation data to the storage account or cosmos DB. By trying this https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-howto-v4-storage?view=azure-bot-service-4.0&tabs=javascript#using-blob-storage
I am able to send the utteranceslog into blob storage. But I want to store end-to-end conversation data which includes data of both users as well as bot responses using javascript.
I tried using saving user state and conversation state but didn't achieve the desired output.
I created a custom logger (based on an old botduilder-samples sample that isn't there anymore) that accomplishes this using TranscriptLoggerMiddleware. I chose CosmosDB instead of Blob Storage because I felt it was easier to store (and retrieve) as a JSON document. But you could tweak this concept to use any DB. Here is what I did.
First, create your custom logger code. As mentioned, I used CosmosDB so you might have to change some things if you're using a different DB. The timing of the activities was creating concurrency issues, so instead of working around that, I'm storing the transcript object locally and overwriting the DB object on each turn. Maybe not the most elegant, but it works. Also, I've found my wait function to be required. Otherwise you only get one side of the conversation. I've been told this type of wait function is not a best practice, but awaiting a promise or other methods of creating a delay did not work for me. Here is the code:
customerLogger.js
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
const { CosmosDbStorage } = require('botbuilder-azure');
const path = require('path');
/**
* CustomLogger, takes in an activity and saves it for the duration of the conversation, writing to an emulator compatible transcript file in the transcriptsPath folder.
*/
class CustomLogger {
/**
* Log an activity to the log file.
* #param activity Activity being logged.
*/
// Set up Cosmos Storage
constructor(appInsightsClient) {
this.transcriptStorage = new CosmosDbStorage({
serviceEndpoint: process.env.COSMOS_SERVICE_ENDPOINT,
authKey: process.env.COSMOS_AUTH_KEY,
databaseId: process.env.DATABASE,
collectionId: 'bot-transcripts'
});
this.conversationLogger = {};
this.appInsightsClient = appInsightsClient;
this.msDelay = 250;
}
async logActivity(activity) {
if (!activity) {
throw new Error('Activity is required.');
}
// Log only if this is type message
if (activity.type === 'message') {
if (activity.attachments) {
var logTextDb = `${activity.from.name}: ${activity.attachments[0].content.text}`;
} else {
var logTextDb = `${activity.from.name}: ${activity.text}`;
}
if (activity.conversation) {
var id = activity.conversation.id;
if (id.indexOf('|') !== -1) {
id = activity.conversation.id.replace(/\|.*/, '');
}
// Get today's date for datestamp
var currentDate = new Date();
var day = currentDate.getDate();
var month = currentDate.getMonth()+1;
var year = currentDate.getFullYear();
var datestamp = year + '-' + month + '-' + day;
var fileName = `${datestamp}_${id}`;
var timestamp = Math.floor(Date.now()/1);
// CosmosDB logging (JK)
if (!(fileName in this.conversationLogger)) {
this.conversationLogger[fileName] = {};
this.conversationLogger[fileName]['botName'] = process.env.BOTNAME;
}
this.conversationLogger[fileName][timestamp] = logTextDb;
let updateObj = {
[fileName]:{
...this.conversationLogger[fileName]
}
}
// Add delay to ensure messages logged sequentially
await this.wait(this.msDelay);
try {
let result = await this.transcriptStorage.write(updateObj);
} catch(err) {
this.appInsightsClient.trackTrace({message: `Logger ${err.name} - ${path.basename(__filename)}`,severity: 3,properties: {'botName': process.env.BOTNAME, 'error':err.message,'callStack':err.stack}});
}
}
}
}
async wait(milliseconds) {
var start = new Date().getTime();
for (var i = 0; i < 1e7; i++) {
if ((new Date().getTime() - start) > milliseconds) {
break;
}
}
}
}
exports.CustomLogger = CustomLogger;
Now you need to attach this to the botframework adapter in your index.js file. The relevant pieces of code are:
index.js
const { TranscriptLoggerMiddleware } = require('botbuilder');
const { CustomLogger } = require('./helpers/CustomLogger');
//
//Your code to create your adapter, etc.
//
const transcriptLogger = new TranscriptLoggerMiddleware(new CustomLogger(appInsightsClient));
adapter.use(transcriptLogger);
I'm assuming here you already have your index.js file figured out, but if you need any assistance getting that set up and getting the transcript logger to work with it, just let me know.
EDIT: By request, here is what the object looks like in CosmosDB. Normally I would have the "from name" displayed, but because of the way I was testing the bot it came through "undefined".
{
"id": "2020-3-21_IfHK46rZV42KH5g3dIUgKu-j",
"realId": "2020-3-21_IfHK46rZV42KH5g3dIUgKu-j",
"document": {
"botName": "itInnovationBot",
"1584797671549": "Innovation Bot: Hi! I'm the IT Innovation Bot. I can answer questions about the innovation team and capture your innovation ideas. Let me know how I can help!",
"1584797692355": "undefined: Hello",
"1584797692623": "Innovation Bot: Hello.",
"1584797725223": "undefined: Tell me about my team",
"1584797725490": "Innovation Bot: The innovation team is responsible for investigating, incubating, and launching new technologies and applications. The innovation focus areas are:\n\n* Chatbots\n\n* Augmented Reality/Virtual Reality\n\n* Blockchain\n\n* Robotic Process Automation\n\n* AI & Machine Learning\n\nLet me know if you want to learn more about any of these technologies!",
"1584797746279": "undefined: Thanks",
"1584797746531": "Innovation Bot: You're welcome."
},
"_rid": "OsYpALLrTn2TAwAAAAAAAA==",
"_self": "dbs/OsYpAA==/colls/OsYpALLrTn0=/docs/OsYpALLrTn2TAwAAAAAAAA==/",
"_etag": "\"a4008d12-0000-0300-0000-5e7618330000\"",
"_attachments": "attachments/",
"_ts": 1584797747
}
To read the conversation back (even if still in the middle of the conversation), you just create a connector in your bot, recreate the key, and read the file as below (in this case id is passed into my function and is the conversation id):
const transcriptStorage = new CosmosDbStorage({
serviceEndpoint: process.env.COSMOS_SERVICE_ENDPOINT,
authKey: process.env.COSMOS_AUTH_KEY,
databaseId: process.env.DATABASE,
collectionId: 'bot-transcripts',
partitionKey: process.env.BOTNAME
});
// Get today's date for datestamp
var currentDate = new Date();
var day = currentDate.getDate();
var month = currentDate.getMonth()+1;
var year = currentDate.getFullYear();
var datestamp = year + '-' + month + '-' + day;
var filename = `${datestamp}_${id}`;
var transcript = await transcriptStorage.read([filename]);

Unable to get a Stripe Checkout session object

Stripe.Net v34.16.0 bounces my code on the creation of the checkout session object responding with:
StripeException: No such plan: plan_myPlanId; a similar object exists in live mode, but a test mode key was used to make this request.
I do not see a means in the Stripe Dashboard to designate a given plan as a test plan .I also do not see
anything resembling a mode property.. my code
public async Task<IActionResult> Index()
{
//var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
//user = await _userManager.FindByIdAsync(userId);
StripeConfiguration.ApiKey = "sk_test_mytestkey";
var options = new Stripe.Checkout.SessionCreateOptions
{
PaymentMethodTypes = new List<string> {
"card",
},
SubscriptionData = new Stripe.Checkout.SessionSubscriptionDataOptions
{
Items = new List<SessionSubscriptionDataItemOptions> {
new SessionSubscriptionDataItemOptions {
Plan = "plan_myplanid",
},
},
},
//to do
SuccessUrl = "localhost://home",
CancelUrl = "localhost://home",
//CancelUrl = "https://example.com/cancel",
};
var service = new Stripe.Checkout.SessionService();
Stripe.Checkout.Session session = service.Create(options); //error out here
StripeCheckoutSessionId stripeCheckoutSessionId = new StripeCheckoutSessionId();
stripeCheckoutSessionId.StripeSessionID = session.Id;
return View(stripeCheckoutSessionId);
}
I am referring to Stripe sample code in the .Net tab here: https://stripe.com/docs/payments/checkout/subscriptions/starting
I appreciate your guidance in correcting my errors.

Creating a C# Amazon SQS Client in 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" });
}

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