Any idea on how to get this id from the conversation between attendee and bot? - facebook

Context:
BotFramework (C# SDK) + Messenger channel, bot handles two types of users: attendees (Messenger users) and organizers (who are Facebook Page's admins).
Use case:
When an attendee requests a human support (using an option in my bot's menu), the organizer will receive a message.
In that message, I would like to add a button that will do the following once clicked by the organizer:
stop the automatic replies from the bot to that user
redirect the organizer to Facebook's Page inbox, with the conversation (between the attendee and the bot) selected
What I have done:
I successfully did the part to stop the automatic replies
I got stuck on how to redirect the organizer to the right conversation in FB Page's inbox
Technically:
When I'm looking in Facebook Page, the link that seems to be the one that I should generate for my action is like the following: https://www.facebook.com/mypage-mypageId/inbox/?selected_item_id=someId
My problem is that I can't find this value for selected_item_id from my bot's conversation.

You will be able to get a link to the facebook page inbox (with the right thread) thanks to Facebook Graph API.
/me/conversations must be called to get the conversations of the Page (so you have to give an access_token of the page to the API call).
Then in those results, you have to make a match with the conversation of the attendee. To do this, you can use the property id of the Activity in your bot (Activity.Id, not Activity.Conversation.Id!) as this value is common between your bot and facebook graph results (just need to add "m_" to match): you can find it in one message.id on your Graph API results (careful: not conversation.id)
Then you can get the link value of the Graph API result for this conversation that you found: "link": "\/myBotName\/manager\/messages\/?threadid=10154736814928655&folder=inbox" in my test
Here is a sample of a Dialog that will search for the link for a specific message id:
[Serializable]
public class FacebookGetLinkTestDialog : IDialog<string>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
}
public async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
var jsonString = "";
var link = "";
using (var client = new HttpClient())
{
using (var response = await client.GetAsync($"https://graph.facebook.com/v2.9/me/conversations?access_token=yourAccessTokenHere").ConfigureAwait(false))
{
jsonString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var conversationList = Newtonsoft.Json.JsonConvert.DeserializeObject<ConversationsRootObject>(jsonString);
link = conversationList.data.Single(c => c.messages.data.Any(d => d.id.Equals("m_" + "yourActivityIdHere"))).link;
}
}
await context.PostAsync($"{link}");
}
}
public class ConversationsRootObject
{
public List<Conversation> data { get; set; }
public Paging paging { get; set; }
}
public class Conversation
{
public string id { get; set; }
public string snippet { get; set; }
public string updated_time { get; set; }
public int message_count { get; set; }
public int unread_count { get; set; }
public Participants participants { get; set; }
public FormerParticipants former_participants { get; set; }
public Senders senders { get; set; }
public Messages messages { get; set; }
public bool can_reply { get; set; }
public bool is_subscribed { get; set; }
public string link { get; set; }
}
public class Participant
{
public string name { get; set; }
public string email { get; set; }
public string id { get; set; }
}
public class Participants
{
public List<Participant> data { get; set; }
}
public class FormerParticipants
{
public List<object> data { get; set; }
}
public class Senders
{
public List<Participant> data { get; set; }
}
public class Messages
{
public List<FbMessage> data { get; set; }
public Paging paging { get; set; }
}
public class FbMessage
{
public string id { get; set; }
public string created_time { get; set; }
}
public class Cursors
{
public string before { get; set; }
public string after { get; set; }
}
public class Paging
{
public Cursors cursors { get; set; }
public string next { get; set; }
}

Related

how to Pull data with paramter using Azure Offline Sync?

I have schema as below and I would like to know if I can get all the TaskCategoryMappings by CategoryId when I have to pulll all data. I went through the documentation here but I cant figure out how to do it ? All the examples are like this one based on UserId. but userid is used for also authentication and on the server side I already handle it fine to return only mappings belong to relevant user and i want in adition to filter by CategoryId?
another SO sample is here also using userId Parameter Passing with Azure Get Service
public class TaskCategoryMapping : TableData
{
public string TaskId { get; set; }
public string CategoryId { get; set; }
public string UserId { get; set; }
}
According to your description, I checked this issue on my side and found that it could work as expected, you could follow the details below to check your code:
Backend models:
public class Tag : EntityData
{
public string TagName { get; set; }
public bool Status { get; set; }
}
public class Message : EntityData
{
public string UserId { get; set; }
public string Text { get; set; }
public virtual Tag Tag { get; set; }
[ForeignKey("Tag")]
public string Tag_Id { get; set; }
}
GetAllMessage action:
// GET tables/Message
public IQueryable<Message> GetAllMessage()
{
return Query();
}
For the client, I just invoke the online table for retrieving the message entities as follows:
Model on client-side:
public class Message
{
public string Id { get; set; }
public string UserId { get; set; }
public string Text { get; set; }
public string Tag_Id { get; set; }
}
var result=await mobileServiceClient.GetTable<Message>().Where(msg => msg.Tag_Id == "c3cd4cf8-7af0-4267-817e-f84c6f0e1733").ToListAsync();
For offline table, the pull operation query would
await messageSyncTable.PullAsync($"messages_{userid}", messageSyncTable.Where(m => m.Tag_Id == "<Tag_Id>"));
Use fiddler, you could find that the request would look like this:
https://{your-app-name}.azurewebsites.net/tables/Message?$filter=Tag_Id eq 'c3cd4cf8-7af0-4267-817e-f84c6f0e1733'

Circular Reference error when serializing objects in ASP.NET Web API

I'm writing a Web API project in C# that uses Entity Framework to pull data from a DB, serialize it and send it to a client.
My project has 2 classes, Post and Comment (foreign key from Post).
These are my classes.
Post class:
public partial class Post
{
public Post()
{
this.Attachment = new HashSet<Attachment>();
this.Comment = new HashSet<Comment>();
}
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public System.DateTime Created { get; set; }
public Nullable<System.DateTime> Modified { get; set; }
public virtual ICollection<Attachment> Attachment { get; set; }
public virtual ICollection<Comment> Comment { get; set; }
}
Comment class:
public partial class Comment
{
public int CommentId { get; set; }
public string Content { get; set; }
public System.DateTime Posted { get; set; }
public bool Approved { get; set; }
public int AnswersTo { get; set; }
public int PostId { get; set; }
public virtual Post Post { get; set; }
}
My problem is that when I try to get via Web API a Post, it spits me the following error:
Object graph for type 'APIServer.Models.Comment' contains cycles and cannot be serialized if reference tracking is disabled.
And when I try to get a Comment via Web API, the error is as follows:
Object graph for type 'System.Collections.Generic.HashSet`1[[APIServer.Models.Comment, APIServer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]' contains cycles and cannot be serialized if reference tracking is disabled.
If I annotate the Comment class with
[DataContract(IsReference = true)]
the errors disappear, but the serialization only returns the ID of the comment and ignores the other fields.
Any suggestions on how to solve this?
Thanks in advance,
Léster
Here are 2 solutions
Solution #1:
I had this same problem and so I decorated my class with DataContract and the members with DataMember like you mention. HOWEVER, I don't like editing auto-generated code directly because I have to redo it every time I regenerate the file. In order to get around this, I used the MetadataType attribute. In your case, it would look like this...
First, you will keep the auto generated entity as is:
public partial class Comment
{
public int CommentId { get; set; }
public string Content { get; set; }
public System.DateTime Posted { get; set; }
public bool Approved { get; set; }
public int AnswersTo { get; set; }
public int PostId { get; set; }
public virtual Post Post { get; set; }
}
Next, in another file, you will create another partial class and decorate it like this:
[MetadataType(typeof(Metadata))]
[DataContract(IsReference = true)]
public partial class Comment
{
private class Metadata
{
[DataMember]
public int CommentId { get; set; }
[DataMember]
public string Content { get; set; }
[DataMember]
public System.DateTime Posted { get; set; }
[DataMember]
public bool Approved { get; set; }
[DataMember]
public int AnswersTo { get; set; }
[DataMember]
public int PostId { get; set; }
[DataMember]
public virtual Post Post { get; set; } // you can remove "virtual" if you wish
}
}
MetadataType will essentially add the attributes from the Metadata buddy class to the ones with the same name in Comment (not directly, but for our purposes, it's close enough... that's a topic for a different post). Of course, if your Comment entity changes, you'll need to update this accordingly.
Solution #2:
Having to edit your second file every time you make a change is only a slight improvement from directly editing auto-generated files. Fortunately, there is another approach that is much easier to maintain. Details can be found here but as a summary, all you need to do is decorate your OperationContract that is consuming Comment with an additional attribute, ReferencePreservingDataContractFormat. Note that there is a slight error in the code provided on that page that would cause infinite recursion. As noted in this post, the fix is quite simple: instead of recursing at all, just create a new DataContractSerializer
The advantage to this approach is that no matter how much you change Comment, you still don't need update anything.
As an example for your code, let's say you are using Comment as follows:
[OperationContract]
Comment FindComment(string criteria);
All you need to do is add
[OperationContract]
[ReferencePreservingDataContractFormat]
Comment FindComment(string criteria);
And then somewhere else you need to define ReferencePreservingDataContractFormat which will look like this:
//From http://blogs.msdn.com/b/sowmy/archive/2006/03/26/561188.aspx and https://stackoverflow.com/questions/4266008/endless-loop-in-a-code-sample-on-serialization
public class ReferencePreservingDataContractFormatAttribute : Attribute, IOperationBehavior
{
public void AddBindingParameters(OperationDescription description, BindingParameterCollection parameters)
{
}
public void ApplyClientBehavior(OperationDescription description, System.ServiceModel.Dispatcher.ClientOperation proxy)
{
IOperationBehavior innerBehavior = new ReferencePreservingDataContractSerializerOperationBehavior(description);
innerBehavior.ApplyClientBehavior(description, proxy);
}
public void ApplyDispatchBehavior(OperationDescription description, System.ServiceModel.Dispatcher.DispatchOperation dispatch)
{
IOperationBehavior innerBehavior = new ReferencePreservingDataContractSerializerOperationBehavior(description);
innerBehavior.ApplyDispatchBehavior(description, dispatch);
}
public void Validate(OperationDescription description)
{
}
}
class ReferencePreservingDataContractSerializerOperationBehavior : DataContractSerializerOperationBehavior
{
public ReferencePreservingDataContractSerializerOperationBehavior(OperationDescription operationDescription) : base(operationDescription) { }
public override XmlObjectSerializer CreateSerializer(Type type, string name, string ns, IList<Type> knownTypes)
{
return new DataContractSerializer(type, name, ns, knownTypes,
0x7FFF, //maxItemsInObjectGraph
false, //ignoreExtensionDataObject
true, //preserveObjectReferences
null //dataContractSurrogate
);
}
public override XmlObjectSerializer CreateSerializer(Type type, XmlDictionaryString name, XmlDictionaryString ns, IList<Type> knownTypes)
{
return new DataContractSerializer(type, name, ns, knownTypes,
0x7FFF, //maxItemsInObjectGraph
false, //ignoreExtensionDataObject
true, //preserveObjectReferences
null //dataContractSurrogate
);
}
}
And that's it!
Either method will work just fine--pick the one that works for you.
You can disable Lazy Loading on your Comment class by removing virtual from the Post property definition...
public partial class Comment
{
public int CommentId { get; set; }
public string Content { get; set; }
public System.DateTime Posted { get; set; }
public bool Approved { get; set; }
public int AnswersTo { get; set; }
public int PostId { get; set; }
public Post Post { get; set; }
}
This should sort out the circular reference exception.

RavenDb-Get list from a list of ids

I'm developing an events manager with RavenDb. The logged user can write a post in a place that follow and then see all posts in the user wall (home page). Every post can be mark as "Like" and "Favourite". A small structure like twitter or facebook.
Wich is the best way to get the posts by the places that I follow? I need to get the count of likes and favourites and if I already mark the post on viewmodel to send to the view and display the list. I'm trying like this, but throw the max requests exception.
var myPlaces = this.SessionDb.Query<EventFollow>()
.Where(ls => ls.UserId == User.Identity.Name)
.Select(l => l.PlaceId);
var listPosts = this.SessionDb
.Advanced.LuceneQuery<Post>().WhereIn("PlaceId", myPlaces)
.Skip(skip)
.Take(20)
.ToList();
List<PostViewModel> posts = new List<SpottedViewModel>();
foreach (var p in listPosts)
{
PostViewModel vm = new PostViewModel();
vm.Creator = p.UserCreatorId == User.Identity.Name;
vm.Like = this.SessionDb.Query<Lol>().Where(lol => lol.SpottedId == p.Id).Count();
vm.Favourites = this.SessionDb.Query<Favourite>().Where(fa => fa.SpottedId == p.Id).Count();
vm.Post= s;
vm.IsLike = this.SessionDb.Query<Like>().FirstOrDefault(l => l.PostId == p.Id) != null;
vm.IsFavourite = this.SessionDb.Query<Favourite>().FirstOrDefault(f => f.SpottedId == s.Id) != null;
posts.Add(vm);
}
I have these models:
public class Post
{
public string Id { get; set; }
public string UtenteCreatorId { get; set; }
public string PlaceId { get; set; }
public string Body{ get; set; }
}
public class Lol
{
public string Id { get; set; }
public string PostId { get; set; }
public string UserId { get; set; }
}
public class Place
{
public string Id { get; set; }
public string UserCreatorId { get; set; }
public string Name{ get; set; }
}
public class Favourite
{
public string Id { get; set; }
public string PostId { get; set; }
public string UserId { get; set; }
}
public class EventFollow
{
public string Id { get; set; }
public string PlaceId { get; set; }
public string UserId { get; set; }
}
Thank you and sorry for my english! ;D
Your problem here isn't really getting a list of documents from a list of IDs, you can do that by either using the IDocumentSession.Load method and sending in a list of document IDs, or by using WhereIn like you do. The problem is your model design.
It seems to me you have some serious re-design of your data models to do. It's strongly relational as it is now, it seems, and RavenDB isn't relational (or optimized towards relational models, anyway). It's hard for me to see exactly what your use cases are, but from what I can see I would probably save 'favourites' and 'likes' in the 'post' model, and put 'EventFollow' in the 'user' model.
Also, by RavenDB's "safe by default" principles, you're limited to 30 requests per session (per default, it's configurable). You have some select N+1 going on here so you'll most likely exceed those 30 requests, by far. That's why you get the exception.

ADO.NET EF what is the purpose of using List<> in the entities

Right now I'm learning ADO.NET Entity Framework and there's one thing that I can't explain to myself. Here is a source code from a tutorial I've been using recently:
public class Blog
{
public int BlogId { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public User UserId { get; set; }
public virtual List<Post> Posts { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
public virtual Blog Blog { get; set; }
}
public class User
{
public int UserId { get; set; }
public string Username { get; set; }
public string DisplayName { get; set; }
}
First I thought that the using of List<> is the way to implement Foreign Key-like behaviour but now knowing that's not the case why we need and for what purpose we use List<> in our entites?
To show that Blog have a lot of Posts, when you will build your project in DB will be the relation 1xBlog--->NxPost where N=unlimited. This will show that each Blog can have unlimited amount of Posts

Entity Framework query returns one record, but its supposed to return more

As title say, i got problem with a query in Asp.net mvc 3 EF.
I got 2 tables with 1 to many relationship.
table1 Users int user_ID string username
table2 Friends int friendshipID int user_ID int friend_ID
The controller:
// // GET: /User/Details/5
public ViewResult Details(int id)
{
User user = db.Users.Include("Friends").FirstOrDefault(u => u.user_ID == id);
//Also for each friend get the User:
foreach (var friend in user.Friends.ToList())
{
friend.User = db.Users.Find(friend.friend_ID);
}
return View(user);
}
The view "details":
#model Social2.Models.User
#{
ViewBag.Title = "Details";
}
<h2>Details</h2>
<div class="display-field">
#foreach (var friend in #Model.Friends)
{
#friend.User.username;
}
</div>
Context:
public partial class ASPNETDBEntities : DbContext
{
public ASPNETDBEntities()
: base("name=ASPNETDBEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<aspnet_Users> aspnet_Users { get; set; }
public DbSet<Friend> Friends { get; set; }
public DbSet<User> Users { get; set; }
}
user model:
public partial class User
{
public User()
{
this.Friends = new HashSet<Friend>();
}
[Key]
public int user_ID { get; set; }
public System.Guid user_UniqueID { get; set; }
public string username { get; set; }
public virtual aspnet_Users aspnet_Users { get; set; }
public virtual ICollection<Friend> Friends { get; set; }
}
friend model
public partial class Friend
{
public int friendship_ID { get; set; }
public int user_fr_ID { get; set; }
public int friend_ID { get; set; }
public virtual User User { get; set; }
}
The problem is, when i go to ~/user/details/1, the view show only one(the last one) friend.For every user it shows their last friend. How to show them all ?
Your database must have two relationships defined like so:
If you create an Entity Model from this schema you also get two one-to-many relationships. And when you apply the DBContext T4 template to this model you should get POCO classes like so:
public partial class Users
{
public Users()
{
this.Friends = new HashSet<Friends>();
this.Friends1 = new HashSet<Friends>();
}
public int user_ID { get; set; }
public string username { get; set; }
public virtual ICollection<Friends> Friends { get; set; }
public virtual ICollection<Friends> Friends1 { get; set; }
}
public partial class Friends
{
public int friendship_ID { get; set; }
public int user_fr_ID { get; set; }
public int friend_ID { get; set; }
public virtual Users Users { get; set; }
public virtual Users Users1 { get; set; }
}
Users.Friends and Friends.Users form a pair for the first relationship and Users.Friends1 and Friends.Users1 are a pair for the second relationship. (You can rename the navigation properties in the model designer to make the names more meaningful.) Your query would look like this then (you can include a "second level" and don't need the loop as you did in your example):
public ViewResult Details(int id)
{
// important to use User1 in Include, not User
User user = db.Users.Include("Friends.User1")
.FirstOrDefault(u => u.user_ID == id);
return View(user);
}
With DbContext you can also use the strongly typed version of Include:
Include(u => u.Friends.Select(f => f.User1))
I think the problem is mapping of the class Friend.
try change to:
public partial class Friend
{
[Key]
public int friendship_ID { get; set; }
public int user_fr_ID { get; set; }
public int friend_ID { get; set; }
[ForeignKey("friend_ID")]
public virtual User User { get; set; }
}
I think the problem is here
User user = db.Users.Include("Friends").FirstOrDefault(u => u.user_ID == id);
FirstOrDefault gives only one record.