Silverlight 4 added the ability to consume Net.TCP WCF services, which is the way the IdeaBlade Remoteable TracePublisher is exposed. So I went about the task to see if I could consume it in a Silverlight 4 app.
I made the publisher remoteable in Global.asax:
// ------------------------------------------------------------
// Silverlight Client Notes:
// ------------------------------------------------------------
// Only ports 4502-4534 are allowed for Silverlight access. Also
// a clientaccesspolicy.xml which allows the connection port
// must exist in the root domain on port 80
// (eg. http://localhost/clientaccesspolicy.xml)
//
// ------------------------------------------------------------
// Sample clientacesspolicy.xml file
// ------------------------------------------------------------
// <?xml version="1.0" encodinga="utf-8"?>
// <access-policy>
// <cross-domain-access>
// <policy>
// <allow-from>
// <domain uri="*" />
// </allow-from>
// <grant-to>
// <socket-resource port="4502-4534" protocol="tcp" />
// </grant-to>
// </policy>
// </cross-domain-access>
// </access-policy>
//
// The port restriction and clientaccesspolicy.xml required for Silverlight is not
// needed for Silverlight trusted (Out of Browser) applications.
IdeaBlade.Core.TracePublisher.LocalInstance.MakeRemotable(4502, "TracePublisher");
The next hurdle was that some of the Silverlight types that need to be passed were not decorated with DataContracts for their Silverlight versions. So I created proxy classes that defiend the contract that are shared between the client and server:
[ServiceContract(Name = "ITracePublisher", CallbackContract = typeof(ITraceSubscriberCallbackEx))]
public interface ITracePublisherEx
{
// Methods
[OperationContract(AsyncPattern = true)]
IAsyncResult BeginPing(AsyncCallback cb, object state);
bool EndPing(IAsyncResult r);
[OperationContract(AsyncPattern = true)]
IAsyncResult BeginSubscribe(Guid pKey, ITraceSubscriberCallback pSubscriber, AsyncCallback cb, object state);
void EndSubscribe(IAsyncResult r);
[OperationContract(AsyncPattern = true)]
IAsyncResult BeginUnsubscribe(Guid pKey, AsyncCallback cb, object state);
void EndUnsubscribe(IAsyncResult r);
}
[ServiceContract(Name = "ITraceSubscriberCallback")]
public interface ITraceSubscriberCallbackEx
{
[OperationContract(IsOneWay = true)]
void OnPublish(TraceMessage pTraceMessage);
}
[KnownType(typeof(CustomTraceSubscriber))]
[DataContract]
public class CustomTraceSubscriber : ITraceSubscriberCallback, IKnownType, ITraceSubscriberCallbackEx
{
public Guid Id { get; private set; }
private ITracePublisherEx _serverTracePublisher;
public CustomTraceSubscriber()
{
Id = Guid.NewGuid();
}
public CustomTraceSubscriber Connect(string hostname = "localhost", string port = "4502", string serviceName = "TracePublisher")
{
if (_serverTracePublisher == null)
{
var customBinding = new CustomBinding(new TcpTransportBindingElement());
var endpointAddress = new EndpointAddress("net.tcp://" + hostname + ":" + port + "/" + serviceName);
var callbackInstance = new InstanceContext(this);
var factory = new DuplexChannelFactory<ITracePublisherEx>(callbackInstance, customBinding, endpointAddress);
_serverTracePublisher = factory.CreateChannel();
}
return this;
}
public void SubscribeAsync(Action<IAsyncResult> action)
{
_serverTracePublisher.BeginSubscribe(Id, this, args =>
{
_serverTracePublisher.EndSubscribe(args);
if (action != null)
{
action(args);
}
}, null);
}
public void UnsubscribeAsync(Action<IAsyncResult> action)
{
_serverTracePublisher.BeginUnsubscribe(Id, args =>
{
_serverTracePublisher.EndUnsubscribe(args);
if (action != null)
{
action(args);
}
}, null);
}
public void PingAsync(Action<bool> action)
{
_serverTracePublisher.BeginPing(
args =>
{
var result = _serverTracePublisher.EndPing(args);
if (action != null)
{
action(result);
}
},
null);
}
[OperationContract(IsOneWay = true)]
public void OnPublish(TraceMessage pTraceMessage)
{
System.Diagnostics.Debug.WriteLine(pTraceMessage.Message);
}
}
Then on the client:
var traceSubscriber = new CustomTraceSubscriber().Connect();
// PingAsync Works fine
traceSubscriber.PingAsync(
result => Execute.OnUIThread(() => MessageBox.Show(result.ToString())));
// SubscribeAsync Throws following exception:
-------------------------------------------
The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter http://tempuri.org/:pKey. The InnerException message was 'XML 'Element' 'http://tempuri.org/:pKey' does not contain expected attribute 'http://schemas.microsoft.com/2003/10/Serialization/:Type'. The deserializer has no knowledge of which type to deserialize. Check that the type being serialized has the same contract as the type being deserialized.'. Please see InnerException for more details.
-------------------------------------------
traceSubscriber.SubscribeAsync(args=>
{
var result = args;
Execute.OnUIThread(() => MessageBox.Show(result.IsCompleted.ToString()));
});
I think the reason the call to Subscribe fails is becasue on the server the Subscribe operation is decorated with a NetDataContractFormat attribute which applies NetDataContractSerializerOperationBehavior which applies as NetDataContractSerializer.
NetDataContractSerializer is not supported in Silverlight (obviously DataContractSerializer is supported). Is there a reason why NetDataContractSerializer is used?
From what I read the use of it is discouraged (http://www.pluralsight-training.net/community/blogs/aaron/archive/2006/04/21/22284.aspx) if possible, which is why you have to create your own custom attribute to even apply it.
Would it be possible in the future to modify your contracts to make them Silverlight compatible? Thanks for any feedback you can provide.