Silverlight TagCloud Now on CodePlex

The complete Silverlight TagCloud series of posts:

  1. A Silverlight TagCloud, Part 1: The WCF Service
  2. A Silverlight TagCloud, Part 2: The TagCloud
  3. A Silverlight TagCloud, Part 2.1: Refinements
  4. Silverlight TagCloud Now on CodePlex

The Silverlight TagCloud control, like my Live Comment Preview control, now has its very own project page on CodePlex:

image

 

I’ll continue to post information about upgrades and new features (if there are any) on this blog, but the source will henceforth live on CodePlex. Feel free to leave feedback either here or on the new Silverlight TagCloud project site.

Download: Silverlight TagCloud control on CodePlex

A Silverlight TagCloud, Part 2.1: Refinements

August 11, 2009 15:21

The complete Silverlight TagCloud series of posts:

  1. A Silverlight TagCloud, Part 1: The WCF Service
  2. A Silverlight TagCloud, Part 2: The TagCloud
  3. A Silverlight TagCloud, Part 2.1: Refinements
  4. Silverlight TagCloud Now on CodePlex

Maybe this post should be “Part 3”, but I think of it as more of an incremental increase over Part 2 of this series, so we’ll leave it at 2.1. In any case, this is the third post in a series about a Silverlight TagCloud control I wrote that began with a discussion of the TagCloud’s back-end WCF service and then moved on to the front-end Silverlight control. This post extends the Silverlight control, adding some additional features while easing deployment.

Let’s get into it.

New Stuff

First, a quick summary of the TagCloud control changes:

1. Removed style information from app.xaml

We don’t need it anymore as this information is now passed in as initial parameters.

2. New ItsCodingTime.Utils.Silverlight dll

This utility dll contains the ColorNames class that I previously discussed. I wanted it here to ease distribution and to use in other Silverlight projects. Also, I’ll likely write a separate post (and release the full source) about the utils dll when it has some real meat in it.

3. New FontFamily initial parameter

Tag item FontFamily can now be set via a property from ASP.NET.

4. New BackgroundColor initial parameter

The control’s Background can now be set via a property from ASP.NET.

5. New TagColor initial parameter

Tag item ForegroundColor can now be set via a property from ASP.NET.

6. New TagHoverColor initial parameter

Tag item MouseOver Foreground color can now be set via a property from ASP.NET.

7. Consolidated the control’s ASP.NET and JavaScript code into a user control

The hosting ASP.NET page was getting a little messy what with all of that <object> stuff and JavaScript. While I left the silverlight.js and jquery references, as well as the onSilverlightError function, in my master page, the TagCloud specific code is now in a user control.

FontFamily, BackgroundColor, TagColor, TagHoverColor properties & the SilverlightTagCloud User Control

It’s probably best to explain the usage of the new properties and user control with an example. I’ll assume you’ve already installed the TagCloud from my previous post and want to “upgrade”.

You’ll need to copy over the SilverlightTagCloud.ascx/SilverlightTagCloud.ascx.cs files into your site’s “user control” folder (wherever that may be based on your blogging or web site platform) and add the following reference to the page which will house the control:

   1: <%@ Register src="~/User controls/SilverlightTagCloud.ascx" TagName="SilverlightTagCloud"
   2:     TagPrefix="ucSilverlightTagCloud" %>
View Plain

Once you’ve got that, you can then replace the entire <object> declaration with (replacing the colors with selections of your own, of course; for possible color names check out my post on WPF Colors in Silverlight):

   1: <ucSilverlightTagCloud:SilverlightTagCloud runat="server" TagThreshold="2" FontFamily="Arial"
   2:     TagColor="#5C80B1" TagHoverColor="DodgerBlue" BackgroundColor="AliceBlue" MinimumFontSize="12" />
View Plain

Also, there was some initialization code in the code-behind. That can all be removed.

If any of this is hard to follow, check out the sample project in the code download below.

As you can see, the end result of these changes is really focused at being able to customize the appearance of the control via settable properties. Hopefully I’ve got enough features baked in now, though adding additional properties of your own should be pretty easy.

I won’t go over the innards of the user control. It’s all the same as what I talked about before (except for the addition of the code to support the above changes), and you can readily check it out in the download. Note that the WCF service code, as before, is in “demo” mode, meaning you can uncomment the BlogEngine.NET specific code if you’re using that platform (and include a reference to BlogEngine.NET), otherwise you’ll need to plug in your own blogging platform’s tag information.

Questions, concerns, or requests for missing or new functionality, let me know.

Download: Silverlight TagCloud control on CodePlex

A Silverlight TagCloud, Part 2: The TagCloud

July 29, 2009 09:36

The complete Silverlight TagCloud series of posts:

  1. A Silverlight TagCloud, Part 1: The WCF Service
  2. A Silverlight TagCloud, Part 2: The TagCloud
  3. A Silverlight TagCloud, Part 2.1: Refinements
  4. Silverlight TagCloud Now on CodePlex

This is the second part of a two part series about a Silverlight TagCloud I developed. In Part 1 of this series I discussed the WCF service component. This part will be about the Silverlight control itself.

A Brief Re-introduction to the TagCloud Architecture

The architecture is a simple producer/consumer model. The producer is a WCF service that gathers tag information. The consumer is the Silverlight control, which gets and renders those tags.

Assuming you’re on the itscodingtime.com web site and not an RSS reader, you can see the TagCloud in action at the right.

Some of the Difficulties

Developing this control would have been a piece of cake if not for one thing: resizing. Think about it. The control needs to respond to resize events based on (1) browser resizing and (2) the number of tags being displayed. I was able to make use of a WrapPanel control with a TextBlock for each tag, which greatly simplified some of the work. But the WrapPanel will gladly overflow beyond its container if you let it.

Now, you might be able to get away with not worrying about #1 depending on how your web site is laid out, but #2 is a hard one to get around. That’s not to say my solution is the only one or the best. There may well be a 100% width here and a 100% height there method that works (maybe even across all browsers), but I couldn’t figure it out.

I did, however, come up with a control that quite nicely resizes based on browser sizing and tag information, so let’s get into the good stuff.

The Silverlight TagCloud Control

First of all, as always, the full source is available for download below. In the .zip, you’ll find projects for the TagCloudService (see Part 1) and TagCloudControl, as well as a basic web app that hosts the service and control and should prove useful in terms of testing. One thing of note: I commented out the BlogEngine.NET specific code in the WCF service and put in a dummy loop to generate tag data. This way you can run the project without having BlogEngine.NET in place.

The TagCloud control uses two controls from the Silverlight Toolkit: WrapPanel and TextBlock. You’ll probably want to download the control toolkit if you plan on doing any debugging (though I do include System.Windows.Controls.dll in the .zip download).

Here’s the project layout so you can get a feel for what files are involved:

image

Pretty standard stuff, though note that I’ve already added the service reference to TagCloudService and a strong name key file for the Silverlight control.

Let’s get into the control’s files.

1.) App.xaml

I defined one style in the Application’s XAML file (per the Future Enhancements list below, I plan to add the ability to specify your own font family as well as tag color, so this style will be going away at some point).

   1: <Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   2:          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   3:          x:Class="TagCloud.TagCloudControl.App">
   4:     <Application.Resources>
   5:         <Style x:Key="TagStyle" TargetType="HyperlinkButton">
   6:             <Setter Property="Foreground" Value="#d0eb55" />
   7:             <Setter Property="FontFamily" Value="Tahoma" />
   8:         </Style>
   9:     </Application.Resources>
  10: </Application>
View Plain

2.) App.xaml.cs

If you look in App.xaml.cs, you’ll see that the control receives a small number of Initial Parameters: baseUrl, controlId, controlHostId, tagThreshold, minimumFontSize. Here’s the Application_Startup method:

   1: private void Application_Startup (object sender, StartupEventArgs e)
   2: {
   3:     if (e.InitParams.Count <= 0)    // no initial param's, no continue
   4:     {
   5:         return;
   6:     }
   7:  
   8:     // NOTE: We'll look for missing params and set defaults in the Page constructor
   9:  
  10:     var baseUrl = string.Empty;
  11:     var controlId = string.Empty;
  12:     var controlHostId = string.Empty;
  13:     var tagThreshold = string.Empty;
  14:     var minimumFontSize = string.Empty;
  15:  
  16:     if (e.InitParams.ContainsKey ("BaseUrl"))
  17:     {
  18:         baseUrl = e.InitParams["BaseUrl"];
  19:     }
  20:  
  21:     if (e.InitParams.ContainsKey ("ControlId"))
  22:     {
  23:         controlId = e.InitParams["ControlId"];
  24:     }
  25:  
  26:     if (e.InitParams.ContainsKey ("ControlHostId"))
  27:     {
  28:         controlHostId = e.InitParams["ControlHostId"];
  29:     }
  30:  
  31:     if (e.InitParams.ContainsKey ("TagThreshold"))
  32:     {
  33:         tagThreshold = e.InitParams["TagThreshold"];
  34:     }
  35:  
  36:     if (e.InitParams.ContainsKey ("MinimumFontSize"))
  37:     {
  38:         minimumFontSize = e.InitParams["MinimumFontSize"];
  39:     }
  40:  
  41:     RootVisual = new Page (baseUrl, controlId, controlHostId, tagThreshold, minimumFontSize);
  42:  
  43:     return;
  44: }
View Plain

And a brief description of each initial parameter:

  • baseUrl: This is the base web site url passed in via the hosting site. I have this because I’ve deployed the control to multiple sites, so this is an easy way to have the site identify itself. Note that you could also use Application.Current.Host.Source, then chop off the “ClientBin/Control.xap” part. I might even go that route as a future enhancement and simplification.
  • controlId: This is the Silverlight control’s id from ASP.NET. We need this for sizing.
  • controlHostId: This is the hosting div’s id, also from ASP.NET. We need this for sizing.
  • tagThreshold: In a TagCloud you typically only want to display those tags whose frequency has surpassed a certain threshold. That’s the purpose of this parameter. Set it to 0 if you want to display all tags.
  • minimumFontSize: The minimum font size to use for the “smallest” tag. Tags are sized up proportionally from there.

All of those parameters are then handed off to the control’s Page class.

3.) Page.xaml

Here’s the Page’s XAML:

   1: <UserControl xmlns:controlsToolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit" x:Class="TagCloud.TagCloudControl.Page"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     Loaded="UserControl_Loaded"
   5:     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   6:     mc:Ignorable="d">
   7:     <controlsToolkit:WrapPanel x:Name="uxWrapPanel" SizeChanged="WrapPanel_SizeChanged" />
   8: </UserControl>
View Plain

Note that I am referencing the Silverlight Control Toolkit here since we are using a WrapPanel.

4.) Page.xaml.cs

This is where all of the work is done. I won’t go through every part, but instead try to hit the highlights.

a.) ScriptableMember

In the Page class constructor we register a scriptable object in order to build a JavaScript to Silverlight bridge:

   1: HtmlPage.RegisterScriptableObject ("JStoSLBridge", this);
View Plain

The method UpdateControlSize is then called from ASP.NET/JavaScript and is initiated from, appropriately enough, the browser resize event:

   1: [ScriptableMember]
   2: public void UpdateControlSize (int nNewWidth)
   3: {
   4:     if (HtmlPage.Document != null)
   5:     {
   6:         var silverlightControlHost = HtmlPage.Document.GetElementById (_tagCloudControlHostId);
   7:         var silverlightObjectTag = HtmlPage.Document.GetElementById (_tagCloudControlClientId);
   8:  
   9:         if ((silverlightControlHost != null) && (silverlightObjectTag != null))
  10:         {
  11:             silverlightObjectTag.SetStyleAttribute ("width", nNewWidth + "px");
  12:             silverlightControlHost.SetStyleAttribute ("width", nNewWidth + "px");
  13:         }
  14:     }
  15:  
  16:     return;
  17: }
View Plain

b.) The WCF TagCloudService Proxy

Here’s where the baseUrl parameter comes in:

   1: var binding = new BasicHttpBinding ();
   2: var address = new EndpointAddress (new Uri (Application.Current.Host.Source, baseUrl + "TagCloudService.svc"));
   3: _tagCloudService = new TagCloudServiceClient (binding, address);
View Plain

Turns out I’m using Application.Current.Host.Source anyway. A good cleanup step here might be to just parse out the base url from Application.Current.Host.Source and not use the baseUrl parameter at all. In any case, this initializes our connection to the TagCloud WCF service from which we will be retrieving tag data. However you construct or get the web site’s address, it’s best to dynamically create the WCF endpoint since it alleviates some of the labor when deploying to multiple sites.

c.) Retrieving Tag data

Once the control has been loaded we fire off an asynchronous call to the TagCloudService:

   1: private void UserControl_Loaded (object sender, RoutedEventArgs e)
   2: {
   3:     if (_tagCloudService != null)
   4:     {
   5:         _tagCloudService.GetTagsCompleted += tagCloudService_GetTagsCompleted;
   6:         _tagCloudService.GetTagsAsync (TagThreshold, BaseUrl);
   7:     }
   8:  
   9:     return;
  10: }
View Plain

Of note is the TagThreshold parameter, which the service will use to only return those tags which meet the specified minimum threshold (no point in sending back all tag data if the control isn’t going to make use of it).

I’ll spare you the whole tagCloudService_GetTagsCompleted method; here’s the main part:

   1: foreach (var cloudTag in _cloudTagCollection)
   2: {
   3:     if (cloudTag.TagOccurrences < TagThreshold)    // if tag occurrence is below threshold, do not add; the service shouldn't return these, but just in case
   4:     {
   5:         continue;
   6:     }
   7:  
   8:     if (cloudTag.Equals (null)) continue;
   9:  
  10:     // we'll wrap a TextBlock in the HyperlinkButton in order to support text wrapping
  11:     var textBlock = new TextBlock { Text = cloudTag.TagName, TextWrapping = TextWrapping.Wrap };
  12:  
  13:     var lnkBtn = new HyperlinkButton
  14:     {
  15:         Content = textBlock,
  16:         FontSize = CalcTagFontSize (cloudTag.TagOccurrences, maxOccurrences),
  17:         NavigateUri = new Uri (cloudTag.TagLink),
  18:         HorizontalAlignment = HorizontalAlignment.Stretch,
  19:         VerticalAlignment = VerticalAlignment.Stretch,
  20:         HorizontalContentAlignment = HorizontalAlignment.Center,
  21:         VerticalContentAlignment = VerticalAlignment.Center,
  22:         Style = Application.Current.Resources["TagStyle"] as Style
  23:     };
  24:  
  25:     lnkBtn.MouseEnter += TagCloudItem_MouseEnter;    // we'll do some styling on mouse over
  26:     lnkBtn.MouseLeave += TagCloudItem_MouseLeave;    // we'll restore styling on mouse leave
  27:  
  28:     uxWrapPanel.Children.Add (lnkBtn);
  29: }
View Plain

We loop through the received tags, creating a new TextBlock for each and then wrapping that TextBlock in a HyperlinkButton so that when a user clicks on the tag it takes them to that series of pages.

What it’s iterating through is a collection of CloudTag objects, defined as part of the TagCloudService’s data contract. More on that can be found in Part 1 of this series.

d.) Calculating Tag Font Size

One of the core features of a TagCloud is, of course, the ability to display tags in varied sizes based on their frequency. Thanks goes to poeticcode for supplying the tag sizing algorithm:

   1: private double CalcTagFontSize (int tagOccurrences, int maxTagOccurrences)
   2: {
   3:     if (maxTagOccurrences == 0)    // this probably shouldn't happen, but you never know...
   4:     {
   5:         return (MinimumFontSize);
   6:     }
   7:  
   8:     // from http://poeticcode.wordpress.com/2007/01/27/tag-cloud-algorithmlogicformula/
   9:  
  10:     var percent = 150 * (1.0 + ((1.5 * tagOccurrences) - (maxTagOccurrences / 2.0)) / maxTagOccurrences);
  11:     var fontSize = (percent / 100) * MinimumFontSize;
  12:  
  13:     return (fontSize);
  14: }
View Plain

Resizing the TagCloud Control

This is a big enough part of the whole control that I thought it deserved its own section. To be honest, I went back and forth trying to consolidate the resizing code either in the control or purely in JavaScript. No matter how you look at it, you really need resizing in both places. That’s because we have to potentially resize the control based on browser resizing as well as the number of tags and their size. While it’s possible to put the control in such a position in the browser that it’s never affected by browser sizing, but you still have to deal with resizing based on tag content. The WrapPanel will resize itself, but not the actual control. Throw in browser incompatibilities and this issue wound up not being trivial at all.

Let’s take a look.

1.) Control Sizing within the Control

There are two places within the control where sizing occurs. First, we have the before-mentioned UpdateControlSize:

   1: [ScriptableMember]
   2: public void UpdateControlSize (int nNewWidth)
   3: {
   4:     if (HtmlPage.Document != null)
   5:     {
   6:         var silverlightControlHost = HtmlPage.Document.GetElementById (_tagCloudControlHostId);
   7:         var silverlightObjectTag = HtmlPage.Document.GetElementById (_tagCloudControlClientId);
   8:  
   9:         if ((silverlightControlHost != null) && (silverlightObjectTag != null))
  10:         {
  11:             silverlightObjectTag.SetStyleAttribute ("width", nNewWidth + "px");
  12:             silverlightControlHost.SetStyleAttribute ("width", nNewWidth + "px");
  13:         }
  14:     }
  15:  
  16:     return;
  17: }
View Plain

UpdateSize is a ScriptableMember, which means it can be called from JavaScript. Here’s that scripting code from the ASP.NET page:

   1: function UpdateTagCloudSize() {
   2:  
   3:     var silverlightControl = document.getElementById('silverlightTagCloud');
   4:     if (silverlightControl == null) return;
   5:  
   6:     var sidebarWidth = jQuery("#sidebar").css("width");
   7:     var newCtrlWidth = parseInt(sidebarWidth); // removes the "px" at the end
   8:     newCtrlWidth -= 10; // some tweaking just for this site
   9:  
  10:     silverlightControl.content.JStoSLBridge.UpdateControlSize(newCtrlWidth); // reminder: don't need "px" at the end
  11: }
  12:  
  13: // addEvent function by John Resig: http://ejohn.org/projects/flexible-javascript-events/
  14: function addEvent(obj, type, fn) {
  15:     if (obj.attachEvent) {
  16:         obj['e' + type + fn] = fn;
  17:         obj[type + fn] = function() { obj['e' + type + fn](window.event); }
  18:         obj.attachEvent('on' + type, obj[type + fn]);
  19:     }
  20:     else
  21:         obj.addEventListener(type, fn, false);
  22: }
  23:  
  24: // add Resize event handler
  25: addEvent(window, 'resize', function(event) {
  26:     UpdateTagCloudSize();
  27: });
View Plain

Courtesy of John Resig we have an event handler that hooks into the browser’s resize event. As the browser is resized, we make a call to our UpdateControlSize via our JavaScript-to-Silverlight Bridge mechanism. Resizing challenge #1 handled.

The other place we deal with resizing is via the WrapPanel’s SizeChanged event handler inside the Silverlight control:

   1: private void WrapPanel_SizeChanged (object sender, SizeChangedEventArgs e)
   2: {
   3:     if (HtmlPage.Document != null && _tagsLoaded)    // don't start resizing until content loaded
   4:     {
   5:         var silverlightControlHost = HtmlPage.Document.GetElementById (_tagCloudControlHostId);
   6:         var silverlightObjectTag = HtmlPage.Document.GetElementById (_tagCloudControlClientId);
   7:  
   8:         if ((silverlightControlHost != null) && (silverlightObjectTag != null))
   9:         {
  10:             silverlightObjectTag.SetStyleAttribute ("height", e.NewSize.Height + "px");
  11:             silverlightControlHost.SetStyleAttribute ("height", e.NewSize.Height + "px");
  12:         }
  13:     }
  14:  
  15:     return;
  16: }
View Plain

We passed in the control id’s from ASP.NET to the control, so that’s how we know the clientId and hostId. We then modify their heights and, if not for an initial sizing issue, we’d be done. Unfortunately, I had to throw in an additional kick-start to get the control properly sized when it first loads.

2.) Kick-starting the Control Sizing with some more JavaScript/jQuery

Not that big of a deal, but I had to give the control a kick in the butt when the page is first loaded. We accomplish this initial sizing with some help from jQuery (if you’re new to jQuery, don’t sweat it—I included the .js file in the project and that’s all you really need for this):

   1: jQuery(document).ready(function() {
   2:  
   3:     var silverlightControlHost = document.getElementById('silverlightControlHost');
   4:     var sidebarWidth = jQuery("#sidebar").css("width");
   5:     sidebarWidth = parseInt(sidebarWidth); // removes the "px" at the end
   6:  
   7:     sidebarWidth -= 10; // bit of a tweak based on the ict template, this will keep the control from overflowing the right border
   8:  
   9:     // resize the control host, NOT the control itself

  10:     silverlightControlHost.style.width = sidebarWidth + "px";
  11: });
View Plain

Using jQuery’s “ready” function, which is essentially an event handler for when the page is ready for user interaction, we get some width information that is very specific to this blog template. That means you’ll have to tweak this code to do what you need based on your own web site layout.

Conclusion

That’s the Silverlight TagCloud control. I’ve already come up with a short list of future enhancements below, so look for an update sometime in the future.

Until next time.

Download: Silverlight TagCloud control on CodePlex

Future Enhancements

Because there’s always room for improvement.

  1. Pass in tag font family as parameter
  2. Pass in tag font color as parameter
  3. Pass in control background color as parameter
  4. Clean-up some of the sizing (if possible)

References

TagCloud Algorithm/Logic/Formula

Tag Clouds Gallery: Examples And Good Practices

A Silverlight TagCloud, Part 1: The WCF Service

July 23, 2009 09:44

The complete Silverlight TagCloud series of posts:

  1. A Silverlight TagCloud, Part 1: The WCF Service
  2. A Silverlight TagCloud, Part 2: The TagCloud
  3. A Silverlight TagCloud, Part 2.1: Refinements
  4. Silverlight TagCloud Now on CodePlex

This is the first of two posts about a new Silverlight TagCloud control I wrote. The architecture is simple: On the client-end we’ll have a Silverlight control hosted in an ASP.NET page. On the server-end we’ll have a WCF service that the TagCloud communicates with to get tag item information.

Here’s the end result (minus some CSS formatting):

image

Since I knew I wanted to deploy the control/service combination to two different web sites, I opted to create both control and service projects in a new Visual Studio 2008 solution rather than adding them to each of the individual web projects where I’d have duplication of code. Turns out the process of getting the WCF bindings, service config, placement of the Silverlight XAP file, references, etc. all working correctly together when the service and control are not located inside the same solution as the web project was a major chore. Long story short, I figured it all out; I’ll spare you my tales of woe and just give you the solution.

In this first post I intend to discuss the WCF service. The next post in the series will cover the Silverlight control.

Let me also add that since I use BlogEngine.NET as my hosting platform that the WCF service is tightly-bound with BlogEngine.NET’s Post class. Replacing this underlying post/tag store shouldn’t be that difficult, though, if you want to adapt this to your own blogging platform.

The TagCloudService

I started with a WCF Service Library since my thought was that a service library would make deployment to two different web sites easier (you can read about the differences between the WCF Service Application and Service Library templates here). Turns out it wasn’t any easier or harder than if I’d just started with a WCF Service Application. In any case, I started with the usual Visual Studio 2008 WCF Service Library template:

image

After some renames and the addition of a TagCloudService.svc file (which you would have gotten for free using the WCF Service Application template), I wound up with this structure:

image

You’ll notice that the TagCloudService.cs file is not tucked behind the TagCloudService.svc file in the usual code-behind manner. This is because the content of the .svc file is this:

  1: <%@ ServiceHost Language="C#" Debug="true" Service="TagCloud.TagCloudService.TagCloudService" %>
View Plain

 

No CodeBehind attribute. This is so because I’ll be deploying the compiled service DLL as the service’s implementation, and not releasing the .cs file at all.

The Contract

Let’s take a look at the service contract. Here’s the entire file:

   1: using System.Collections.Generic;
   2: using System.Runtime.Serialization;
   3: using System.ServiceModel;
   4:  
   5: namespace TagCloud.TagCloudService
   6: {
   7:     [ServiceContract (Namespace = "TagCloud.TagCloudService")]
   8:     public interface ITagCloudService
   9:     {
  10:         [OperationContract]
  11:         List<CloudTag> GetTags (int threshold, string baseUrl);
  12:     }
  13:  
  14:     [DataContract]
  15:     public class CloudTag
  16:     {
  17:         public CloudTag (string tagName, string tagLink, int tagOccurrences)
  18:         {
  19:             TagName = tagName;
  20:             TagLink = tagLink;
  21:             TagOccurrences = tagOccurrences;
  22:         }
  23:  
  24:         [DataMember]
  25:         public string TagName;
  26:  
  27:         [DataMember]
  28:         public string TagLink;
  29:  
  30:         [DataMember]
  31:         public int TagOccurrences;
  32:     }
  33: }
View Plain

The service contract defines just one operation: GetTags. It takes as parameters the tag occurrence threshold, which is a minimum number of times a tag must have been used in our blog before we’ll display it in the TagCloud control, and the baseUrl, which is the hosting web site’s base address, as in . We’ll see how the base url is used in a moment. GetTags returns a collection of CloudTag objects.

CloudTag is defined with the [DataContract] attribute; it’s members are [DataMember]’s. CloudTag holds relevant information for a single tag on our blog, including the name of the tag, the url to the tag so that visitors can click on a tag in the cloud and be taken to the corresponding posts, and the number of occurrences of the tag.

The Implementation

The service implementation is rather short:

   1: using System.Collections.Generic;
   2: using System.ServiceModel.Activation;
   3: using BlogEngine.Core;
   4:  
   5: namespace TagCloud.TagCloudService
   6: {
   7:     [AspNetCompatibilityRequirements (RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
   8:     public class TagCloudService : ITagCloudService
   9:     {
  10:         public List<CloudTag> GetTags (int threshold, string baseUrl)
  11:         {
  12:             var cloudTagCollection = new List<CloudTag> ();
  13:  
  14:             if (Post.Posts == null)
  15:             {
  16:                 return (cloudTagCollection);
  17:             }
  18:  
  19:             // get all tags and the number of times each occurs
  20:             var sortedDict = new SortedDictionary<string, int> ();
  21:             foreach (var post in Post.Posts)
  22:             {
  23:                 if (!post.IsVisible) continue;
  24:  
  25:                 foreach (var tag in post.Tags)
  26:                 {
  27:                     if (sortedDict.ContainsKey (tag))
  28:                         sortedDict[tag]++;
  29:                     else
  30:                         sortedDict[tag] = 1;
  31:                 }
  32:             }
  33:  
  34:             // we have all the tags. only save those that meet our minimum occurrences threshold
  35:             foreach (var tag in sortedDict)
  36:             {
  37:                 if (tag.Value > threshold)
  38:                 {
  39:                     cloudTagCollection.Add (new CloudTag (tag.Key, baseUrl + "/?tag=/" + tag.Key, tag.Value));
  40:                 }
  41:             }
  42:  
  43:             return (cloudTagCollection);
  44:         }
  45:     }
  46: }
View Plain

 

The main part is, of course, the GetTags operation, which utilizes BlogEngine.NET’s Posts collection to retrieve all tags and the number of times each appears across the blog. It then adds only those tags that meet the minimum threshold to the CloudTag collection. This collection is then what is returned to the consuming app, which in this case will be the Silverlight control.