in Development

Community Server, RSS and MSDN-ES Forums

In the last TTT we had a session to discuss about several things regarding the .NET User Groups and one of the subjects was the MSDN forums.

Microsoft told us that they were investing some efforts to improve the Spanish MSDN forums and immediately after that we talked about the local forums each .NUG has in their sites. The idea proposed was to remove the local forums and to support the MSDN forums, this would benefit the developer community since instead of having microsites we can sum our efforts to have a centralized help site.

In Baleares on .NET we thought that the best was to have our own forums in order to provide better assistance to our community, we wanted to be sure that the .NET developers of Baleares had answers to their questions. Because of that some "volunteers" are monitoring the forums to provide solutions, but as the community grows we can give less support and the idea to post the questions to bigger communities does not seem to be a bad idea.

I must say that I'm not 100% convinced yet that this is best solution for our community, since I trust the "closeness" concept we tried to offer and that had good results until now.

Anyway I've decided to give a try and to introduce progressively some of the MSDN forums in order our users know them. I wanted not only to insert a simple link to the MSDN forums, but to display some of the posts in our website in line with the current appearance. 

To do it, we can save lot of time taking profit of the Community Server platform to add the latest posts in our home page in the same way the Recent Post List appears. If we take a look about how CS displays the recent posts, we can see that it uses the control IndexPostList, which basically consists of display the results of a search done when the page inits. Below you can see the code the guys from CS use:

   1: List<IndexPost> recentPosts = CSCache.Get("HomePageSearch-" + CurrentCSContext.User.RoleKey) as List<IndexPost>;
   2: if (recentPosts == null)
   3: {
   4:     SearchQuery query = new SearchQuery();
   5:     query.StartDate = DateTime.Now.AddDays(-10);
   6:     query.EndDate = DateTime.Now.AddDays(1);
   7:     query.PageSize = 5;
   9:     recentPosts = CSSearch.Search(query).Posts;
  10:     CSCache.Insert("HomePageSearch-" + CurrentCSContext.User.RoleKey, recentPosts, CSCache.MinuteFactor * 5);
  11: }
  12: RecentPostList.DataSource = recentPosts;

It doesn't seem too complicated, basically we need to introduce a new IndexPostList control in the main page and set the DataSource property with our own List of IndexPost. All the MSDN ES forums allow to syndicate the content, this means that we can access it via RSS. The easiest way to do this with .NET is to use a XmlTextReader and read the contents inside a DataSet.

As you can see in the code below we also make use of the XslCompiledTransform, this is done because the feeds contain two comments elements, even if they have different namespaces the ReadXml method of the dataset will throw an exception, since it will consider both of them as the same column. I got the idea to use to Transform from the next post, where the author got into the same troubles when loading two "comments" elements from the feed.

   1: XslCompiledTransform rssTransform = new XslCompiledTransform();
   2: rssTransform.Load(xsltPath);
   5: using (XmlTextReader xmlTextReader = new XmlTextReader(feeds[i].Url))
   6: {
   7:     using (StringWriter stringWriter = new StringWriter())
   8:     {
   9:         XPathDocument rssXPath = new System.Xml.XPath.XPathDocument(xmlTextReader);
  10:         rssTransform.Transform(rssXPath, null, stringWriter);
  12:         DataSet ds = new DataSet();
  13:         using (System.IO.StringReader reader = new System.IO.StringReader(stringWriter.ToString()))
  14:         {
  15:             ds.ReadXml(reader);
  16:         }
  18:         DataTable dt = ds.Tables["item"];
  19:         int maxItems = feeds[i].MaxItems;
  20:         DateTime maxDate = DateTime.Now.AddDays(feeds[i].MaxDays * -1);
  21:         for (int idx = 0; idx < maxItems && idx < dt.Rows.Count; idx++)
  22:         {
  23:             DateTime postDate = DateTime.Parse((string)dt.Rows[idx]["date"]);
  25:             if (postDate >= maxDate)
  26:             {
  27:                 IndexPost post = new IndexPost();
  28:                 post.ApplicationKey = feeds[i].Name;
  29:                 post.ApplicationType = ApplicationType.Forum;
  30:                 post.ApplicationUrl = feeds[i].Url;
  31:                 post.FormattedBody = (string)dt.Rows[idx]["description"];
  32:                 post.PostDate = postDate;
  33:                 post.SectionName = feeds[i].Name;
  34:                 post.Title = (string)dt.Rows[idx]["title"];
  35:                 post.Url = (string)dt.Rows[idx]["link"];
  36:                 post.UserName = (string)dt.Rows[idx]["creator"];
  38:                 items.Add(post);
  39:             }
  40:         }
  42:     }
  43: }

From lines 27 to 36 we initialize the class IndexPost with the values obtained from the feed, this values are then used by the IndexPostList control and allow some interaction with them like providing links to the post and forum URLs.

Aside loading the XML you will see that I added some extra features to be able to configure a few parameters regarding the posts we will display. This is done via a custom section I added to the web.config that is handled by the classes SectionHandler, FeedConfigurationCollection and FeedConfiguration. These classes will allow us configuring the feeds from which we will get the posts, the maximum number of posts to display per feed and the oldness of them. You can download the source code to see the full implementation. Do not forget to place the new assembly in the bin folder of CS and to set the configuration in the web.config.

Now that we can configure the feeds and to obtain the desired posts we need to modify the page home.aspx for each of the themes supported on our CS. First of all is to add our IndexPostList control with the information to show.

   1: <CSControl:IndexPostList runat="server" ShowHeaderFooterOnNone="false" ID="RecentMsdnPostList">
   2:     <HeaderTemplate>
   3:         <p />
   4:         <h2 class="CommonTitle"><CSControl:ResourceControl ResourceName="default_homepage_msdnrecentposts" runat="server" /></h2>
   5:         <div class="CommonContent">
   6:         <ul class="CommonSearchResultList">
   7:     </HeaderTemplate>
   8:     <ItemTemplate>
   9:         <li>
  10:         <CSControl:IndexPostData runat="server" Property="ApplicationType" Text="&lt;div class=&quot;CommonSearchResultArea Msdn&quot;&gt;" />
  11:             <CSControl:IndexPostData runat="server" Property="Title" LinkTo="Post" Tag="h4" CssClass="CommonSearchResultName" />
  12:             <div class="CommonSearchResult">
  13:                 <CSControl:IndexPostData Property="FormattedBody" runat="server" TruncateAt="350" />
  14:             </div>
  15:             <div class="CommonSearchResultDetails">
  16:                 <CSControl:ResourceControl runat="server" ResourceName="SearchResults_PostTo" />
  17:                 <CSControl:IndexPostData Property="SectionName" LinkTo="Section" runat="server" />
  18:                 <CSControl:IndexPostData Property="ApplicationType" LinkTo="Application" Text="({0})" runat="server" />
  19:                 <CSControl:ResourceControl runat="server" ResourceName="SearchResults_By" />
  20:                 <CSControl:IndexPostData Property="UserName" LinkTo="Nothing" runat="server" />
  22:                 <CSControl:IndexPostData Property="PostDate" runat="server" />
  23:             </div>
  24:         </div>
  25:         </li>
  26:     </ItemTemplate>
  27:     <FooterTemplate>
  28:         </ul>
  29:         </div>
  30:     </FooterTemplate>
  31:     <NoneTemplate></NoneTemplate>
  32: </CSControl:IndexPostList>

The main things to have in consideration is that we added the a new resource default_homepage_msdnrecentposts (file resources.xml of each language) to support several languages and a new css style CommonSearchResultArea.Msdn (file common.css of each theme), with this style we will modify the background image that appears left to the post.

The next step is to load the feeds and to bind the list of posts retrieved to the control, this is done in the OnInit event of the home page.

   1: List<IndexPost> msdnPosts = CSCache.Get("HomePageMsdn-" + CurrentCSContext.User.RoleKey) as List<IndexPost>;
   2: if (msdnPosts == null)
   3: {
   4:     BalearesOnNet.Rss.FeedHelper helper = new BalearesOnNet.Rss.FeedHelper();
   5:     msdnPosts = helper.LoadFeeds(Request.PhysicalApplicationPath + "rssToDataSet.xslt");
   6:     if (msdnPosts.Count > 0)
   7:         CSCache.Insert("HomePageMsdn-" + CurrentCSContext.User.RoleKey, msdnPosts, CSCache.HourFactor);
   8: }
   9: RecentMsdnPostList.DataSource = msdnPosts;

As you can see we also add the retrieved posts to the cache to avoid loading them from MSDN forums each time a user connects to the page, you can play with several factors to find which one fits better with your site.

Below you can see a screenshot of how the posts are displayed or even better just go to Baleares on .NET

 MSDN forums

That's all I hope you liked it.