Sunday, 3 February 2013

Accessing UI from a background thread in WinForms and WPF/Silverlight

Introduction

Updating UI in both WinForms and WPF/Silverlight can only be performed from the UI thread. Any attempt to update visual controls from a background thread will fail with UnauthorizedAccessException. Also an attempt to update an object bound to visual control will fail, if it results in updating a visual control.

Unfortunately scenarios where it's necessary to update UI from another thread are quite common. For example accessing a Web Service asynchronously relies on a callback methods which execute in the background. Note that in case of Silverlight, asynchronous is the only way to access a Web Service.

Example

Let's use Echo Web Service Demo at http://www.aspspider.info/burzyn/echoservice.svc as an example. Assuming that its service reference has been added with Generate Asynchronous operations option enabled, the following two methods are present in IEchoService interface

IAsyncResult BeginDescription(System.AsyncCallback callback, object asyncState);
string EndDescription(System.IAsyncResult result);

They are equivalent to a synchronous function with the signature of:
string Description()

Typically asynchronous calls to a Web Service use callbacks to complete the operation, as illustrated below.

IEchoService echoService;
void Init() 
{   ...
    echoService.BeginDescription(DescriptionCallback, echoService);
    ...
}
void DescriptionCallback(IAsyncResult ar)
{            
    string description = (ar.AsyncState as IEchoService).EndDescription(ar);
    // this will fail with UnauthorizedAccessException
    lblDescription.Content = description;
}

This fails because DescriptionCallback is trying to access UI while executing in a background thread.

Solution

In case of WPF/Silverlight, it can be resolved by using the System.Windows.Threading.Dispatcher namespace. Every control class has the Dispatcher property, which can be used to render changes from any thread. Using the Dispatcher property of the current Page, DescriptionCallback needs to be modified as follows.

private delegate void DispatchHandler(string arg);

private void UpdateDescription(string description)
{
    lblDescription.Content = description;
}

void DescriptionCallback(IAsyncResult ar)
{            
    string description = (ar.AsyncState as IEchoService).EndDescription(ar);
    Dispatcher dispatcher = this.Dispatcher;
    DispatchHandler handler = new DispatchHandler(UpdateDescription);  
    Object[] args = { description };
    this.Dispatcher.BeginInvoke(handler, args);
}



BeginInvoke takes two parameters - delegate to be executed in the UI thread and array of objects used as parameters of the delegate. Parameters are matched with object in the array by position. This is rather error prone solution - there's no type safety and changes to the number or type of the parameters might break the code. It can be done in much safer and clearer way by using an anonymous method.
 
private delegate void DispatchHandler();
echoService.BeginEchoString(txtString.Text, delegate(IAsyncResult ar) 
{
    Dispatcher dispatcher = this.Dispatcher;
    DispatchHandler handler = delegate()
            {
                lblDescription.Content = echoService.EndEchoString(ar);
            };
    dispatcher.BeginInvoke(handler, null);                                 
}, null);

Note that it's also shorter and more readable. Of course, it can be even further shortened by nesting anonymous methods.

{
   echoService.BeginDescription(delegate(IAsyncResult ar)
           {
               string description = echoService.EndDescription(ar);
               DispatchHandler handler = new DispatchHandler(delegate()
               {
                  lblDescription.Content = description;
               });
               this.Dispatcher.BeginInvoke(handler, null); 
           }  
   null);      
}

How about WinForms?


Although the WinForms controls don't have the Dispatcher property, they expose Invoke and BeginInvoke methods, which can be used in the same manner.

 echoService.BeginEchoString(txtString.Text, delegate(IAsyncResult ar) 

 {
    string description = echoService.EndDescription(ar);

    DispatchHandler handler = delegate()
        {
            lblDescription.Text = description;
        };
        this.BeginInvoke(handler);
 }

Additionally WinForms controls expose InvokeRequired property to determine if a call in on a different thread than the control, essentially meaning if the control can be access directly. You can take advantage of the InvokeRequired property using the like below.

    if (lblDescription.InvokeRequired)
    {
        DispatchHandler handler = delegate()
            {
                lblDescription.Text = description;
            };
            this.BeginInvoke(handler);
    }
    else
       lblDescription.Text = description;

Summary

Although updating UI from background might look like an issue, but it's fairly easy to find the way around it. Using anonymous methods allows for clearer and less error prone code.

Quick note on Delphi. Within VCL, access to UI from background thread is not thread safe, but it's not blocked. It's up to a developer to resolve any potential problems. It's recommended to wrap any code executed in a background thread in the Synchronize procedure of the TThread class.



Monday, 21 January 2013

Interoperable WCF

Introduction

Web Services have been designed for interoperability, so you would expect that clients and servers created  using different tools interoperate smoothly. In reality it's far from being that simple. Multiple and evolving standards and features created the situation where no two systems are the same. It led to creation of Web Services Interoperability Organization with the purpose to establish best practices for interoperability for select group of standards. Further information can be found at http://www.ws-i.org

WS-I Basic Profile

As the name suggests,WS-I Basic Profile defines minimal set of standards a Web Service should support. Unfortunately, the Basic Profile specification has number of versions which are not necessarily interoperable. For example, system compliant with WS-I version 1.0 might not be able to interoperate with a system compliant with WS-I version 2.0.

Windows Communication Foundation supports WS-I with BasicHttpBinding which conforms to WS-I version 1.1. Basically, it means compliance with SOAP 1.1, WSDL 1.2 and UDDI 2.0. It is also understood that internet standards like XML Schema, SSL, X.509 etc are also supported.

Delphi client

Although Delphi introduced support for Web Services early, it doesn't have many advanced features and interoperability with Web Services created with other systems has always  been limited. It's getting better with every new version of Delphi, though. 

It's fairly easy to consume WCF service configured to use BasicHttpBinding. Let's use Interoperable Web Service Demo at http://aspspider.info/burzyn/echoservice.svc as an example. The WSDL document can be generated using the following link http://aspspider.info/burzyn/echoservice.svc?WSDL

To import the Echo Service into a Delphi project select File | New | Others | WebServices |  WSDL Importer and paste http://aspspider.info/burzyn/echoservice.svc?WSDL into the WSDL Source.
Also in Delphi XE2 or newer, there's a new option in the Components menu, which is an alternative way to import WSDL. Use Components | Import WSDL to create a unit to be shared by multiple projects.

WSDL document generated from WCF service usually contains two file, the main file and the second file referenced by using the following line.
<wsdl:import location="http://aspspider.info/Burzyn/EchoService.svc?wsdl=wsdl0"/>
Delphi older then 2009 will not be able to import it correctly, as it processes just the main file and information from this file alone might not be sufficient. This has been remedied in .NET Framework 4.5 by providing an option to generate WSDL document as a single file. Single file WSDl document is accesible at http://aspspider.info/burzyn/echoservice.svc?singleWSDL

Summary

This is just the beginning. Services based on BasicHttpBinding might not be sufficient for every requirement. Connecting to WCF services built using other bindings might be a bigger challenge, but it's not impossible. It might require significant SOAP expertise, though.


Thursday, 17 January 2013

Interoperable Web Service Demo

Common scenario

I've been involved in interfacing to WCF using other tools than Visual Studio (Delphi) more than once. Short discussion can be found at Interoprable WCF. I thought it would be a good idea to have a reference service available and this is how the idea of Echo Service was born. Echo Service illustrates typical issues associated with accessing web services - calling a method with parameters of simple and complex type, handling declared and undeclared exception, as well as downloading a large binary data.

The Implementation

The Interoperable Web Service Demo is currently hosted in http://aspspider.info/burzyn/echoservice.svc. Apart from BasicHttpBinding, it exposes WsHttpBinding and WebHttpBinding at http://aspspider.info/burzyn/echoservice.svc/ws and http://aspspider.info/burzyn/echoservice.svc/web respectively. WebHttpBinding can be accessed using the browser with the syntax like:
http://aspspider.info/burzyn/echoservice.svc/web/echostring?value=test1.

Here's the the 'service.model/ group from web.config:
  <system.serviceModel>
    <services>
      <service behaviorConfiguration="Metadata" name="Altaira.Demos.EchoService">
        <endpoint name="BindingInterop" address="" binding="basicHttpBinding" bindingConfiguration="" contract="Altaira.Demos.IEchoService"/>
        <endpoint name="BindingREST" address="web" binding="webHttpBinding" behaviorConfiguration="WebBehavior" contract="Altaira.Demos.IEchoService"/>
        <endpoint name="BindingStandard" address="ws" binding="wsHttpBinding" contract="Altaira.Demos.IEchoService"/>
        <host>
          <baseAddresses>
            <add baseAddress="'http://aspspider.info/burzyn/echoservice.svc"/>
          </baseAddresses>
        </host>
      </service>
    </services>
    <behaviors>
      <endpointBehaviors>
        <behavior name="WebBehavior">
          <webHttp/>
        </behavior>
      </endpointBehaviors>
      <serviceBehaviors>
        <behavior name="Metadata">
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true"/>
  </system.serviceModel>
 Note that includeExceptionDetailinFaults is set to 'false'. It' intended for testing the scenario when an undeclared exception is raised on the server side, so that the CommunicationException is raised rather than  FaultException. You can test it calling UndeclaredFault() method.

Further information is available at request.