Tuesday, May 31, 2011

Trial and Error–WCF Services Part 1

Decided a while ago that I would teach myself WCF (Windows Communication Framework) services.  Saw the book,  Programming WCF Services by Juval Lowy (ISBN 978-0-596-80548-7), at one of the local bookstores and decided to pick it up.  While the book is a good read, after the first chapter I was like “going to need to take notes to figure this out.”  Well, instead of doing that, I figured I’d start on a project I’ve been working on (ExpressRecipe) – the hard way – via trial and error.  Hopefully my pain will help someone else out there…
I wanted to start out with something simple first – being able to get a host running and then get the description (also known as the WSDL) of the service through a web browser.  To make things simple, I started out with a console app for the host in VS 2010.  I created a separate project under the same solution to hold the WCF service itself.
First minor sticking point – the WCF reference is not automatically included so that must be included if you’re expecting to use either the host or any of the service attributes.  So, be sure to add System.ServiceModel to all the projects that will be using WCF features.  Be sure to include the using statement for that reference as well…
My first task was to create a simple interface that will be the contract for the service.  I started out with a “user” contract that allows the client to create a new user with a first name, last name, username, and password as simple strings.  So, the interface looks like:
Code Snippet
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.ServiceModel;
  6.  
  7. namespace ExpressRecipeWCFLibrary
  8. {
  9.     [ServiceContract]
  10.     public interface IExpressRecipeUserContract
  11.     {
  12.         [OperationContract]
  13.         void AddUser(string firstName, string lastName, string username, string password);
  14.     }
  15. }
First things you’ll notice that’s difference than a normal interface are the ServiceContract and OperationContract attributes.  In their simple forms, the service contract is placed on the interface itself.  The operation contract is placed on all the methods you want the host to expose to the client.  Only one method here so the operation contract is specified only once.  One thing of note – according to the book and the code they showed for the attributes, the attributes shown here are not inheritable – so they’ll need to be reapplied if this interface is subclassed [subinterfaced?]…they will be applied to a class that implements them though.
Ok, interface is pretty simple…next – the implementation of the interface.
Code Snippet
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5.  
  6. namespace ExpressRecipeWCFLibrary
  7. {
  8.     public class UserService:IExpressRecipeUserContract
  9.     {
  10.         #region IExpressRecipeUserContract Members
  11.  
  12.         public void AddUser(string firstName, string lastName, string username, string password)
  13.         {
  14.             System.Console.WriteLine(String.Format("{0} {1} {2} {3}", firstName, lastName, username, password));
  15.         }
  16.  
  17.         #endregion
  18.     }
  19. }
Well, actually, nothing surprising here at all – simple implementation of a normal interface…not even a mention of the System.ServiceModel reference in the usings.  Well, that’s nice and fairly easy to do…just add the ServiceContract and OperationsContract to the interface and everything else is “business as usual.”  [note that interfaces are used here – while it is recommended to use an interface and add the attributes to that, nothing prevents you from adding them to the class itself – but, the design principle applies – “program to an interface, not an implementation” so try to use an interface if at all possible].
The operations I wanted exposed as a service are done so now it’s time for the host.
In the console Program.cs file, I created a new ServiceHost and passed it the type of my UserService class (notice not the interface but the class itself).  I also decided, for debugging purposes, to attach event handlers to each of the events raised by the host.  So, the code looks something like this…
Code Snippet
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.ServiceModel;
  6. using ExpressRecipeWCFLibrary;
  7.  
  8. namespace ExpressRecipeWCFServices
  9. {
  10.     class Program
  11.     {
  12.         static void Main(string[] args)
  13.         {
  14.             ServiceHost host=new ServiceHost(typeof(UserService));
  15.             host.Closed += new EventHandler(host_Closed);
  16.             host.Closing += new EventHandler(host_Closing);
  17.             host.Faulted += new EventHandler(host_Faulted);
  18.             host.Opened += new EventHandler(host_Opened);
  19.             host.Opening += new EventHandler(host_Opening);
  20.             host.UnknownMessageReceived += new EventHandler<UnknownMessageReceivedEventArgs>(host_UnknownMessageReceived);
  21.  
  22.             host.Open();
  23.             System.Console.WriteLine("User service started");
  24.  
  25.             System.Console.WriteLine("Press <enter> to close");
  26.             System.Console.ReadLine();
  27.             host.Close();
  28.         }
  29.  
  30.         static void host_UnknownMessageReceived(object sender, UnknownMessageReceivedEventArgs e)
  31.         {
  32.             System.Console.WriteLine("Host unknown message");
  33.         }
  34.  
  35.         static void host_Opening(object sender, EventArgs e)
  36.         {
  37.             System.Console.WriteLine("Host opening");
  38.         }
  39.  
  40.         static void host_Opened(object sender, EventArgs e)
  41.         {
  42.             System.Console.WriteLine("Host opened");
  43.         }
  44.  
  45.         static void host_Faulted(object sender, EventArgs e)
  46.         {
  47.             System.Console.WriteLine("Host faulted");
  48.         }
  49.  
  50.         static void host_Closing(object sender, EventArgs e)
  51.         {
  52.             System.Console.WriteLine("Host closing");
  53.         }
  54.  
  55.         static void host_Closed(object sender, EventArgs e)
  56.         {
  57.             System.Console.WriteLine("Host closed");
  58.         }
  59.     }
  60. }
Don’t worry, really not much code – most of it is writing to the console, mainly for the event handlers.  Ok, pretty normal code…guess we have the host so let’s run it.
Um…whoops – exception.  It runs the “host opening” event, then complains with:
“An unhandled exception of type 'System.InvalidOperationException' occurred in System.ServiceModel.dll
Additional information: Service 'ExpressRecipeWCFLibrary.UserService' has zero application (non-infrastructure) endpoints. This might be because no configuration file was found for your application, or because no service element matching the service name could be found in the configuration file, or because no endpoints were defined in the service element.”
Man, you mean VS 2010 doesn’t create a default endpoint?  And not even an App.Config file.  Oh well.  Yep, must add an App.Config file.  [this can be found in add new item under the project menu – application configuration file].  For simplicity sake, I left the name as the default…
Ok, so, now to reference the book, what needs to be in this config file?  Well, need an endpoint for the service, that part is obvious from the exception.  Need to add the service to the confile file as well…so something like:
Code Snippet
  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <configuration>
  3.   <system.serviceModel>
  4.     <services>
  5.       <service name="ExpressRecipeWCFLibrary.UserService">
  6.         <endpoint address="http://localhost:8000/UserService/" binding="wsHttpBinding" contract="ExpressRecipeWCFLibrary.IExpressRecipeUserContract"/>
  7.       </service>
  8.     </services>
  9.   </system.serviceModel>
  10. </configuration>
Ok, not obvious but it works…have to add the service class to the services list with full namespace (required to be full? – don’t know).  Then to specify the endpoint - an endpoint is the combination of address, binding, and contract so all of that is specified.  I wanted http and decided to use the web service binding (instead of basic binding, which would be the default for an HTTP address) so that I could access this service via non-WCF clients [i.e. Java].  The contract is the interface for the service with namespace specified (again – required – not sure).
So, does it work this time?  Well, let’s see – Ctrl-F5. 
image
Hmm, yep…ok, let’s try to get to the WSDL of the service.  In the browser: http://localhost:8000/UserService/ – um, nope – “The webpage cannot be found.”  Ok, well, let’s try http://localhost:8000/ – nope – still “The webpage cannot be found.”
Now what…well, according to the book, page 39, Metadata Exchange – “by default, the service will not publish its metadata.”  Well, further reading (after the very discouraging “publishing your service’s metadata involves significant effort”) we find that we can do this in a couple ways.  I chose one that seemed the easiest to me – enabling the metadata behavior (httpGetEnabled) on the endpoint.
So, going off the example in the book, my App.Config now looks like:
Code Snippet
  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <configuration>
  3.   <system.serviceModel>
  4.     <services>
  5.         <service name="ExpressRecipeWCFLibrary.UserService"behaviorConfiguration="MEXGET">
  6.         <endpoint address="http://localhost:8000/UserService/" binding="wsHttpBinding" contract="ExpressRecipeWCFLibrary.IExpressRecipeUserContract"/>
  7.       </service>
  8.     </services>
  9.     <behaviors>
  10.       <serviceBehaviors>
  11.         <behavior name="MEXGET">
  12.           <serviceMetadata httpGetEnabled="true"/>
  13.         </behavior>
  14.       </serviceBehaviors>
  15.     </behaviors>
  16.   </system.serviceModel>
  17. </configuration>
Does it run?  Nope.  It actually stopped after the host faulted event was called and gave this exception:
“The HttpGetEnabled property of ServiceMetadataBehavior is set to true and the HttpGetUrl property is a relative address, but there is no http base address.  Either supply an http base address or set HttpGetUrl to an absolute address.”
Ok, so now what?  Looking at the book’s example a bit more closely it shows the base address off of the host element of the service…and no endpoint element.  Ok, so, I replaced it with:
Code Snippet
  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <configuration>
  3.   <system.serviceModel>
  4.     <services>
  5.       <service name="ExpressRecipeWCFLibrary.UserService" behaviorConfiguration="MEXGET">
  6.         <host>
  7.           <baseAddresses>
  8.             <add baseAddress="http://localhost:8000/UserService/"/>
  9.           </baseAddresses>
  10.         </host>
  11.         <!--<endpoint address="UserService/" binding="wsHttpBinding" contract="ExpressRecipeWCFLibrary.IExpressRecipeUserContract"/>-->
  12.       </service>
  13.     </services>
  14.     <behaviors>
  15.       <serviceBehaviors>
  16.         <behavior name="MEXGET">
  17.           <serviceMetadata httpGetEnabled="true"/>
  18.         </behavior>
  19.       </serviceBehaviors>
  20.     </behaviors>
  21.   </system.serviceModel>
  22. </configuration>
I list my binding by doing this, something I’ll get back at a later time…and the contract is now gone as well.  Well, will this even work?
image
How’d that happen?  Ok, let’s see if we can access the WSDL in the browser by going to the base address specified (http://localhost:8000/UserService/).
image
Oh, cool…appears to be working…will try to get the binding back at a later time and create a client to call the AddUser method.

No comments:

Post a Comment