Chris Banner
May 17, 2017
  9540
(8 votes)

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.

May 17, 2017

Comments

Vincent
Vincent May 18, 2017 12:00 PM

Thanks chris. Nice and well explained. 

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

Chris Banner
Chris Banner May 19, 2017 02:07 PM

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

Please login to comment.
Latest blogs
Opti ID overview

Opti ID allows you to log in once and switch between Optimizely products using Okta, Entra ID, or a local account. You can also manage all your use...

K Khan | Jul 26, 2024

Getting Started with Optimizely SaaS using Next.js Starter App - Extend a component - Part 3

This is the final part of our Optimizely SaaS CMS proof-of-concept (POC) blog series. In this post, we'll dive into extending a component within th...

Raghavendra Murthy | Jul 23, 2024 | Syndicated blog

Optimizely Graph – Faceting with Geta Categories

Overview As Optimizely Graph (and Content Cloud SaaS) makes its global debut, it is known that there are going to be some bugs and quirks. One of t...

Eric Markson | Jul 22, 2024 | Syndicated blog

Integration Bynder (DAM) with Optimizely

Bynder is a comprehensive digital asset management (DAM) platform that enables businesses to efficiently manage, store, organize, and share their...

Sanjay Kumar | Jul 22, 2024

Frontend Hosting for SaaS CMS Solutions

Introduction Now that CMS SaaS Core has gone into general availability, it is a good time to start discussing where to host the head. SaaS Core is...

Minesh Shah (Netcel) | Jul 20, 2024

Optimizely London Dev Meetup 11th July 2024

On 11th July 2024 in London Niteco and Netcel along with Optimizely ran the London Developer meetup. There was an great agenda of talks that we put...

Scott Reed | Jul 19, 2024