(X) Hide this
    • Login
    • Join
      • Generate New Image
        By clicking 'Register' you accept the terms of use .

Windows Phone 7 Data Access Strategies: Web Services

(3 votes)
Andrea Boschin
>
Andrea Boschin
Joined Nov 17, 2009
Articles:   91
Comments:   9
More Articles
1 comments   /   posted on Aug 31, 2011
Categories:   Data Access , Windows Phone

In the two previous articles of this series I've spoken about a couple of tools that are really useful to retrieve remote resources by the HTTP protocol. Also if WebClient and HttpWebRequest are both complete and easy to use, the problem is that their usage is for sure heavy, because you need to rely on your own protocol made for example of XML files. The process of serializing and deserializing can become very annoying and if you have more than two or three files, the wasted time is unacceptable.

With this problem in mind it is easy to understand that the real answer to this problem are the Web Services, meaning with this term something that is based on a known protocol - obviously built on top of HTTP - and makes easy the process of sharing data between client and server, hiding transport and serialization problems.

Speaking about Web Services in this article would require lot of pages, so I will assume you are already familiar with the concept behind this term and also probably you are familiar with the Windows Communication Foundation that is currently the best way of creating and consuming a Web Service in a Windows environment. Assuming this I will show how to consume a WCF Web Service from Windows Phone 7 and how to configure both the client and server side.

A, B, C...

As you probably already know, working with WCF you have to be aware of three things that uniquely define the Web Service you have to consume: Address, Binding and Contract. Since "Address" says where the service is located, in the form of a Uri, the "Binding" describes how the client and server will speak each other. Serialization and deserialization matter is part of the binding but also the transport: HTTP, HTTPS, and so on. But the most important part is the "Contract" that describes what the service can do. In WCF, a contract is an interface with a bunch of methods - usually called "Operations" - and each one do something with some kind of input and output.

When you call a WCF service from a Windows Phone device, you have some limitations, specifically in the Bindings you can use. Since WP7 is based on Silverlight 3.0 (but the incoming "Mango" release probably will change this someway) you can only use BasicHttpBinding or a special form of Binary XML that makes the communications, more compact. The current version of the operating system does not support TCP transport.

Once you have the "Address" of the service you have to consume, the first thing you have to do is creating a Proxy. The proxy is a client side class that implements the "Contract" of the service and hides to the developer the presence of a Web Service. What it happens is that you call a method on the local class and the proxy take care of serialize request, making the network call, reading the response and deserializes it.

To create the proxy you can left-click the References node of the Windows Phone's project where you want the proxy resides and select "Add Service Reference". This action will start a wizard to configure the proxy.

In the side figure I've already filled the fields of the form but the process is simple; enter the Address and press the "OK" button. The wizard will analyze the contract of the Web Service and it will show the details in the Services and Operations frames. In the lower textbox you can specify the namespace where the proxy class have to be added.

The "Advanced..." button opens a configuration form where you can specify more details about the creation of the proxy. The one I usually change is "Collection Type". it defaults to "ObservableCollection" and I ask always to have an "Array".

When you hit the "OK" button the proxy is created together with a lot of files that you can simply ignore. The sole file you have to know is "ServiceReference.ClientConfig" where it is located the client configuration of the WebService. We will return back to this file later in the article.

The process I've summarized here is good only if you have to consume a single Web Service or different services that are completely insulated each other. But in the common case you have many different services that share some type, you need to rely on a command line utility that is able to receive all the endpoints at a time and generate the proxies, being aware of the common types. The tools is slsvcutil.exe and it is located in the SDK folder at %ProgramFiles(x86)%\Microsoft SDKs\Windows Phone\v7.0\Tools. The tool has many options on the command line you can explore using the -h switch. Here is an example:

   1: @echo off
   2:  
   3: "%ProgramFiles(x86)%\Microsoft SDKs\Windows Phone\v7.0\Tools\SlSvcUtil.exe" ^
   4:     http://localhost:8000/names.svc ^
   5:     /config:ServiceReferences.ClientConfig ^
   6:     /out:Proxies/GeneratedProxies.cs ^
   7:     /n:*,SilverlightPlayground.ServiceProxies ^
   8:     /ct:System.Collections.Generic.List`1 ^
   9:     /enableDataBinding
  10:  
  11: @echo on
If you need to generate more than one proxy, you can add the service's Uris following the first one in the example and then place the configuration switches. Once the tool finished its work, you will find the ServiceReferences.ClientConfig file and a GeneratedProxies.cs file that you have to add to the project. Important is to remember that every time you change something on the service you have to run again the service reference wizard (it has also an update option in the context menu) or the batch.

Consuming the Web Service

The generation tool that I've mentioned generates lot of code. If you open the GeneratedProxies.cs file you will find a long series of classes, according with the number of services you have referenced and the complexity of each one. As an example if the method of a service returns a complex object, the type is added to the generated code. All what you need is this code. It is not required to make any references to the types in the service's assemblies. When you call the proxy, the runtime works mapping the server types to the client copy and vice versa. It is the process of serialization and deserialization.

The most important class in the generated code is the proxy that is something that puts together the name of the service and the "Client" suffix. In the case of the service in the attached code it is NamesServiceClient. This class represents the service and contains all the things you need, to call the methods. In Windows Phone 7, like in Silverlight, the proxy cannot be so transparent as you would like it to be. Due to the asynchronous model of the communications, calling a method imply to subscribe an event that notifies the call completion:

   1: public void GetNames()
   2: {
   3:     NamesServiceClient client = new NamesServiceClient();
   4:     client.GetNamesCompleted += new EventHandler<GetNamesCompletedEventArgs>(client_GetNamesCompleted);
   5: }
   6:  
   7: void client_GetNamesCompleted(object sender, GetNamesCompletedEventArgs e)
   8: {
   9:     IEnumerable<string> names = e.Result;
  10:     // do something with resulting names
  11: }

We are back here to an event-driven paradigm so, like with the WebClient class, we have to subscribe the event and get the result in a separated method. In a layered architecture you can apply the same pattern I've used with the WebClient. The NamesServiceClient instance can be used across different calls, so you can have an instance as a global reference in your DataProvider and use it every time is is required. Additionally the proxy supports concurrent calls but you have to handle by your own the returning event to identify the corresponding call:

   1: public override void GetNames(Action<System.Collections.Generic.IEnumerable<string>> success, Action<Exception> fail)
   2: {
   3:     Guid callId = Guid.NewGuid();
   4:     EventHandler<GetNamesCompletedEventArgs> handler = null;
   5:     
   6:     handler = (s, e) =>
   7:     {
   8:         if (callId.Equals(e.UserState))
   9:         {
  10:             this.Client.GetNamesCompleted -= handler;
  11:                 success(e.Result);
  12:         }
  13:     };
  14:  
  15:     this.Client.GetNamesCompleted += handler;
  16:     this.Client.GetNamesAsync(callId);
  17: }

Again, in the pattern I attach the event handler and I ensure myself to remove the reference once the method returns the result. Here I added a Guid that I use as call identifier. I put it in the "user state". When the service returns a result I check that the "user state" corresponds to the Guid I generated. This prevent that two concurrent calls to the same method will raise a doubled callback to each entry point.

Handling errors and call cancellation

The proxy class also supports the handling of errors and the cancellation of the calls. Differently from WebClient and HttpWebRequest the proxy is aware of application errors raised as exceptions and it is able to deserialize and deserialize them. WCF implements also a FaultException and the proxy is able to correctly handle this type of exceptions, reading also the details of the errors. Since if you have to catch regular exceptions, you have to enable a configuration flag (includeExceptionDetailInFaults), with FaultException you can pass your own exception detail as a FaultContract and provide the error information to display. Given this new requirement and given that you have also to handle network errors, here is the new code:

   1: public override void GetNames(Action<System.Collections.Generic.IEnumerable<string>> success, Action<Exception> fail)
   2: {
   3:     Guid callId = Guid.NewGuid();
   4:     EventHandler<GetNamesCompletedEventArgs> handler = null;
   5:  
   6:     handler = (s, e) =>
   7:     {
   8:         if (callId.Equals(e.UserState))
   9:         {
  10:             this.Client.GetNamesCompleted -= handler;
  11:  
  12:             if (!e.Cancelled)
  13:             {
  14:                 if (e.Error == null)
  15:                     success(e.Result);
  16:                 else
  17:                     fail(this.HandleFault(e.Error));
  18:             }
  19:         }
  20:     };
  21:  
  22:     this.Client.GetNamesCompleted += handler;
  23:     this.Client.GetNamesAsync(callId);
  24: }
  25:  
  26: private Exception HandleFault(Exception exception)
  27: {
  28:     FaultException<NamesServiceFault> fault = exception as FaultException<NamesServiceFault>;
  29:  
  30:     if (fault != null)
  31:         return new Exception(fault.Detail.Message);
  32:     else
  33:         return exception;
  34: }

In the above code I handle also the cancellation condition. The NamesServiceClient class has an Abort method that breaks all the pending network operations. When this method is called every call will raise the completion event with the Cancelled flag set to true.

Configure for Binary XML binding

At the start of the article I spoken about two available bindings. By default Visual Studio configures the service and the client for BasicHttpBinding that is a simple format that replicates in WCF the protocol used by asmx services. This protocol is SOAP based and it is verbose. For the purpose of reducing the size of the messages, Silverlight 3.0 added a new BinaryXML binding that is also supported by Windows Phone 7. To configure this binding you have to slightly change the configuration on the server and on the client. Here is the configuration for both Basic and Binary binding on the server side:

   1: <system.serviceModel>
   2:     <bindings>
   3:         <basicHttpBinding>
   4:             <binding name="NamesService_basicHttpBinding" allowCookies="True" />
   5:         </basicHttpBinding>
   6:         <customBinding>
   7:             <binding name="NamesService_binaryXML">
   8:                 <binaryMessageEncoding />
   9:                 <httpTransport />
  10:             </binding>
  11:         </customBinding>
  12:     </bindings>
  13:     <behaviors>
  14:         <serviceBehaviors>
  15:             <behavior name="">
  16:                 <serviceMetadata httpGetEnabled="true" />
  17:                 <serviceDebug includeExceptionDetailInFaults="false" />
  18:             </behavior>
  19:         </serviceBehaviors>
  20:     </behaviors>
  21:     <services>
  22:         <service name="SilverlightPlayground.DataAccessStrategies.Web.Code.NamesService">
  23:             <endpoint address="basic" 
  24:                       binding="basicHttpBinding" bindingConfiguration="NamesService_basicHttpBinding" 
  25:                       contract="SilverlightPlayground.DataAccessStrategies.Web.Code.INamesService" />
  26:             <endpoint address="binary"
  27:                       binding="customBinding" bindingConfiguration="NamesService_binaryXML"
  28:                       contract="SilverlightPlayground.DataAccessStrategies.Web.Code.INamesService" />
  29:         </service>
  30:     </services>
  31:     <serviceHostingEnvironment aspNetCompatibilityEnabled="True" 
  32:                                multipleSiteBindingsEnabled="true" />
  33: </system.serviceModel>

The service configured in this way exposes two endpoint, the one ending with "basic" is configured with BasicHttpBinding and the one ending with "binary" is configured with the Binary XML. This binding is exposed as a customBinding with <binaryMessageEncoding /> and <httpTransport />. On the client side you have to replicate the same duality:

   1: <system.serviceModel>
   2:     <bindings>
   3:         <basicHttpBinding>
   4:             <binding name="BasicHttpBinding_INamesService"
   5:                      maxBufferSize="2147483647"
   6:                      enableHttpCookieContainer="True"
   7:                      maxReceivedMessageSize="2147483647">
   8:                 <security mode="None" />
   9:             </binding>
  10:         </basicHttpBinding>
  11:         <customBinding>
  12:             <binding name="NamesService_customBinding">
  13:                 <binaryMessageEncoding />
  14:                 <httpCookieContainer />
  15:                 <httpTransport />
  16:             </binding>
  17:         </customBinding>
  18:     </bindings>
  19:     <client>
  20:         <endpoint name="BasicHttp"
  21:                   address="http://localhost:8000/names.svc/basic"
  22:                   binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_INamesService"
  23:                   contract="Services.INamesService" />
  24:         <endpoint name="BinaryXML"
  25:                   address="http://localhost:8000/names.svc/binary"
  26:                   binding="customBinding" bindingConfiguration="NamesService_customBinding"
  27:                   contract="Services.INamesService" />
  28:     </client>
  29: </system.serviceModel>
 

Each endpoint has a different name in this configuration (BasicHttp and BinaryXML). This name is useful if you need to use one endpoint or the other. When you create the NamesServiceClient instance you can specify the endpoint name, to read one configuration or the other

this.Client = new NamesServiceClient("BinaryXML");

Using Cookies and Credentials

Cookies and Credentials are also supported by the generated proxy. Differently, when you call a Web Service, you are not able to access Headers and Verbs. Once you created the instance of the proxy class, it exposes a ClientCredentials property, ready to receive the credentials:

this.Client.ClientCredentials.UserName.UserName = "username";
this.Client.ClientCredentials.UserName.Password = "p@ssw0rd";
 
The use of cookies should not be needed with a Web Service. Every call to the service should be stateless but when you enable AspNetCompatibilityRequirements on the server side, using a CookieContainer you can enable the Session variables and sometimes these may be useful. From my point of view it is better to not use Session variables but I'm aware that someone can take some advantage from it. The proxy exposes a CookieContainer property but you do not need to set the container by yourself. You have only to enable support for cookies in configuration. For BasicHttpBinding this means to set the enableHttpCookieContainer attribute to True as I did on the line 6 of the previous box. For BasicXML instead you have to add, only on the client side, a <httpCookieContainer /> element in the customBinding between the encoding and the transport (see line 14 of the box)

A step forward

There is not any doubt you have to take in serious consideration the use of a web service if your application is something of advanced. And probably, thanks to WCF, the easy of the creation of a Web Service overwhelm every other networking tool when you need to use it for data access. From my point of view WebClient and HttpWebRequest are both useful when I have to download a generic file, an rss feed or communicate with some kind of legacy system. But when I have under control both the server and client side, the Web Service are for sure the most productive approach.


Subscribe

Comments

  • -_-

    Re: Windows Phone 7 Data Access Strategies: Web Services


    posted by on Nov 11, 2011 05:48

    billig kvinners Canada Goose Kensington parka salg, velge passende parka og holde pengene dine på rett plass.

Add Comment

Login to comment:
  *      *       

From this series