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     E-mail

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

That was very helpful. Thanks.

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

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

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

There is nothing beyond what you see here.

Bhavesh
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

Vladimir Bodurov
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

Andre Sobreiro
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?

Roberts
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?

Vladimir Bodurov
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); 
}); 
Vladimir Bodurov
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; 
    } 
} 
Mikels
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?

Erion
Posted on 2/2/2011 6:42:40 AM

Thanks for the article.
I'm trying to push the proxy-less approach a bit further and make the web method call totally parametrized - no need to know the contract prior to invoking the web service methods. In other words, I'm trying to save the web service address and the web method name in a configuration file to then be able to invoke the method dynamically from code like this:

public class CustomWebServiceProxy : SoapHttpClientProtocol 
{ 
    public object[] CustomInvoke(string wsAddress, string wsMethod, object[] parameters) 
    { 
        this.Url = wsAddress; 
        return this.Invoke(wsMethod, parameters); 
    } 
} 

When I try doing this, sometimes (but not always) it raises a System.ArgumentException saying that the web method name (which I've specified in the parameters) isn't valid. Other times it works correctly! I can't understand why it behaves like this.

Have you got any ideas?

Is what I'm trying to do possible?

Thanks.

Vladimir Bodurov
Posted on 2/3/2011 3:29:00 AM

Thanks Erion, this is interesting approach!

Pst Repair
Posted on 4/13/2011 6:18:06 AM

I am excited to hear that WCF offers a better approach to using web proxies. I like eliminating the middle man and keeping the contract between the client and the server. After reading your article, I was trying to implement your suggestions and I am having trouble with the BasicHttpBinding when trying the custom binding. I keep getting errors. Do you know what the problem might be? Thanks for your help.

Commenting temporarily disabled