简体   繁体   English

WCF Duplex:将回调发送到特定订阅的ASP.NET Webforms客户端

[英]WCF Duplex: Send callback to a specific subscribed ASP.NET Webforms client

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. 我正在开发一个连接到WCF 4.0 Duplex服务的ASP.NET 4.0 Webforms客户端应用程序,以实现某种平面文件处理。 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: 当用户进入Page_Load事件的页面时,我将客户端订阅到双工服务,这是因为我需要在某些情况下通知所有客户端:

A) The client that started the process must be notified when the process starts. A)进程启动时必须通知启动进程的客户端。

B) The client that started the process must be notified when a file is processed. B)处理文件时必须通知启动该进程的客户端。

C) The client that started the process must be notified when the whole process finished. C)整个过程完成后,必须通知启动过程的客户端。

D) If a new client (subscriber) enters when the process already started, must receive a specific notification. D)如果新客户(订户)在流程已经启动时进入,则必须收到特定通知。

E) If there are more than one client (subscriber) active when one of them starts the process, the others must receive a specific notification. E)如果有多个客户端(订户)处于活动状态,其中一个客户端启动该流程,则其他客户端必须收到特定通知。

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. 我已经编写了这个逻辑,但是我在尝试完成特定的订阅者通知时遇到了很多问题,似乎WCF所有客户端/ Web应用程序的实例都被识别为相同,我收到的所有通知都在启动该过程的客户端,如果我打开其他浏览器并启动新会话(在ASP.NET上),我收到相同的通知,没有具体的。

Here you can see a reduced version of my code 在这里,您可以看到我的代码的简化版本

WCF Service Interfaces WCF服务接口

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();
    }
}

WCF Service Implementation WCF服务实现

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<KeyValuePair<string, IService1DuplexCallback>> _clients = new List<KeyValuePair<string, IService1DuplexCallback>>();
        private static bool _isProcessStarted;
        private static string _sessionStarted = string.Empty;

        public void Subscribe(string idSesion)
        {
            lock (_clients)
            {
                if (!_clients.Any(x => string.Equals(x.Key, idSesion, StringComparison.InvariantCultureIgnoreCase)))
                {
                    var callback = OperationContext.Current.GetCallbackChannel<IService1DuplexCallback>();

                    if (callback != null)
                    {
                        var currentSubscriber = new KeyValuePair<string, IService1DuplexCallback>(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) =>
                {
                    //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 => 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 => 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 => string.Equals(x.Key, idSesion, StringComparison.InvariantCultureIgnoreCase));

                try
                {
                    c.Value.NotifyProcessWorking();
                }
                catch (Exception)
                {
                    lock (_clients)
                    {
                        _clients.Remove(c);
                    }
                }
            }
        }
    }
}

WCF Service Web.config WCF服务Web.config

<?xml version="1.0"?>
<configuration>
  <appSettings/>
  <system.web>
    <compilation debug="true" targetFramework="4.0"/>
    <httpRuntime/>
  </system.web>
  <system.serviceModel>
    <services>
      <service name="WcfService.Service1">
        <endpoint address="" binding="wsDualHttpBinding" bindingConfiguration="FileProcessorDuplexBinding" 
                  name="FileProcessorDuplexEndPoint" contract="WcfService.IService1"/>
      </service>
    </services>
    <bindings>
      <wsDualHttpBinding>
        <binding name="FileProcessorDuplexBinding" closeTimeout="00:30:00" openTimeout="00:30:00" 
                 sendTimeout="00:30:00" receiveTimeout="00:30:00" maxBufferPoolSize="2147483647"
                 maxReceivedMessageSize="2147483647">
          <reliableSession inactivityTimeout="00:30:00"/>
          <readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647" maxArrayLength="2147483647" 
                        maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647"/>
          <security mode="None"/>
        </binding>
      </wsDualHttpBinding>
    </bindings>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment  multipleSiteBindingsEnabled="true"/>
  </system.serviceModel>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
    <directoryBrowse enabled="true"/>
  </system.webServer>
</configuration>

ASP.NET WebForm client UI ASP.NET WebForm客户端UI

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication.Default" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <script src="Scripts/jquery-2.1.4.min.js"></script>
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <asp:Button ID="btnStart" runat="server" Text="Start Process" OnClientClick="Start();"/>
            <br/>
            <br/>
            <asp:Label ID="lblStatus" runat="server" Text="[Process Status]"></asp:Label>
        </div>
        <script>

            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":
                        $('#<%=lblStatus.ClientID%>').html("Process started");
                        break;

                    case "finished":
                        $('#<%=lblStatus.ClientID%>').html("Process finished");
                        break;

                    default:
                        var data = msg.split(":");
                        $('#<%=lblStatus.ClientID%>').html("File Processed: " + data[1]);
                        break;
                    }
                }
            }
        </script>
    </form>
</body>
</html>

ASP.NET WebForm client Code-Behind ASP.NET WebForm客户端Code-Behind

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<string> _serviceReceivedMessages = new ConcurrentQueue<string>();
        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");
        }
    }
}

ASP.NET WebForm client Web.config ASP.NET WebForm客户端Web.config

<?xml version="1.0"?>
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.0"/>
    <httpRuntime/>
  </system.web>
  <system.serviceModel>
    <bindings>
      <wsDualHttpBinding>
        <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">
          <reliableSession inactivityTimeout="00:30:00" />
          <readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647"
            maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647" />
          <security mode="None" />
        </binding>
      </wsDualHttpBinding>
    </bindings>
    <client>
      <endpoint address="http://localhost:62778/Service1.svc" binding="wsDualHttpBinding"
        bindingConfiguration="FileProcessorDuplexBinding" contract="ServiceReference1.IService1"
        name="FileProcessorDuplexEndPoint" />
    </client>
  </system.serviceModel>
</configuration>

Here you can download a Visual Studio 2015 solution with the whole code. 在这里,您可以下载包含完整代码的Visual Studio 2015解决方案。

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. 我想知道我的代码有什么问题,我认为这种行为是可能的,但无法理解为什么WCF没有通知特定的客户端。

Thanks 谢谢

Update 1 更新1

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 我做了所有更改@JuanK建议我(当时)没有运气,行为继续相同,我添加了一个新的控制台项目来测试相同的服务,并在该项目工作正常

在此输入图像描述

But in the ASP.NET project the error continues, the 2nd client gets all notifications 但是在ASP.NET项目中错误仍然存​​在,第二个客户端获取所有通知

在此输入图像描述

Here you can download the VS Solution updated (at this time) 在这里您可以下载更新的VS解决方案(此时)

WCF isn't notifying specific clients because you have coded that service must send notifications to everyone. WCF未通知特定客户端,因为您已编码该服务必须向所有人发送通知。

private static void NotifyProcessFinished()
{
    //FOR EACH CLIENT
    foreach (var c in _clients)
    {
        try
        {
            c.Value.NotifyProcessFinished();
        }
        catch (Exception ex)
        {
            lock (_clients)
            {
                _clients.Remove(c);
            }
        }
    }
}

On other hand there are some ServiceBehavior and static fields issues I have fixed here: 另一方面,我在这里修复了一些ServiceBehavior静态字段问题:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class Service1 : IService1
{
    private static List<KeyValuePair<string, IService1DuplexCallback>> _clients = new List<KeyValuePair<string, IService1DuplexCallback>>();
    private static bool _isProcessStarted;
    private string _sessionStarted = string.Empty;

    public void Subscribe(string idSesion)
    {
        lock (_clients)
        {
            if (!_clients.Any(x => string.Equals(x.Key, idSesion, StringComparison.InvariantCultureIgnoreCase)))
            {
                var callback = OperationContext.Current.GetCallbackChannel<IService1DuplexCallback>();

                if (callback != null)
                {
                    var currentSubscriber = new KeyValuePair<string, IService1DuplexCallback>(idSesion, callback);
                    _clients.Add(currentSubscriber);
                }
            }
        }

        if (_isProcessStarted)
        {
            NotifyProcessWorking(idSesion);
        }
    }

    public void ProcessFiles(string idSesion)
    {
        _isProcessStarted = true;
        _sessionStarted = idSesion;

        try
        {
            var mockFileCount = 2;
            var r = new Random();

            NotifyStarted();
            NotifyProcessWorking();

            Parallel.For(0, mockFileCount, (i) =>
            {
                //Do a lot of specific validations... (time betweeen 5 secs and 2 minutes per file)
                var time = 5000;//r.Next(5000, 120000);

                Thread.Sleep(time);

                NotifyFileProcessed(i);
            });

            NotifyProcessFinished();
        }
        catch (Exception ex)
        {
            throw;
        }

        _isProcessStarted = false;
    }

    private void NotifyStarted()
    {
        var c = _clients.FirstOrDefault(x => string.Equals(x.Key, _sessionStarted, StringComparison.InvariantCultureIgnoreCase));

        try
        {
            c.Value.NotifyProcessStarted();
        }
        catch (Exception ex)
        {
            lock (_clients)
            {
                _clients.Remove(c);
            }
        }
    }

    private void NotifyFileProcessed(int idFile)
    {
        var c = _clients.FirstOrDefault(
            x => string.Equals(x.Key, _sessionStarted,
            StringComparison.InvariantCultureIgnoreCase)
            );

        try
        {
            c.Value.NotifyFileProcessed(idFile);
        }
        catch (Exception ex)
        {
            lock (_clients)
            {
                _clients.Remove(c);
            }
        }
    }

    private void NotifyProcessFinished()
    {
        //STILL SAME YOU HAVE IT. JUST IN CASE
        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 => string.Equals(x.Key, idSesion, StringComparison.InvariantCultureIgnoreCase));

            try
            {
                c.Value.NotifyProcessWorking();
            }
            catch (Exception)
            {
                lock (_clients)
                {
                    _clients.Remove(c);
                }
            }
        }
    }
}

If you have a single instance behavior and an static field: the static field is shared between all connections, so for example _sessionStarted is always getting the status for last connections. 如果您有单个实例行为和静态字段:静态字段在所有连接之间共享,因此例如 _sessionStarted始终获取上次连接的状态。

So I have changed the servicebehavior to PerSession to allow non-static fields to be preserved by each specific session/connection while static fields like List<KeyValuePair<string, IService1DuplexCallback>> _clients still being shared among them. 因此,我已将servicebehavior更改为PerSession以允许每个特定会话/连接保留非静态字段,而List<KeyValuePair<string, IService1DuplexCallback>> _clients等静态字段仍在其中共享。 This also implied that some methods are now non-static methods . 这也暗示了一些方法现在是非静态方法

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 WCF双工:FatalException在ASP.NET 4.0 Webforms客户端中接收回调 - WCF Duplex: FatalException receiving callback in ASP.NET 4.0 Webforms client 使用双工WCF服务调用结果更新ASP.NET页 - Update an ASP.NET page with Duplex WCF Service Call Result 来自ASP.NET客户端的WCF回调使ASP.net辅助进程崩溃 - WCF callback from asp.net client crashes ASP.net worker process 从异步WCF双工调用更新asp.net页-对象范围-ASP.NET - Updating the asp.net page from the asynchronous WCF duplex call - object scope - ASP.NET javascript函数“ $ find”是否特定于asp.net网络表单中的telerik客户端对象? - Is the javascript function “$find” specific for telerik client objects in asp.net webforms? WCF双工服务是否可以在该服务尝试调用之前确定客户端的回调通道是否已实现特定方法? - Can a WCF duplex service determine if the callback channel of a client has implemented a specific method before the service tries to call it? 在ASP.NET WebForms C#中使用WCF - Use of WCF in ASP.NET WebForms C# 在ASP.NET WebForms中将服务器变量传递给客户端的正确方法 - Proper way to pass server variables to client in ASP.NET WebForms 从 ASP.NET 核心 Web Z72664DC0959F3B0C04891F8F7C 消费 WCF 双工服务 - Consuming WCF Duplex Service from ASP.NET Core Web Api 我们可以使用wcf WsDualHttpBinding(DUPLEX comm)在asp.net中的两个不同网站之间进行通信吗 - Can we use wcf WsDualHttpBinding(DUPLEX comm) for communicating between two different websites in asp.net
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM