EPiServer property getter cleaner-upper

Just a daily time saver, for reuse at another time.

Any old time windows developer, will remember the fun of using the ini files with GetPrivateProfileString. As much as ini files sucked there is one nice aspect of that call – you can setup a default value it is to return in case a value is not specified in the file. Similarly in a daily episerver programming you usually want to read a value but if one is not epcified you will usually want to use another value and just move along with the progress, and not really care to have an if there to do the filling in. Not to limit the property to any specific type – the task can be nicely solved with generics to handle pretty much any type of property.

/// <summary> /// Gets a property from a page by its name - if a property does not exist or is not set - returns a default value. /// </summary> /// <typeparam name="T">The type of the property</typeparam> /// <param name="page">The page to pull the data from</param> /// <param name="propertyName">The name of the property</param> /// <param name="defaultValue">The value to be returned if the property does not exist or is not set.</param> /// <returns>The value of the property.</returns> public static T GetPropertyDefault<T>(PageData page, string propertyName, T defaultValue) { PropertyData property = page.Property[propertyName]; if (property == null || property.Value == null) { return defaultValue; } else { return (T) property.Value; } }

It’s hard not to appreciate how clean that resolve is (with strong typing being forced with use of generics):

string link = PageUtility.GetPropertyDefault(CurrentPage, "ALink", "#top");

Another speedup coming from the TryParse background – when I want to deviate from the normal execution if a property is not defined but still have a clean code and strong typing…

/// <summary> /// Gets a property from a page by its name - if a property does not exist or is not set, /// returns the presence status of the property existence as a boolean value. /// </summary> /// <typeparam name="T">The type of the property.</typeparam> /// <param name="page">The page to pull the data from</param> /// <param name="propertyName">The name of the property</param> /// <param name="value">The value to be returned if the property exist and has a value, /// if the proeprty does not exist or is not set the value witll remain unchanged.</param> /// <returns>True if the property was retrieved succesfully, false otherwise.</returns> public static bool TryGetProperty<T>(PageData page, string propertyName, ref T value) { PropertyData property = page.Property[propertyName]; if (property == null || property.Value == null) { return false; } value = (T)property.Value; return true; }

Is EpiServer a hard-shelled clam or what?

As much as I seem to be enjoying my trip with EpiServer there are some little things I don’t seem to appreciate all that much and I’m not quite sure how to work around some of them in an elegant way.

EpiServer has a fairly advanced way of dealing with properties but it also seems to be a bit tough on the developer whenever you try to do something more than just strictly using its API-s. One of the areas I don’t really enjoy is the dealing with the pages that are expired or generally unavailable for the user for various reasons.

In the project that we’ve been implementing recently we needed to store page ids for further reference in numerous places and although this generally works, accessing a page that’s been deleted, expired or not published yet, has proven to be a challenge and I can’t seem to be able to find an elegant solution around it.

For instance, we have a list of bloggers, that are stored in our faceted navigation with links to their pages, our system lists them and the links to their pages, should someone’s page be unpublished yet – we run into problems.

Another good sample of where this is needed is a list of pages (A multi-page property of sorts). There seems to be no implementation of a multi-page property in EpiServer and the only reasonable implementation that I’ve been able to find is available through EpiCode. The following is it deals with the pages going in or out of the system, which leads me to think that the only way of checking whether a page is available for me is to instantiate it with all the consequences of it:

// get the page with error handling for // access denied or deleted page try { PageData page = Global.EPDataFactory.GetPage(pageref); isExternalLink = (page.StaticLinkURL != multipageLinkItem.Url); if (page != null && isExternalLink == false) _selectedPages.Add(page); } catch (PageNotFoundException notFoundEx) { // We should not add the page if it // does not exist } catch (EPiServer.Core.AccessDeniedException accessDeniedEx) { // User is not allowed to see page, skip it } catch (Exception ex) { // The page could not be loaded, for some other // reason. System.Diagnostics.Debug.Write("Page could not be loaded: " + pageref.ToString(), "PropertyMultiPage"); }

What I do not like about this part (of an otherwise remarkable piece) of code is that exceptions are not supposed to be the driving force of the program flow. But in this case they seem to have been forced to do it. It’s like I had to open a file to check its size or whether it even exists.

I can see why the system will not let me visit the page if I’m not allowed to do it as a user, but the fact that the API frowns at me whenever I even try to instantiate it just to check its existence or my rights to it has proven to be quite problematic. After all a user is not supposed to see a login screen in the list of pages, but rather when he/she enters a page that he/she no no rights to. Better yet, give me a way to check whether I even can access it.

Is there one already? Has anyone heard? Did anyone see?

The great missing feature of EPiServer

As we’ve been debating with Steve in the EpiCode IRC channel (Come on, join us there! You know you want it!) a few days ago, probably one of the biggest missing features in EPiServer is multi-page property.

Yes there seems to be a fairly robust implementation of a similar functionality on EpiCode however it’s got 2 serious drawbacks:

  1. adding a great number of consecutive pages is a tedious process
  2. it’s not native to EPiServer, meaning – if I use it in my module that I would like to distribute later I need to put the control there. Short of potential licensing issues, this introduces an unnecessary complication level for such distributable modules

Another big issue with the page selecting dialog – apart form being unable to select multiple pages is its inability to root it anywhere outside the original EPiServer repository root. This really limits its quality in terms of re-using of its functionality to be able to use it for selecting of a limited set of pages.

I really wish I could have a clear API for it like I can use the Windows Forms  Open/Save Dialog in desktop applications. I mean seriously – I thought it was impossible that in a product as well thought out as EPiServer something as basic would be missing – there must be something out there to do it, right? WRONG. I looked all around the EPiServer code assemblies and short of re-coding the dialog form grounds up, all I have been able to dig out was a way to re-use the a part of the favorites functionality of the dialog.

You might find a way to reuse the following piece of code. If you put this in your .ASP :

Read the rest of this article »

Posted in ASP.NET, EPiServer, Software Development, Web applications
1 Star2 Stars3 Stars4 Stars5 Stars (2 votes, average: 5.00 out of 5)
Loading...
| 45 Comments »

Database-based paged EPiServer searches

Searches paged in the database have posed a problem in SQL Server at least prior to version 2005. I’ve found some solution to the problem on the net but they were so cludgy that I would never really put anything like that in a production server. I hope the following will shed some light on how they work in general by using in in EPiServer context.

Theoretically in EPiServer you can pull the pages that match the criteria from the database with EPiServer.Global.EPDataFactory.FindPagesWithCriteria() into the PageList but that seemed to be imposing a strong performance penalty with increased number of pages meeting the criteria. Since this search is sometimes done even multiple times on a page request in our project we needed something better.

Read the rest of this article »

Posted in EPiServer, Microsoft SqlServer, Web applications
1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...
| 15 Comments »

Our friends at EPiServer AB has just let us know that they are in need of EPiServer CMS specialists that might be looking forward to working with them directly, so if you’re an EPiServer professional and meet the requirements specified on the recruitment page give them a shout!

If you’re not already familiar with EPiServer you’re probably not going to make it this round, but then again I suggest you start looking at it now. EPiServer AB is a really dynamic company recently expanding aggresively on the international markets – and rightly so. EPiServer is deserving every credit it can get. I can say that my journey with it so far has been really smooth and I’ve enjoyed every bit of it. So if you’re not up to it, get ready for the next round, in the mean time, grab yourself a login – download a copy of the documentation from the Knowledge Center, a demo license and join us on the Developer Forum.

It’s a great bunch, really fun to work with.

Posted in Blogging, EPiServer, Software Development, Web applications
1 Star2 Stars3 Stars4 Stars5 Stars (2 votes, average: 3.00 out of 5)
Loading...
| 9 Comments »

EPiCode hits IRC

I’ve discovered yesterday on Steve’s blog that him and our other friends on EpiCode have gathered on IRC (something I’ve been lobbying here at our company for quite a while). Come, drop by, let’s meet!

I’ll definitely try to hang out there as much as I can. great to meet you guys.

As Steve suggests – grab yourself a copy of XChat or aMirc, connect to irc.freenode.net and /join #epicode
Read the rest of this article »

Posted in EPiCode, EPiServer, Software Development, Web applications
1 Star2 Stars3 Stars4 Stars5 Stars (2 votes, average: 5.00 out of 5)
Loading...
| 12 Comments »

Google Maps control (property) for EPiServer

Since my effort towards making this control final has been somewhat limited lately, I’ve decided to simply release the code at its current stage so that others can play with it and perhaps we can have something decent done together. The control operation is described in my previous article therefore I will not be going much into it any longer. the deployment of it is something worth mention though…

Download the control form here. Extract the contents of the zip file to a folder and attach the project to your solution, make sure you have the proper EPiServer libraries referenced form the project or it may complain about the references being broken.

Once you have it compiling, copy the GoogleMapEditor.ascx, GoogleMapViewer.ascx and the contents of the resources folder to a folder named GoogleMap within your project folder. Also copy the contents of the lang folder to the lang folder of your site.

The controls the property instantiate try to be smart about resolving the location of its files, but the property does not know its location thus if you decide to place the scripts in a folder other than just GoogleMaps in the app main folder, you need to add to your web.config the following:

<add key="CogGoogleMapControlLocation" value="\GoogleMaps\" />

And in web.config you define the API keys for all the addresses the control will be available from as “CogGoogleMapApiKey_%HOSTNAME%” values.

e.g. set for my machine localhost (for me) & dune(for access from other computers within our network):

<add key="CogGoogleMapApiKey_localhost" value="A value generated for 'localhost'" />
<add key="CogGoogleMapApiKey_dune" value="A value generated for 'dune'" />

The controls determine by themselves which key to use based on the http request so that the Google API does not complain about the key being improper.

Other than this the control should be self registering and all you need to do is to add it to your Page Type in the Admin section of the site and add:

<EPiServer:Property ID="GoogleMapData" runat="server" PropertyName="GoogleMapData" />

to the template you want to use it with.

The control really needs an improved support for translation the stuff currently there is used for learning more that than to actually be useful. Should we decide to go further with it, it definitely will be extended.

The scripts used for the DOPE editing are released under GPL (the scripts were originally released with the MediaWiki GoogleMaps editor under the same license). Those scripts although modified slightly are also released under GPL and are NOT a part of the control – they just happen to be used by it. I am still trying to decide what license use with the rest of the control, so be aware that this is still a subject to be changed, for now just feel free to use the code and should you make any changes to it, please feed them back so that I can improve the control further. The control will most probably end up as a part of Epicode, as soon as I get a response on the epicode forums from Steve on how to add them.

To finalize my mini series on the object store I’d like to put a simple page comments library. The library takes care of everything that is required for you to post and retrieve a list of comments. It does not (so far) offer any moderation functionality or even facilitates any comments removal. It’s something that I will most probably be added in the process.

I am in the process of figuring out how I can contribute it through the Community EpiCode effort on CodeResort. As soon as I get some answers from Steve, I’ll get it uploaded there. In the mean time let me document how to start using it.

For the time being you can get the code here or the compiled library with the intellisense help form here.

Posting comments

the posting is somewhat manual in terms of not having a pre-made control for it. Which if you look at the code does not have much sense to have.

All you need to do is put two edit boxes on a page and a submit button, and then bind the action of the submit button to a code looking somewhat like:

protected void SubmitComment(object sender, System.EventArgs e)
{
    IPageComment newComment =
        PageCommentFactory.createInstance(Guid.Empty,
        string.Format("CommentForPage{0}", CurrentPage.PageLink.ID),
        CurrentPage.PageLink.ID,
        SubjectTextBox.Text, ContentTextBox.Text, DateTime.Now, false, true, false);
    newComment.Save();
}

I honestly don’t feel like making a custom control for creating those would be worthwhile since no one would end up using it anyway.

The listing of comments however…

The comments can be accessed in a number of ways.

Probably the easiest one would be by using the templated control I’ve written in the library, which is a simple descendant of the ASP.NET repeater. The page could would look something like:

...

<%@ Register TagPrefix="CognifideControls"
    Namespace="Cognifide.EPiServerControls.PageComments.Controls"
    Assembly="Cognifide.EPiServerControls.PageComments" %>

...


<CognifideControls:PageCommentsList ID="CommentControl" runat="server"
    PageLinkIdProperty="<%# CurrentPage.PageLink.ID %>">
    <ItemTemplate> 
        <b><%# CommentControl.CurrentComment.Title %></b> - 
        <%# CommentControl.CurrentComment.SubmitDate.ToString() %><br />
        <%# CommentControl.CurrentComment.Content%><br /><br />
    </ItemTemplate>
</CognifideControls:PageCommentsList>

I’ve chose this way since that’s pretty much the standard way of adding controls that are defined in Episerver and just generally ASP.Net.

But nothing stops you from accessing the comments directly,  and then filling in the data for the repeater yourself like:

<asp:Repeater ID="CurrentComments" runat="server" 
    OnItemDataBound="CurrentComments_ItemDataBound"> 
    <ItemTemplate>
        <b><asp:Label ID="CommentSubjectLabel" runat="server"></asp:Label></b><br />
        <asp:Label ID="CommentContentLabel" runat="server"></asp:Label><br /><br />
    </ItemTemplate>
</asp:Repeater>

And then in the code-behind

protected void Page_Load(object sender, EventArgs e)
{
    List<IPageComment> comments =
        PageCommentFactory.GetCommentsForPage(CurrentPage.PageLink.ID, 
        DateTime.MinValue, DateTime.MaxValue, true);
    CurrentComments.DataSource = comments;
    CurrentComments.DataBind();
}

protected void CurrentComments_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
    IPageComment comment = (e.Item.DataItem as IPageComment);
    Label commentSubjectLabel = (Label)e.Item.FindControl("CommentSubjectLabel");
    if (comment != null)
    {
        commentSubjectLabel.Text = comment.Title;
    }
 
    Label commentContentLabel = (Label)e.Item.FindControl("CommentContentLabel");
    if (comment != null)
    {
        commentContentLabel.Text = comment.Content;
    }
}

That’s pretty much what my implementation does anyway.

I hope to be able to put it up on CodeResort soon so that we can see what else could be done. Additionally my library allows for replacing the persistence provider, which we will probably have implemented using nHibernate to test its speed versus the ObjectStore. Should you be interested in providing some help with this, or adding come moderation code to the admin side of the site on top of the interface, it would definitely be greatly appreciated.

I have started implementing the Property based on the code so that it can be easily displayed on the editor’s page, but for now, I’ll have to delay it since we’ve got some other stuff to do related to the project I’m currently working on.

Posted in ASP.NET, C#, Downloadable, EPiServer, Software Development, Web applications
1 Star2 Stars3 Stars4 Stars5 Stars (3 votes, average: 3.67 out of 5)
Loading...
| 3 Comments »

EPiServer’s ObjectStore (Part 2)

I think we’re mostly finished investigating ObjectStore for now. In this article I’ll try to finish up on the apsects of using the Object store in a real life solution that is a basic Page comments. In my previous article concerning ObjectStore I have described a way of storing and retrieving an object from the Store, which is fine and dandy if we know exactly the object’s OD, for example if we reference it from a page. But what good is a store like that if we cannot search it for content? The problem we were trying to solve using ObjectStore was storing comments for ANY Episerver page without having to do anything to the page type. We might need that for the upcoming project so the discovery may prove useful since this is a really neat way of storing objects.

So the class that we are going to store needs to persists the following values:

namespace Cognifide.EPiServerTest.ObjectStore
{
    [Serializable, XmlInclude(typeof (PageCommentDO))]
    public class PageCommentDO : IItem, IPageComment
    {
        private object id;
        private string name;
        [Indexed(true)]
        private int pageId;
        private string title;
        private string content;
        [Indexed(true)]
        private DateTime submitDate;
        [Indexed(true)]
        private bool moderated;
        [Indexed(true)]
        private bool published = true;
        [Indexed(true)]
        private bool reported;
 
    }
}

 You might have noticed that contrary to what I did before this class has some of its fields tagged with [Indexed(true)] attribute. This is required by the store object if we ever want to query the store for the class using those fields as a filtering criteria. The page also implements the required IItem interface (although the properties required by the interface are not shown in the excerpt) as well as our internal IPageComment which is added there for the purpose of easy switching of implementations. We intend to implement it on both ObjectStore and using NHibernate to make sure we get the best performance possible out of our solution. This interface is to allow us to easily switch between the 2 implementations.

Back to the ObjectStore

 First we need to introduce a few new classes EPiServer uses for querying the Store. The namespace that scopes the querying consists of the following entities: 

namespace EPiServer.BaseLibrary.Search
{    
    public class BetweenExpression : IExpression;
    public class EqualExpression : IExpression;
    public sealed class Expression;
    public interface IExpression;
    public class Order;
    public class Query : IEnumerable;
}

The Query class is at the center of any search in the Store, let’s start with the samples right away. The saving is almost identical to the sample that I’ve shown in my previous article:

public void Save()
{
    ISession session = null;
    try
    {
        // check if there is a schema for the type, if not create it.
        if (Context.Repository.SchemaForType(GetType()) == null)
        {
            TypeSchemaBuilder.RegisterSchemaAndType("CognifidePageCommentsStorage", GetType());
        }
 
        // get a new session from the current context.
        session = Context.Repository.CreateSession();
 
        // wrap it in a transaction 
        session.BeginTransaction();
 
        // make sure the id has been defined.
        if (Id.Equals(Guid.Empty))
        {
            Id = Guid.NewGuid();
        }
 
        //persist
        session.Save(this);
        session.CommitTransaction();
    }
    catch (ElektroPostException exception)
    {
        // ... rollback the transaction and do some reporting
        if (session != null)
        {
            session.RollbackTransaction();
        }
        throw;
    }
    finally
    {
        if (session != null)
        {
            session.Close();
        }
    }
}

Really there is not much meat there. The interesting part comes in the object retrieval. Even though the Search namespace does not look like it offers much, you can still build a fairly fophisticated filter with it. following code retrieves comments for a specific PageLinkID. The comments can be filtered by date and since the call is used to retrieve the page for viewing, we probably only want the ones that are public (meaning they have not been removed by the comment moderator).

 

public static List<PageCommentDO> GetCommentsForPage(int pageId, 
    DateTime beforeDate, DateTime afterDate, bool includeOnlyPublished)
{
    ISession session = null;
 
    try
    {
        // make sure there is a schema to read from, otherwise there is no point
        if (Context.Repository.SchemaForType( typeof (PageCommentDO)) == null)
        {
            return null;
        }
 
        //acquire a session
        session = Context.Repository.CreateSession();
 
        //create the query 
        Query query = new Query(typeof (PageCommentDO));
 
        // we only want comments for a singler page 
        query.Add(Expression.Equal("pageId", pageId));
 
        // should we filter to only wshow comments from some set time period?
        // the following demonstrate how to request a value from within a range
        if ((beforeDate != DateTime.MinValue) ||
            ((afterDate != DateTime.MaxValue) && (afterDate != DateTime.MinValue)))
        {
            query.Add(
                Expression.Between("submitDate",
                                   (beforeDate != DateTime.MinValue) ? beforeDate : new DateTime(0x76c, 1, 1),
                                   ((afterDate != DateTime.MinValue) && (afterDate != DateTime.MaxValue))
                                       ? afterDate
                                       : new DateTime(0x834, 1, 1)));
        }
 
        // probably for a displayed page we will only want to show published comments
        if (includeOnlyPublished)
        {
            query.Add(Expression.Equal("published", true));
        }
 
        // order the comments by date/time so that we get the most recent first
        query.AddOrder(new Order("submitDate", true));
 
        IList list = session.ExecuteQueryObject(query);
        List<PageCommentDO> result = new List<PageCommentDO>(list.Count);
        foreach (object item in list)
        {
            result.Add((PageCommentDO) item);
        }
        return result;
    }
    catch (ElektroPostException ex)
    {
        // ... do some reporting
        throw;
    }
    finally
    {
        if (session != null)
        {
            session.Close();
        }
    }
}

You nahe to agree, this really IS neat. All you need to do now to place the comments on a page is a few lines of code to bind the data from the Store with a repeated of a kind, and a set of edit boxes with a submit button :)

This is basically how much code the submission and retrieval the comments comment take:

// Load the comments to a repeater
protected void Page_Load(object sender, EventArgs e)
{
        List<PageCommentDO> comments = 
            PageCommentDO.GetCommentsForPage(CurrentPage.PageLink.ID, DateTime.MinValue, DateTime.MaxValue);
        CurrentComments.DataSource = comments;
        CurrentComments.DataBind();
}
 
// Comment submission
protected void SubmitComment(object sender, System.EventArgs e)
{
    PageCommentDO newComment = new
        PageCommentDO(Guid.Empty,
        string.Format("CommentForPage{0}", CurrentPage.PageLink.ID),
        CurrentPage.PageLink.ID,
        SubjectTextBox.Text, ContentTextBox.Text, DateTime.Now, false, true, false);
    newComment.Save();
}
 
// the data binding for the labels
protected void CurrentComments_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
    PageCommentDO comment = (e.Item.DataItem as PageCommentDO);
 
    Label commentSubjectLabel = (Label)e.Item.FindControl("CommentSubjectLabel");
    if (comment != null)
    {
        commentSubjectLabel.Text = comment.Title;
    }
 
    Label commentContentLabel = (Label)e.Item.FindControl("CommentContentLabel");
    if (comment != null)
    {
        commentContentLabel.Text = comment.Content;
    }
}

Short of having a control pre=built for you I cannot think of it being able to make it easier.

I will look into packing it into a nice control that we would be looking into contributing some time in the future and of course if there is actually a demand for it.

EPiServer?s ObjectStore (Part 2)

I think we’re mostly finished investigating ObjectStore for now. In this article I’ll try to finish up on the apsects of using the Object store in a real life solution that is a basic Page comments. In my previous article concerning ObjectStore I have described a way of storing and retrieving an object from the Store, which is fine and dandy if we know exactly the object’s OD, for example if we reference it from a page. But what good is a store like that if we cannot search it for content? The problem we were trying to solve using ObjectStore was storing comments for ANY Episerver page without having to do anything to the page type. We might need that for the upcoming project so the discovery may prove useful since this is a really neat way of storing objects.

So the class that we are going to store needs to persists the following values:

namespace Cognifide.EPiServerTest.ObjectStore
{
    [Serializable, XmlInclude(typeof (PageCommentDO))]
    public class PageCommentDO : IItem, IPageComment
    {
        private object id;
        private string name;
        [Indexed(true)]
        private int pageId;
        private string title;
        private string content;
        [Indexed(true)]
        private DateTime submitDate;
        [Indexed(true)]
        private bool moderated;
        [Indexed(true)]
        private bool published = true;
        [Indexed(true)]
        private bool reported;
 
    }
}

 You might have noticed that contrary to what I did before this class has some of its fields tagged with [Indexed(true)] attribute. This is required by the store object if we ever want to query the store for the class using those fields as a filtering criteria. The page also implements the required IItem interface (although the properties required by the interface are not shown in the excerpt) as well as our internal IPageComment which is added there for the purpose of easy switching of implementations. We intend to implement it on both ObjectStore and using NHibernate to make sure we get the best performance possible out of our solution. This interface is to allow us to easily switch between the 2 implementations.

Back to the ObjectStore

 First we need to introduce a few new classes EPiServer uses for querying the Store. The namespace that scopes the querying consists of the following entities: 

namespace EPiServer.BaseLibrary.Search
{    
    public class BetweenExpression : IExpression;
    public class EqualExpression : IExpression;
    public sealed class Expression;
    public interface IExpression;
    public class Order;
    public class Query : IEnumerable;
}

The Query class is at the center of any search in the Store, let’s start with the samples right away. The saving is almost identical to the sample that I’ve shown in my previous article:

public void Save()
{
    ISession session = null;
    try
    {
        // check if there is a schema for the type, if not create it.
        if (Context.Repository.SchemaForType(GetType()) == null)
        {
            TypeSchemaBuilder.RegisterSchemaAndType("CognifidePageCommentsStorage", GetType());
        }
 
        // get a new session from the current context.
        session = Context.Repository.CreateSession();
 
        // wrap it in a transaction 
        session.BeginTransaction();
 
        // make sure the id has been defined.
        if (Id.Equals(Guid.Empty))
        {
            Id = Guid.NewGuid();
        }
 
        //persist
        session.Save(this);
        session.CommitTransaction();
    }
    catch (ElektroPostException exception)
    {
        // ... rollback the transaction and do some reporting
        if (session != null)
        {
            session.RollbackTransaction();
        }
        throw;
    }
    finally
    {
        if (session != null)
        {
            session.Close();
        }
    }
}

Really there is not much meat there. The interesting part comes in the object retrieval. Even though the Search namespace does not look like it offers much, you can still build a fairly fophisticated filter with it. following code retrieves comments for a specific PageLinkID. The comments can be filtered by date and since the call is used to retrieve the page for viewing, we probably only want the ones that are public (meaning they have not been removed by the comment moderator).

 

public static List<PageCommentDO> GetCommentsForPage(int pageId, 
    DateTime beforeDate, DateTime afterDate, bool includeOnlyPublished)
{
    ISession session = null;
 
    try
    {
        // make sure there is a schema to read from, otherwise there is no point
        if (Context.Repository.SchemaForType( typeof (PageCommentDO)) == null)
        {
            return null;
        }
 
        //acquire a session
        session = Context.Repository.CreateSession();
 
        //create the query 
        Query query = new Query(typeof (PageCommentDO));
 
        // we only want comments for a singler page 
        query.Add(Expression.Equal("pageId", pageId));
 
        // should we filter to only wshow comments from some set time period?
        // the following demonstrate how to request a value from within a range
        if ((beforeDate != DateTime.MinValue) ||
            ((afterDate != DateTime.MaxValue) && (afterDate != DateTime.MinValue)))
        {
            query.Add(
                Expression.Between("submitDate",
                                   (beforeDate != DateTime.MinValue) ? beforeDate : new DateTime(0x76c, 1, 1),
                                   ((afterDate != DateTime.MinValue) && (afterDate != DateTime.MaxValue))
                                       ? afterDate
                                       : new DateTime(0x834, 1, 1)));
        }
 
        // probably for a displayed page we will only want to show published comments
        if (includeOnlyPublished)
        {
            query.Add(Expression.Equal("published", true));
        }
 
        // order the comments by date/time so that we get the most recent first
        query.AddOrder(new Order("submitDate", true));
 
        IList list = session.ExecuteQueryObject(query);
        List<PageCommentDO> result = new List<PageCommentDO>(list.Count);
        foreach (object item in list)
        {
            result.Add((PageCommentDO) item);
        }
        return result;
    }
    catch (ElektroPostException ex)
    {
        // ... do some reporting
        throw;
    }
    finally
    {
        if (session != null)
        {
            session.Close();
        }
    }
}

You nahe to agree, this really IS neat. All you need to do now to place the comments on a page is a few lines of code to bind the data from the Store with a repeated of a kind, and a set of edit boxes with a submit button :)

This is basically how much code the submission and retrieval the comments comment take:

// Load the comments to a repeater
protected void Page_Load(object sender, EventArgs e)
{
        List<PageCommentDO> comments = 
            PageCommentDO.GetCommentsForPage(CurrentPage.PageLink.ID, DateTime.MinValue, DateTime.MaxValue);
        CurrentComments.DataSource = comments;
        CurrentComments.DataBind();
}
 
// Comment submission
protected void SubmitComment(object sender, System.EventArgs e)
{
    PageCommentDO newComment = new
        PageCommentDO(Guid.Empty,
        string.Format("CommentForPage{0}", CurrentPage.PageLink.ID),
        CurrentPage.PageLink.ID,
        SubjectTextBox.Text, ContentTextBox.Text, DateTime.Now, false, true, false);
    newComment.Save();
}
 
// the data binding for the labels
protected void CurrentComments_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
    PageCommentDO comment = (e.Item.DataItem as PageCommentDO);
 
    Label commentSubjectLabel = (Label)e.Item.FindControl("CommentSubjectLabel");
    if (comment != null)
    {
        commentSubjectLabel.Text = comment.Title;
    }
 
    Label commentContentLabel = (Label)e.Item.FindControl("CommentContentLabel");
    if (comment != null)
    {
        commentContentLabel.Text = comment.Content;
    }
}

Short of having a control pre=built for you I cannot think of it being able to make it easier.

I will look into packing it into a nice control that we would be looking into contributing some time in the future and of course if there is actually a demand for it.