![](/img/trans.png)
[英]WCF Duplex: FatalException receiving callback in ASP.NET 4.0 Webforms client
[英]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.