views:

82

answers:

1

I have an idea, but I need help implementing it.

WCF does not support delegates in its contracts. Instead it has a cumbersome callback contracts mechanism, and I'm looking for a way to overcome this limitation.

I thought about using a IDataContractSurrogate to replace each delegate in the contract with a token that will be serialized to the remote endpoint. There, the token will be deserialized into a generated delegate. This generated delegate will send a generic callback message which encapsulates all the arguments (that the delegate was invoked with).

The generic callback message will reach the first endpoint, and there the original delegate would be invoked with the arguments.

Here is the purposed (simplified) sequence:

  1. A calls B-proxy.Foo(callback)
  2. callback is serialized through a DelegateSurrogate.
  3. The DelegateSurrogate stores the delegate in a dedicated delegate storage and replaces it with a token
  4. The message arrives to B's endpoint
  5. the token is deserialized through a DelegateSurrogate
  6. The DelegateSurrogate constructs a generated delegate
  7. B.Foo(generatedCallback) is invoked
  8. Later, B is invoking generatedCallback(args)
  9. generatedCallback(args) calls a dedicated generic contract on A's endpoint: CallbackContract-proxy.GenericCallback(args)
  10. CallbackContract.GenericCallback(args) is invoked on A's endpoint
  11. The original callback is retrieved from the storage and is invoked: callback(args)

I have already implemented this previously using service bus (NServiceBus), but I want to adapt the idea to WCF and I'm having hard time. I know how to implement steps 3,6,9 and 11. I don't know yet how to wire everything in WCF - especially the surrogate part.

That's it - I hope my question made sense, and that the collective wisdom here will be able to help me build this up.

Here's a sample usage for my desired solution:

// client side
remoteSvc.GetEmployeeById(17, emp => 
{
    employees.Add(emp);
    logger.log("Result received");
});

// server side
public void GetEmployeeById(int id, Action<Employee> callback)
{
    var emp = getEmpFromDb(id);
    callback(emp);
}
+1  A: 

Actually, in this scenario I would look into the Expression API. Unlike a delegate, an Expression can be deconstructed at runtime. You can't serialize them by default, but a lot of work has been done in that space. It is also a bit like what a lot of LINQ providers do in the background, for example WCF Data Services.

Of course, another approach is simply to use a lambda expression as the hook for RPC, which is what I describe here. The code that implements this is freely available in the protobuf-net tree. You could customize this by using an attribute to associate your token with the method, and obtain the attribute from the MethodInfo.

IMO, the problem with delegates is that they are too tightly coupled to the implementation, so you can't have different implementations at each end (which is a common requirement).

Expressions have the advantage that lambdas still support intellisense etc, so you can do things like:

client.Invoke(svc => svc.Foo(123, "abc"));

and from that obtain Foo (the MethodInfo), 123 and "abc" separately, including captured variables, ref/out, etc. It all works.

Marc Gravell
wow, excellent answer, I'll have to look into these techs
andy
Thanks for the answer but this is not what I was looking for.First, expression trees are not an option for a lambdas with statement bodies.Second, I still don't know how to write my surrogate and wire it to WCF.I'll add a sample use-case to the main post to try and make it clearer.
Omer Mor
@Omer - in 4.0, `Expression` supports statement bodies. The C# compiler *doesn't* support it, but `Expression` *does*. It can be done.
Marc Gravell
It doesn't help if the compiler does not support it.See my sample usage, and see what I'm going after.I still think there's no need to serialize the delegate and/or the expression. All we need is an ad-hoc token, and to store the delegate in the client-side until the server-side calls the "proxy delegate" back.
Omer Mor
@Omer - the example you give works just fine in C# - the thing over the wire isn't a statement body. And if you don't want to do it that way: fine. But I know that this would work.
Marc Gravell
Yeah, the example didn't use a statement body because I wanted to be succinct in my requirements. But I changed it now.
Omer Mor