Firstly you'll need a Custom BindingElement:
/// <summary>
/// Custom binding element - builds the inner channel
/// </summary>
public class ClientChannelBindingElement : BindingElement
{
#region Constructors
/// <summary>
/// Default constructor
/// </summary>
public ClientChannelBindingElement()
{
}
/// <summary>
/// Protected constructor
/// </summary>
/// <param name="other">Another binding element</param>
protected ClientChannelBindingElement(ClientChannelBindingElement other)
: base(other)
{
}
#endregion
#region Public methods
/// <summary>
/// Build a channel factory
/// </summary>
/// <typeparam name="TChannel">Channel of interest</typeparam>
/// <param name="context">Binding context</param>
/// <returns>A new channel factory</returns>
public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)
{
ClientChannelFactory<TChannel> factory = new ClientChannelFactory<TChannel>();
factory.InnerChannelFactory = context.BuildInnerChannelFactory<TChannel>();
return factory;
}
/// <summary>
/// Copy this object
/// </summary>
/// <returns>A copy of this</returns>
public override BindingElement Clone()
{
return new ClientChannelBindingElement(this);
}
/// <summary>
/// Gets an element property
/// </summary>
/// <typeparam name="T">Channel of interest</typeparam>
/// <param name="context">Binding context</param>
/// <returns>Property value</returns>
public override T GetProperty<T>(BindingContext context)
{
return context.GetInnerProperty<T>();
}
#endregion
}
Secondly, you'll need to reference this from a binding. I've made this binding look the same as a BasicHttpBinding, so that it may be used from Silverlight:
/// <summary>
/// Custom Silverlight binding (mimics BasicHttpBinding)
/// </summary>
public class ClientChannelBinding : CustomBinding
{
#region Constructor
/// <summary>
/// Default constructor
/// </summary>
public ClientChannelBinding()
{
BasicHttpBinding basic = new BasicHttpBinding();
this.Elements.Add(new ClientChannelBindingElement());
this.Elements.Add(new TextMessageEncodingBindingElement(basic.MessageVersion, basic.TextEncoding));
this.Elements.Add(new HttpTransportBindingElement());
}
#endregion
#region Public methods
/// <summary>
/// Builds the channel factory
/// </summary>
/// <typeparam name="TChannel">Channel of interest</typeparam>
/// <param name="parameters">Binding parameters</param>
/// <returns>An encapsulated channel factory</returns>
public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingParameterCollection parameters)
{
ClientChannelFactory<TChannel> factory = new ClientChannelFactory<TChannel>();
BasicHttpBinding binding = new BasicHttpBinding();
factory.InnerChannelFactory = binding.BuildChannelFactory<TChannel>();
return factory;
}
#endregion
#region Internal methods
/// <summary>
/// Async Opened event handler
/// </summary>
/// <typeparam name="TChannel">Channel of interest</typeparam>
/// <param name="result">Async result</param>
internal void OnOpened<TChannel>(IAsyncResult result)
{
if (result.CompletedSynchronously)
{
return;
}
else
{
CompleteOpen<TChannel>(result);
}
}
/// <summary>
/// Async callback handler
/// </summary>
/// <typeparam name="TChannel">Channel of interest</typeparam>
/// <param name="result">Async result</param>
internal void CompleteOpen<TChannel>(IAsyncResult result)
{
IChannelFactory<TChannel> factory = (IChannelFactory<TChannel>)result.AsyncState;
factory.EndOpen(result);
}
#endregion
}
Next, you'll need a channel ...
/// <summary>
/// Silverlight client channel class
/// </summary>
public class ClientChannel : IRequestChannel
{
#region Fields
/// <summary>
/// Internal channel
/// </summary>
private IRequestChannel innerChannel;
#endregion
#region Constructors
/// <summary>
/// Channel Constructor
/// </summary>
/// <param name="innerChannel">Inner channel</param>
public ClientChannel(IRequestChannel innerChannel)
{
this.innerChannel = innerChannel;
}
#endregion
#region Events
/// <summary>
/// Close event handler
/// </summary>
public event EventHandler Closed;
/// <summary>
/// Closing event handler
/// </summary>
public event EventHandler Closing;
/// <summary>
/// Faulted event handler
/// </summary>
public event EventHandler Faulted;
/// <summary>
/// Open event handler
/// </summary>
public event EventHandler Opened;
/// <summary>
/// Opening event handler
/// </summary>
public event EventHandler Opening;
#endregion
#region Properties
/// <summary>
/// Gets the remote address of the channel
/// </summary>
public EndpointAddress RemoteAddress
{
get
{
return this.innerChannel.RemoteAddress;
}
}
/// <summary>
/// Gets the coomunication state object
/// </summary>
public CommunicationState State
{
get
{
return this.innerChannel.State;
}
}
/// <summary>
/// Gets the URI (from inner channel)
/// </summary>
public Uri Via
{
get
{
return this.innerChannel.Via;
}
}
#endregion
#region Methods
/// <summary>
/// Async begin request
/// </summary>
/// <param name="message">Message to send</param>
/// <param name="timeout">Timeout period</param>
/// <param name="callback">Callback method</param>
/// <param name="state">State information</param>
/// <returns>Asynchronous result</returns>
public IAsyncResult BeginRequest(Message message, TimeSpan timeout, AsyncCallback callback, object state)
{
// In Silverlight this is the place to add the header to the outgoing request
... ADD THE HEADER HERE ...
this.Decorate(message);
IAsyncResult result = this.innerChannel.BeginRequest(message, timeout, callback, state);
return result;
}
/// <summary>
/// Async begin request
/// </summary>
/// <param name="message">Message to send</param>
/// <param name="callback">Callback method</param>
/// <param name="state">State information</param>
/// <returns>Asynchronous result</returns>
public IAsyncResult BeginRequest(Message message, AsyncCallback callback, object state)
{
... ADD THE HEADER HERE ...
this.Decorate(message);
IAsyncResult result = this.innerChannel.BeginRequest(message, callback, state);
return result;
}
/// <summary>
/// End the request - parse the reply for the security header
/// </summary>
/// <param name="result">Async results object</param>
/// <returns><c>Message</c> reply</returns>
public Message EndRequest(IAsyncResult result)
{
// In Silverlight this is the place to read the header from the incoming reply
Message reply = this.innerChannel.EndRequest(result);
... READ THE HEADER HERE ...
return reply;
}
/// <summary>
/// Synchronous request
/// </summary>
/// <param name="message">Outgoing message</param>
/// <param name="timeout">Timeout period</param>
/// <returns>Modified message</returns>
public Message Request(Message message, TimeSpan timeout)
{
this.Decorate(message);
return this.innerChannel.Request(message, timeout);
}
/// <summary>
/// Synchronous request
/// </summary>
/// <param name="message">Outgoing message</param>
/// <returns>Modified message</returns>
public Message Request(Message message)
{
this.Decorate(message);
return this.innerChannel.Request(message);
}
/// <summary>
/// Gets a channel property
/// </summary>
/// <typeparam name="T">Type of property</typeparam>
/// <returns>Property result</returns>
public T GetProperty<T>() where T : class
{
return this.innerChannel.GetProperty<T>();
}
/// <summary>
/// Abort this channel
/// </summary>
public void Abort()
{
this.innerChannel.Abort();
}
/// <summary>
/// Asynchronous close
/// </summary>
/// <param name="timeout">Timeout period</param>
/// <param name="callback">Callback method</param>
/// <param name="state">State information</param>
/// <returns>Asynchronous result</returns>
public IAsyncResult BeginClose(TimeSpan timeout, AsyncCallback callback, object state)
{
return this.innerChannel.BeginClose(timeout, callback, state);
}
/// <summary>
/// Asynchronous close
/// </summary>
/// <param name="callback">Callback method</param>
/// <param name="state">State information</param>
/// <returns>Asynchronous result</returns>
public IAsyncResult BeginClose(AsyncCallback callback, object state)
{
return this.innerChannel.BeginClose(callback, state);
}
/// <summary>
/// Asynchronous open
/// </summary>
/// <param name="timeout">Timeout period</param>
/// <param name="callback">Callback method</param>
/// <param name="state">State information</param>
/// <returns>Asynchronous result</returns>
public IAsyncResult BeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
{
return this.innerChannel.BeginOpen(timeout, callback, state);
}
/// <summary>
/// Asynchronous open
/// </summary>
/// <param name="callback">Callback method</param>
/// <param name="state">State information</param>
/// <returns>Asynchronous result</returns>
public IAsyncResult BeginOpen(AsyncCallback callback, object state)
{
return this.innerChannel.BeginOpen(callback, state);
}
/// <summary>
/// Synchronous close
/// </summary>
/// <param name="timeout">Timeout period</param>
public void Close(TimeSpan timeout)
{
this.innerChannel.Close(timeout);
}
/// <summary>
/// Synchronous close
/// </summary>
public void Close()
{
this.innerChannel.Close();
}
/// <summary>
/// Asynchronous callback - close
/// </summary>
/// <param name="result">Async result</param>
public void EndClose(IAsyncResult result)
{
this.innerChannel.EndClose(result);
}
/// <summary>
/// Asynchronous callback - open
/// </summary>
/// <param name="result">Async result</param>
public void EndOpen(IAsyncResult result)
{
this.innerChannel.EndOpen(result);
}
/// <summary>
/// Synchronous open
/// </summary>
/// <param name="timeout">Timeout period</param>
public void Open(TimeSpan timeout)
{
this.innerChannel.Open(timeout);
}
/// <summary>
/// Synchronous open
/// </summary>
public void Open()
{
this.innerChannel.Open();
}
#endregion
#region Protected Methods
/// <summary>
/// Decorate the message with the new headers
/// </summary>
/// <param name="message">Message to decorate</param>
protected virtual void Decorate(Message message)
{
message.Headers.Add(new SecurityHeader());
}
#endregion
}
[Note: the SecurityHeader class is derived from the MessageHeader class]
... and an associated channel factory:
/// <summary>
/// Overriden custom channel factory
/// </summary>
/// <typeparam name="TChannel">Type of channel to create</typeparam>
public class ClientChannelFactory<TChannel> : ChannelFactoryBase<TChannel>
{
#region Fields
/// <summary>
/// Inner channel factory
/// </summary>
private IChannelFactory<TChannel> innerChannelFactory;
#endregion
#region Constructor
/// <summary>
/// Default constructor
/// </summary>
public ClientChannelFactory()
{
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the inner channel factory
/// </summary>
public IChannelFactory<TChannel> InnerChannelFactory
{
get
{
return this.innerChannelFactory;
}
set
{
this.innerChannelFactory = value;
}
}
#endregion
#region Protected methods
/// <summary>
/// Open event handler
/// </summary>
/// <param name="timeout">Timeout period</param>
protected override void OnOpen(TimeSpan timeout)
{
this.innerChannelFactory.Open(timeout);
}
/// <summary>
/// Create channel evebt handler
/// </summary>
/// <param name="to">Destination of channel</param>
/// <param name="via">Via this URI</param>
/// <returns>Newly created channel</returns>
protected override TChannel OnCreateChannel(EndpointAddress to, Uri via)
{
TChannel innerchannel = this.innerChannelFactory.CreateChannel(to, via);
if (innerchannel is IRequestChannel)
{
ClientChannel clientChannel = new ClientChannel((IRequestChannel)innerchannel);
return (TChannel)(object)clientChannel;
}
return innerchannel;
}
/// <summary>
/// End open async callback handler
/// </summary>
/// <param name="result">Async result</param>
protected override void OnEndOpen(IAsyncResult result)
{
this.innerChannelFactory.EndOpen(result);
}
/// <summary>
/// Begin open async handler
/// </summary>
/// <param name="span">Timeout span</param>
/// <param name="callback">Callback method</param>
/// <param name="asyncState">Async state object</param>
/// <returns>Async result</returns>
protected override IAsyncResult OnBeginOpen(TimeSpan span, AsyncCallback callback, object asyncState)
{
return this.innerChannelFactory.BeginOpen(callback, asyncState);
}
#endregion
}
Finally, you'll need to create the channel from your code (the client class is autogenerated when you import the service):
ClientChannelBinding binding = new ClientChannelBinding();
EndpointAddress endpoint = new EndpointAddress("http://localhost:3532/Authentication.svc"); ////TODO get this from config
this.client = new Authentication.AuthenticationClient(binding, endpoint);
Happy coding!
Graeme