I’m currently working on a kiosk application for a company called TicketDirect. They sell tickets to different events all around New Zealand and have decided to create a kiosk based solution where customers can buy tickets. The application needs to be Silverlight based for a couple of reasons, and must run Silverlight 3 as 4 doesn’t have a go-live license yet. The problem with this is that the application needs to talk to a credit/debit card unit as well as a ticket printer. The problem as you probably realize quickly, is that these units are connected to the computer physically and therefore are not available to Silverlight. And yeah…as you know, printing support is “limited” in Silverlight, so printing custom ticket layouts to a special ticket printer will not work…
This is how I solved it. I created a couple of WCF services to run locally on the machine and then had Silverlight connect to those to access the local system. The problem was that I did not want to run IIS on the local machines. Why? Well, for different reasons…such as the need for simple XCopy deployment. So the services would be hosted by a Windows application instead.
The service I am going to be using needs to be hosted somewhere. In this case I want it simple, so I create a console application. When it project is created, it creates little more than a Program.cs file, which is nice and simple.
Let’s add a WCF service to the console application project. The service will be ridiculously simple and not access anything locally or so, but it will show the hosting part of it. When adding a WCF service to a console application, there is no nice little “Silverlight-enabled WCF Service”, which is good and bad… The bad part is that we need to create a regular WCF service and modify it a little. The good part is that we get a nicer implementation. Nicer implementation? Well, check this blog post for more information about it…
Anyhow…I add a WCF service called TimeService. That creates an interface, a class implementing the interface and a configuration file. Let’s change the interface to look like this
[ServiceContract]
public interface ITimeService
{
[OperationContract]
DateTime GetTime();
}
We then need to change the class that implements the interface in the same way and create a simple implementation
public class TimeService : ITimeService
{
public DateTime GetTime()
{
return DateTime.Now;
}
}
Unfortunately we need to make some other changes to the implementation as well to make it accessible from Silverlight. We first need to add an attribute called AspNetCompatibilityRequirements to the service code. This makes it “Silverlight-enabled”. This is one part of what the “Silverlight-enabled WCF Service” template fixes for us.
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class TimeService : ITimeService
{
public DateTime GetTime()
{
return DateTime.Now;
}
}
To get the AspNetCompatibilityRequirements to work, you need to add a using statement for System.ServiceModel.Activation. This can either be done manually or by writing the name of the attribute with the correct spelling and casing and then press Ctrl+. and then Enter…
So…that’s it for implementing the service. But before we look at the hosting side of it, I need to make some changes to the configuration. My configuration looks as below. All I have done is that I have changed the binding attribute of my endpoint to use basicHttpBinding instead of the standard wsHttpBinding. I also changed the baseAddress to go to a simpler address than the default one.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="DarksideCookie.WCFHosting.ServiceConsole.TimeServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="DarksideCookie.WCFHosting.ServiceConsole.TimeServiceBehavior"
name="DarksideCookie.WCFHosting.ServiceConsole.TimeService">
<endpoint address="" binding="basicHttpBinding" contract="DarksideCookie.WCFHosting.ServiceConsole.ITimeService">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8731/TimeService/" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>
Now it is time to take a look at how you host a WCF service in a console application. The secret is a simple class called ServiceHost. It might not be that simple since it handles threading and things like that, but it is simple to work with. It is in the System.ServiceModel namespace. Just create a new instance, passing in either a Type or an instance of the service it should host. Then call Open() to start the service and Close() to stop it. And as we are in a console application, we need to keep the application running by using a Console.ReadKey() call…
class Program
{
static void Main(string[] args)
{
ServiceHost host = new ServiceHost(typeof(TimeService));
host.Open();
Console.WriteLine("Listening...press any key to close");
Console.ReadKey();
host.Close();
}
}
If you try running this, you might get it up and running and everything is fine. You might also get an exception saying that the application is not allowed to open a port. If you get an exception, you need to close down your VS and start it up running as an administrator. Annoying, but safe… :)
Next up, we need a Silverlight application to test it. So I add a new Silverlight application, but skip the accompanying test web site and just leave it up to VS to create a test page for me…
Then I press Ctrl+F5 to start the console application without debugging so that I can add my service reference to my Silverlight application. I then right click the “References” folder in the Silverlight project and choose “Add Service Reference…”. In the “Add Service Reference” window I write the address to my service, in this case http://localhost:8731/TimeService/, and press “Go”. Next I select the TimeService and press “OK”. This will create a nice service client for me that I can use. If you need to modify what it creates, this can be changed by clicking the “Advanced…” button. In there you can define some settings that change what code is auto generated. But for now, it will just be default settings and a name of ServiceReference1.
Next I focus on my Xaml. I create a simple Button, a TextBlock and an event handler for the Button’s Click event.
<UserControl x:Class="DarksideCookie.WCFHosting.SilverlightClient.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
<Grid x:Name="LayoutRoot">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock x:Name="Time" />
<Button Content="Click Me" Click="Button_Click" />
</StackPanel>
</Grid>
</UserControl>
Next I implement my event handler. In it, I create a new instance of the ServiceReference1.TimeServiceClient class, attach a handler for the GetTimeCompleted event and then call the GetTimeAsync method.
In the GetTimeCompleted event handler, I set the text of the TextBlock to the returned time.
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
ServiceReference1.TimeServiceClient svc = new ServiceReference1.TimeServiceClient();
svc.GetTimeCompleted += svc_GetTimeCompleted;
svc.GetTimeAsync();
}
void svc_GetTimeCompleted(object sender, ServiceReference1.GetTimeCompletedEventArgs e)
{
Time.Text = e.Result.ToString();
}
}
And to get both of the applications running when I press F5, I open the properties for the solution, check the “Multiple startup projects” and define that both the projects should start.
Then I press F5 to start it all and see if it works… Oops…remember to close down the Console that was opened before… F5 again… And you get a warning from Visual Studio saying that webservice calls will fail unless hosted in the same application as the Silverlight application… Just press “Yes”.
Pressing the button should now give us the time, but instead it will result in an error. Saying something like
“An error occurred while trying to make a request to URI 'http://localhost:8731/TimeService/'. This could be due to attempting to access a service in a cross-domain way without a proper cross-domain policy in place, or a policy that is unsuitable for SOAP services…”
And that is so true. If you were to run Fiddler or something similar, you would see that Silverlight is trying to access a clientaccesspolicy.xml located at http://localhost:8731. And it will of course not find one, because there isn’t one, so the call fails. This needs to be handled ASAP to get everything working…
So let’s start by adding that pesky clientaccespolicy.xml file to the project. I just add a regular XML file, availably under the Data node in the Add New Item dialog. I then add the following XML to it
<?xml version="1.0" encoding="utf-8"?>
<access-policy>
<cross-domain-access>
<policy>
<allow-from http-request-headers="*">
<domain uri="*"/>
</allow-from>
<grant-to>
<resource path="/" include-subpaths="true"/>
</grant-to>
</policy>
</cross-domain-access>
</access-policy>
Finally I change the Build Action to embedded resource.
Next I add a new WCF Service to the project. The interface is simple. It has a single method that I call GetClientAccessPolicy() that returns a Stream. I also need to add an extra attribute, called WebGet, to it. The WebGet attribute is available in the System.ServiceModel.Web assembly, so I ad a reference to it before adding a using statement and finally adding the attribute to the method.
[ServiceContract]
public interface ISilverlightService
{
[OperationContract, WebGet(UriTemplate = "/clientaccesspolicy.xml")]
Stream GetClientAccessPolicy();
}
The WebGet attribute makes it possible to call a WCF service with a normal HTTP GET call. At least after some extra configuration… :)
The implementation of the GetClientAccessPolicy() method is simple. Just pull out the resource stream from the assembly and return it…
public class SilverlightService : ISilverlightService
{
public System.IO.Stream GetClientAccessPolicy()
{
return Assembly.GetExecutingAssembly()
.GetManifestResourceStream("DarksideCookie.WCFHosting.ServiceConsole.ClientAccessPolicy.xml");
}
}
Next, I modify and clean up the App.config file. First of all, they both share the same service behavior, so I remove one of the available ones and rename the other to DefaultServiceBehavior. I the update service entries with the changed behavior. Next I need to modify the service entry for the Silverlight service. It needs to answer at the root of the host, so I change the base address to http://localhost:8731/. It also needs a different endpoint behavior and binding. SO I add a new endpoint behavior, called webHttpBehavior, to the file. Next I change the endpoint entry so that the behavior is set to webHttpBehavior and the binding is set to webHttpBinding. This configuration makes it possible to call the service using a HTTP GET call.
The completed config file looks like this
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="webHttpBehavior">
<webHttp />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="DefaultServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="DefaultServiceBehavior" name="DarksideCookie.WCFHosting.ServiceConsole.SilverlightService">
<endpoint address="" behaviorConfiguration="webHttpBehavior"
binding="webHttpBinding" contract="DarksideCookie.WCFHosting.ServiceConsole.ISilverlightService">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<host>
<baseAddresses>
<add baseAddress="http://localhost:8731/" />
</baseAddresses>
</host>
</service>
<service behaviorConfiguration="DefaultServiceBehavior"
name="DarksideCookie.WCFHosting.ServiceConsole.TimeService">
<endpoint address="" binding="basicHttpBinding"
contract="DarksideCookie.WCFHosting.ServiceConsole.ITimeService">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8731/TimeService/" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>
Next, I press F5 to start the application again. But this time, pressing the button should cause the current date and time to be displayed in the window above the button…
Mission accomplished! Hosting WCF services outside of IIS. As long as we get the clientaccesspolicy.xml file hosted like this, we can host as many services we want at that host. We can also take the WebGet attribute a step longer and create a really funky “windows based” Silverlight application…sort of… But more about that in the next post…
Source code available here: WCF Service Console.zip (91.63 kb)
Cheers!