How do I handle an exception thrown in a callback method on the client in a WCF duplex setup?
Currently, the client does not appear to raise the faulted event (unless I'm monitoring it incorrectly?) but any subsequent to call Ping() using the the client fails with CommunicationException: "The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it has been Aborted.".
How do I deal with this and recreate the client etc? My first question is how to find out when it happens. Secondly, how best to deal with it?
My service and callback contracts:
[ServiceContract(CallbackContract = typeof(ICallback), SessionMode = SessionMode.Required)]
public interface IService
{
[OperationContract]
bool Ping();
}
public interface ICallback
{
[OperationContract(IsOneWay = true)]
void Pong();
}
My server implementation:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Single)]
public class Service : IService
{
public bool Ping()
{
var remoteMachine = OperationContext.Current.GetCallbackChannel<ICallback>();
remoteMachine.Pong();
}
}
My client implementation:
[CallbackBehavior(UseSynchronizationContext = false, ConcurrencyMode = ConcurrencyMode.Single)]
public class Client : ICallback
{
public Client ()
{
var context = new InstanceContext(this);
var proxy = new WcfDuplexProxy<IApplicationService>(context);
(proxy as ICommunicationObject).Faulted += new EventHandler(proxy_Faulted);
//First Ping will call the Pong callback. The exception is thrown
proxy.ServiceChannel.Ping();
//Second Ping call fails as the client is in Aborted state
try
{
proxy.ServiceChannel.Ping();
}
catch (Exception)
{
//CommunicationException here
throw;
}
}
void Pong()
{
throw new Exception();
}
//These event handlers never get called
void proxy_Faulted(object sender, EventArgs e)
{
Console.WriteLine("client faulted proxy_Faulted");
}
}
As it turns out, you cannot expect the Faulted event to be raised. So, the best way to re-establish the connection is to do it when the subsequent call to Ping() fails:
I'll keep the code simple here:
public class Client : ICallback
{
public Client ()
{
var context = new InstanceContext(this);
var proxy = new WcfDuplexProxy<IApplicationService>(context);
(proxy.ServiceChannel as ICommunicationObject).Faulted +=new EventHandler(ServiceChannel_Faulted);
//First Ping will call the Pong callback. The exception is thrown
proxy.ServiceChannel.Ping();
//Second Ping call fails as the client is in Aborted state
try
{
proxy.ServiceChannel.Ping();
}
catch (Exception)
{
//Re-establish the connection and try again
proxy.Abort();
proxy = new WcfDuplexProxy<IApplicationService>(context);
proxy.ServiceChannel.Ping();
}
}
/*
[...The rest of the code is the same...]
//*/
}
Obviously, in my example code, the Exception will be thrown again but I hope this is useful to give people an idea of how to re-establish the connection.
Related
I'm well aware of how to use FaultException with details. I know I can declare details contract, then I need to decorate the method which is expected to throw this kind of exceptions with [FaultContract(type(DetailsContractType))] and then I do throw FaultException in that method. All that is understood and worked. What I need is to be able to throw FaultException from all methods of all contracts in my WCF host. Adding [FaultContract(type(DetailsContractType))] to each method of each operation contract seems to much to me. Is there another way to allow this kind of exceptions without decorating methods with that attribute? If I just remove that attribute everything stops working and the exception becomes just FaultException on the client side. I was thinking about DataContractResolver but it looks like it is not involved in DetailsContractType resolution. Any ideas, hints, solutions?
Using IErrorHandler does not relieve you from decorating contract operations with the FaultContractAttribute what I'm trying to avoid. It is even stated in the example you referred, there is a comment there
// This behavior requires that the contract have a SOAP fault with a detail type of
GreetingFault.
and
throw new InvalidOperationException(String.Format(
"EnforceGreetingFaultBehavior requires a "
+ "FaultContractAttribute(typeof(GreetingFault)) in each operation contract. "
+ "The \"{0}\" operation contains no FaultContractAttribute.",
opDesc.Name)
);
You can implement the IErrorHandler interface to uniformly handle errors in WCF,here is a Demo:
[ServiceContract]
public interface IDemo {
[OperationContract]
void DeleteData(int dataId);
}
class DemoService : IDemo
{
public void DeleteData(int dataId)
{
if (dataId<0) {
throw new ArgumentException("error");
}
}
}
The above code is the interface and implementation class of WCF service.
class MyCustErrorHandler : IErrorHandler
{
public bool HandleError(Exception error)
{
return true;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
FaultException faultException = new FaultException(error.Message);
MessageFault messageFault = faultException.CreateMessageFault();
fault = Message.CreateMessage(version,messageFault,"my-test-error");
}
}
The above code is the implementation class of the IErrorHandler interface.
class MyEndpointBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
return;
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
return;
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
MyCustErrorHandler handler = new MyCustErrorHandler();
endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(handler);
}
public void Validate(ServiceEndpoint endpoint)
{
return;
}
}
We add a custom error handling class by extending the Behavior method of the endpoint.
ServiceEndpoint ep = selfHost.AddServiceEndpoint(typeof(IDemo), new BasicHttpBinding(), "CalculatorService");
MyEndpointBehavior myEndpointBehavior = new MyEndpointBehavior();
ep.EndpointBehaviors.Add(myEndpointBehavior);
The client executes the following code will print "error" in the console:
try {
demoClient.DeleteData(-3);
}
catch (FaultException fault) {
string err = fault.Reason.GetMatchingTranslation().Text;
Console.WriteLine(err);
}
For more information about IErrorhandler,Please refer to the following link:
https://learn.microsoft.com/en-us/dotnet/api/system.servicemodel.dispatcher.ierrorhandler?view=netframework-4.8
UPDATE
If you don’t want to use IErrorhandler, you can also use FaultReason:
public string SayHello(string name) {
if (name.Length<2) {
FaultReasonText faultReasonText = new FaultReasonText("name length cannot be less than 2");
FaultReason reason = new FaultReason(faultReasonText);
throw new FaultException(reason);
}
return "hello";
}
The client needs to catch exceptions when calling:
try {
string res = channnel.Sayhello("B");
}
catch (FaultException fex) {
if (fex.Reason != null) {
FaultReason reason = fex.Reason;
//Get error information
FaultReasonText txt = reason.GetMatchingTranslation();
Console.WriteLine(txt.Text);
}
}
I have a simple WCF service with one method:
[ServiceContract]
public interface TestServiceContract
{
[OperationContract]
int[] Test();
}
public class TestService:TestServiceContract
{
public int[] Test()
{
return new int[1000000];
}
}
When on the client side I call
client.Test();
it fails, obviously because object I pass is too large.
BUT
instead of a meaningful description I get a totally useless
The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication
because it is in the Faulted state.
I tried enabling
<serviceDebug includeExceptionDetailInFaults="true" />
but it doesn't help.
Is it possible to get a meaningful error description?
Use "try catch" to catch exceptions when creating service endpoints.According to your description, I did a test and found that if the passed object is too large, there will be exceptions. Here is the exception I got:
Here is my demo:
namespace Test
{
[ServiceContract]
public interface TestServiceContract
{
[OperationContract]
int[] Test();
}
public class TestService : TestServiceContract
{
public int[] Test()
{
return new int[1000000];
}
}
class Program
{
static void Main(string[] args)
{
Uri baseAddress = new Uri("http://localhost:8000/GettingStarted/");
ServiceHost selfHost = new ServiceHost(typeof(TestService), baseAddress);
try
{
selfHost.AddServiceEndpoint(typeof(TestServiceContract), new WSHttpBinding(), "Test");
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
selfHost.Description.Behaviors.Add(smb);
selfHost.Open();
Console.WriteLine("The service is ready.");
Console.WriteLine("Press <Enter> to terminate the service.");
Console.WriteLine();
Console.ReadLine();
}
catch (CommunicationException ce)
{
Console.WriteLine("An exception occurred: {0}", ce.Message);
selfHost.Abort();
}
}
}
}
This is the server-side code.
static void Main(string[] args)
{
WSHttpBinding myBinding = new WSHttpBinding();
EndpointAddress myEndpoint = new EndpointAddress("http://localhost:8000/GettingStarted/Test");
ChannelFactory<TestServiceContract> myChannelFactory = new ChannelFactory<TestServiceContract>(myBinding, myEndpoint);
TestServiceContract wcfClient1 = myChannelFactory.CreateChannel();
wcfClient1.Test();
}
This is the client-side code.I create a channel factory to call the service. You can also use Svcutil to generate proxy classes to call services.
I have an application that sends AMQP messages via RabbitMQ. message sending is triggered on an http request. Recently I have noticed that some messages appear to be getting lost (as in never delivered). I also noticed that the list of channels being managed by the server is steadily increasing. The first thing I have corrected is to close channels after they are no longer required. However, I am still not sure my code is correctly structured to ensure delivery. Two sections of code are below; the first is a section of a singleton that manages the connection (does not recreate on every call), the second is the sending code. Any advice / guidance would be appreciated.
#Service
public class PersistentConnection {
private static Connection myConnection = null;
private Boolean blocked = false;
#Autowired ApplicationConfiguration applicationConfiguration;
#Autowired ConfigurationService configurationService;
#PostConstruct
private void init() {
}
#PreDestroy
private void destroy() {
try {
myConnection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public Connection getConnection( ) {
if (myConnection == null) {
start();
}
else if (!myConnection.isOpen()) {
log.warn("AMQP Connection closed. Attempting to start.");
start();
}
return myConnection;
}
private void start() {
log.debug("Building AMQP Connection");
ConnectionFactory factory = new ConnectionFactory();
String ipAddress = applicationConfiguration.getAMQPHost();
String password = applicationConfiguration.getAMQPUser();
String user = applicationConfiguration.getAMQPPassword();
String virtualHost = applicationConfiguration.getAMQPVirtualHost();
String port = applicationConfiguration.getAMQPPort();
try {
factory.setUsername(user);
factory.setPassword(password);
factory.setVirtualHost(virtualHost);
factory.setPort(Integer.parseInt(port));
factory.setHost(ipAddress);
myConnection = factory.newConnection();
}
catch (Exception e) {
e.printStackTrace();
}
myConnection.addBlockedListener(new BlockedListener() {
public void handleBlocked(String reason) throws IOException {
// Connection is now blocked
blocked = true;
}
public void handleUnblocked() throws IOException {
// Connection is now unblocked
blocked = false;
}
});
}
public Boolean isBlocked() {
return blocked;
}
}
/*
* Sends ADT message to AMQP server.
*/
private void send(String routingKey, String message) throws Exception {
String exchange = applicationConfiguration.getAMQPExchange();
String exchangeType = applicationConfiguration.getAMQPExchangeType();
Connection connection = myConnection.getConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(exchange, exchangeType);
channel.basicPublish(exchange, routingKey, null, message.getBytes());
// Close the channel if it is no longer needed in this thread
channel.close();
}
Try this code:
#Service
public class PersistentConnection {
private Connection myConnection = null;
private Boolean blocked = false;
#Autowired ApplicationConfiguration applicationConfiguration;
#Autowired ConfigurationService configurationService;
#PostConstruct
private void init() {
start(); /// In this way you can initthe connection and you are sure it is called only one time.
}
#PreDestroy
private void destroy() {
try {
myConnection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public Connection getConnection( ) {
return myConnection;
}
private void start() {
log.debug("Building AMQP Connection");
ConnectionFactory factory = new ConnectionFactory();
String ipAddress = applicationConfiguration.getAMQPHost();
String password = applicationConfiguration.getAMQPUser();
String user = applicationConfiguration.getAMQPPassword();
String virtualHost = applicationConfiguration.getAMQPVirtualHost();
String port = applicationConfiguration.getAMQPPort();
try {
factory.setUsername(user);
factory.setPassword(password);
factory.setVirtualHost(virtualHost);
factory.setPort(Integer.parseInt(port));
factory.setHost(ipAddress);
myConnection = factory.newConnection();
}
catch (Exception e) {
e.printStackTrace();
}
myConnection.addBlockedListener(new BlockedListener() {
public void handleBlocked(String reason) throws IOException {
// Connection is now blocked
blocked = true;
}
public void handleUnblocked() throws IOException {
// Connection is now unblocked
blocked = false;
}
});
}
public Boolean isBlocked() {
return blocked;
}
}
/*
* Sends ADT message to AMQP server.
*/
private void send(String routingKey, String message) throws Exception {
String exchange = applicationConfiguration.getAMQPExchange();
String exchangeType = applicationConfiguration.getAMQPExchangeType();
Connection connection = myConnection.getConnection();
if (connection!=null){
Channel channel = connection.createChannel();
try{
channel.exchangeDeclare(exchange, exchangeType);
channel.basicPublish(exchange, routingKey, null, message.getBytes());
} finally{
// Close the channel if it is no longer needed in this thread
channel.close();
}
}
}
This could be enough, you have an connection with rabbitmq when the system starts.
If you an lazy singleton, the code is just a bit different.
I suggest to not use isOpen() method, please read here:
isOpen
boolean isOpen() Determine whether the component is currently open.
Will return false if we are currently closing. Checking this method
should be only for information, because of the race conditions - state
can change after the call. Instead just execute and try to catch
ShutdownSignalException and IOException Returns: true when component
is open, false otherwise
EDIT**
Question 1:
What are you looking for is the HA client.
RabbitMQ java client by default doesn't support this features, since the version 3.3.0 supports only the reconnect,read this:
...allows Java-based clients to reconnect automatically after network
failure. If you want be sure about your messages you have to create an
robust client able to resists to all fails.
Generally you should consider the fails, for example:
what happen if there is an error during the message publish?
In your case you simply lose the message,You should re-queue the message manually.
Question 2:
I don’t know your code, but connection == null shouldn’t happen, because this procedure is called for first:
#PostConstruct
private void init() {
start(); /// In this way you can initthe connection and you are sure it is called only one time.
}
Anyway you can raise an exception, the question is:
What do I have to do with the message that I was trying to send?
See the question 1
I’d like to suggest to read more about the HA, for example this:
http://www.rabbitmq.com/ha.html
https://www.rabbitmq.com/reliability.html
And this for the client:
https://github.com/jhalterman/lyra (I never used it)
Create a reliable system with rabbitmq is not complex, but you should know some basic concept.
Anyway .. Let me know!
I am experimenting with a WCF service in a Visual Studio unit test. Both the client and the service are configured programmatically.
Currently my code looks like this:
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Tests
{
public abstract class EntityBase
{
}
public class TestEntity : EntityBase
{
public string Name { get; set; }
}
[ServiceContract]
[ServiceKnownType("GetKnownTypes", typeof(ServiceKnownTypesDiscoveryHelper))]
public interface ITestService
{
[OperationContract]
EntityBase GetEntity(string entityName);
}
public class TestService : ITestService
{
public EntityBase GetEntity(string entityName)
{
Type t = Type.GetType(entityName);
return (EntityBase)Activator.CreateInstance(t);
}
}
[TestClass]
public class ServiceTests
{
private static ServiceHost ServiceHost { get; set; }
[ClassInitialize]
public static void ClassInitialize(TestContext testContext)
{
ServiceHost = new ServiceHost(typeof(TestService));
NetTcpBinding wsBinding = new NetTcpBinding();
ServiceHost.AddServiceEndpoint(typeof(ITestService), wsBinding,
"net.tcp://localhost:8011/TestService");
// trying to turn on debugging here
var behavior = ServiceHost.Description.Behaviors.Find<ServiceDebugBehavior>();
behavior.IncludeExceptionDetailInFaults = true;
ServiceHost.Open();
}
[ClassCleanup]
public static void ClassCleanup()
{
ServiceHost.Close();
}
[TestMethod]
public void TestSomething()
{
var binding = new NetTcpBinding();
var endpoint = new EndpointAddress("net.tcp://localhost:8011/TestService");
using (ChannelFactory<ITestService> testServiceFactory =
new ChannelFactory<ITestService>(binding, endpoint))
{
var proxy = testServiceFactory.CreateChannel();
using (proxy as IDisposable)
{
try
{
var entity = proxy.GetEntity(typeof(TestEntity).FullName);
Assert.IsInstanceOfType(entity, typeof(TestEntity));
}
catch (FaultException ex)
{
// copied this from MSDN example
string msg = "FaultException: " + ex.Message;
MessageFault fault = ex.CreateMessageFault();
if (fault.HasDetail == true)
{
var reader = fault.GetReaderAtDetailContents();
if (reader.Name == "ExceptionDetail")
{
ExceptionDetail detail = fault.GetDetail<ExceptionDetail>();
msg += "\n\nStack Trace: " + detail.StackTrace;
}
}
System.Diagnostics.Trace.WriteLine(msg);
}
}
}
}
}
}
If my ServiceKnownTypesDiscoveryHelper does not return known types, I know that my service and client should throw something serialisation related somewhere deep in .NET servicemodel code (if I modify it to return my TestEntity then of course everything works without any issues).
But currently if the service fails, I get only some vague exception messages like:
The socket connection was aborted. This could be caused by an error processing your message or a receive timeout being exceeded by the remote host, or an underlying network resource issue.
and at the end of using() I get
The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state.
(which also is weird - why can't I even dispose the ServiceChannel if it's in a faulted state...)
How do I catch the actual fault which caused the service or the client to fail instead of those vague exception messages?
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);
}
}