Views: 584
Number of votes: 6
Average rating:

Getting Started with Comments

Digital communities offer a myriad of ways for users to contribute content. Blog responses, product reviews, forum posts, and direct messages just to name a few. And while each of these mechanisms may serve a different purpose, they share quite a lot in common. At the heart of it, each represents a simple, hierarchical, contribution.

Episerver Social's Comments feature distills this concept down into a simple data structure that, in combination with the platform's data modeling concepts, can be applied to implement any such scenario.

What is a Comment?

The Episerver Social framework represents hierarchical content using a class called Comment (EPiServer.Social.Comments.Core.Comment). Fundamentally, a Comment is comprised of an Author, a Parent, and a Body. With these three things, we have enough information to build a conversation.

  • The Body represents the content, which has been contributed.
  • The Author identifies the contributor of the content. Who posted the comment? Who contributed the review?
  • The Parent identifies what the content is in reference to. Is this a comment on a page in your site? Is this a reply to another comment?

Relationships

What makes hierarchical content hierarchical are its relationships. When working with comments, there are two relationships to be aware of. The first is a comment's relationship to its Author. The second is a comment's relationship to its Parent

Episerver Social uses an open system of references to allow an application to represent such relationships. This gives a developer the freedom to identify elements in their application (pages, users, products, etc.) in a manner that is appropriate for that application. The framework provides a class called Reference (EPiServer.Social.Common.Reference), which uses a custom string to identify an application element.

Let's look at how the concept applies to a Comment.

Author

A Comment's Author is specified as a Reference, which identifies the contributing user. As the developer, you have the freedom to define the scheme used to identify that user.

So, in a simple web application, the username of an authenticated user may suffice for identifying a contributor.

var contributor = Reference.Create(HttpContext.Current.User.Identity.Name);

In scenarios where user identities come from many different providers, a more sophisticated scheme may be required. You might consider a custom URI scheme to identify contributors.

var provider = ... // Set provider name
var username = ... // Set user name
var contributor = Reference.Create($"user://{provider}/{username}");

These are just two examples. Others might include a user ID, a GUID, a serialized identity object. The key takeaway is that your contibutors can be identified in whatever manner suits your application. 

Parent

A Comment's Parent identifies what the comment is attached to. A comment can be attached to anything: a page, an Episerver Block, a product, an image, a user. Like the Author, a Parent is also identified using a Reference. So, you have the freedom to attach a Comment to anything that you can identify.

Let's say that we want to contribute a comment to an Episerver page. We might go about constructing our Parent Reference in the following manner.

public Reference CreateReferenceFor(PageData page)
{
    return Reference.Create(page.ContentGuid.ToString());
}

Similary, if we wanted to contribute a comment to an Episerver Commerce product, our Reference might be constructed as shown below.

public Reference CreateReferenceFor(ProductContent product)
{
    return Reference.Create(product.Code);
}

Now, while things like pages and products may be physically represented in a database somewhere, we're not strictly limited to such entities as a Parent. The ability to define our references means that we can also represent conceptual entities with no physical representation. Consider a feature where you'd like visitors to be able to discuss the facets of a product. A shirt's fit or a monitor's clarity. These are qualities of a product that likely have no stored entity to represent them but we can still have a conversation about them.

var product = product.Code;
var facet = "fit";
var parent = Reference.Create($"product://{product}/{facet}");

Contributing Comments

With an understanding of what comprises a Comment, we're ready to begin contributing them. Adding a Comment is a simple, two step, process:

  1. Construct the comment, defining its BodyAuthor, and Parent.
  2. Submit the Comment to Episerver Social.

All operations on Comments are performed through the ICommentService interface (EPiServer.Social.Comments.Core.ICommentService). A reference to this service can be obtained using Episerver's service locator.

Let's continue the example of contributing comments to a page within Episerver. Using the concepts that we covered above, the sample below demonstrates how we might implement such a scenario.

public class MyPageController : ContentController<MyContent>
{
    private readonly ICommentService commentService;
    
    public MyPageController()
    {
        commentService = EPiServer.ServiceLocation.ServiceLocator.Current.GetInstance<ICommentService>();
    }
    
    // ...

    private void SubmitComment(PageData page, string body)
    {
        // 1. Construct the comment, defining its Body, Author, and Parent
        
        var comment = new Comment(
            CreateReferenceFor(page),
            CreateReferenceFor(HttpContext.Current.User.Identity),
            body,
            true
        );
        
        // 2. Submit the comment to Episerver Social.
        
        commentService.Add(comment);
        
    }
    
    private Reference CreateReferenceFor(PageData page)
    {
        return Reference.Create(page.ContentGuid.ToString());
    }
        
    private Reference CreateReferenceFor(IIdentity user)
    {        
        return user.IsAuthenticated 
            ? Reference.Create(user.Name)
            : Reference.Create("Anonymous");
    }
}

Now we can contribute comments to a page but conversation requires give-and-take. So, how do we reply to an existing comment? The process is very similar to what we've seen already with one small variation.

When a Comment is added to Episerver Social, ICommentService returns us a copy of that Comment populated with some system-generated information. The information includes a unique Id for the new Comment. When submitting a Comment as a reply to another, a Comment's Id is used as a Parent Reference.

Let's expand the controller class defined above with a new method for submitting a reply.

private void SubmitReply(CommentId parentCommentId, string body)
{
    // Convert the CommentId of the parent Comment into a Reference.
    
    var parentCommentReference = parentCommentId.ToReference();
    
     // Construct the comment, defining its Body, Author, and Parent
    
    var comment = new Comment(
        parentCommentReference,
        CreateReferenceFor(HttpContext.Current.User.Identity),
        body,
        true
    );
    
    // Submit the comment to Episerver Social.
    
    commentService.Add(comment);
}

Retrieving Comments

With the ability to add and reply to Comments, our application likely also needs to deliver them. Episerver Social provides a number of ways to filter your Comments for retrieval. Your application may leverage any or all of those filtering capabilities as needed. Let's cover a couple of scenarios to introduce some of the options.

Let's first assume that our application simply maintains a linear list of Comments (i.e. no replies) for a page. To present such a list, our application would want to retrieve a set of Comments with a Parent corresponding to that page.

private ResultPage<Comment> RetrieveComments(PageData page)
{
    // Determine the parent for which Comments should be retrieved
	
    var parent = CreateReferenceFor(page);
    
    // Define the criteria by which Comments should be retrieved
	
    var criteria = new Criteria<CommentFilter>()
    {
        Filter = new CommentFilter
        {
            Parent = parent
        },
        PageInfo = new PageInfo
        {
            PageSize = 10
        }
    };
	
    // Retrieve the Comments from Episerver Social
	
    return commentService.Get(criteria);
}

In this example, we resolve the Reference representing the desired page and assign that Reference as a filter in the Criteria that is submitted to Episerver Social. As a result, Episerver Social delivers only Comments that are immediate children of the identified page. (The method also limits the result set to a maximum of 10 items.)

If we revise this method a bit, we can make it flexible enough to deliver Comments, regardless of whether the Parent is a page or another Comment.

private ResultPage<Comment> RetrieveComments(Reference parent, int pageOffset = 0, int pageSize = 10)
{
    // Define the criteria by which Comments should be retrieved
    
    var criteria = new Criteria<CommentFilter>()
    {
        Filter = new CommentFilter
        {
            Parent = parent
        },
        PageInfo = new PageInfo
        {
            PageOffset = pageOffset,
            PageSize = pageSize
        }
    };
    
    // Retrieve the Comments from Episerver Social
    
    return commentService.Get(criteria);
}

Now we have the flexibility to retrieve comments that are children of any Parent. We can also dictate the size of our result set and the offset at which our result set should begin. 

A best practice is to use such a method to reveal Comments to visitors on demand. Delivering a large volume of Comments can take time. To ensure the best experience, it is recommended that your application deliver smaller result sets (e.g. 10 items) and prompt the visitor to reveal more. Visitors often only read the first few Comments, so this approach helps to maximize performance without hindering the visitor's experience. Using the same method, the children of a Comment (its replies) can also be delivered.

The image below presents a mockup of how such an experience might prompt a visitor to show more Comments.

Image comment-paging.PNG

In scenarios where only a handful of comments are expected, you can filter for an entire tree of Comments. To do so, we filter by a Comment's Ancestors rather than its Parent.

Episerver Social automatically tracks the lineage of References comprising each Comment's ancestry: a Comment's parent, grandparent, great-grandparent, and so on to the root. By filtering within that lineage, we can deliver an entire set of Comments which descend from it. So, to deliver the entirety of a Comment tree for a page we can filter for Comments where the page's Reference is present as an ancestor.

private ResultPage<Comment> RetrieveAllComments(PageData page, int pageSize = 10)
{
    // Define the criteria by which Comments should be retrieved
    
    var criteria = new Criteria<CommentFilter>()
    {
        Filter = new CommentFilter
        {
            Ancestor = CreateReferenceFor(page)
        },
        PageInfo = new PageInfo
        {
            PageSize = pageSize
        }
    };
    
    // Retrieve the Comments from Episerver Social
    
    return commentService.Get(criteria);
}

Going Deeper

With that, you have all that you need to get started with Comments in Episerver Social. There's plenty of more waiting for you when you're looking to dive deeper.

    (By code monkey , 18 May 2017 12:00, Permanent link)

    Thanks chris. Nice and well explained. 

    Hopefully, there will be a linq provider in the further. 

    (By Chris Banner , 19 May 2017 14:07, Permanent link)

    Yes -- that's something that we'll be exploring in the future. Thanks for the feedback!

  Please login to post a comment