title: WCF Duplex: Send callback to a specific subscribed ASP.NET Webforms client
body: <p>I'm working on an ASP.NET 4.0 Webforms client application that connects to a WCF 4.0 Duplex service to realize some kind of flat file processing. When an user enters to a page on the Page_Load event I subscribe the client to the duplex service, this is because I need to notify all clients under some circumstances:</p>
  
  <p>A) The client that started the process must be notified when the process starts.</p>
  
  <p>B) The client that started the process must be notified when a file is processed.</p>
  
  <p>C) The client that started the process must be notified when the whole process finished.</p>
  
  <p>D) If a new client (subscriber) enters when the process already started, must receive a specific notification.</p>
  
  <p>E) If there are more than one client (subscriber) active when one of them starts the process, the others must receive a specific notification.</p>
  
  <p>I already have this logic written but I'm having a lot of issues trying to accomplish the specific subscriber notification, it seems like to WCF all clients/instances of the web application are recognized as the same, I'm receiving all notifications in the client that started the process, if I open other browsers and start new sessions (on ASP.NET) I receive the same notifications, nothing specific.</p>
  
  <p>Here you can see a reduced version of my code</p>
  
  <p><strong>WCF Service Interfaces</strong></p>
  
  <pre><code>using System.ServiceModel;
  
  namespace WcfService
  {
      [ServiceContract(CallbackContract = typeof(IService1DuplexCallback))]
      public interface IService1
      {
          [OperationContract(IsOneWay = true)]
          void Subscribe(string idSesion);
  
          [OperationContract(IsOneWay = true)]
          void ProcessFiles(string idSesion);
      }
  
      public interface IService1DuplexCallback
      {
          [OperationContract(IsOneWay = true)]
          void NotifyProcessWorking();
  
          [OperationContract(IsOneWay = true)]
          void NotifyProcessStarted();
  
          [OperationContract(IsOneWay = true)]
          void NotifyFileProcessed(int id);
  
          [OperationContract(IsOneWay = true)]
          void NotifyProcessFinished();
      }
  }
  </code></pre>
  
  <p><strong>WCF Service Implementation</strong></p>
  
  <pre><code>using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.ServiceModel;
  using System.Threading;
  using System.Threading.Tasks;
  
  namespace WcfService
  {
      [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
      public class Service1 : IService1
      {
          private static List&lt;KeyValuePair&lt;string, IService1DuplexCallback&gt;&gt; _clients = new List&lt;KeyValuePair&lt;string, IService1DuplexCallback&gt;&gt;();
          private static bool _isProcessStarted;
          private static string _sessionStarted = string.Empty;
  
          public void Subscribe(string idSesion)
          {
              lock (_clients)
              {
                  if (!_clients.Any(x =&gt; string.Equals(x.Key, idSesion, StringComparison.InvariantCultureIgnoreCase)))
                  {
                      var callback = OperationContext.Current.GetCallbackChannel&lt;IService1DuplexCallback&gt;();
  
                      if (callback != null)
                      {
                          var currentSubscriber = new KeyValuePair&lt;string, IService1DuplexCallback&gt;(idSesion, callback);
                          _clients.Add(currentSubscriber);
                      }
                  }
              }
  
              if (_isProcessStarted)
              {
                  NotifyProcessWorking(idSesion);
              }
          }
  
          public void ProcessFiles(string idSesion)
          {
              _isProcessStarted = true;
              _sessionStarted = idSesion;
  
              try
              {
                  var mockFileCount = 23;
                  var r = new Random();
  
                  NotifyStarted();
                  NotifyProcessWorking();
  
                  Parallel.For(0, mockFileCount, (i) =&gt;
                  {
                      //Do a lot of specific validations... (time betweeen 5 secs and 2 minutes per file)
                      var time = r.Next(5000, 120000);
  
                      Thread.Sleep(time);
  
                      NotifyFileProcessed(i);
                  });
  
                  NotifyProcessFinished();
              }
              catch (Exception ex)
              {
                  throw;
              }
  
              _isProcessStarted = false;
          }
  
          private static void NotifyStarted()
          {
              var c = _clients.FirstOrDefault(x =&gt; string.Equals(x.Key, _sessionStarted, StringComparison.InvariantCultureIgnoreCase));
  
              try
              {
                  c.Value.NotifyProcessStarted();
              }
              catch (Exception ex)
              {
                  lock (_clients)
                  {
                      _clients.Remove(c);
                  }
              }
          }
  
          private static void NotifyFileProcessed(int idFile)
          {
              var c = _clients.FirstOrDefault(x =&gt; string.Equals(x.Key, _sessionStarted, StringComparison.InvariantCultureIgnoreCase));
  
              try
              {
                  c.Value.NotifyFileProcessed(idFile);
              }
              catch (Exception ex)
              {
                  lock (_clients)
                  {
                      _clients.Remove(c);
                  }
              }
          }
  
          private static void NotifyProcessFinished()
          {
              foreach (var c in _clients)
              {
                  try
                  {
                      c.Value.NotifyProcessFinished();
                  }
                  catch (Exception ex)
                  {
                      lock (_clients)
                      {
                          _clients.Remove(c);
                      }
                  }
              }
          }
  
          private static void NotifyProcessWorking(string idSesion = "")
          {
              if (string.IsNullOrEmpty(idSesion))
              {
                  foreach (var c in _clients)
                  {
                      try
                      {
                          c.Value.NotifyProcessWorking();
                      }
                      catch (Exception ex)
                      {
                          lock (_clients)
                          {
                              _clients.Remove(c);
                          }
                      }
                  }
              }
              else
              {
                  var c = _clients.FirstOrDefault(x =&gt; string.Equals(x.Key, idSesion, StringComparison.InvariantCultureIgnoreCase));
  
                  try
                  {
                      c.Value.NotifyProcessWorking();
                  }
                  catch (Exception)
                  {
                      lock (_clients)
                      {
                          _clients.Remove(c);
                      }
                  }
              }
          }
      }
  }
  </code></pre>
  
  <p><strong>WCF Service Web.config</strong></p>
  
  <pre><code>&lt;?xml version="1.0"?&gt;
  &lt;configuration&gt;
    &lt;appSettings/&gt;
    &lt;system.web&gt;
      &lt;compilation debug="true" targetFramework="4.0"/&gt;
      &lt;httpRuntime/&gt;
    &lt;/system.web&gt;
    &lt;system.serviceModel&gt;
      &lt;services&gt;
        &lt;service name="WcfService.Service1"&gt;
          &lt;endpoint address="" binding="wsDualHttpBinding" bindingConfiguration="FileProcessorDuplexBinding" 
                    name="FileProcessorDuplexEndPoint" contract="WcfService.IService1"/&gt;
        &lt;/service&gt;
      &lt;/services&gt;
      &lt;bindings&gt;
        &lt;wsDualHttpBinding&gt;
          &lt;binding name="FileProcessorDuplexBinding" closeTimeout="00:30:00" openTimeout="00:30:00" 
                   sendTimeout="00:30:00" receiveTimeout="00:30:00" maxBufferPoolSize="2147483647"
                   maxReceivedMessageSize="2147483647"&gt;
            &lt;reliableSession inactivityTimeout="00:30:00"/&gt;
            &lt;readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647" maxArrayLength="2147483647" 
                          maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647"/&gt;
            &lt;security mode="None"/&gt;
          &lt;/binding&gt;
        &lt;/wsDualHttpBinding&gt;
      &lt;/bindings&gt;
      &lt;behaviors&gt;
        &lt;serviceBehaviors&gt;
          &lt;behavior&gt;
            &lt;serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/&gt;
            &lt;serviceDebug includeExceptionDetailInFaults="false"/&gt;
          &lt;/behavior&gt;
        &lt;/serviceBehaviors&gt;
      &lt;/behaviors&gt;
      &lt;serviceHostingEnvironment  multipleSiteBindingsEnabled="true"/&gt;
    &lt;/system.serviceModel&gt;
    &lt;system.webServer&gt;
      &lt;modules runAllManagedModulesForAllRequests="true"/&gt;
      &lt;directoryBrowse enabled="true"/&gt;
    &lt;/system.webServer&gt;
  &lt;/configuration&gt;
  </code></pre>
  
  <p><strong>ASP.NET WebForm client UI</strong></p>
  
  <pre><code>&lt;%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication.Default" %&gt;
  
  &lt;!DOCTYPE html&gt;
  
  &lt;html xmlns="http://www.w3.org/1999/xhtml"&gt;
  &lt;head runat="server"&gt;
      &lt;script src="Scripts/jquery-2.1.4.min.js"&gt;&lt;/script&gt;
      &lt;title&gt;&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
      &lt;form id="form1" runat="server"&gt;
          &lt;div&gt;
              &lt;asp:Button ID="btnStart" runat="server" Text="Start Process" OnClientClick="Start();"/&gt;
              &lt;br/&gt;
              &lt;br/&gt;
              &lt;asp:Label ID="lblStatus" runat="server" Text="[Process Status]"&gt;&lt;/asp:Label&gt;
          &lt;/div&gt;
          &lt;script&gt;
  
              function Start() {
  
                  var loc = window.location.href;
                  var dataValue = "{}";
  
                  $.ajax({
                      type: "POST",
                      url: loc + "/StartProcess",
                      contentType: 'application/json',
                      data: dataValue,
                      dataType: 'json',
                      error: function(XMLHttpRequest, textStatus, errorThrown) {
                          alert("Request: " + XMLHttpRequest.toString() + "\n\nStatus: " + textStatus + "\n\nError: " + errorThrown);
                      },
                      success: function(result) {
                      }
                  });
  
              }
  
              setInterval(function () {
  
                  var loc = window.location.href;
                  var dataValue = "{ id: '1' }";
  
                  $.ajax({
                      type: "POST",
                      url: loc + "/CheckMessage",
                      contentType: 'application/json',
                      data: dataValue,
                      dataType: 'json',
                      error: function(XMLHttpRequest, textStatus, errorThrown) {
                          alert("Request: " + XMLHttpRequest.toString() + "\n\nStatus: " + textStatus + "\n\nError: " + errorThrown);
                      },
                      success: function(result) {
                          processMessage(result.d);
                      }
                  });
  
  
              }, 1000);
  
              function processMessage(msg) {
  
                  if (msg) {
                      switch (msg) {
                      case "working":
                          alert("Process currently working");
                          $('[id$=lblStatus]').attr('disabled', true);
                          break;
  
                      case "started":
                          $('#&lt;%=lblStatus.ClientID%&gt;').html("Process started");
                          break;
  
                      case "finished":
                          $('#&lt;%=lblStatus.ClientID%&gt;').html("Process finished");
                          break;
  
                      default:
                          var data = msg.split(":");
                          $('#&lt;%=lblStatus.ClientID%&gt;').html("File Processed: " + data[1]);
                          break;
                      }
                  }
              }
          &lt;/script&gt;
      &lt;/form&gt;
  &lt;/body&gt;
  &lt;/html&gt;
  </code></pre>
  
  <p><strong>ASP.NET WebForm client Code-Behind</strong></p>
  
  <pre><code>using System;
  using System.Collections.Concurrent;
  using System.ServiceModel;
  using System.Web.Services;
  using System.Web.UI;
  using WebApplication.ServiceReference1;
  
  namespace WebApplication
  {
      [CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, UseSynchronizationContext = false)]
      public partial class Default : Page, IService1Callback
      {
          private static ConcurrentQueue&lt;string&gt; _serviceReceivedMessages = new ConcurrentQueue&lt;string&gt;();
          private static string _sessionId = string.Empty;
  
          protected void Page_Load(object sender, EventArgs e)
          {
              if (!IsPostBack)
              {
                  _sessionId = Session.SessionID;
  
                  var proxyDuplex = new Service1Client(new InstanceContext(new Default()));
                  proxyDuplex.Subscribe(_sessionId);
              }
          }
  
          [WebMethod]
          public static void StartProcess()
          {
              var proxyDuplex = new Service1Client(new InstanceContext(new Default()));
              proxyDuplex.ProcessFiles(_sessionId);
          }
  
          [WebMethod]
          public static string CheckMessage(string id)
          {
              var message = string.Empty;
  
              _serviceReceivedMessages.TryDequeue(out message);
  
              return message ?? (message = string.Empty);
          }
  
          public void NotifyProcessWorking()
          {
              _serviceReceivedMessages.Enqueue("working");
          }
  
          public void NotifyProcessStarted()
          {
              _serviceReceivedMessages.Enqueue("started");
          }
  
          public void NotifyFileProcessed(int id)
          {
              _serviceReceivedMessages.Enqueue("processed:"+id);
          }
  
          public void NotifyProcessFinished()
          {
              _serviceReceivedMessages.Enqueue("finished");
          }
      }
  }
  </code></pre>
  
  <p><strong>ASP.NET WebForm client Web.config</strong></p>
  
  <pre><code>&lt;?xml version="1.0"?&gt;
  &lt;configuration&gt;
    &lt;system.web&gt;
      &lt;compilation debug="true" targetFramework="4.0"/&gt;
      &lt;httpRuntime/&gt;
    &lt;/system.web&gt;
    &lt;system.serviceModel&gt;
      &lt;bindings&gt;
        &lt;wsDualHttpBinding&gt;
          &lt;binding name="FileProcessorDuplexBinding" 
                   closeTimeout="00:30:00" openTimeout="00:30:00" receiveTimeout="00:30:00"
                   sendTimeout="00:30:00" maxBufferPoolSize="2147483647" maxReceivedMessageSize="2147483647"
                   clientBaseAddress="http://localhost:62778/TempUri"&gt;
            &lt;reliableSession inactivityTimeout="00:30:00" /&gt;
            &lt;readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647"
              maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647" /&gt;
            &lt;security mode="None" /&gt;
          &lt;/binding&gt;
        &lt;/wsDualHttpBinding&gt;
      &lt;/bindings&gt;
      &lt;client&gt;
        &lt;endpoint address="http://localhost:62778/Service1.svc" binding="wsDualHttpBinding"
          bindingConfiguration="FileProcessorDuplexBinding" contract="ServiceReference1.IService1"
          name="FileProcessorDuplexEndPoint" /&gt;
      &lt;/client&gt;
    &lt;/system.serviceModel&gt;
  &lt;/configuration&gt;
  </code></pre>
  
  <p>Here you can <a href="https://mega.nz/#!YtBSVCDD!8ooCsiLrwXR9VuQ1z6E2J1fIbkWl7p4qZjvdp3Bm4-E" rel="nofollow noreferrer">download</a> a Visual Studio 2015 solution with the whole code.</p>
  
  <p>I would like to know what is wrong with my code, I think this behavior is possible but can not understand why WCF is not notifying the specific clients.</p>
  
  <p>Thanks </p>
  
  <p><strong>Update 1</strong></p>
  
  <p>I made all changes @JuanK suggested me (at the time) with no luck, the behavior continues the same, I added a new console project to test the same service and in that project works fine</p>
  
  <p><a href="https://i.stack.imgur.com/n53ub.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/n53ub.png" alt="enter image description here"></a></p>
  
  <p>But in the ASP.NET project the error continues, the 2nd client gets all notifications</p>
  
  <p><a href="https://i.stack.imgur.com/RXqvW.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/RXqvW.png" alt="enter image description here"></a></p>
  
  <p>Here you can <a href="https://mega.nz/#!d5JzRKwZ!O6MJ8dqX9hXg_s_7AF2QmQXfmmxijFcnB_Dftx1rEMs" rel="nofollow noreferrer">download</a> the VS Solution updated (at this time)</p>