This article explains details on how to use Custom Action to resolve the issue we found before, weather status icon (svg/xml) could not be displayed in Teams Channel:
Publish Bot App to Teams Channel with Bot Framework Composer and Teams Developer Portal
And the article also applys the tips I shared before:
Tips of Building Custom Action in Bot Framework Composer V2
Now let’s start:
npm i -g @microsoft/botframework-cli
Note: The MyWeatherBot uses 4.15.0 for Microsoft.Bot.Builder packages, so we choose the same version for this Custom Action project as well. If your bot project uses newer version, you can also choose the same.
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using AdaptiveCards;
using AdaptiveExpressions.Properties;
using Microsoft.Bot.Builder.Dialogs;
using Newtonsoft.Json;
using Svg;
public class MyCustomActionDialog : Dialog
{
[JsonConstructor]
public MyCustomActionDialog([CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
: base()
{
// enable instances of this command as debug break point
RegisterSourceLocation(sourceFilePath, sourceLineNumber);
}
[JsonProperty("$kind")]
public const string Kind = "MyCustomActionDialog";
[JsonProperty("adaptiveCardString")]
public StringExpression AdaptiveCardString { get; set; }
[JsonProperty("resultProperty")]
public StringExpression ResultProperty { get; set; }
public override Task<DialogTurnResult> BeginDialogAsync(DialogContext dc, object options = null, CancellationToken cancellationToken = default(CancellationToken))
{
var cardString = AdaptiveCardString.GetValue(dc.State);
var cardParseResult = AdaptiveCard.FromJson(cardString);
//Convert Weather SVG to PNG
AdaptiveColumnSet columnSet = (AdaptiveColumnSet)cardParseResult.Card.Body[1];
AdaptiveImage image = (AdaptiveImage)columnSet.Columns[0].Items[0];
string result;
string svgheader = "data:image/svg+xml;base64,";
string pngheader = "data:image/png;base64,";
byte[] svgBytes = Convert.FromBase64String(image.Url.ToString().Replace(svgheader,""));
byte[] pngBytes;
string pngBase64String = "";
using (Stream stream = new MemoryStream(svgBytes))
{
var svgDocument = SvgDocument.Open<SvgDocument>(stream);
var bitmap = svgDocument.Draw(120, 120);
using (var pngstream = new MemoryStream())
{
bitmap.Save(pngstream, System.Drawing.Imaging.ImageFormat.Png);
pngBytes = pngstream.ToArray();
}
}
pngBase64String = pngheader + Convert.ToBase64String(pngBytes);
image.Url = new Uri(pngBase64String);
//Cusomize the card foot
AdaptiveContainer container = (AdaptiveContainer)cardParseResult.Card.Body[2];
AdaptiveTextBlock footText = (AdaptiveTextBlock)container.Items[0];
footText.Text = "Generated by my ***custom action***";
footText.Wrap = true;
result = cardParseResult.Card.ToJson();
if (this.ResultProperty != null)
{
dc.State.SetValue(this.ResultProperty.GetValue(dc.State), result);
}
return dc.EndDialogAsync(result: result, cancellationToken: cancellationToken);
}
}
IMPORTANT:
The name of the .schema file must match the Kind variable defined in the MyCustomActionDialog.cs file exactly, including casing.
{
"$schema": "https://schemas.botframework.com/schemas/component/v1.0/component.schema",
"$role": "implements(Microsoft.IDialog)",
"title": "AdaptiveCardFixing",
"description": "This will return proper weather adaptive card for Teams ",
"type": "object",
"additionalProperties": false,
"properties": {
"adaptiveCardString": {
"$ref": "schema:#/definitions/stringExpression",
"title": "AdaptiveCardString",
"description": "original weather adaptive card string"
},
"resultProperty": {
"$ref": "schema:#/definitions/stringExpression",
"title": "Result",
"description": " Weather adaptive card string for Teams"
}
}
}
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs.Declarative;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
public class MyCustomActionDialogBotComponent : BotComponent
{
public override void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
// Anything that could be done in Startup.ConfigureServices can be done here.
// In this case, the MyCustomActionDialog needs to be added as a new DeclarativeType.
services.AddSingleton<DeclarativeType>(sp => new DeclarativeType<MyCustomActionDialog>(MyCustomActionDialog.Kind));
}
}
"runtimeSettings": {
....
"components": [
{
"name": "MyCustomActionDialog"
}
],
....
}
bf dialog:merge "*.schema" "!**/sdk-backup.schema" "*.uischema" "!**/sdk-backup.uischema" "!**/sdk.override.uischema" "!**/generated" "../*.csproj" "../package.json" -o $SCHEMA_FILE
To:
bf dialog:merge "*.schema" "!**/sdk-backup.schema" "*.uischema" "!**/sdk-backup.uischema" "!**/sdk.override.uischema" "!../generated" "!../dialogs/imported" "../*.csproj" "../package.json" -o $SCHEMA_FILE
Save the file.
Note: Refer to bug to understand why we need to modify the update-schema.sh:
.\update-schema.ps1
IMPORTANT:
Deactivated action. Components of $kind "Microsoft.AdaptiveDialog" are not supported
Note: Refer to bug to understand why we need to remove it:
Without this step, other previous installed nuget packages default dialogs may be used by composer again from under MyWeatherBot\MyWeatherBot\schemas\Imported, instead of the customized version we have modified before in dialogs\imported, such as the highlighted one:
The CardActivity contains adaptive card json content. We will pass the json content to our custom action, so that the SVG base64 string can be replaced by PNG base64 string:
> !# @Namespace = Weather
> !# @Exports = CardActivity
Note: About the lg export option, refer to:
.lg file format - Bot Service | Microsoft Docs
Configure the new property and Value as below:
Property: turn.cardstring
Value: =Weather.CardActivity()
In the AdaptiveCardFixing custom action, configure its properties as below:
AdaptiveCardString: ${turn.cardstring}
Result: dialog.result
In the action, click + to add a attachment, use below content in the attachment field:
- ```
${dialog.result}
```
Now this part layout is:
After following all above steps, you successfully use Custom Action to solve the real issue specifically for Teams Channel. Congratulations!
Happy Bot Development!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.