簡體   English   中英

WCF Duplex:將回調發送到特定訂閱的ASP.NET Webforms客戶端

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

我正在開發一個連接到WCF 4.0 Duplex服務的ASP.NET 4.0 Webforms客戶端應用程序,以實現某種平面文件處理。 當用戶進入Page_Load事件的頁面時,我將客戶端訂閱到雙工服務,這是因為我需要在某些情況下通知所有客戶端:

A)進程啟動時必須通知啟動進程的客戶端。

B)處理文件時必須通知啟動該進程的客戶端。

C)整個過程完成后,必須通知啟動過程的客戶端。

D)如果新客戶(訂戶)在流程已經啟動時進入,則必須收到特定通知。

E)如果有多個客戶端(訂戶)處於活動狀態,其中一個客戶端啟動該流程,則其他客戶端必須收到特定通知。

我已經編寫了這個邏輯,但是我在嘗試完成特定的訂閱者通知時遇到了很多問題,似乎WCF所有客戶端/ Web應用程序的實例都被識別為相同,我收到的所有通知都在啟動該過程的客戶端,如果我打開其他瀏覽器並啟動新會話(在ASP.NET上),我收到相同的通知,沒有具體的。

在這里,您可以看到我的代碼的簡化版本

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服務實現

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服務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客戶端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客戶端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客戶端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>

在這里,您可以下載包含完整代碼的Visual Studio 2015解決方案。

我想知道我的代碼有什么問題,我認為這種行為是可能的,但無法理解為什么WCF沒有通知特定的客戶端。

謝謝

更新1

我做了所有更改@JuanK建議我(當時)沒有運氣,行為繼續相同,我添加了一個新的控制台項目來測試相同的服務,並在該項目工作正常

在此輸入圖像描述

但是在ASP.NET項目中錯誤仍然存​​在,第二個客戶端獲取所有通知

在此輸入圖像描述

在這里您可以下載更新的VS解決方案(此時)

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

另一方面,我在這里修復了一些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);
                }
            }
        }
    }
}

如果您有單個實例行為和靜態字段:靜態字段在所有連接之間共享,因此例如 _sessionStarted始終獲取上次連接的狀態。

因此,我已將servicebehavior更改為PerSession以允許每個特定會話/連接保留非靜態字段,而List<KeyValuePair<string, IService1DuplexCallback>> _clients等靜態字段仍在其中共享。 這也暗示了一些方法現在是非靜態方法

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM