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
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.ServiceModel;
- namespace ExpressRecipeWCFLibrary
- {
- [ServiceContract]
- public interface IExpressRecipeUserContract
- {
- [OperationContract]
- void AddUser(string firstName, string lastName, string username, string password);
- }
- }
Ok, interface is pretty simple…next – the implementation of the interface.
Code Snippet
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- namespace ExpressRecipeWCFLibrary
- {
- public class UserService:IExpressRecipeUserContract
- {
- #region IExpressRecipeUserContract Members
- public void AddUser(string firstName, string lastName, string username, string password)
- {
- System.Console.WriteLine(String.Format("{0} {1} {2} {3}", firstName, lastName, username, password));
- }
- #endregion
- }
- }
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
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.ServiceModel;
- using ExpressRecipeWCFLibrary;
- namespace ExpressRecipeWCFServices
- {
- class Program
- {
- static void Main(string[] args)
- {
- ServiceHost host=new ServiceHost(typeof(UserService));
- host.Closed += new EventHandler(host_Closed);
- host.Closing += new EventHandler(host_Closing);
- host.Faulted += new EventHandler(host_Faulted);
- host.Opened += new EventHandler(host_Opened);
- host.Opening += new EventHandler(host_Opening);
- host.UnknownMessageReceived += new EventHandler<UnknownMessageReceivedEventArgs>(host_UnknownMessageReceived);
- host.Open();
- System.Console.WriteLine("User service started");
- System.Console.WriteLine("Press <enter> to close");
- System.Console.ReadLine();
- host.Close();
- }
- static void host_UnknownMessageReceived(object sender, UnknownMessageReceivedEventArgs e)
- {
- System.Console.WriteLine("Host unknown message");
- }
- static void host_Opening(object sender, EventArgs e)
- {
- System.Console.WriteLine("Host opening");
- }
- static void host_Opened(object sender, EventArgs e)
- {
- System.Console.WriteLine("Host opened");
- }
- static void host_Faulted(object sender, EventArgs e)
- {
- System.Console.WriteLine("Host faulted");
- }
- static void host_Closing(object sender, EventArgs e)
- {
- System.Console.WriteLine("Host closing");
- }
- static void host_Closed(object sender, EventArgs e)
- {
- System.Console.WriteLine("Host closed");
- }
- }
- }
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
- <?xml version="1.0" encoding="utf-8" ?>
- <configuration>
- <system.serviceModel>
- <services>
- <service name="ExpressRecipeWCFLibrary.UserService">
- <endpoint address="http://localhost:8000/UserService/" binding="wsHttpBinding" contract="ExpressRecipeWCFLibrary.IExpressRecipeUserContract"/>
- </service>
- </services>
- </system.serviceModel>
- </configuration>
So, does it work this time? Well, let’s see – Ctrl-F5.
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
- <?xml version="1.0" encoding="utf-8" ?>
- <configuration>
- <system.serviceModel>
- <services>
- <service name="ExpressRecipeWCFLibrary.UserService"behaviorConfiguration="MEXGET">
- <endpoint address="http://localhost:8000/UserService/" binding="wsHttpBinding" contract="ExpressRecipeWCFLibrary.IExpressRecipeUserContract"/>
- </service>
- </services>
- <behaviors>
- <serviceBehaviors>
- <behavior name="MEXGET">
- <serviceMetadata httpGetEnabled="true"/>
- </behavior>
- </serviceBehaviors>
- </behaviors>
- </system.serviceModel>
- </configuration>
“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
- <?xml version="1.0" encoding="utf-8" ?>
- <configuration>
- <system.serviceModel>
- <services>
- <service name="ExpressRecipeWCFLibrary.UserService" behaviorConfiguration="MEXGET">
- <host>
- <baseAddresses>
- <add baseAddress="http://localhost:8000/UserService/"/>
- </baseAddresses>
- </host>
- <!--<endpoint address="UserService/" binding="wsHttpBinding" contract="ExpressRecipeWCFLibrary.IExpressRecipeUserContract"/>-->
- </service>
- </services>
- <behaviors>
- <serviceBehaviors>
- <behavior name="MEXGET">
- <serviceMetadata httpGetEnabled="true"/>
- </behavior>
- </serviceBehaviors>
- </behaviors>
- </system.serviceModel>
- </configuration>
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/).
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