Basic Client and Service Working with the .NET Service Bus
Our goal here is organic. It is the fundamental “Hello World” in terms of communications.
Access Control Service, Relay Service
This blog entry introduces an authenticating mechanism, which determines which clients can connect to our service.
We also introduce a relay service, which now makes it possibles to connect two or more parties, regardless of whether any connections are behind any firewall, NAT device, switch, or router.
The code below is simple - some service that exposes some endpoint, some protocol, some security model. That service is secure and can be reached by almost any client on earth.
Primarily it is about using Azure and it’s fundamental relationship to the AppFabric.
The application we are about to write will allow this person to send the name of a city to our weather service, and our weather service responds with a string about weather conditions for the city name passed in.
https://brunoblogfiles.com/Projects/WeatherClientService/Service.zip
Step 1: The client sends the string “Novato” (or some other city) to the Service.
Step 2: The Service takes that city, goes to a web site that has weather about that city.
Step 3: The Service scrapes the weather from the screen and sends back to client.
The AppFabric is hugely important.
The logical view abstracts the relay and access control service away to give us this clean depiction. No view of the relay service or the access control service here. We just see the ability to send and receive messages between client and server.
The Infrastructure for the Service Bus
Here we can see that a whole infrastructure is transparently available to the developer.
This infrastructure makes it easy to model client / server scenarios without worrying about NATs, Firewalls, routers, and switches. From the server side, your service will expose an authenticated endpoint. From the client, you will connect to the endpoint.
Notice the client interacting with the service and the use of (1) access control service; (2) relay service.
Download the Windows Azure Platform Training Toolkit
There are a great many hands-on labs that I used to help generate my samples. It is a great kit and I thank all those who created it.
Many argue that the .NET Service Bus is the most important part of the Windows Azure Platform. I call it the glue that binds the world, everything from your on-premise apps to your apps in the cloud. I’m I think any cloud is appropriate. There are enough flexible client-side libraries in multiple languages that the world is your pearl. You can connect everything to everything.
Relay or Tunnel
Typically, connecting two computers over a switch and firewall or router is complex. The .NET Service Bus gives us the ability insert an intermediary between two computers to dramatically simplify communications. There is also some sophisticated communications built into the .NET Service Bus to allow a direct peer-to-peer connection. We can talk about “Hybrid” connections in a future blog post.
The goal is simple
The goal is to interconnect different applications securely without the need to write and maintain complex logic and code to traverse networks. It is that simple everybody.
The infrastructure for security is there
Identity and authentication management service is easily integrated into your applications. You can apply security to traditional SOAP-based connections or REST-based interfaces.
The Access Control Service is all about give you a token if you have the rights to. That token is then used to communicate with the service via a “Relay Service.” That means all messages go through an intermediary (unless you upgrade to a direct peer to peer).
Getting started
Here are the three parts of this blog
So let’s get started
Login with a previously created live id, go to https://windows.azure.com
Here is our portal, which describes our basic project.
Right below “Project Name” you will see “Basic Chat Application.” I created this previously. There is very little to creating a project in AppFabric.
Here is where I take note of brunochat.
Also notice the “Default Issuer Name” and the “Default Issuer Key.” We will need those when we build the client and the server (aka “Service”).
We need to take note of names and security information from the web portal
About the Token
It is a security token (called Simple Web Token) that is then trusted by the service itself.
Start Visual Studio 2010 Beta 2. It is free to download. Start as “administrator.”
Quick Review | |||
We have completed | We have gotten our (1) Service namespace, (2) Default Issuer Name (3) Default Issuer Key | ||
We are now doing | Create a new Visual Studio Project | ||
What we will do next | Implement our code for Client and Server | ||
Start Visual Studio 2010
1. On the File menu, point to New, and click Project. In the New Project dialog box, select Visual C# | Windows. Make sure that .NET Framework 3.5 is selected and choose the Console Application project type. Set the project name to Service.
Nothing surprising here. Just a simple console app with the entry point - “main.”
Connecting to useful DLLs - Adding a reference
2. Add a reference to System.ServiceModel.dll assembly. After all, we are programming WCF Services. I am using VS 2010 Beta 2, which saves me from browsing. Those of you using VS 2008 will need to find the SDK and the physical file System.ServiceModel.dll. System.ServiceModel.dll is one of WCF core assemblies.
Also, add “System.Drawing.”
3. Create the WCF service contract. A service contract specifies what an endpoint communicates to the outside world. When you build it you define how it will behave in terms of request/reply, one-way, or duplex.
Also remember your ABCs
The ABC of Windows Communication Foundation
"ABC" is the WCF mantra. "ABC" is the key to understanding how a WCF service endpoint is composed.
- "A" stands for Address: Where is the service?
- "B" stands for Binding: How do I talk to the service?
- "C" stands for Contract: What can the service do for me?
Therefore, right now we are talking about the “C” or contract part.
Source code coming soon
The source code is posted below (later in this blog entry).
Quick Review | |||
We have completed | (1) We have completed a new project (Console app, called “Service”) (2) We have added a contract (IEchoContract) that the client will use to communicate with the service. | ||
We are now doing | Adding a Channel to the project | ||
What we will do next | Implement the Channel | ||
Why are we adding a channel?
This application leverages Channels, one of the fundamental pillars of the AppFabric.
Channel Responsibilities | ||
Sending and receiving messages to and from other parties | ||
Transforming the Message object to and from the format used to communicate with other parties | ||
Adding headers or encrypting the body | ||
Sending and receiving their own protocol control messages, for example, receipt acknowledgments | ||
4. Now add another interface. Call it IEchoChannel.cs
Adding a class module
5. Add a class module. Call it EchoService.
Give it the name “EchoService.”
Now you can see our two interfaces (IEchoChannel, IEchoContract) and the class (EchoService).
Now the source code.
The screen scraping class is in "class MyWeatherData." It has about 4 private variables to help with the scraping of weather data from a web page.
private string rawHtml;
private string result;
private string url;
private string data;
EchoService contains the class MyWeatherData. MyWeatherData is what actually does the lookup of a city and "scrape" the information from the web page to return to the client.
I recommend you set breakpoints on one of the functions, such as "GetHiRaw()" to see what is happening in the code. You will note the use of "Regular Expressions" (RegEx) in the code.
Notice the code snippet below that searches through matches. It is a very convenient and terse way to parse out a web page.
Code Snippet
- SortedList uniqueMatches = new SortedList();
- Match[] retArray = null;
- Regex RE = new Regex(matchPattern, RegexOptions.Multiline);
- MatchCollection theMatches = RE.Matches(source);
- if (findAllUnique)
- {
- for (int counter = 0; counter < theMatches.Count; counter++)
- {
- if (!uniqueMatches.ContainsKey(theMatches[counter].Value))
- {
- uniqueMatches.Add(theMatches[counter].Value,
- theMatches[counter]);
- }
- }
- retArray = new Match[uniqueMatches.Count];
- uniqueMatches.Values.CopyTo(retArray, 0);
- }
Of course if the external web site I'm using to scrape weather data changes it's layout, I am out of luck. What I could do is find a weather "service," which abstracts away the vagueries of html.
“EchoService” in the project “Service”
Code Snippet
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- // Need to add "using"
- using System.ServiceModel;
- using System.IO;
- using System.Net;
- using System.Text.RegularExpressions;
- using System.Collections;
- using System.Collections.Specialized;
- using System.Drawing;
- namespace Service
- {
- class MyWeatherData
- {
- private string rawHtml; // private Global variable to store a copy of returned raw HTML
- private string result;
- private string url;
- private string data;
- public string Data
- {
- get { return data; }
- set { data = value; }
- }
- public string state;
- public MyWeatherData(string city, string zip)
- {
- //url = "https://weather.cnn.com/weather/forecast.jsp?locCode=CAPT&zipCode=" + zip;
- if (zip == "94945")
- {
- url = "https://weather.cnn.com/weather/forecast.jsp?locCode=056&zipCode=94998"; //novato
- state = "good";
- }
- else
- {
- state = "Unrecognized zip code = " + zip;
- return;
- }
- WebRequest objRequest = HttpWebRequest.Create(url);
- WebResponse objResponse = objRequest.GetResponse();
- Stream stream = objResponse.GetResponseStream();
- StreamReader sr = new StreamReader(stream);
- result = sr.ReadToEnd();
- rawHtml = result.ToLower();
- SetupData(rawHtml);
- objResponse.Close();
- }
- public Image GetImageFromUrl(string url)
- {
- url = "https://www.ssd.noaa.gov/goes/west/nepac/avn-l.jpg";
- WebClient wc = new WebClient();
- Stream st = wc.OpenRead(url);
- Image im = Image.FromStream(st);
- st.Close();
- return im;
- }
- public string SetupData(string rawHtml)
- {
- Regex regex = new Regex("<div id=\"cnnWeatherForecastCurrentContent\">((.|\n)*?)<div class=\"cnnWireBoxFooter\">",
- RegexOptions.IgnoreCase);
- Match oM = regex.Match(rawHtml);
- string newResults = oM.Value;
- data += GetHiRaw(newResults);
- data += GetLowRaw(newResults);
- data += GetBarometer(newResults);
- data += GetCloudCondition(newResults);
- data += GetHumidity(newResults);
- data += GetFeelsLike(newResults);
- return data;
- }
- public static Match[] FindSubstrings(string source,
- string matchPattern, bool findAllUnique)
- {
- SortedList uniqueMatches = new SortedList();
- Match[] retArray = null;
- Regex RE = new Regex(matchPattern, RegexOptions.Multiline);
- MatchCollection theMatches = RE.Matches(source);
- if (findAllUnique)
- {
- for (int counter = 0; counter < theMatches.Count; counter++)
- {
- if (!uniqueMatches.ContainsKey(theMatches[counter].Value))
- {
- uniqueMatches.Add(theMatches[counter].Value,
- theMatches[counter]);
- }
- }
- retArray = new Match[uniqueMatches.Count];
- uniqueMatches.Values.CopyTo(retArray, 0);
- }
- else
- {
- retArray = new Match[theMatches.Count];
- theMatches.CopyTo(retArray, 0);
- }
- return (retArray);
- }
- #region Get raw text code functions
- public string AddSpaces(int len, string the_temp)
- {
- if (the_temp.Length > 30)
- return "<error>";
- int sp = 40 - len;
- return "".PadLeft(sp) + the_temp;
- }
- public string GetHiRaw(string rawHtml)
- {
- string front_part = "<b>hi ";
- string end_part = "°";
- string msg = "Hi temp: ";
- string end_msg = " degrees";
- string result = fetchValue(front_part, end_part, msg, end_msg, rawHtml);
- return result;
- }
- public string GetBarometer(string rawHtml)
- {
- string front_part = "<b>barometer: </b>";
- string end_part = "\" hg\r\n";
- string msg = "Barometer: ";
- string end_msg = " hg";
- string result = fetchValue(front_part, end_part, msg, end_msg, rawHtml);
- return result;
- }
- public string fetchValue(string front_part, string end_part, string msg, string end_msg, string rawHtml)
- {
- string matchPattern = front_part + "((.|\n)*?)" + end_part;
- Match[] x1 = FindSubstrings(rawHtml, matchPattern, false);
- StringBuilder sb = new StringBuilder();
- foreach (Match m in x1)
- {
- sb.Append(m.Value);
- }
- string s = sb.ToString();
- int start = s.IndexOf(front_part) + front_part.Length;
- int end = s.IndexOf(end_part);
- string the_value = s.Substring(start, end - start);
- return msg + AddSpaces(msg.Length, the_value) + end_msg + "\n";
- }
- public string GetLowRaw(string rawHtml)
- {
- string front_part = "<b>lo ";
- string end_part = "°";
- string msg = "Low temp: ";
- string end_msg = " degrees";
- string result = fetchValue(front_part, end_part, msg, end_msg, rawHtml);
- return result;
- }
- public string GetCloudCondition(string rawHtml)
- {
- string front_part = "</div>\r\n\t\t\t\t<span class=\"cnnweatherconditioncurrent\">";
- string end_part = "</span>";
- string msg = "Overall: ";
- string end_msg = "";
- string result = fetchValue(front_part, end_part, msg, end_msg, rawHtml);
- return result;
- }
- public string GetHumidity(string rawHtml)
- {
- string front_part = "<b>humidity: </b>";
- string end_part = "%<br />";
- string msg = "Humidity: ";
- string end_msg = " %";
- string result = fetchValue(front_part, end_part, msg, end_msg, rawHtml);
- return result;
- }
- public string GetFeelsLike(string rawHtml)
- {
- string front_part = "<b>feels like: </b>";
- string end_part = "°<br />";
- string msg = "Feels like: ";
- string end_msg = " degrees";
- string result = fetchValue(front_part, end_part, msg, end_msg, rawHtml);
- return result;
- }
- #endregion
- public string GetHtml()
- {
- return data;
- }
- }
- // Add this attribute
- [ServiceBehavior(Name = "EchoService", Namespace = "https://samples.microsoft.com/ServiceModel/Relay/")]
- public class EchoService : IEchoContract // Inherit from IEchoContract
- {
- // Implement the contract method
- public string Echo(string text)
- {
- MyWeatherData weather_data = new MyWeatherData("", "94945");
- if (weather_data.state != "good")
- return "Error with \"" + text + "\", " + weather_data.state;
- Console.WriteLine("Echoing: \n{0}", weather_data.Data);
- return weather_data.Data;
- }
- // Implement the contract method
- public Image EchoImage(string text)
- {
- MyWeatherData weather_data = new MyWeatherData("", "94945");
- return weather_data.GetImageFromUrl("https://www.ssd.noaa.gov/goes/west/nepac/avn-l.jpg");
- }
- }
- }
Code Snippet
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- // Need to add the "using"
- using System.ServiceModel;
- namespace Service
- {
- public interface IEchoChannel : IEchoContract, IClientChannel
- {
- }
- }
Code Snippet
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- // Need to add the "using"
- using System.ServiceModel;
- using System.Drawing;
- namespace Service
- {
- // Add the ServiceContract.
- [ServiceContract(Name = "IEchoContract", Namespace = "https://samples.microsoft.com/ServiceModel/Relay/")]
- public interface IEchoContract
- {
- [OperationContract]
- string Echo(string text);
- [OperationContract]
- Image EchoImage(string text);
- }
- }
It is time to compile. I like to compile frequently to see if I missed anything.
Here is what I like to see when I view the “Output Window.”
Here’s where we are at.
Quick Review | |||
We have completed | (1) We have added a big chunk of code to Service. (2) We have added to interfaces and one class module (3) We have the methods ready to return weather information to the client | ||
We are now doing | Writing the main driver code. We still don’t have any authentication taking place, the code to open a channel, etc. | ||
What we will do next | Finish wiring up the “Service” side. Next we will build the “Client” side. | ||
Scraping Weather
Note this code snippet below. It allows us to scrape the weather from a web page at CNN.
Before what we start any coding, we need to set a couple of things. First, make sure you’ve installed the .NET Services SDK.
You will need that installed so you can set a reference to Microsoft.ServiceBus.dll
Adding a reference to Microsoft.ServiceBus.dll Navigate to the folder where the .NET Services SDK got installed.
Add some code to main() as follows:
How our application interacts with the service bus
Your application interacts with the AppFabric Service Bus via the Microsoft.ServiceBus.dll
We will work with the authentication features available through the Windows Azure platform AppFabric.
The binding being used is WebHttpRelayBinding, which is the default binding for web applications.
Why WebHttpRelayBinding?
WebHttpRelayBinding has been created to meet the most common scenarios. Most commonly, Web-style clients talk to services that choose to accept all incoming traffic and perform only lightweight authentication using a variety of custom techniques to enable and enrich AJAX-style user experiences.
The serviceNamespaceDomain
Got it at the Microsoft portal
The issuerName
Got it at the Microsoft portal
The issuerSecret
Got it at the Microsoft portal
Main Driver Loop. I have broken it down into 4 parts.
- Part 1 – Utilize your namespace
The Service will expose endpoints. But before those endpoints can be exposed we need to configure those endpoints to use some credentials and to be discoverable by a client.
Where endpoints are defined
The service host has endpoints to configure in App.config.
The first part of the code establishes the issuerName and the issuerSecret. This can be obtained from the Microsoft Web Portal for Azure. Later it will be part of the endpoints to enforce security for client connections.
------------------------- Part 1 of main()-------------------------Project = “Service”Module = “Program.cs”Function = “main()”
Code Snippet
|
The code below is exposing and endpoint by leveraging the following Azure’s AppFabric Framework:
- CreateServiceUri()
- TransportClientEndpointBehavior()
------------------------- Part 2 of main()-------------------------Project = “Service”Module = “Program.cs”Function = “main()”
Code Snippet
|
Here we setup the actual host of “EchoService.”
Code Review | |||
We have completed | (1) We are finishing up the details for our “Service,” specifically the configuration of endpoint attributes, such as security and discoverability (2) We are using our security credentials that we retrieved from the web portal for Azure App Fabric | ||
We are now doing | (1) Writing the main driver code. (2) We now need to setup our endpoints by looping through them (3) We are ensuring that the endpoint is published into the discovery feed so clients can see it (4) We also need to set security attributes from each endpoint | ||
What we will do next | (1) Open ServiceHost when we are done setting up endpoints | ||
------------------------- Part 3 of main()-------------------------Project = “Service”Module = “Program.cs”Function = “main()”
Code Snippet
|
Soon, we will begin working on the client. We are pretty much ready to open the endpoints and wait for clients to come in.
Code Review | |||
We have completed | (1) We are finished setting up the Service prior to being opened | ||
We are now doing | (1) Simply open the service and wait for connections | ||
What we will do next | Start working on the client code | ||
------------------------- Part 4 of main()-------------------------Project = “Service”Module = “Program.cs”Function = “main()”
Code Snippet
|
1. Right-click Service project, point to Add and select New Item. In the Add New Item dialog box, select the Application Configuration File template, leave the name by default and click Add
Notice we are defining the contract and the binding. Remember that WCF is about the ABCs. Address, Binding, Contract.
Endpoint
- Contract = Service.IEchoContract
- binding = netTcpRelayBinding
NetTcpRelayBinding Class[This is prerelease documentation and is subject to change in future releases. Blank topics are included as placeholders.] A secure, reliable binding suitable for cross-machine communication.Namespace: Microsoft.ServiceBusAssembly: Microsoft.ServiceBus (in microsoft.servicebus.dll) |
Code Snippet
- <?xml version="1.0" encoding="utf-8" ?>
- <configuration>
- <system.serviceModel>
- <services>
- <service name="Service.EchoService">
- <endpoint contract="Service.IEchoContract"
- binding="netTcpRelayBinding" />
- </service>
- </services>
- </system.serviceModel>
- </configuration>
Let’s get a few things out of the way. What is the big picture for us to create this client application. Here is the big picture.
Here is the breakdown:
Preparation Understanding what you are connecting to Retrieve a copy of the contract of the service and place it in your code Retrieve your service namespace and relevant credential information Define the security credentials to use with the service Prepping the client to talk through the service bus - Setting References Add the System.ServiceModel and Microsoft.ServiceBus namespaces to your project Defining your connection (Uri, Binding, Channels) Create an URI object pointing to the service bus service Define the binding to use to connect to the service Running the Client Instantiate a channel factory Apply the credentials and any other behaviors to the endpoint Create a new channel to the service and open Ready to perform business functions Perform whatever tasks are necessary to your scenario |
What server and client share
Both the server and client pieces define an interface called IEchoChannel, which is a derived interface from IEchoContract and IClientChannel. All together, the IEchoChannel (1) defines the methods that the client will access, and (2) defines the channel by which server and client will communicate.
What is a channel?
The Service Bus (AppFabric) is a layered communication stack with one or more channels that process messages. At the bottom of the stack is a transport channel that is responsible for adapting the channel stack to the underlying transport (for example, TCP, HTTP, SMTP and other types of transport.).
Bottom Line
Channels provide a low-level programming model for sending and receiving messages.
Channels – details about message delivery, protocols used, transports
I like that Channels abstract away details of protocols, transports, message delivery details. |
Channel stack is designed to provide an abstraction of not only how the message is delivered, that is, the transport, but also other features such as what is in the message or what protocol is used for communication, including the transport.
Channel Responsibilities
Sending and receiving messages to and from other parties.
Transforming the Message object to and from the format used to communicate with other parties.
Adding headers or encrypting the body,
Sending and receiving their own protocol control messages, for example, receipt acknowledgments.
Channels handle difference protocols
There can be any number of protocol channels each responsible for providing a communication function such as reliable delivery guarantees.
1. Add a New Project.
You will create the Client project. To do this, in Solution Explorer, right-click Service solution, point to Add and select New Project. In the New Project dialog box, select the Console Application project type. Set the project name to Client, and click OK.
Click on ‘Service’ or topmost item in Solution Explorer.
Choose “Console Application.”
2. Create the Echo client proxy. To do this, in Solution Explorer right-click the Client project , point to Add and select New Item. In the Add New Item dialog box, select Visual C# | Code and choose the Interface template. Specify a Name value of EchoProxy.cs. Click Add to create the Interface [illustrated previously].
3. Name the interface EchoProxy.cs.
The client needs to communicate with the server through “pre-agreed” contracts. Our contract is in the form of an interface.
Adding the client reference
Just as with the server side, we need to provide references to allow our client to use the AppFabric.
4. Add a reference to ServiceModel assembly. To do this, in Solution Explorer, right-click the Client project and select Add Reference. In the Add Reference dialog box, in the .NET tab, select System.ServiceModel assembly. Click OK to add the reference
5. Add references to Microsoft .Net Services SDK core assembly. To do this, right-click Client project in Solution Explorer and select Add Reference. In the Add Reference dialog window:
Adding a reference to Microsoft.ServiceBus.dll Navigate to the folder where the .NET Services SDK got installed.
Here we setup the actual host of “EchoService.”
Code Review | |||
We have completed | (1) The “Service” that the client will connect to is finished (2) We are done with server side | ||
| |||
We are now doing | (1) The Client side that connects to the server (2) Write code to connect to service using security credentials, service name, contract information, etc | ||
| |||
What we will do next | (1) Run the client and complete this blog entry | ||
------------------------- Part 1 of main()-------------------------Project = “Service”Module = “Program.cs”Function = “main()”
Code Snippet
|
| |||
Code Review | |||
We have completed | (1) We have already setup credentials for endpoints | ||
| |||
We are now doing | (1) Creating a Channel, which will help us abstract away protocols and other transport details | ||
| |||
What we will do next | (1) Write the code that reads user input at the client and passes that input to Service (2) Call the “Echo()” method of our Service | ||
------------------------- Part 2 of main()-------------------------Project = “Service”Module = “Program.cs”Function = “main()”
Code Snippet
|
|
| ||
Code Review | |||
We have completed | (1) Setup our client to connect to the service and prepare to send a string from client to service. | ||
| |||
We are now doing | (1) Writing code that gets user input from the console window of the client, passes that input to our Service (2) For example, if the client passes in the zip code for Novato (“94945”) to the Service, the Service will return a string with the weather for Novato | ||
| |||
What we will do next | (1) x | ||
------------------------- Part 3 of main()-------------------------Project = “Client”Module = “Program.cs”Function = “main()”
Code Snippet
|
Code Review | |||
We have completed | (1) Written the client code (2) Used client to connect to Service (3) Client sends a zip code and receives weather back | ||
| |||
We are now doing | (1) Closing connections. We are done | ||
| |||
What we will do next | (1) Re-read this blog entry to learn it better | ||
------------------------- Part 4 of main()-------------------------Project = “Client”Module = “Program.cs”Function = “main()”
Code Snippet
|
Don’t be confused about terminology
There are a lot of similar technologies here. WCF, ServiceBus, AppFabric. Do your own research to dis-ambiguate what they mean.
Add a new configuration file.
Your app.config should like this. Notice that we specify a binding and a contract.
Code Snippet
- <?xml version="1.0" encoding="utf-8" ?>
- <configuration>
- <system.serviceModel>
- <client>
- <!-- Application Endpoint -->
- <endpoint name="RelayEndpoint"
- contract="Client.IEchoContract"
- binding="netTcpRelayBinding"/>
- </client>
- </system.serviceModel>
- </configuration>
But before compiling we need to adjust the framework that our project compiles against. First, go the “Service” project and right mouse click and choose Properties.
Make sure you have .NET Framework 3.5 for both the client and service.
1. Make sure you’ve gone to the web portal and registered a project. As you can see I have clicked on “App Fabric” to get things started.
Run the Service. To do this, right-click Service project, point to Debug and select StartNewInstance.
Note that we have various management keys, registered URLs, endpoints, Issuer Keys and Issuer Name. Issuer Keys and Issuer name is what we need to work with our example.
https://brunoblogfiles.com/Projects/WeatherClientService/Service.zip