I need my WCF service to raise events to the clients. I've read that that happens through callback channel, and I've implemented it in the following manner: Service interfaces:
public interface IServiceCallback { [OperationContract(IsOneWay = true)] void OnNewAlert(Alert a); [OperationContract(IsOneWay = true)] void OnProductEdited(Product p); [OperationContract(IsOneWay = true)] void OnHighlightChanged(Dictionary<User, List<Product>> highlighted); [OperationContract(IsOneWay = true)] void OnCatalogUpdated(); event EventHandler NewAlert; event EventHandler ProductEdited; event EventHandler HighlightChanged; event EventHandler CatalogUpdated; } [ServiceContract(CallbackContract = typeof(IServiceCallback))] public interface IService : IDisposable { [OperationContract] List<Product> GetProducts(Predicate<Product> match = null, int limit = 0, string username = null); [OperationContract] Product GetProduct(Predicate<Product> match, string username = null); [OperationContract] Product GetRandomProduct(Predicate<Product> match = null, string username = null); [OperationContract] int GetFlagIndex(string flagName); [OperationContract] void SetFlag(string pid, string flagName, bool value); [OperationContract] List<Alert> GetAlerts(string username); [OperationContract] void DismissAlert(Alert alert, String username); [OperationContract] void HighlightProduct(List<string> pids, string user); [OperationContract] void EditProduct(string pid, Dictionary<string, object> fieldValues, string username = null); [OperationContract] void AttachModule(IModule m); [OperationContract] void Ping(); event EventHandler NewAlert; event EventHandler ProductEdited; event EventHandler HighlightChanged; event EventHandler CatalogUpdated; }
Service implementation:
namespace Service { [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Reentrant)] public class ServiceInstance : IService { List<IServiceCallback> callbackChannels = new List<IServiceCallback>(); //other vars public ServiceInstance() { //lots of stuff here } private User SignalUser(string username) { if (username == null) return null; IServiceCallback channel = OperationContext.Current.GetCallbackChannel<IServiceCallback>(); if (!callbackChannels.Contains(channel)) //if CallbackChannels not contain current one. { callbackChannels.Add(channel); } User user = knownUsers.Find(p => p.username == username); if (user == null) { user = new User(); user.username = username; user.highlighColor = Color.FromArgb(r.Next(0, 128), r.Next(0, 128), r.Next(0, 128)); knownUsers.Add(user); foreach (KeyValuePair<Alert, List<User>> kvp in alerts) { kvp.Value.Add(user); } } user.lastOnline = DateTime.Now; if(!onlineUsers.Contains(user)) onlineUsers.Add(user); return user; } //lots of other things here } }
Callback implementation on Client:
class ServiceEventHandler : IServiceCallback { public event EventHandler NewAlert; public event EventHandler ProductEdited; public event EventHandler HighlightChanged; public event EventHandler CatalogUpdated; public void OnCatalogUpdated() { CatalogUpdated?.BeginInvoke(null, null, null, null); } public void OnHighlightChanged(Dictionary<User, List<Product>> highlighted) { HighlightChanged?.BeginInvoke(highlighted, EventArgs.Empty, null, null); } public void OnNewAlert(Alert a) { NewAlert?.BeginInvoke(a, EventArgs.Empty, null, null); } public void OnProductEdited(Product p) { ProductEdited?.BeginInvoke(p, EventArgs.Empty, null, null); } }
But here is my problem: On the client side, I'm supposed to pass it to the service like this:
EventHandler eventHandler = new EventHandler(); MyServiceClient client = new MyServiceClient(new InstanceContext(eventHandler));
according to this StackOverflow answer: http://stackoverflow.com/a/1143777/2018696
But I don't connect to my service like this, because my client does not know about the implementation of the service, it only knows the two interfaces! So I connect like this:
public static IService GetService(string serviceAddress) { Uri service_uri = new Uri(serviceAddress); var endpoint = new EndpointAddress(service_uri, new[] { AddressHeader.CreateAddressHeader(settings["username"], "", "") }); IService service = ChannelFactory<IService>.CreateChannel(new BasicHttpBinding(), endpoint); return service; }
So how do I get the callbacks to work? Thanks!
UPDATE:
Ok, so as proposed by a comment, I replaced ChannelFactory with DuplexChannelFactory and BasicHTTPBinding with WsDualHTTPBinding, and I don't get response from the server. I do get response with BasicHTTPBinding if I scratch the callback handler. So essentially:
[ServiceContract] BasicHttpBinding(); ChannelFactory<IService>.CreateChannel(binding, endpoint);
^ this works
[ServiceContract(CallbackContract = typeof(IServiceCallback))] WSDualHttpBinding(WSDualHttpSecurityMode.None); DuplexChannelFactory<IService>.CreateChannel(new InstanceContext(handler), binding, endpoint);
^ this doesn't.
It works on localhost, but not on LAN or Internet. Firewalls are off on both server and client. I'm getting 60 second timeout when I try to contact the server.
1 Answers
Answers 1
I have discovered that my connection issues are due to no port forwarding being set on the path to the client. Since I can't ensure proper port access to clients, I have reverted back to non-callback model, and will be using regular request from clients to received accumulated event data from the service. Probably not efficient, but a foolproof method that seems to be working so far. Thank you all for your attention.
0 comments:
Post a Comment