QnA Maker Bot AdaptiveCards: how to add Data object in C# - botframework

I used the "no code" way to generate a Bot in Azure and connect it to a QnA Maker knowledge base.
I have then modified the code so that the Bot uses AdaptiveCards instead of HeroCards to support Markdown formatting in MS Teams channel (format used by QnA Maker).
I am trying to add SubmitActions to these adaptive cards when there are some prompts coming from the Knowledge Base. The objective is that if the user clicks on these SubmitActions it automatically send a message back to the Bot.
Please find below the code I implemented:
// adaptive card creation
var plCardBis = new AdaptiveCard(new AdaptiveSchemaVersion(1, 0));
plCardBis.Body.Add(new AdaptiveTextBlock()
{
Text = result.Answer,
Wrap = true
});
// Add all prompt
foreach (var prompt in result.Context.Prompts)
{
plCardBis.Actions.Add(new AdaptiveCards.AdaptiveSubmitAction()
{
Title = prompt.DisplayText,
Data = prompt.DisplayText
});
}
//create the the attachment
var attachmentBis = new Attachment()
{
ContentType = AdaptiveCard.ContentType,
Content = plCardBis
};
//add the attachment
chatActivity.Attachments.Add(attachmentBis);
return chatActivity;
This works fine in the WebChat however in Teams if I click on a prompt it generates an error. Looking on internet I found that I should use an object for the Data field for Teams, instead of a simple string:
"data": {
"msteams": {
"type": "imBack",
"value": "Text to reply in chat"
},
}
Do you know how I could do that in C#? How could I update my code to add this object for the Data field? The number of Actions can vary according to the question asked by the user...
Any help would be greatly appreciated

Basically, there are two options for what you can attach to "Data" - either a plain string value, or any custom object. For your scenario, you need a custom object, so you need to define a class in your project to match what you need, something like:
public class MsTeamsDataResponseWrapper
{
[JsonProperty("msteams")]
public MsTeamsResponse MsTeamsResponse { get; set; }
}
public class MsTeamsResponse
{
[JsonProperty("type")]
public string Type { get; set; } = "imBack";
[JsonProperty("value")]
public string Value { get; set; }
}
then you'd use it like this:
...
Data = new MsTeamsDataResponseWrapper() { MsTeamsResponse = new MsTeamsResponse() { Value = prompt.DisplayText } }
...
In this case "Type" already defaults to "imBack", but you could also use it for "messageBack" at a later stage if you want to overwrite the default.

Related

Unable to write to bot state property after accessor has been created

Perhaps a misunderstanding on my behalf, but in botframework SDKv4, once I create a property accessor for a set of variables I wish to capture in an adaptive dialog, I can no longer write to them directly from input actions.
e.g. prior to initializing a property I can have an input action such as
new TextInput()
{
Prompt = new ActivityTemplate("${RequestPhoneNumber()}"),
Property = "user.profile.mobilenumber",
}
Then later I can refer to that property in an adaptive expression, or by checking the dialog context
var phone = dc.State["user.profile.mobilenumber"];
and I'll find the text the user entered inside that property, as expected.
However, if I go and create a data class like:
public class Person
{
public string MobileNumber { get; set; }
}
then create it in my dialog
var personAccessor = userState.CreateProperty<Person>("profile");
If I read the mobilephone property later I'll find the property null after the TextInput action has completed
Person profile = await personAccessor.GetAsync(dc.Context, () => new Profile());
Console.WriteLine(profile.MobileNumber);
The output on the console is null, and I can't quite work out what I'm doing wrong or misunderstanding.

Custom ASP.NET Core MVC validation response

ASP.NET Core MVC has a great model binding & model validation subsystem which supports almost any scenario. But when developing APIs things can go a little more complicated.
Suppose we have a model class with a property XYZ which is annotated with [MinLength(5)].
public class ViewModel
{
[MinLength(5)]
public string XYZ { get; set; }
}
If anything goes wrong with this property what MVC will give you is something like this:
{ "XYZ": [ "The field XYZ must be a string or array type with minimum length of '5'" ] }
But this is not what the client needs! The client needs an object with specific details. She will create her own message however she wants to:
{ "error": "minLength", "property": "XYZ", "minimum": 5 }
Possible Solutions:
You can use InvalidModelStateResponseFactory to generate customized responses. It gives you the ActionContext which contains the ModelState property. But all you can do is to process error messages which are pure strings! That could lead to some problems.
Another option is to completely disable the MVC Validation and implement one for yourself.
I appreciate any other solutions.
For general validation message, it is pure string. And for minLength and minimum are different for different validation attribute. I am wondering how the client will check the different node.
For server side, InvalidModelStateResponseFactory would be better to return json object. and you need to check the ValidationAttribute for return different object like
services.Configure<ApiBehaviorOptions>(o =>
{
o.InvalidModelStateResponseFactory = actionContext =>
{
var error = new Dictionary<string, string>();
foreach (var key in actionContext.ModelState.Keys)
{
foreach (var parameter in actionContext.ActionDescriptor.Parameters)
{
var prop = parameter.ParameterType.GetProperty(key);
if (prop != null)
{
var attr = prop.GetCustomAttributes(typeof(ValidationAttribute), false).FirstOrDefault() as ValidationAttribute;
if (attr is MinLengthAttribute minLengthAttribute)
{
error.Add("Error", "minLength");
error.Add("Property", key);
error.Add("minimum", minLengthAttribute.Length.ToString());
}
}
}
}
return new BadRequestObjectResult(error);
};
});

net core API controller is returning incomplete json

I asked a question a few days ago regarding a net core game I'm making that is using Entity Framework.
I had one issue where a controller was returning duplicate JSON data.
Based on one of the answers, I changed that controller to this:
[HttpGet("GetDungeonAndRoomData/{dungeonId}")]
public async Task<ActionResult<GameDungeon>> GetDungeonAndRoomData(Guid dungeonID)
{
{
var dungeon = await _context.DungeonList
.Select(c => new GameDungeon
{
DungeonId = c.DungeonId,
DungeonName = c.DungeonName,
StartRoom = c.StartRoom,
Rooms = c.Rooms.Select(n => new GameDungeonRoom
{
RoomId = n.RoomId,
RoomText = n.RoomText,
TreasureId = n.TreasureId
})
}).SingleOrDefaultAsync(c => c.DungeonId == dungeonID);
Since I changed the controller, I had to modify this model class, so I added a new property called Rooms.
public partial class GameDungeon
{
[Key]
public string DungeonId { get; set; }
public string DungeonName { get; set; }
public string StartRoom { get; set; }
public IEnumerable<GameDungeonRoom> Rooms { get; set; }
}
Since I added that new "Rooms" property, I had to create a new model called GameDungeonRoom:
public partial class GameDungeonRoom
{
public Guid DungeonId { get; set; }
[Key]
public string RoomId { get; set; }
public string RoomText { get; set; }
public string TreasureId { get; set; }
}
Building and running the game, I now get one set of dungeon data, but it is not returning any rooms.
At first, and based off this Stack Overflow question, .net core : incomplete JSON response,I thought it was because I needed to add this to my Startup.cs file:
services.AddMvc()
.AddJsonOptions(
options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
But that was not the case.
So I then spent the evening trying a bunch of different ways of writing the controller, but they either produced the same results or just threw an error.
Reviewing the code this morning, I realized something. In my controller, the first select statement that creates the new "GameDungeon" is getting data from _context.DungeonList.
DungeonList is a model generated by Entity Framework from a real table in my database.
But GameDungeonRoom is just a new class model I created. It's based off a table in my database called RoomList, but there is nothing in _context that specifically handles GameDungeonRoom.
So I am wondering, should I introduce another controller that kind of looks like this?
var rooms = await _context.RoomList
.Select(c => new GameDungeonRoom ...
And then somehow append that to the GameDungeon object.
I sort of tried that after lunch but ended up just creating a mess of code that created an even bigger mess of errors so I just deleted it all.
Anyway, after all that, this is where my JSON currently stands:
{
"dungeonId" : "293hf938",
"dungeonName" : "Dungeon of Dread",
"startRoom" : "bjgh39811ffr",
"roomId" : "fgf4h635j",
"roomText" : "A big empty room",
"treasureId" : "12a",
"rooms": [
You'll notice that "rooms" is empty. I'm not quite sure why it is, or what's going on.
One idea I had, is maybe I should just create an API controller that get's the dungeon data for a particular dungeon. Then create another API controller that gets the Room data for a particular dungeon.
Then let the client call both controllers(using the same dungeonId) and combine the data on the client side.
So I was wondering if anyone could think of an idea as to why the "rooms" object is empty.
Thanks!
Just guessing you might have hit a cyclic reference in your result set due to Data Context being cached. Hence Json serializer cannot serialize it properly and give incomplete json content. So can you try following to pin point that.
var dungeon = await _context.DungeonList
.Select(c => new GameDungeon
{
DungeonId = c.DungeonId,
DungeonName = c.DungeonName,
StartRoom = c.StartRoom,
Rooms = c.Rooms.Select(n => new GameDungeonRoom
{
RoomId = n.RoomId,
RoomText = n.RoomText,
TreasureId = n.TreasureId
})
})
.AsNoTracking() //This ignore the cached data
.SingleOrDefaultAsync(c => c.DungeonId == dungeonID);

How to do validation using mvvmcross

I have just started learning to write mobile apps using Xamarin and MvvmCross. I have found it quite easy to pick up the basics due to the great support including the N+1 days of MvvmCross videos on YouTube (Huge thanks to Stuart Lodge).
However I am struggling with valudation data. I'm hoping someone on Stackoverflow can point me in the direction of some useful blogs or tutorials on performing validation using MvvmCross. I want to be able validate the data entered and then update the view indicating the issue.
I need something from first principles as I don't know what I don't know (If that makes sense). I need some best practice to follow.
Data validation can be displayed in the UI in different ways.
For example, you can show a message box or show a label.
Suppose you want to have a label with red text somewhere in the UI to show the error.
I assume you have a 'Save' button or similar in your UI.
You can bind the button to a SaveCommand in the view-model.
In the implementation of the SaveCommand, you can check if all the data is valid and set an Error string property.
You can have a label's text bound to the Error property. Moreover, you could also bind the label's visibility to the condition (Error != null).
public class SettingsViewModel : MvxViewModel
{
string firstName;
public string FirstName
{
get { return this.firstName; }
set
{
if(this.firstName != value)
{
this.firstName = value;
this.RaisePropertyChanged(()=> this.FirstName);
this.Error = null; // reset error
}
}
}
public string Error { get; private set; }
public ICommand SaveCommand { get { return new MvxCommand(this.Save); } }
void Save()
{
// reset error
this.Error = null;
if(string.IsNullOrEmpty(this.FirstName))
{
this.Error = "First name is empty";
}
if(string.IsNullOrEmtpy(this.Error))
{
// no error, save settings...
}
else
{
this.RaisePropertyChanged(()=> this.Error);
}
}
}

How to Parse Json webService response in WP7

I want to display data in Listbox without using any DLL.And my webservice responding in json format.
My Web service Response is as below.it has more than 800 records
[
{
"st_id":"1",
"st_name":"name xyz"
},
{
"st_id":"2",
"st_name":"name ABC"
},
{
"st_id":"3",
"st_name":"name HIJK"
},
{
"st_id":"4",
"st_name":"name OPQ"
},
]
my Class for the data is as below
[DataContract]
public class Student
{
[DataMember=("st_id")]
public bool st_id { get; set; }
[DataMember=("st_name")]
public string st_name { get; set; }
}
i m trying serialize object using DataContractJsonSerializer & m getting WS response in Stream.But i am not able to serialize.Suggest links or basic tutorial for Serilize and Deserilize of the json
DataContractJsonSerializer stdserialize =
new DataContractJsonSerializer(typeof(Student));
Student stuser = (Student)stdserialize.ReadObject(responseStream);
so please help for the json response parsing & suggest link for datacontract and all which gives knowledge from basics.
Thanks,
You declared st_id as a bool, but the type of data you're trying to deserialize is string (which can be converted to numbers - not to booleans). Try declaring it as string and it should work.
Also, the response is an array of objects, so the type you should use is Student[]:
DataContractJsonSerializer stdserialize =
new DataContractJsonSerializer(typeof(Student[]));
Student stuser = (Student[])stdserialize.ReadObject(responseStream);

Resources