A Silverlight TagCloud, Part 1: The WCF Service

July 23, 2009 08: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" %>

 

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: }

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 http://www.itscodingtime.com. 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: }

 

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.

Deployment

You can deploy the WCF service right now (since nothing is actually using it yet). I've written a couple of posts on how to do that, but what you basically need to do is make the needed changes to your web.config, then copy over the .svc and assembly DLL. You can then access the service to make sure it's running with, for example, http://www.itscodingtime.com/TagCloudService.svc.

Conclusion

As you can see the WCF service part of the implementation is pretty straightforward. Deployment of the service is (hopefully) no more difficult.

The second part of this post will discuss the consumer of this service: the Silverlight TagCloud control. Until then.

Download: Silverlight TagCloud control on CodePlex


The different ways to host a WCF Service Application in a Web App

July 20, 2009 07:51

Introduction

There are at least three different ways to configure the hosting of a WCF Service Application inside a Web Application. They are:

  1. Add a WCF Service directly via "Add New Item…"
  2. By copying files, including the service implementation code-behind file
  3. By copying files, including the compiled, service implementation DLL

In this post, I'm going to take a walk through each of these methods.

Method #1: Add a WCF Service directly via "Add New Item…"

This is the most straightforward of the methods. Right-click on the Web App project name and select Add | New Item…:

image

Then, select WCF Service and click Add:

image

All the grunt work has been done for you: a reference to System.ServiceModel has been added, the Web App's web.config has been modified, and the service contract and implementation files have been added. Your project layout will look something like this (new files highlighted):

image

That's it for this method. You can run and see the service by navigating to Service1.svc. But this model does not fit all situations. To see why, let's move on to Method #2.

Method #2: By copying files, including the service implementation code-behind file

The first method is fine when you have a Web Application and just want to host a WCF service in that one and only project. But what about reusability? If you want to use the WCF service across two or more web sites you generally don't want to have duplicate code existing in two different places. You can create a base class, and derive each service from that, but I'd rather just have the entire service contained within a single project. Then it's just a matter of deploying the service onto the different web sites (or web projects).

One way of doing that is to copy the service's contract, implementation, and config into the web project:

1.) First, create a new WCF Service Application in a separate solution.

2.) Copy each of the following files from the root of the service application folder to the root of the web application folder:

  • IService1.cs
  • Service1.svc
  • Service1.svc.cs

Right-click on your web app project name and add these files via the Add Existing Item… dialog.

3.) Add a reference to System.ServiceModel:

image

4.) From your WCF Service Application's web.config, copy the <system.serviceModel> section and paste into your Web Application's web.config (right after the </runtime> closing tag).

5.) Run the Web Application and navigate to Service1.svc. You should see the service.

That's not horrible; you can probably automate most of the file copies and really only need to add the files themselves to the web project the first time. There is, however, a slightly easier way.

Method #3: By copying files, including the compiled, service implementation DLL

The WCF Service Application compiles down to a DLL. Let's take advantage of that. While this step doesn't differ that much from Method #2, it is another alternative.

1.) Compile the WCF Service Application.

2.) Copy the resulting DLL into the Web App's bin folder or alternatively an "imports" folder.

3.) In the Web Application, add a reference to the service DLL:

image

4.) Add a reference to System.ServiceModel.

5.) Copy only the Service1.svc file (not the code-behind or contract files) from the service project folder to the web project folder and add it to the Web Application via the Add Existing Item… dialog.

6.) Open the Service1.svc file and remove the CodeBehind attribute from ServiceHost.

4.) From your WCF Service Application's web.config, copy the <system.serviceModel> section and paste into your Web Application's web.config (right after the </runtime> closing tag).

5.) Run the Web Application and navigate to Service1.svc. You should see the service.

It's really the same number of steps as Method #2. The biggest difference is that instead of copying over the contract and implementation files, you instead copy the single DLL. The advantage of this method, however, is that once you've done the initial setup (add references, copy over config info) the only thing you'll have to do if the service implementation changes is copy over the DLL.

Conclusion

There's never only one way of doing these things, but this is how I approach web hosting of a WCF service. I prefer Method #3 because I then really only need to worry about deploying a single DLL once the initial hosting setup is done. I like Method #1 the least, especially for a service which I intend to use across multiple web sites. Having the entire WCF service contained within a single location/solution is simply a best practice and helps promote code reusability and ease of maintenance.

References

Deploying an Internet Information Services-Hosted WCF Service


How to workaround Could not load file or assembly App_Web_ The system cannot find the file specified

July 17, 2009 06:49

Even though many others have blogged solutions to this issue (presenting a wide variety of potential workarounds, I might add), I'm posting this anyway for my own reference. I'd especially like to log the details specific to my situation as I'm seeing it a bit too much lately on my production (hosted) site.

Some suggest deleting temporary internet files, another suggests making a small change to the web.config file, Rick Strahl suggests a full recompile, and Scott Guthrie talks about the official Microsoft hotfix and suggested workarounds.

Before I get into the solution, I'll take a moment to look at my situation and the symptoms I was seeing.

The Symptoms

I'm running a Silverlight control, hosted in an ASP.NET web app which also hosts a WCF service. When I load up the main web aspx page my Silverlight control doesn't do anything. IE displays the yellow error icon in the lower left; clicking on it brings up this:

image

Copying error details yields:

Webpage error details

User Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.5.21022; .NET CLR 3.5.30729; Tablet PC 2.0; OfficeLivePatch.1.3; .NET CLR 3.0.30729; OfficeLiveConnector.1.4)
Timestamp: Thu, 16 Jul 2009 00:32:45 UTC

Message: Unhandled Error in Silverlight 2 Application An exception occurred during the operation, making the result invalid.  Check InnerException for exception details.   at System.ComponentModel.AsyncCompletedEventArgs.RaiseExceptionIfNecessary()
   at TagCloud.TagCloudControl.TagCloudService.GetTagsCompletedEventArgs.get_Result()
   at TagCloud.TagCloudControl.Page.tagCloudService_GetTagsCompleted(Object sender, GetTagsCompletedEventArgs e)
   at TagCloud.TagCloudControl.TagCloudService.TagCloudServiceClient.OnGetTagsCompleted(Object state)

Because this Silverlight control calls into a WCF service (and because I'd seen this exception before), I immediately went to my service's .svc file. Trying to bring that up in the browser brought up this:

Could not load file or assembly 'App_Web_ivxpj_7c, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.

sshot-69

The Solution(s)

There's more than one solution to this problem. I'll detail the ones I found, but keep in mind people have had various levels of success with most of them.

  1. If you're seeing this problem on your development machine, do a full recompile. This one seemed to do the trick… sometimes.
  2. If you're seeing this problem on your development machine, delete temporary internet files. I had no success with this one.
  3. If you're seeing this problem on your development machine, remove and re-add the WCF service dll reference (assuming you have a dll reference). This is a home-grown solution which worked every time for me but was kind of a pain.
  4. If you're seeing this problem on your (hosted) server, copy over the service code or dll, overwriting the existing file(s). This should force a recompile by IIS. I have had good success with this one.
  5. Modify your web.config by adding the batch attribute. I haven't tried this one yet, though others have reported success with it.
   1: <system.web>
   2:     <compilation defaultLanguage="c#" debug="false" batch="false"/>
   3: </system.web>

I am looking forward to trying solution #4, but I'm hesitant to deploy any changes to my web.config. I just got everything working again. ;-)


Installing a WCF Service on a Shared Hosting Site, Revisited

July 13, 2009 19:30

I wrote a post not too long ago discussing How to Install a WCF Service on a GoDaddy hosted site. In it, I presented a couple of different ways to get around the "This collection already contains an address with scheme http" error, which looks like:

image

While those methods do work, I found myself in perhaps a special situation where only a combination of those solutions resolved my particular problem. Let me explain by first detailing my hosting situation.

My Hosting Schema

I have two sites: scottmarlowe.com and itscodingtime.com. Both are hosted with GoDaddy.com under their Deluxe Hosting Plan, which basically means I have multiple domain names that share server space. One is the "root"; any other domains exist in a folder beneath the root. That last point is important as we get into this.

In my case, my "root" domain is scottmarlowe.com. The sub-domain of interest here is this site, itscodingtime.com.

The Situation

I've deployed a new Silverlight TagCloud control (upgraded to Silverlight 3, of course) to both sites. Each site has its own ClientBin where the control is housed. Now, each Silverlight control accesses their own individual copies of the same WCF service. In other words, I have a .svc and accompanying DLL hosted on each site. Therein begins the problem.

The Problem

Now, I'd already uploaded all the new files to the sub-site the other day and everything was working fine on both root and sub-sites. The problem came about this morning when I did the same update (Silverlight and WCF components) to the root site, then uploaded the web.config (which, in retrospect, I wonder if really needed updating). It was the web.config update that did it. I started seeing the "This collection already contains an address with scheme http" error on the sub-site (itscodingtime.com).

The Solution

I had chosen to modify both web.config's as my previous post suggested. Clearly this was no longer working, so I took a hybrid approach. I left the root's web.config alone, but modified the sub-site's (which, at this time, was the not working).

First, I removed the entire "serviceHostingEnvironment" section from the sub-site's web.config.

Second, for the sub-site only, I went back to the CustomHostFactory approach by creating a new class file called "CustomHostFactory.cs". This file goes in the site's App_Code folder. It's full contents are:

  1:  
  2: using System;
  3: using System.ServiceModel;
  4: using System.ServiceModel.Activation;
  5:  
  6: class CustomHostFactory : ServiceHostFactory
  7: {
  8:   protected override ServiceHost CreateServiceHost (Type serviceType, Uri[] baseAddresses)
  9:   {
 10:     // Specify the exact URL of your web service
 11:     var webServiceAddress = new Uri (
 12:       "http://www.itscodingtime.com/itscodingtime/TagCloudService.svc");
 13:  
 14:     return (new CustomHost (serviceType, webServiceAddress));
 15:   }
 16: }
 17:  
 18: public class CustomHost : ServiceHost
 19: {
 20:   public CustomHost (Type serviceType, params Uri[] baseAddresses) : base (serviceType,
 21:     baseAddresses)
 22:   { }
 23: }

One notable difference between this implementation and the one suggested in the other post is that this one explicitly specifies the url of the WCF service (but we're not quite done; see code below).

Also, note the address (line 11): http://www.itscodingtime.com/itscodingtime/TagCloudService.svc

That extra "itscodingtime" folder is necessary because of my hosting situation as described above. In fact, if I leave it out, and instead specify "http://www.itscodingtime.com/TagCloudService.svc" (which seems more logical) as the WCF service url, I start seeing "There is no compatible TransportManager found for URI" errors:

sshot-72

So, I have to specify the full url with the extra "itscodingtime" folder, which actually corresponds to the folder it is stored at under the root.

Back to the CustomHostFactory. If we leave it as above we'll soon run into problems while running against localhost. So, the final version is this:

  1:  
  2: using System;
  3: using System.ServiceModel;
  4: using System.ServiceModel.Activation;
  5:  
  6: class CustomHostFactory : ServiceHostFactory
  7: {
  8:   protected override ServiceHost CreateServiceHost (Type serviceType, Uri[] baseAddresses)
  9:   {
 10:     // Specify the exact URL of your web service
 11:     var webServiceAddress = baseAddresses[0].ToString ().Contains ("localhost")
 12:       ? baseAddresses[0]
 13:       : new Uri ("http://www.itscodingtime.com/itscodingtime/TagCloudService.svc");
 14:  
 15:     return (new CustomHost (serviceType, webServiceAddress));
 16:   }
 17: }
 18:  
 19: public class CustomHost : ServiceHost
 20: {
 21:   public CustomHost (Type serviceType, params Uri[] baseAddresses) : base (serviceType, baseAddresses)
 22:   { }
 23: }

Lines 11-13 will set our WCF service address based on whether we are running locally or if we're in production.

As a final step, remember to add the "Factory" attribute to the .svc's "ServiceHost" and you're set.

As you can see, I can once more access my WCF service:

sshot-73

Conclusion

If you've got a similar situation where you host multiple domains under the same root location and you find yourself seeing similar errors, then the method described above should get your services up and running again.