Posts By Category

Posts By Date

Web service proxies for asmx web services are quite annoying. They are supposed to represent the contract between the service and the client and without any doubt there should be such a contract. But in reality they represent not only the contract between the service and the client but also the tool that generates them. And they make you vulnerable to any inefficiencies of that tool. Do you like to be dependant in such a way?

Well, fortunately WCF offers much better approach. The contract between the service and the client is represented by an interface decorated with the right attributes. That is all you need for the contract between the client and the server. There is no third party involved and everything is clear. I like that.

Here is an example of a wrapper around the WCF client.

using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Text;

public class WebServiceClient<T> : IDisposable
{
    private readonly T _channel;
    private readonly IClientChannel _clientChannel;

    public WebServiceClient(string url) : this(url, null)
    {
    }
    /// <summary>
    /// Use action to change some of the connection properties before creating the channel
    /// </summary>
    public WebServiceClient(string url, 
         Action<CustomBinding, HttpTransportBindingElement, EndpointAddress, ChannelFactory> init)
    {
            var binding = new CustomBinding();
            binding.Elements.Add(
                new TextMessageEncodingBindingElement(MessageVersion.Soap12, Encoding.UTF8));
            var transport = url.StartsWith("https", StringComparison.InvariantCultureIgnoreCase) 
                                ? new HttpsTransportBindingElement()
                                : new HttpTransportBindingElement();
            binding.Elements.Add(transport);
            var address = new EndpointAddress(url);
            var factory = new ChannelFactory<T>(binding, address);            
            if(init != null)
            {
                init(binding, transport, address, factory);
            }
            this._clientChannel = (IClientChannel)factory.CreateChannel();
            this._channel = (T)this._clientChannel;
    }
    
    /// <summary>
    /// Use this property to call service methods
    /// </summary>
    public T Channel
    {
        get { return this._channel; }
    }
    /// <summary>
    /// Use this porperty when working with
    /// Session or Cookies
    /// </summary>
    public IClientChannel ClientChannel
    {
        get { return this._clientChannel; }
    }

    public void Dispose()
    {
        this._clientChannel.Dispose();
    }
}

And the way you use it is just.

var client = 
      new WebServiceClient<IMyContractInterface>(@"http://www.MySite.com/MyService.asmx");
var result = client.Channel.SayHello("John");

You can use the init action to change some of the connection properties before creating the channel. For example to increase the MaxReceivedMessageSize you could use:

var client = 
      new WebServiceClient<IMyContractInterface>(
            serviceUrl,
            (binding, transport, address, channel) => 
                transport.MaxReceivedMessageSize = Int32.MaxValue
      );

Your interface contains the signitures of the methods in your service. And a sample attribute decoration for the interface contract could look like that.

using System.ServiceModel;

[XmlSerializerFormat]
[ServiceContract(Namespace = "http://blog.bodurov.com/")]
public interface IMyContractInterface
{
  [OperationContract]
  string SayHello(string name);
  [OperationContract]
  BusinessObj ReturnBusinessObject();
}

Here is a sample attribute decoration of a business object BusinessObj on the side of the client returned by the service.

using System.Runtime.Serialization;

[DataContract(Name = "BusinessObj", Namespace = "")]
public struct BusinessObj
{
  [DataMember(IsRequired = true)]
  public string ID { get; set; }
  [DataMember(IsRequired = false)]
  public string Name { get; set; }
}

One consideration here is that the code in the constructor of WebServiceClient may be costly in terms of performance. So you probably want to cache it like that:

public class MyObj{
  
  private static WebServiceClient<IMyContractInterface> _client;
  
  public IMyContractInterface Channel{
    get{
      if(_client == null){
        _client = new WebServiceClient<IMyContractInterface>(
                                @"http://www.MySite.com/MyService.asmx");
      }
      return _client.Channel;
    }
  }
  
  public void MyMethod(){
    var result1 = this.Channel.SayHello("John");
    
    ...
  }
  
}

Or if you want to make it thread safe:

public class MyObj{
  
  private static readonly object _synch = new object();
  private static volatile WebServiceClient<IMyContractInterface> _client;
  
  public IMyContractInterface Channel{
    get{
      if(_client == null){
        lock(_synch){
          if(_client == null){
            _client = new WebServiceClient<IMyContractInterface>(
                                    @"http://www.MySite.com/MyService.asmx");
          }
        }
      }
      return _client.Channel;
    }
  }
  
  public void MyMethod(){
    var result1 = this.Channel.SayHello("John");
    
    ...
  }
  
}

Share this post:   digg     Stumble Upon     del.icio.us     Technorati     E-mail

Feedback

Posted on 1/29/2009 6:22:35 PM

That was very helpful. Thanks.

Posted on 4/20/2009 3:12:49 AM

can u give me the entire source code in zip format
download link.......?

Posted on 4/20/2009 8:19:23 AM

There is nothing beyond what you see here.

Posted on 4/21/2009 6:57:55 PM

This is really good example for normal Web Service Call.
But I need to call https web service with X509 certificate.

Please can you help me here becuase it gives error for custombinding and I have changed that to BasicHttpBinding but still no luck.

Any idea?

Thanks
Bhavesh

Posted on 4/21/2009 9:19:29 PM

I haven't done this but my guess is that you'd have to do this:
1. Add using statement

using System.Security.Cryptography.X509Certificates; 

then when you initialize it do this:

var client =  
      new WebServiceClient<IMyContractInterface>( 
            serviceUrl, 
            (binding, transport, address, channel) =>  
                factory.Credentials.ClientCertificate.SetCertificate(StoreLocation.CurrentUser, StoreName.My, X509FindType.FindBySubjectName, "mysite.com"); 
      ); 

More about wcf client here http://msdn.microsoft.com/en-us/library/aa702621.aspx

Posted on 9/28/2009 1:19:54 PM

Very good article, helped a lot.

I have a quastion.

it breaks if the contract has an one way opetarion. The wcf client throws an exception that says:

"The one-way operation returned a non-null message with Action=" ...

the same when I tried to put a "response.clear()" in the service function.

any idea?

Posted on 1/29/2010 5:39:58 AM

HI,
Excellent article, when I use it for calling a legacy asmx web service I get the following error:
{"The HTTP request is unauthorized with client authentication scheme 'Anonymous'. The authentication header received from the server was 'Negotiate,NTLM,Basic realm=\"dkbs\"'."}

I have added some creditials setting to the webserviceclient but hasn't made any difference:
NetworkCredential mycredentials = new NetworkCredential( "roberts", "1234506", "dlgroups" );

factory.Credentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Identification;
factory.Credentials.HttpDigest.ClientCredential = mycredentials;
Any ideas?

Posted on 1/29/2010 2:49:09 PM
var client = new WebServiceClient<IService>(Url, (binding, httpTransport, address, factory) =>{ 
    var basicAuthBehavior = new BasicAuthBehavior(Username, Password); 
    factory.Endpoint.Behaviors.Add(basicAuthBehavior); 
}); 
Posted on 1/29/2010 2:51:12 PM
using System.ServiceModel.Channels; 
using System.ServiceModel.Description; 
using System.ServiceModel.Dispatcher; 
 
public class BasicAuthBehavior : IEndpointBehavior { 
 
    public string Password { get; set; } 
    public string Username { get; set; } 
 
    public BasicAuthBehavior(string username, string password) { 
        Username = username; 
        Password = password; 
    } 
    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } 
    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { 
        var inspector = new BasicAuthInspector(Username, Password); 
        clientRuntime.MessageInspectors.Add(inspector); 
    } 
    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { } 
    public void Validate(ServiceEndpoint endpoint) { } 
} 
 
 
 
using System; 
using System.ServiceModel; 
using System.ServiceModel.Channels; 
using System.ServiceModel.Dispatcher; 
using System.Text; 
 
public class BasicAuthInspector : IClientMessageInspector { 
 
    public string Password { get; set; } 
    public string Username { get; set; } 
 
    public BasicAuthInspector(string username, string password) { 
        Username = username; 
        Password = password; 
    } 
    public void AfterReceiveReply(ref Message reply, object correlationState) { } 
    public object BeforeSendRequest(ref Message request, IClientChannel channel) { 
 
        // we add the headers manually rather than using credentials 
        // due to proxying issues, and with the 101-continue http verb 
        var authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(Username + ":" + Password)); 
        var messageProperty = new HttpRequestMessageProperty(); 
        messageProperty.Headers.Add("Authorization", "Basic " + authInfo); 
        request.Properties[HttpRequestMessageProperty.Name] = messageProperty; 
        return null; 
    } 
} 
Posted on 2/4/2010 7:11:59 PM

Thanks for the great article! Unfortunately it all breaks for me with netMsmqBinding, because all my operations are OneWay. How I can create Channel which will work with one way operations?

Please post your comments:

Name:  
Email (optional): Your email address will not be posted.
URL (optional):
Comments: HTML will be ignored, URLs will be converted to hyperlinks
Use [code] if(true) alert("OK"); [/code] for source code
 
Enter the text you see in the box: