我有一个小的WCF pub / sup服务正在运行,而远程客户端订阅并发送消息(尝试了各种复杂的对象),并且工作正常。所有接口都反映(ed)正在使用的对象的类型。切换到另一个对象类型需要调整接口以适应该对象类型。所有订阅者都会获得该邮件的副本。
现在我正在尝试做同样的事情,但使用Message类消息。客户端创建新消息并将其对象封装在消息中,并将其发送到(远程)服务,在该服务中正确接收(检查对象)。但是,当服务器通过将消息重新发送(回调)回原始客户端进行回复时,客户端会收到以下消息:
“服务器未提供有意义的回复;这可能是由合同不匹配,过早的会话关闭或内部服务器错误引起的。“
事件顺序(客户):
客户创建消息, (DuplexChannelFactory)方法addMessage, - 以上错误
事件顺序(服务器):
服务主机接收消息, 检查消息(复制并重新创建), 执行回调, 没有错误。
切换回基本或用户定义的类型,所有问题都会消失。我一直在努力解决这个问题一个星期,并没有接近任何解决方案。尝试操纵标题,重新创建消息,切换到消息合同,并尝试解释跟踪日志的内容等。希望我会在这里找到一些答案。
使用的主要代码(剥离了大部分错误处理):
客户端界面:
namespace WCFSQL
{
public class ClientInterfaces
{
[ServiceContract(Namespace = "WCFServer", Name = "CallBacks")]
public interface IMessageCallback
{
[OperationContract(Name = "OnMessageAdded", Action = "WCFServer/IMessageCallback/OnMessageAdded", IsOneWay = true)]
void OnMessageAdded(Message SQLMessage, DateTime timestamp);
}
[ServiceContract(Namespace = "WCFServer", CallbackContract = typeof(IMessageCallback))]
public interface IMessage
{
[OperationContract(Name = "AddMessage", Action = "WCFServer/IMessage/AddMessage")]
void AddMessage(Message SQLMessage);
[OperationContract(Name = "Subscribe", Action = "WCFServer/IMessage/Subscribe")]
bool Subscribe();
[OperationContract(Name = "Unsubscribe", Action = "WCFServer/IMessage/Unsubscribe")]
bool Unsubscribe();
}
}
}
服务器接口:
namespace WCFSQL
{
public class ServerInterfaces
{
[ServiceContract(Namespace = "WCFServer")]
public interface IMessageCallback
{
[OperationContract(Name = "OnMessageAdded", Action = "WCFServer/IMessageCallback/OnMessageAdded", IsOneWay = true)]
void OnMessageAdded(Message SQLMessage, DateTime timestamp);
}
[ServiceContract(Namespace = "WCFServer", CallbackContract = typeof(IMessageCallback), SessionMode = SessionMode.Required)]
public interface IMessage
{
[OperationContract(Name = "AddMessage", Action = "WCFServer/IMessage/AddMessage")]
void AddMessage(Message SQLMessage);
[OperationContract(Name = "Subscribe", Action = "WCFServer/IMessage/Subscribe")]
bool Subscribe();
[OperationContract(Name = "Unsubscribe", Action = "WCFServer/IMessage/Unsubscribe")]
bool Unsubscribe();
}
}
}
消息创建:
// client proxy instance created and opened before
public static bool WCFSqlLogger(string Program, WCFSQLErrorLogMessage SQLErrorMessage, WCFSqlClientProxy client)
{
MessageVersion ver = MessageVersion.CreateVersion(EnvelopeVersion.Soap12, AddressingVersion.WSAddressing10);
Message Out = Message.CreateMessage(ver, "WCFServer/IMessage/AddMessage", SQLErrorMessage);
if (!client.SendMessage(Out))
{
Console.WriteLine("Client Main: Unable to send");
return false;
}
return true;
}
客户代理:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, IncludeExceptionDetailInFaults = true)]
[CallbackBehavior(IncludeExceptionDetailInFaults = true, ConcurrencyMode = ConcurrencyMode.Single, UseSynchronizationContext = false)]
public class WCFSqlClientProxy : ClientInterfaces.IMessageCallback, IDisposable
{
public ClientInterfaces.IMessage pipeProxy = null;
DuplexChannelFactory<ClientInterfaces.IMessage> pipeFactory;
public bool Connect()
{
NetTcpBinding newBinding = new NetTcpBinding(SecurityMode.TransportWithMessageCredential);// NetTcpBinding newBinding = new NetTcpBinding(SecurityMode.Transport)
newBinding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;
EndpointAddress newEndpoint = new EndpointAddress(new Uri("net.tcp://host:8000/ISubscribe"), EndpointIdentity.CreateDnsIdentity("Domain"));
pipeFactory = new DuplexChannelFactory<ClientInterfaces.IMessage>(new InstanceContext(this), newBinding, newEndpoint);
pipeFactory.Credentials.Peer.PeerAuthentication.CertificateValidationMode = X509CertificateValidationMode.PeerOrChainTrust;
pipeFactory.Credentials.ServiceCertificate.Authentication.RevocationMode = X509RevocationMode.NoCheck;
pipeFactory.Credentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.TrustedPeople, X509FindType.FindByThumbprint, "somestring");
try
{
pipeProxy = pipeFactory.CreateChannel();
pipeProxy.Subscribe();
return true;
}
catch (Exception e)
{
Console.WriteLine("Error opening: {0}", e.Message);
return false;
}
}
public void Close()
{
pipeProxy.Unsubscribe();
}
public bool SendMessage(Message SQLMessage)
{
try
{
Console.WriteLine("Proxy Sending:");
pipeProxy.AddMessage(SQLMessage); // This is where the eror occurs !!!!!!!!!!!!!!!!!!
return true;
}
catch (Exception e)
{
Console.WriteLine("Client Proxy: Error sending: {0}", e.Message);
}
return false;
}
public void OnMessageAdded(Message SQLMessage, DateTime timestamp)
{
WCFSQLErrorLogMessage message = SQLMessage.GetBody<WCFSQLErrorLogMessage>();
Console.WriteLine(message.LogProgram + ": " + timestamp.ToString("hh:mm:ss"));
}
public void Dispose()
{
Console.WriteLine("Dispose: Unsubscribe");
pipeProxy.Unsubscribe();
}
}
服务:
namespace WCFSQL
{
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, IncludeExceptionDetailInFaults = true)]
[CallbackBehavior(IncludeExceptionDetailInFaults = true, ConcurrencyMode = ConcurrencyMode.Single, UseSynchronizationContext = false)] // or ConcurrencyMode.Reentrant
public class WCFSqlServerProxy : ServerInterfaces.IMessage
{
private static List<ServerInterfaces.IMessageCallback> subscribers = new List<ServerInterfaces.IMessageCallback>();
private static Uri target;
private static ServiceHost serviceHost;
public WCFSqlServerProxy(Uri Target) // Singleton
{
target = Target;
}
public bool Connect()
{
serviceHost = new ServiceHost(typeof(WCFSqlServerProxy), target);
NetTcpBinding newBinding = new NetTcpBinding(SecurityMode.TransportWithMessageCredential);
newBinding.Security.Transport.ClientCredentialType = TcpClientCredentialType.Certificate;
newBinding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;
serviceHost.Credentials.ClientCertificate.Authentication.RevocationMode = X509RevocationMode.NoCheck; // Non-domain members cannot follow the chain?
serviceHost.Credentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.TrustedPeople, X509FindType.FindByThumbprint, "somestring");
serviceHost.Credentials.ClientCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.PeerOrChainTrust;
serviceHost.AddServiceEndpoint(typeof(ServerInterfaces.IMessage), newBinding, "ISubscribe");
return true;
}
public bool Open()
{
serviceHost.Open();
return true;
}
public bool Close()
{
serviceHost.Close();
return true;
}
public bool Subscribe()
{
try
{
ServerInterfaces.IMessageCallback callback = OperationContext.Current.GetCallbackChannel<ServerInterfaces.IMessageCallback>();
if (!subscribers.Contains(callback))
{
subscribers.Add(callback);
return true;
}
else
{
return false;
}
}
catch (Exception e)
{
return false;
}
}
public bool Unsubscribe()
{
try
{
ServerInterfaces.IMessageCallback callback = OperationContext.Current.GetCallbackChannel<ServerInterfaces.IMessageCallback>();
if (subscribers.Contains(callback))
{
subscribers.Remove(callback);
return true;
}
return false;
}
catch (Exception e)
{
Console.WriteLine("WCFSqlServerProxy: Unsubscribe - Unsubscribe error {0}", e);
return false;
}
}
private string GetData()
{
MessageProperties messageProperties = ((OperationContext)OperationContext.Current).IncomingMessageProperties;
RemoteEndpointMessageProperty endpointProperty = messageProperties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;
string computerName = null;
try
{
string[] computer_name = Dns.GetHostEntry(endpointProperty.Address).HostName.Split(new Char[] { '.' });
computerName = computer_name[0].ToString();
}
catch (Exception e)
{
computerName = "NOTFOUND";
Console.WriteLine("WCFSqlServerProxy: Hostname error: {0}", e);
}
return string.Format("{0} - {1}:{2}", computerName, endpointProperty.Address, endpointProperty.Port);
}
public void AddMessage(Message SQLMessage) //Go through the list of connections and call their callback funciton
{
subscribers.ForEach(delegate(ServerInterfaces.IMessageCallback callback)
{
if (((ICommunicationObject)callback).State == CommunicationState.Opened)
{
MessageVersion ver = MessageVersion.CreateVersion(EnvelopeVersion.Soap12, AddressingVersion.WSAddressing10);
MessageBuffer buffer = SQLMessage.CreateBufferedCopy(4096);
Message msgCopy = buffer.CreateMessage();
//System.Xml.XmlDictionaryReader xrdr = msgCopy.GetReaderAtBodyContents();
WCFSQLErrorLogMessage p = msgCopy.GetBody<WCFSQLErrorLogMessage>();
SQLMessage = buffer.CreateMessage();
buffer.Close();
Message In = Message.CreateMessage(ver, "WCFServer/IMessage/AddMessage", p); // Tried recreating messsage, with same results
//Console.WriteLine("Message: Header To: {0}", In.Headers.To);
//Console.WriteLine("Message: Header From: {0}", In.Headers.From);
//Console.WriteLine("Message: Header Action: {0}", In.Headers.Action);
//Console.WriteLine("Message: Header ReplyTo: {0}", In.Headers.ReplyTo);
//Console.WriteLine("Message: IsFault: {0}", In.IsFault);
//Console.WriteLine("Message: Properties {0}", In.Properties);
//Console.WriteLine("Message: State {0}", In.State);
//Console.WriteLine("Message: Type {0}", In.GetType());
//Console.WriteLine("Proxy Sending: Copy created");
//Console.WriteLine("Remote: {0}, Hash: {1}", GetData(), callback.GetHashCode());
callback.OnMessageAdded(SQLMessage, DateTime.Now); // This should echo the message back with a timeslot.
}
else
{
Console.WriteLine("WCFSqlServerProxy:addmessage connected state: {0}", ((ICommunicationObject)callback).State == CommunicationState.Opened);
subscribers.Remove(callback);
}
});
}
}
答案 0 :(得分:2)
我刚从微软WCF论坛上的Tanvir Huda那里得到了我的问题答案。
“在操作中使用消息类
您可以将Message类用作操作的输入参数,操作的返回值或两者。如果在操作中的任何位置使用Message,则适用以下限制:
•操作不能有任何参数参数。
•不能有多个输入参数。如果参数存在,则它必须是Message或消息协定类型。
•返回类型必须为void,Message或消息合同类型。“
我无法相信我错过了;必须至少读过三次,但从未将这些规则应用于回调。我所描述的接口中的回调确实有返回类型的void,但它有一个Message和一个DateTime参数。
删除DateTime参数后,回调确实(尝试)重新序列化原始消息,但由于无效操作失败(仍然为AddMessage设置了操作,而现在它应该是OnMessageAdded)。将回调操作更改为Action =“*”后,它完美地工作。一个(可能很讨厌)详细说明我在回调中并不需要消息类型,但是我感到非常沮丧,我没有让它工作