Friday, 29 August 2008
Method decoration using Lambda expressions
To illustrate, in my particular example I wanted a 'RunAs' functionality for any existing method; so the workflow goes as follows:
1 - Cache any existing user details
2 - Logon a new user
3 - Execute some code
4 - Return the previous context
I didn't want other developers writing their own code to do this, because the potential for mistakes and misunderstanding is very high.
In the above example steps 1, 2 and 4 aren't important, I could have equally written
1 - Do something
2 - Execute some code
3 - Do something else
I found a neat way of doing this, involving both Lambda expressions and Extension methods (although I'm sure there's plenty of other ways to achieve the same thing).
Firstly, we'll create an extension method (mine is of System.Object so that ALL objects can do this, but you may want to restrict yours).
/// <summary>
/// Enables ANY Function to be executed as another user (prompts logon first)
/// </summary>
/// <typeparam name="R">Return type</typeparam>
/// <param name="source">Type to extend</param>
/// <param name="f">Function to be executed</param>
/// <returns>The response from the wrapped method</returns>
/// <remarks>Call this method like so:
/// <c>var result = this.RunAs(() => this.DoSomething());</c>
/// </remarks>
public static R RunAs<R>(this object source, Func<R> f)
{
DO SOMETHING PRIOR...
R result = f();
... DO SOMETHING POST
return result;
}
To call this method, we may write something like:
var result = this.RunAs(() => this.DoSomething());
Notice that the lambda expression is clever enough to figure out the return type for us. Pretty cool. Another remarkable fact is that the signature of the method you're calling doesn't even have to match the Func<R> delegate type in the extension method...
var result this.RunAs(() => this.DoSomethingElse( 1, 2, 3));
... would be equally valid! This is because the lambda expression actually evaluates to a Func<R> delegate, i.e. a function that takes no arguments (hence the () construct) and that returns something of type R (the return type of 'DoSomething').
An overload of the RunAs method is needed to execute an Action delegate (i.e. one with no return type)
public static void RunAs(this object source, Action a)
{
DO SOMETHING PRIOR...
a();
... DO SOMETHING POST
}
Does anybody else think this is totally cool?
Lambda expressions really are a very powerful tool for your programming locker.
Cheers!
Graeme
Monday, 18 August 2008
Custom WCF Channel in Silverlight
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