How to publish and subscribe to Message class messages using WCF - wcf

I have a small WCF pub/sup service running, and remote clients subscribe and send messages (tried will all sorts of complex objects) and that works fine. All interfaces reflect(ed) the type of object being used. Switching to another object type requires that the interfaces be adjusted to accommodate that object type. All subscribers get a copy of the message.
Now I am trying to do the same thing, but with Message class messages. Client creates a new message and encapsulates its object in the message, and sends it to the (remote) service, where it is received properly (inspected the object). However, when the server replies by resending (callback) the message back to the originating client, the client receives the following message:
“The server did not provide a meaningful reply; this might be caused by a contract mismatch, a premature session shutdown or an internal server error.”
Sequence of events (Client):
Client creates Message,
(DuplexChannelFactory)AddMessage,
-Catch above error
Sequence of events (Server):
Service host receives message,
Message inspected (copy and recreate),
Perform callback,
No errors.
Switching back to a basic, or user defined, type and all problems go away. I have been struggling with this for a week now and not closer to any solution. Tried manipulating headers, recreating the message, switching to Message Contracts, and trying to interpret the contents of the trace logs etc. Hope I will find some answers here.
Primary code used (stripped of most of the error handling):
Client interfaces:
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();
}
}
}
Server interfaces:
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();
}
}
}
Message creation:
// 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;
}
Client proxy:
[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();
}
}
Service:
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);
}
});
}
}

I just got an answer to my question from Tanvir Huda, on the microsoft WCF forum.
"Using the Message Class in Operations
You can use the Message class as an input parameter of an operation, the return value of an operation, or both. If Message is used anywhere in an operation, the following restrictions apply:
•The operation cannot have anyoutorrefparameters.
•There cannot be more than oneinputparameter. If the parameter is present, it must be either Message or a message contract type.
•The return type must be either void,Message, or a message contract type."
I cannot beleive I missed that; must have read it at least three times, but never applied those rules to the callback. The callback in my interfaces described, do have a return type of void, but it has a Message and a DateTime parameter.
After removing the DateTime parameter, the callback did (try) to re-serialize the original Message, but failed because of on invalid action (action was still set for the AddMessage, while now it should be OnMessageAdded). After changing the action on the callback to Action="*" it workt perfectly. A (maybe annoying) detail it that i do not really require a Message type on the callback, but I was very frustrated that I did not get it to work

Related

SoapCore Asp.net core 3.1 Header

Any idea how I can add a header for my calls using SoapCore?
what I have so far:
at startup.cs:
app.UseSoapEndpoint<IMyService>("/MyService.svc", new BasicHttpBinding(), SoapSerializer.DataContractSerializer);
in IMyService
[ServiceContract]
public interface IMyService
{
[OperationContract]
public List<SOADataGetService> GetService(string ServiceType, string ServiceName, string ServiceVersion);
}
then my soap ends up like that:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/">
<soapenv:Header/>
<soapenv:Body>
<tem:GetService>
<tem:ServiceType>?</tem:ServiceType>
<tem:ServiceName>?</tem:ServiceName>
<tem:ServiceVersion>?</tem:ServiceVersion>
</tem:GetService>
</soapenv:Body>
</soapenv:Envelope>
I need to get in <soapenv:Header/> like user and password
You can access the header in SoapCore by implementing and registering a custom IServiceOperationTuner as described in the docs.
e.g.
public class MyServiceOperationTuner : IServiceOperationTuner
{
public void Tune(HttpContext httpContext, object serviceInstance, SoapCore.ServiceModel.OperationDescription operation)
{
if (operation.Name.Equals(nameof(MyService.SomeOperationName)))
{
MyService service = serviceInstance as MyService;
service.SetHttpRequest(httpContext.Request);
}
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.TryAddSingleton<IMyService, MyService>();
services.TryAddSingleton<IServiceOperationTuner>(provider => new MyServiceOperationTuner());
}
}
public class MyService : IMyService
{
private ThreadLocal<HttpRequest> _httpRequest = new ThreadLocal<HttpRequest>() { Value = null };
public void SetHttpRequest(HttpRequest request)
{
_httpRequest.Value = request;
}
public string SomeOperationName()
{
var soapHeader = GetHeaderFromRequest(_httpRequest.Value)
return $"SOAP Header: {soapHeader}";
}
private XmlNode GetHeaderFromRequest(HttpRequest request)
{
var bytes = (request.Body as MemoryStream)?.ToArray();
if (bytes == null)
{
// Body missing from request
return null;
}
var envelope = new XmlDocument();
envelope.LoadXml(Encoding.UTF8.GetString(bytes));
return envelope.DocumentElement?.ChildNodes.Cast<XmlNode>().FirstOrDefault(n => n.LocalName == "Header");
}
}
I hope this helps someone. I'm using SoapCore 1.1.0.28 with .Net Core 6. I tried the Tune method listed by #wolfyuk, but Core always returned bytes as null, so I was never able to get past the null check.
The most straightforward way I found is to use IMessageInspector2 from SoapCore to create middleware to intercept the SOAP request on the way in and intercept the SOAP response on the way out. Your class that implements IMessageInspector2 has access to the message so you can extract headers on the way in (that's what I needed), and add headers on the way out. I needed the request headers to be included in my response (a requirement of the system I'm communicating with).
public class AuthMessageFilter : IMessageInspector2
{
private const string WsNamespaceSecurityUri = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
private const string WsUserNameTokenNodeName = "UsernameToken";
private const string WsSecurityNodeName = "Security";
private const string WsTimestampNodeName = "Timestamp";
private readonly IMyService _service;
private readonly IHttpContextAccessor _acc;
private readonly ILogger _logger;
private MessageHeaders _messageHeaders;
public AuthMessageFilter(IHttpContextAccessor acc, IMyService service, ILogger logger)
{
_acc = acc;
_service = service;
_logger = logger;
}
public object AfterReceiveRequest(ref Message message, ServiceDescription serviceDescription)
{
ValidateSoapAction();
var token = GetUserNameToken(message);
var userIsAuthenticated = _service.ValidateUser(token.Username, token.Password.Value).GetAwaiter().GetResult();
if (userIsAuthenticated)
{
_messageHeaders = message.Headers; // use in response.
return null;
}
const string msg = "The user credentials did not authenticate.";
_logger.LogEntry(msg);
throw new AuthenticationFailedException(msg);
}
private void ValidateSoapAction()
{
try
{
var soapAction = _acc.HttpContext?.Request.Headers["SOAPAction"].FirstOrDefault()?.Replace("\"", "");
if (soapAction == null)
{
throw new Exception(
"Error: Could not extract SoapAction from HttpContext.Request.Headers. Aborting SOAP operation.");
}
}
catch (Exception ex)
{
_logger.LogEntry("No SOAP Action found.", ex);
}
}
private WsUsernameToken GetUserNameToken(Message message)
{
WsUsernameToken wsUsernameToken = null;
for (var i = 0; i < _messageHeaders.Count; i++)
{
if (!_messageHeaders[i].Name.Equals(WsSecurityNodeName, StringComparison.OrdinalIgnoreCase))
continue;
using var reader = _messageHeaders.GetReaderAtHeader(i);
while (reader.Read())
{
if (reader.IsStartElement() &&
reader.NamespaceURI.Equals(WsNamespaceSecurityUri, StringComparison.OrdinalIgnoreCase) &&
reader.LocalName.Equals(WsUserNameTokenNodeName, StringComparison.OrdinalIgnoreCase))
{
var serializer = new XmlSerializer(typeof(WsUsernameToken));
wsUsernameToken = (WsUsernameToken)serializer.Deserialize(reader);
break;
}
}
break;
}
if (wsUsernameToken == null)
{
var ex = new SecurityException("An exception occurred when verifying security for the message.");
_logger.LogEntry(LoggingCategory.Service, LoggingLevel.Error, ex.Message, ex);
throw ex;
}
return wsUsernameToken;
}
public void BeforeSendReply(ref Message reply, ServiceDescription serviceDescription, object correlationState)
{
for (var i = 0; i < _messageHeaders.Count; i++)
{
if (!_messageHeaders[i].Name.Equals(WsSecurityNodeName, StringComparison.OrdinalIgnoreCase))
continue;
using var reader = _messageHeaders.GetReaderAtHeader(i);
while (reader.Read())
{
if (reader.IsStartElement() &&
reader.NamespaceURI.Equals(WsNamespaceSecurityUri, StringComparison.OrdinalIgnoreCase) &&
reader.LocalName.Equals(WsTimestampNodeName, StringComparison.OrdinalIgnoreCase))
{
reply.Headers.Add(_messageHeaders[i] as MessageHeader);
break;
}
}
break;
}
}
}
Do not use SoapCore it is outdated, try to use SmartSoap:
https://github.com/Raffa50/SmartSoap
it is also available as a nugetPackage:
https://www.nuget.org/packages/Aldrigos.SmartSoap.AspNet/
Have a look at it, try it and if you need further support I will be pleased to help you!

Detect disconnect in WCF

We're building a WCF server (.NET 4.5). It will only use net.pipe transport.
When a client closes the PIPE connection, the server gets unhandled CommunicationException, and terminates.
Q1. How do I handle the CommunicationException so the server does not terminate and continues serving other clients?
Q2. In the handler, how do I get SessionId of the session that was aborted? I need this to do clean up some session-specific data.
Thanks in advance!
contract
[ServiceContract(CallbackContract = typeof(IContractCallback))]
public interface IContractServer
{
[OperationContract(IsOneWay = true)]
void Connect(bool status);
[OperationContract(IsOneWay = false)]
void Disconnect(IContractServer _channelCallback);
[OperationContract(IsOneWay = true)]
void Play(bool status);
}
service
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class Service : IContractServer
{
public List<IContractCallback> _channeList = new List<IContractCallback>();
public void Connect(bool status)
{
IContractCallback a = OperationContext.Current.GetCallbackChannel<IContractCallback>();
int call = 0;
foreach (var callBack in _channeList)
{
if (callBack == a)
{
call++;
}
}
if (call == 0)
{
_channeList.Add(a);
a.ConnectCallback(true);
}
else
{
a.ConnectCallback(false);
}
}
public void Disconnect(IContractServer _channelCallback)
{
foreach (var contractCallback in _channeList)
{
if (contractCallback == _channelCallback)
{
_channeList.Remove(contractCallback);
}
}
}
public void Play(bool status)
{
foreach (var contractCallback in _channeList)
{
contractCallback.PlayCallback(status);
}
}
}
client
using System.ComponentModel;
using System.ServiceModel;
using System.Windows;
using Host;
namespace VideoPlayer
{
public partial class MainWindow : Window, IContractCallback
{
private IContractServer Proxy = null;
public MainWindow()
{
InitializeComponent();
InstanceContext context = new InstanceContext(this);
DuplexChannelFactory<IContractServer> factory = new DuplexChannelFactory<IContractServer>(context, new NetNamedPipeBinding(), "net.pipe://localhost");
Proxy = factory.CreateChannel();
Proxy.Connect(true);
}
public void ConnectCallback(bool status)
{
MessageBox.Show(status ? "connected" : "no connected");
}
public void PlayCallback(bool status)
{
if (status)
{
MessageBox.Show("status true");
}
else
{
MessageBox.Show("status false");
}
}
private void ButtonPlay(object sender, RoutedEventArgs e)
{
Proxy.Play(true);
}
private void MainWindow_OnClosing(object sender, CancelEventArgs e)
{
//хочу отправить сообщение о закрытии
Proxy.Disconnect(Proxy);
}
I faced with this problem before in my duplex services when an event raised from server side the exception occurred if there was no alive channel between client and server so server dropped to Fault state and all next requests won't be responded,
For this reason I came to this conclusion to check the channel and if it was alive then let call back methods to be raised.
In service side the trick would be like ↓
bool IsChannelAlive()
{
Logging logging = new Logging(LogFile);
try
{
if (((ICommunicationObject)_callbackChannel).State == CommunicationState.Opened)
{
logging.Log(LoggingMode.Prompt, "Channeld is still alive, can raise events...");
return true;
}
}
catch (Exception exp)
{
logging.Log(LoggingMode.Error, "IsChannelAlive()=> failed, EXP: {0}", exp);
}
logging.Log(LoggingMode.Warning, "Channeld is not alive so events won't raised...");
return false;
}
and in one of my events I use it like :
void stran_OperationTimedOut(object sender, EventArgs e)
{
if (IsChannelAlive())
_callbackChannel.OnOperationTimedOut();
}
But for a while I use this trick to know closed channel to do something:
public ImportService()
{
//Handle ContextClose to Audit all actions made in session
OperationContext.Current.InstanceContext.Closed += delegate
{
//Here
};
}
which is not reliable.
I am still using that IsAliveChannel() in my services.
Hope this answer resolve your problem or give you the clue.

WCF large size request data handling concurrently

I have a self hosted WCF service for handling HTTP POST request.
My WCF service method will handle 2 types of request (1. getting data, 2. Do some action with given data).
My scenario :
1. Started Action request with large amount of data. : during the starting of that action do some processing.
2. client reads some results.
3. sending the remaining data for the previous action.
I implemented this using 'TcpListener' it is working.
Problem:
when i try to implement this scenario using WCF, i have got the 'Bad request' during the first action request.
[ServiceContract]
public interface ITest
{
[OperationContract, WebInvoke(Method = "POST", UriTemplate = "{*pathname}")]
Stream GetResponse(string pathname, Stream requestBody);
}
[ServiceBehavior(AddressFilterMode = AddressFilterMode.Exact, ConcurrencyMode = ConcurrencyMode.Single, InstanceContextMode = InstanceContextMode.Single, UseSynchronizationContext = false, IncludeExceptionDetailInFaults = true)]
class TestService : ITest
{
public Uri BaseUri { get; private set; }
public IPAddress Address { get; private set; }
public Int32 Port { get; private set; }
public bool IsStarted { get { return Host != null; } }
public ServiceHost Host { get; private set; }
public TestService()
{
}
public TestService(IPAddress ipAddress, int port)
{
if (ipAddress == null) { throw new ArgumentNullException("Address"); }
this.Address = ipAddress;
this.Port = port;
this.BaseUri = new Uri(string.Format("http://{0}:{1}", this.Address, this.Port));
}
public void Start()
{
if (IsStarted) { throw new InvalidOperationException("Service is already started."); }
Host = CreateServiceHost();
Host.Open();
}
public void Stop()
{
if (!IsStarted) { throw new InvalidOperationException("Service is already stopped."); }
Host.Close();
Host = null;
}
private ServiceHost CreateServiceHost()
{
ServiceHost host = new ServiceHost(typeof(TestService), this.BaseUri);
WebHttpBinding webhttpBinding = new WebHttpBinding();
webhttpBinding.MaxBufferPoolSize = int.MaxValue;
webhttpBinding.MaxBufferSize = int.MaxValue;
webhttpBinding.MaxReceivedMessageSize = int.MaxValue;
CustomBinding binding = new CustomBinding(webhttpBinding);
WebMessageEncodingBindingElement webEncoding = binding.Elements.Find<WebMessageEncodingBindingElement>();
webEncoding.ContentTypeMapper = new RawMapper();
host.AddServiceEndpoint(typeof(ITest), binding, new Uri(this.BaseUri.AbsoluteUri)).Behaviors.Add(new WebHttpBehavior());
return host;
}
public Stream GetResponse(string pathname, Stream requestBody)
{
Stream response=null;
/************** Process 'requestBody' **************/
//requestBody contains "MY_ACTION"
//{
// Find the start delimeter from response : START processing
// Find the end delimeter from response : STOP processing
//}
//requestBody contains "GET_MY_DATA"
//{
// Find data and send response
//}
return response;
}
}
public class RawMapper : WebContentTypeMapper
{
public override WebContentFormat GetMessageFormatForContentType(string contentType)
{
return WebContentFormat.Raw;
}
}
Any help/guidance would be great as I'm really stuck on this.

wcf callback timeout when sending more than one messages

I have a wcf callback programm and it can send a message to a client.
If i try to send a second message the whole programm freezes and i get a timeoutexception.
Here is the servercode:
public void SendMessageToClient(string computerName, string message)
{
foreach (var session in connectedClients.Values)
{
if (session.ComputerName == computerName)
{
var asyncResult = session.Callback.BeginOnMessageReceived(message, new AsyncCallback(OnPushMessageComplete), session.Callback);
if (asyncResult.CompletedSynchronously)
CompletePushMessage(asyncResult);
}
}
}
void OnPushMessageComplete(IAsyncResult asyncResult)
{
CompletePushMessage(asyncResult);
}
void CompletePushMessage(IAsyncResult asyncResult)
{
var callbackChannel = (IServiceCallback)asyncResult.AsyncState;
try
{
callbackChannel.EndOnMessageReceived(asyncResult);
}
catch { }
}
And this is the Callbackinterface:
[OperationContract(IsOneWay = true, AsyncPattern = true)]
IAsyncResult BeginOnMessageReceived(string message, AsyncCallback acb, object state);
void EndOnMessageReceived(IAsyncResult iar);
And this is the client code:
[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant, UseSynchronizationContext = false)]
public class ServiceHandler : IServiceCallback
{
public delegate void MessageReceivedHandler(string message);
public event MessageReceivedHandler OnMessageReceivedEvent;
public void OnMessageReceived(string message)
{
if (this.OnMessageReceivedEvent != null)
this.OnMessageReceivedEvent(message);
}
}
void callback_OnMessageReceivedEvent(string message)
{
setlb_info(message)
}
public void setlb_info(string wert)
{
if (this.lb_info.InvokeRequired)
{
setlb_infoCallback d = new setlb_infoCallback(setlb_info);
this.Invoke(d, new object[] { wert });
}
else
{
this.lb_info.Text = wert;
}
}
And if i try this:
Service.CurrentInstance.SendMessageToClient(client_name, message);
the client will get the message but if i call the same method a second time i get the timeoutexception (which is set to 1 minute).
I'm using code from this project(german):
http://www.flexbit.at/blog/wcf-duplex-zwischen-windows-sevice-und-gui-frontend/
I hope someone can help me because i can't finish my work if this function won't work.
Best regards
EDIT: I forgot a code on the client side:
var callback = new ServiceHandler();
callback.OnMessageReceivedEvent += new ServiceHandler.MessageReceivedHandler(callback_OnMessageReceivedEvent);
var callbackInstanceContext = new InstanceContext(callback);
client = new ServiceClient(callbackInstanceContext);
client.Subscribe(System.Environment.MachineName);
Try change
[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant, UseSynchronizationContext = false)]
to
[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, UseSynchronizationContext = false)]

WCF duplex TCP communication error

I have a sample service to test WCF net.tcp communication. It is very simple service and all it does is subscribing a client to the service and then calls callbackchannel to notify all connected clients about broadcasted message. The service is hosted inside IIS 7.5.
Here is service code and test client to test it.
[ServiceContract(CallbackContract = typeof(ISampleServiceCallBack), SessionMode = SessionMode.Required)]
public interface ISampleCuratioService
{
[OperationContract(IsOneWay = true)]
void SubcribeToService(string sub);
[OperationContract]
string GetData(int value);
[OperationContract(IsOneWay = true)]
void Broadcast(string message);
}
public interface ISampleServiceCallBack
{
[OperationContract(IsOneWay = true)]
void NotifyClient(string message);
}
Here is the service implementation:
[ServiceBehavior(Name = "CuratioCSMService", InstanceContextMode = InstanceContextMode.PerSession)]
public class Service1 : ISampleCuratioService
{
private static List<ISampleServiceCallBack> JoinedClien = new List<ISampleServiceCallBack>();
public void SubcribeToService(string sub)
{
var subscriber = OperationContext.Current.GetCallbackChannel<ISampleServiceCallBack>();
if (!JoinedClien.Contains(subscriber))
{
JoinedClien.Add(subscriber);
}
}
public string GetData(int value)
{
return string.Format("You entered: {0}", value);
}
public void Broadcast(string message)
{
JoinedClien.ForEach(c => c.NotifyClient("message was received " + message));
}
}
I can not understand the behavior I get when running it. After the first client runs everything works fine but as I close and open test client app, it throws exception notifying that channel can not be used for communication as it is in fault state.
This is sample test client:
static void Main(string[] args)
{
var callneckclient = new ServiceClientProxy();
var client = new SampleCuratioServiceClient(new InstanceContext(callneckclient));
client.SubcribeToService("me");
Console.ReadLine();
for (int i = 0; i < 15; i++)
{
Console.WriteLine(client.GetData(5));
client.Broadcast("this is from client me");
}
client.Close();
Console.Read();
}
public class ServiceClientProxy : ISampleCuratioServiceCallback, IDisposable
{
public void NotifyClient(string message)
{
Console.WriteLine(message);
}
public void Dispose()
{
GC.SuppressFinalize(this);
}
}
The situation gets even buggy when I run 5 clients. Non of those send or receive messages.
When a client calls SubcribeToService you add its operation context to a List called JoinedClien.
When you call Broadcast in your server, you call the method NotifyClient on all collected operation contexts for every client that has ever connected.
The problem is, that a disconnected client won't get removed from your JoinedClien list.
When you try to call an operation method on a disconnected operation context, you get the channel is in faulted state error.
To work around, you should subscribe to the Channel_Closed and Channel_Faulted events and also catch the CommunicationException when calling back into your clients and remove the operation context of the faulted clients:
public void Broadcast(string message)
{
// copy list of clients
List<OperationContext> clientsCopy = new List<OperationContext>();
lock(JoinedClien) {
clientsCopy.AddRange(JoinedClien);
}
// send message and collect faulted clients in separate list
List<OperationContext> clientsToRemove = new List<OperationContext>();
foreach (var c in JoinedClien)
{
try {
c.NotifyClient("message was received " + message));
}
catch (CommunicationException ex) {
clientsToRemove.Add(c);
}
}
foreach (var c in clientsToRemove)
{
lock(JoinedClien) {
if(JoinedClien.Contains(c))
JoinedClien.Remove(c);
}
}
}
When adding new clients you have to lock that operation, too:
var subscriber = OperationContext.Current.GetCallbackChannel<ISampleServiceCallBack>();
lock(JoinedClien)
{
if (!JoinedClien.Contains(subscriber))
{
JoinedClien.Add(subscriber);
}
}