I'm developing a Teams Message Extension and I'm using ThumbnailCard to display my results, however I wanted to use a custom adaptive card. Is that possible?
var resultCardList = GetAttachments(title);
var response = new ComposeExtensionResponse(new ComposeExtensionResult("list", "result"));
response.ComposeExtension.Attachments = resultCardList.ToList();
return response;
foreach (var contract in contractItems)
{
var lastModified = (DateTime)contract["Modified"];
var justificativa = contract["JustificativaContrato"];
var card = new ThumbnailCard
{
Title = $"{contract.Client_Title} - {lastModified.ToShortDateString()} {lastModified.ToLongTimeString()}",
Text = $"Justificativa: {justificativa}",
Tap = new CardAction { Type = "openUrl", Value = $"{Tenant}{ContractList.DefaultEditFormUrl}?ID={contract.Id}" },
Images = new List<CardImage> { new CardImage("http://lorempixel.com/640/480?rand=" + DateTime.Now.Ticks.ToString()) }
};
cardList.Add(card
.ToAttachment()
.ToComposeExtensionAttachment());
}
return cardList;
I've tried to use the below method to generate the Adaptive Card and just add it to the list:
private static Microsoft.Bot.Connector.Attachment CreateAdaptiveCardAttachment()
{
// combine path for cross platform support
string[] paths = { ".", "Cards", "welcomeCard.json" };
string fullPath = Path.Combine(paths);
var adaptiveCard = System.IO.File.ReadAllText(#"Cards\welcomeCard.json");
return new Microsoft.Bot.Connector.Attachment()
{
ContentType = "application/vnd.microsoft.card.adaptive",
Content = JsonConvert.DeserializeObject(adaptiveCard),
};
}
The messaging extension does not allow sending adaptive cards like that.
It requires using the "MessagingExtensionResult" of the framework and just the card sent in the response. The documentation is a bit lacking here.
When you get a call from the messaging extension its action is of type "composeExtension/query"
Create the general "result" list like this:
var invokeResponse = new MessagingExtensionResponse();
var results = new MessagingExtensionResult
{
AttachmentLayout = "list",
Type = "result",
Attachments = new List<MessagingExtensionAttachment>(),
};
For each result in the list you need to create a MessagingExtensionAttachment like this: (Note: Cards need to have a preview!)
results.Attachments.Add(new MessagingExtensionAttachment
{
ContentType = "application/vnd.microsoft.teams.card.adaptive",
Content = JsonConvert.DeserializeObject(cardData),
Preview = new Attachment
{
ContentType = "application/vnd.microsoft.card.thumbnail",
Content = new AttachmentContent
{
text = "Project: " + task.ProjectName,
title = task.Name,
},
}
});
Finally send the result as "InvokeResponse"
return new InvokeResponse
{
Body = invokeResponse,
Status = 200,
};
While "content" of the attachment is the full adaptive card.
You can find an example for the response in json here:
https://learn.microsoft.com/de-de/microsoftteams/platform/concepts/messaging-extensions/search-extensions#response-example
You can freely mix card types based on that, but i never got that working tho...as of now you need to limit to one specific card type as far as i know.
All above is if you're using
Microsoft.Bot.Builder.Teams
Microsoft.Bot.Connector.Teams
Microsoft.Bot.Schema.Teams
in the latest versions.
Related
Hi i tried all these methods of attaching a video to a bot. All of them are working fine in bot emulator. But when i publish it to messenger it is throwing an exception . (I can't see the exception by the way i just know because of the message. Is there a way to see or log exceptions?). Is video card not supported in messenger? Or is youtube not supported as an url link?
Here are the codes:
AddStep(async (stepContext, cancellationToken) =>
{
var reply = stepContext.Context.Activity.CreateReply();
reply.Attachments = new List<Attachment>();
reply.Attachments.Add(GetVideoCard().ToAttachment());
await stepContext.Context.SendActivityAsync(reply, cancellationToken);
return await stepContext.NextAsync();
});
////////////////
private static VideoCard GetVideoCard()
{
var videoCard = new VideoCard
{
Title = "Budgeting Introduction",
Subtitle = "by Finko",
Media = new List<MediaUrl>
{
new MediaUrl()
{
Url = "https://www.youtube.com/watch?v=XLo1geVokhA",
},
},
Buttons = new List<CardAction>
{
new CardAction()
{
Title = "Learn More at Finko.PH",
Type = ActionTypes.OpenUrl,
Value = "https://m-moreno.wixsite.com/finkoph?fbclid=IwAR1NVtlyKfzZ0mYFIWva8L-d8TUv4KFpt_m1i1ij3raT-pbWr2c3-kqzB2Q",
},
},
};
return videoCard;
}
and
AddStep(async (stepContext, cancellationToken) =>
{
var activity = stepContext.Context.Activity;
await stepContext.Context.SendActivityAsync(CreateResponse(activity, CreateVideoCardAttacment()));
return await stepContext.NextAsync();
});
////////////////////////
private Activity CreateResponse(Activity activity, Attachment attachment)
{
var response = activity.CreateReply();
response.Attachments = new List<Attachment>() { attachment };
return response;
}
private Attachment CreateVideoCardAttacment()
{
return new VideoCard()
{
Title = "Are you a Seafarer? OFW? FREE PERSONAL FINANCIAL ADVICE HERE!!",
Media = new List<MediaUrl>()
{
new MediaUrl("https://www.youtube.com/watch?v=XLo1geVokhA")
},
Buttons = new List<CardAction>()
{
new CardAction()
{
Type = ActionTypes.OpenUrl,
Title = "Learn More at Finko.PH",
Value = "https://m-moreno.wixsite.com/finkoph?fbclid=IwAR1NVtlyKfzZ0mYFIWva8L-d8TUv4KFpt_m1i1ij3raT-pbWr2c3-kqzB2Q"
}
},
Subtitle = "by Finko.Ph",
Text = "Are you tired of getting bogus financial advice? Tired of having 'kape' just to find out it was networking, or a pyramid scheme? Tired of scouring the internet for HOURS but not finding what you're looking for? We're here to help! We give financial advice and will educate you on financial literacy topics, ABSOLUTELY FREE!!"
}.ToAttachment();
}
and
Activity reply = stepContext.Context.Activity.CreateReply();
var card = new VideoCard
{
Title = "Finko.ph",
Media = new List<MediaUrl>()
{
new MediaUrl("https://www.youtube.com/watch?v=XLo1geVokhA")
},
Buttons = new List<CardAction>()
{
new CardAction()
{
Type = ActionTypes.OpenUrl,
Title = "Learn More at Finko.PH",
Value = "https://m-moreno.wixsite.com/finkoph?fbclid=IwAR1NVtlyKfzZ0mYFIWva8L-d8TUv4KFpt_m1i1ij3raT-pbWr2c3-kqzB2Q"
}
},
};
reply.Attachments.Add(card.ToAttachment());
await stepContext.Context.SendActivityAsync(reply);
return await stepContext.NextAsync();
and
var reply1 = stepContext.Context.Activity.CreateReply();
var attachment1 = new Attachment
{
ContentUrl = "https://www.youtube.com/watch?v=XLo1geVokhA",
ContentType = "video/mp4",
Name = "imageName1",
};
reply1.Attachments = new List<Attachment>() { attachment1 };
await stepContext.Context.SendActivityAsync(reply1, cancellationToken);
return await stepContext.NextAsync();
All of these codes are working in bot emulator but not in messenger. Any help would be appreciated thank you.
The BotFramework converts Video Cards into Media Templates for Facebook Messenger, and per Facebook's Developer documentation, media templates do not allow any external URLs, only those on Facebook. You must either upload the video to Facebook or provide a URL directly to the mp4 file which, unfortunately, YouTube doesn't make readily available.
For more details, take a look at Facebooks documentation regarding Media Templates.
Within a bot,we have an adaptive card where the user has a choice to select yes or no.
On selecting YES, user is prompted to enter the keywords.
After the user gives input in the textblock in adaptive card, the input has to be captured and sent as input parameter to web api.
The user input will be given in Placeholder of the AdaptiveTextInput block.
public static Attachment GetUserInputForCustomPPT()
{
AdaptiveCard card = new AdaptiveCard()
{
Id = "GetCustomPPT",
Body = new List<AdaptiveElement>()
{
new AdaptiveTextBlock()
{
Text = "Do you want to apply filter and customise the PPT?",
Wrap=true,
Size = AdaptiveTextSize.Small
},
new AdaptiveContainer()
{
Id = "getCustomPPTNo",
SelectAction = new AdaptiveSubmitAction()
{
Id = "getCustomPPTNo",
Title = "No",
DataJson = "{ \"Type\": \"GetCustomPPT\" }",
}
},
new AdaptiveContainer()
{
Id = "getCustomPPTYes",
Items = new List<AdaptiveElement>()
{
new AdaptiveTextBlock()
{
Text = "Please select an option",
Wrap=true,
Size = AdaptiveTextSize.Small
}
}
},
},
Actions = new List<AdaptiveAction>()
{
new AdaptiveShowCardAction()
{
Id = "GetPPTYes",
Title = "Yes",
Card = new AdaptiveCard()
{
Body = new List<AdaptiveElement>()
{
new AdaptiveTextBlock()
{
Text = "Please enter your input",
Wrap = true
},
new AdaptiveTextInput()
{
Id="GetUserInputKeywords",
Placeholder="Please enter the keyword list separated by ',' Ex:RPA,FS ",
MaxLength=490,
IsMultiline=true
}
},
Actions = new List<AdaptiveAction>()
{
new AdaptiveSubmitAction()
{
Id = "contactSubmit",
Title = "Submit",
DataJson = "{ \"Type\": \"GetPPT\" }"
},
new AdaptiveOpenUrlAction()
{
Id="CallApi",
Url=new Uri("https://xyz"+"RPA")
//card.Actions.Card.AdaptiveTextInput.Placeholder
}
}
}
},
new AdaptiveShowCardAction()
{
Id = "GetPPTNo",
Title = "No",
Card = new AdaptiveCard()
{
Body = new List<AdaptiveElement>()
{
},
Actions = new List<AdaptiveAction>()
{
new AdaptiveSubmitAction()
{
Id = "contactSubmit",
Title = "Submit",
DataJson = "{ \"Type\": \"GetPPTNo\" }"
}
}
}
}
}
};
// Create the attachment with adapative card.
Attachment attachment = new Attachment()
{
ContentType = AdaptiveCard.ContentType,
Content = card
};
return attachment;
}
Yes, you can retrieve the input values from an AdaptiveCard and use them as parameters in an HTTP request to an API. When the user submits an AdaptiveCard, the input fields are sent to the bot through the activity in the Value attribute. You can parse the resulting JSON string with JObject and retrieve the values for your API call. See the example below.
public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
if (turnContext.Activity.Type == ActivityTypes.Message)
{
// Check if user submitted AdaptiveCard input
if (turnContext.Activity.Value != null) {
// Convert String to JObject
String value = turnContext.Activity.Value.ToString();
JObject results = JObject.Parse(value);
// Get type from input field
String name = results.GetValue("Type").ToString();
// Get Keywords from input field
String userInputKeywords = "";
if (name == "GetPPT") {
userInputKeywords = results.GetValue("GetUserInputKeywords").ToString();
}
// Make Http request to api with paramaters
String myUrl = $"http://myurl.com/api/{userInputKeywords}";
...
// Respond to user
await turnContext.SendActivityAsync("Respond to user", cancellationToken: cancellationToken);
} else {
// Send user AdaptiveCard
var cardAttachment = GetUserInputForCustomPPT();
var reply = turnContext.Activity.CreateReply();
reply.Attachments = new List<Attachment>() { cardAttachment };
await turnContext.SendActivityAsync(reply, cancellationToken);
}
}
}
Hope this helps!
Using routing you can pass multiple parameters either on the route itself or pass parameters on the query string, via Model Binding or content value binding. For most common scenarios this actually works very well. As long as you are passing either a single complex type via a POST operation, or multiple simple types via query string or POST buffer, there's no issue. But if you need to pass multiple parameters as was easily done with WCF REST or ASP.NET AJAX things are not so obvious.
RouteTable.Routes.MapHttpRoute(
name: "ApiName",
routeTemplate: "photos/**{action}**/{title}",
defaults: new {
title = RouteParameter.Optional,
controller = "PhotoApi",
**action =** **"GetPhotos"** });
I made a bot in C# using BotFramework, Adaptive Cards, LUIS and FormFlow. It/he is responsible for managing meetings in a team.
return new FormBuilder<MeetingRequestInput>()
...
.Field(
nameof(MeetingRequestInput.RequestedDate),
"What date would you like to meet?"
)
...
.Build();
During tests we noticed problems when a user was supposed to type the desired meeting date/time (people would type dd/mm/yy, mm/dd/yy, dd-mm-yy, only dd, etcetra) so we would like to use some kind of "Form" with formatted inputs to avoid parsing problems and retaining usability.
I think we can't change the desired keyboard type (kinda like in mobile, where sometimes the keyboard only shows numbers, or shows a DateTime Picker), nor apply a pattern auto-complete, at least using BotFramework.
To solve this, I'd like to use an AdaptiveCard with Date and Time pickers in my FormFlow prompt, at least when the user is asked to type the requested date, like so:
Input example using Adaptive Cards
In this example, the user would fill the AdaptiveDateInput and the AdaptiveTimeInput, then press the Confirm button. Doing so, it would grab the values inside the inputs, then "type and send" for the user the desired DateTime in a specific template, avoiding the previous parsing problems.
Problem is, I can't replace the "normal" FormFlow Card (who's expecting a simple string as a prompt parameter) with an entire Adaptive Card. How do I solve this problem? Are AdaptiveCards the best answer or are there alternatives?
Right now I'm displaying the card manually like so:
AdaptiveCard card = new AdaptiveCard();
card.Body = new List<AdaptiveElement>()
{
new AdaptiveTextBlock()
{
Text = "What date would you like to meet?"
},
new AdaptiveDateInput()
{
Value = DateTime.Now.ToShortDateString(),
Id = "dateInp"
},
new AdaptiveTimeInput()
{
Value = DateTime.Now.ToShortTimeString(),
Id = "timeInp"
}
};
card.Actions = new List<AdaptiveAction>()
{
new AdaptiveSubmitAction()
{
Type = "Action.Submit",
Title = "Confirm"
}
};
var msg = context.MakeMessage();
msg.Attachments.Add(
new Attachment()
{
Content = card,
ContentType = "application/vnd.microsoft.card.adaptive",
Name = "Requested Date Adaptive Card"
}
);
await context.PostAsync(msg);
I've read this question, but I don't know if we have the exact same problem. And their solution does not apply to me: even though I made the examples above in english, the bot will actually expect inputs in other languages, so yeah we can parse "February 2th" using a Recognizer, but we don't have the same luck with "2 de Fevereiro" nor "2 Fevralya".
Using the FormBuilder.Prompter, you can customize FormFlow messages any way you like. However, since AdaptiveCards send responses in the .Value, the code will need to transfer .Value to .Text property before validation.
Here is an example of a FormFlow form that sends an AdaptiveCard for a RequestedDate field:
[Serializable]
public class AdaptiveCardsFormFlow
{
public string Name { get; set; }
public DateTime? RequestedDate { get; set; }
public static IForm<AdaptiveCardsFormFlow> BuildForm()
{
IFormBuilder<AdaptiveCardsFormFlow> formBuilder = GetFormbuilder();
var built = formBuilder
.Field(nameof(Name), "What is your name?")
.Field(nameof(RequestedDate))
.Confirm("Is this information correct? {*}")
.Build();
return built;
}
private static AdaptiveCard GetDateCard()
{
AdaptiveCard card = new AdaptiveCard();
card.Body = new List<AdaptiveElement>()
{
new AdaptiveTextBlock()
{
Text = "What date would you like to meet?"
},
new AdaptiveDateInput()
{
Value = DateTime.Now.ToShortDateString(),
Id = "dateInp"
},
new AdaptiveTimeInput()
{
Value = DateTime.Now.ToShortTimeString(),
Id = "timeInp"
}
};
card.Actions = new List<AdaptiveAction>()
{
new AdaptiveSubmitAction()
{
Type = "Action.Submit",
Title = "Confirm"
}
};
return card;
}
private static IFormBuilder<AdaptiveCardsFormFlow> GetFormbuilder()
{
IFormBuilder<AdaptiveCardsFormFlow> formBuilder = new FormBuilder<AdaptiveCardsFormFlow>()
.Prompter(async (context, prompt, state, field) =>
{
var preamble = context.MakeMessage();
var promptMessage = context.MakeMessage();
if (prompt.GenerateMessages(preamble, promptMessage))
{
await context.PostAsync(preamble);
}
if (field != null && field.Name == nameof(AdaptiveCardsFormFlow.RequestedDate))
{
var attachment = new Attachment()
{
Content = GetDateCard(),
ContentType = AdaptiveCard.ContentType,
Name = "Requested Date Adaptive Card"
};
promptMessage.Attachments.Add(attachment);
}
await context.PostAsync(promptMessage);
return prompt;
}).Message("Please enter your information to schedule a callback.");
return formBuilder;
}
}
Using this class:
private class DateTimeInp
{
public string dateInp { get; set; }
public string timeInp { get; set; }
public DateTime? ToDateTime()
{
string fullDateTime = dateInp + " " + timeInp;
DateTime toDateTime;
if(DateTime.TryParse(fullDateTime, out toDateTime))
{
return toDateTime;
}
return null;
}
}
Then, in the Messages Controller, add the Adaptive Card's return value to the .Text property of the activity:
if(activity.Value != null)
{
DateTimeInp input = JsonConvert.DeserializeObject<DateTimeInp>(activity.Value.ToString());
var toDateTime = input.ToDateTime();
if(toDateTime != null)
{
activity.Text = toDateTime.ToString();
}
}
I want to get media from a website. That media should be listed something like Carousel card template.
With loop i want to store all the media in one object.
Have this :
resultMessage.AttachmentLayout = AttachmentLayoutTypes.Carousel;
resultMessage.Attachments = new List<Attachment>();
var fbObject = new object[activities.Count];
while (!stop)
{
if (activities[counter].MediaTypeValue != (int)MediaTypeEnum.Video)
{
fbObject[counter] = new
{
type = "image",
payload = new object[]
{
new
{
url = activities[counter].DocumentPath
},
}
};
}
else
{
fbObject[counter] = new
{
type = "video",
buttons = new object[]
{
new
{
type = "web_url",
url = activities[counter].DocumentPath,
title = activities[counter].FirstName + " " + activities[counter].LastName + " posted " + BotHelper.UserPosted(activities[counter].MediaTypeValue),
webview_height_ratio = "compact",
messenger_extensions = true
}
}
};
}
counter--;
if (counter < 0)
stop = true;
}
resultMessage.ChannelData = JObject.FromObject(new { fbObject }); ;
await context.PostAsync(resultMessage);
But Facebook messenger does not render it as "carousel".
Any idea how to show object like Carousel type of card ?
Facebook will render the carousel if you have Attachments in your message. Your attachments collection is empty and you are sending channel data info, which won't be rendered as a carousel.
Both Image and Video are supported attachments in BotFramework and you can just use the available rich cards instead of using channel data to send them to the user.
Take a look to the RichCards sample to understand how to create each of the supported cards. Also, you might also want to review the Carousel sample.
Finally, it's always a good idea to review the documentation around attachments and Rich cards. See this and this.
I am new to using acumatica web service. is it possible to upload a document/ file in specific screen as attachment like business account attachment.
eg. below screen in which we add manually.
I find the solution and posted to help anyone.
To upload any file we must convert that file into bytes and submit bytes which convert your file.
//Get bytes of file
byte[] filedata;
using(System.IO.FileStream file =
System.IO.File.Open(#"D:\Test.pdf",System.IO.FileMode.Open))
{
filedata = new byte[file.Length];
file.Read(filedata,0,filedata.Length);
}
// Import Data Now to Business Account
BAccount.CR303000ImportResult[] lstObjContent = context.CR303000Import
(
new BAccount.Command[]
{
// Must Pass BusinessAccount in which we want to update or add data
new BAccount.Value { Value="XXXXXX",LinkedCommand=objContent.AccountSummary.BusinessAccount},
new BAccount.Value { Value="TestValue123",LinkedCommand=objContent.AccountSetup.CurrentMethod},
new BAccount.Value { FieldName="NameOfFileWithExtension",LinkedCommand=objContent.AccountSummary.ServiceCommands.Attachment},
objContent.Actions.Save
},null,new string[][] { new string[] { Convert.ToBase64String(filedata) },new string[] { Convert.ToBase64String(filedata) },}
,false,false,true
);
Even the below code works. Verified in version 18R1.
var content = _context.CR306000GetSchema(); _context.CR306000Clear();
var commands = new List();
ReqParameter(content, ref commands);
commands.Add(content.Actions.Save);
commands.Add(content.CaseSummary.CaseID);
var orderResults = _context.CR306000Submit(commands.ToArray());
private static void ReqParameter(CR306000Content content, ref List<Command> cmds)
{
if (cmds == null) throw new ArgumentNullException("cmds");
byte[] filedata= null;
Uri uri = new Uri("https://acmdev.baccahq.com/Icons/login_bg5.jpg"); // change the required url of the data that has to be fetched
if (uri.IsFile)
{
string filename = System.IO.Path.GetFileName(uri.LocalPath);
filedata = System.Text.Encoding.UTF8.GetBytes(uri.LocalPath);
}
if (filedata == null)
{
WebClient wc = new WebClient();
filedata = wc.DownloadData(uri);
}
cmds = new List<Command>
{
//Case Header Details
new Value { Value="<NEW>",LinkedCommand = content.CaseSummary.CaseID},
new Value { Value="L41",LinkedCommand = content.CaseSummary.ClassID},
new Value { Value="ABCSTUDIOS",LinkedCommand = content.CaseSummary.BusinessAccount, Commit = true},
new Value { Value="Test subject created from envelop call 11C",LinkedCommand = content.CaseSummary.Subject},
// body of the case
new Value{Value= "Body of the content for created through envelop call 11B", LinkedCommand = content.Details.Description},
//Attaching a file
new Value
{
Value = Convert.ToBase64String(filedata), // byte data that is passed to through envelop
FieldName = "Test.jpg",
LinkedCommand =
content.CaseSummary.ServiceCommands.Attachment
},
};
}