Trying to test the reliability of my wcf services. I am calling a wcf service within a loop from the client side. The wcf rest service (webhttpbinding) does some data processing and inserts
records into a database. The entire operation is done within a transaction.
I find that out of about 60 messages (60 times the service being called from inside a loop) only 40 are going through to the db if I set my InstanceContextMode to PerCall. There are no errors no exceptions. The messages are just being dropped.
If I set the InstanceContextMode to Single then I see all messages getting to the db.
Is the InstanceContextMode.Percall being lossy the expected behavior? Also, I do not have concurrency set. Any clarifications would be greatly helpful. Added the code. Using MySQL as the db...
EDIT My bad - I just noticed that I get an exception on the server side - {"Deadlock found when trying to get lock; try restarting transaction"}
This is because there is another transaction invoked on the same records while a transaction is under progress. Fixing it by restarting the transaction if it fails once.
The service
[WebInvoke(UriTemplate = "employee", Method = "POST")]
public long AddNewEmployee(EmployeeEntity newEmployee)
{
EmployeeRepository eRep = new EmployeeRepository();
return eRep.AddNewEmployee(newEventEntity);
}
The repository class constructor initializes the object context
public EmployeeRepository()
{
bd = new EmployeeEntity();
}
Code - The service call
//bd is the object context
//there are two tables
internal long AddNewEmployee(EmployeeEntity newEmployee)
{
bool tSuccess = false;
using (TransactionScope transaction = new TransactionScope())
{
try
{
//get existing employees
var existingEmployees = from employee
in bd.employees select employee;
List<employee> returnedEmployees = new List<employee>();
//Do some processing
returnedEmployees = DoSomeprocessing(existingEmployees);
//Insert returned employees as updates
foreach (employee e in returnedEmployees)
{
bd.employees.AddObject(e);
}
bd.SaveChanges();
returnedEmployees.Clear();
//add to second table
bd.otherdetails.AddObject(newEmployee);
bd.SaveChanges();
//Transaction Complete
transaction.Complete();
tSuccess = true;
}
catch (Exception e)
{
//return something meaningful
return -1;
}
}
if (tSuccess)
{
//End Transaction
bd.AcceptAllChanges();
return 200;
}
else
{
return -1;
}
}
The client side just calls the service in a loop
I highly suggest adding global exception handling for any WCF. It has helped me save many hours of debugging and will catch any unhandled exceptions. It's a little bit more involved than global.ascx in ASP.NET
Step 1 - Implement IErroHander and IServiceBehavior
Notice inside HandleError I'm using Enterprise Library to handle the exception. You can use your custom implementation here as well.
public class ErrorHandler : IErrorHandler, IServiceBehavior
{
public bool HandleError(Exception error)
{
// Returning true indicates you performed your behavior.
return true;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
// Log Exception
ExceptionPolicy.HandleException(error, "ExceptionPolicy");
// Shield the unknown exception
FaultException faultException = new FaultException(
"Server error encountered. All details have been logged.");
MessageFault messageFault = faultException.CreateMessageFault();
fault = Message.CreateMessage(version, messageFault, faultException.Action);
}
private IErrorHandler errorHandler = null;
public ErrorHandler()
{
}
public ErrorHandler(IErrorHandler errorHandler)
{
this.errorHandler = errorHandler;
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher cd = cdb as ChannelDispatcher;
if (cd != null)
{
cd.ErrorHandlers.Add(new ErrorHandler());
}
}
}
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher cd = cdb as ChannelDispatcher;
if (cd != null)
{
cd.ErrorHandlers.Add(new ErrorHandler());
}
}
}
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
}
}
Step 2 - Create Error Element
public class ErrorHandlerElement : BehaviorExtensionElement
{
protected override object CreateBehavior()
{
return new ErrorHandler();
}
public override Type BehaviorType
{
get
{
return typeof(ErrorHandler);
}
}
}
Step 3 - Add Element to web.config
<serviceBehaviors>
<behavior>
<ErrorHandler />
</behavior>
</serviceBehaviors>
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);
}
}
Many posts here on SO asks for help in solving a 400 http bad request. However, I couldn't find a post that explains how to get the underlying cause of the exception.
Assume that both supplier and client running .NET, and also assume that we're on debug time:
If I'm the web service supplier, how can I make sure the client will recieve the reason for the bad request?
(I tried decorating the web service class with the attribute [ServiceBehavior(IncludeExceptionDetailInFaults = true)], but it seems to have no effect).
If I'm the web service client, how can I see what's the reason for the bad request?
You can try including the exception in the fault in the rest service
catch (Exception ex)
{
Log.Fatal("Could not complete process", ex);
throw new WebFaultException<string>(
string.Format(ex.Message),
HttpStatusCode.BadRequest);
}
The above code will handle both scenarios, assuming you have a logger enabled, you can just get the log files on the server, the client would receive the exception message
I guess you could just send the client a friendly message so that they don't anything about your specific implementation, or just create a class that you can call
WebFaultException< T >
with.
EDIT 2
If you are getting an issue before you can get the request, you can try the following
Add a FaultHandler to your Service
[System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class GenericErrorHandlingBehavior : Attribute, IErrorHandler, IServiceBehavior
{
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
//throw new NotImplementedException();
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
//throw new NotImplementedException();
// Adds a CoreServiceBehavior to each ChannelDispatcher
foreach (var channelDispatcherBase in serviceHostBase.ChannelDispatchers)
{
var channelDispatcher = channelDispatcherBase as ChannelDispatcher;
channelDispatcher.ErrorHandlers.Add(new GenericErrorHandlingBehavior());
}
}
public bool HandleError(Exception error)
{
//throw new NotImplementedException();
return true;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
if (error is FaultException)
return;//nothing to do if the error is a fault
//the type cannot be determined[ default to this value]
var exception = new FaultException<GenericServiceFault>(
new GenericServiceFault(), new FaultReason(error.Message));
MessageFault messageFault = exception.CreateMessageFault();
fault = Message.CreateMessage(version, messageFault, exception.Action);
}
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
//throw new NotImplementedException();
}
}
The GenericServiceFault class is just a simple DataContract class,
You can then Log the error in your ProvideFaultMethod, or The HandleError method and provide a meaningful error to the user
You can use the GenericErrorHandlingBehavior on your service implementation as an attribute, i.e [GenericErrorHandlingBehavior]
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.
For a service, there is a group of faults which can be thrown by all operations, so in order to centralize that, I have made a behavior, FaultAdderBehavior, which adds fault contracts to all operations on a service. It seems to work fine as the contracts are added to the WSDL and the client can catch the fault with a line like:
...
catch(FaultException<MyFault> e){ ... }
...
I have also made an IErrorHandler which converts non-fault exceptions to a certain kind of fault. See below.
The problem is, that the fault which is constructed in the error handler cannot be caught on the client. That is, it cannot be caught as a generic FaultException<MyFault>, but only as FaultException.
If I explicitly adds a FaultContract(typeof(MyFault)) to the operation, the client can suddenly catch the generic fault exception just fine.
So that could indicate that something is wrong with my FaultAdderBehavior after all. Or is there something wrong with my error handler?
I have noticed, that the fault.Action given as argument to CreateMessage() is null. This raises my concern.
Below is an example illustrating the problem. It is the method ShouldThrowFault() that causes headaches, while ThrowsDirectly() works exactly as desired.
To summarize, my question is: why can't the client catch the generic FaultException<MyFault> when it comes from the error handler?
[ServiceContract]
public interface IUncatchableFaultService
{
[OperationContract]
// [FaultContract(typeof(MyFault))]
void ShouldThrowFault(string arg1);
[OperationContract]
void ThrowsDirectly();
}
[FaultAdderBehavior(typeof(MyFault), typeof(MyFault2))]
[MyErrorHandlerBehavior]
internal class UncatchableFaultService : IUncatchableFaultService
{
public void ShouldThrowFault(string arg1)
{
throw new Exception();
}
public void ThrowsDirectly()
{
throw new FaultException<MyFault>(new MyFault());
}
}
[DataContract]
public class MyFault
{
}
[DataContract]
public class MyFault2
{
}
public class MyErrorHandlerBehaviorAttribute : Attribute, IServiceBehavior
{
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcherBase dispatcherBase in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher channelDispatcher = dispatcherBase as ChannelDispatcher;
if (channelDispatcher == null) continue;
channelDispatcher.ErrorHandlers.Add(new MyErrorHandler());
}
}
private class MyErrorHandler : IErrorHandler
{
public void ProvideFault(Exception error, MessageVersion version, ref Message message)
{
if (error is FaultException) return;
var fault = new FaultException<MyFault>(new MyFault(), "I am a fault.");
MessageFault messageFault = fault.CreateMessageFault();
message = Message.CreateMessage(version, messageFault, fault.Action);
}
public bool HandleError(Exception error)
{
return false;
}
}
}
public class FaultAdderBehaviorAttribute : Attribute, IContractBehavior
{
private Type[] faults;
public FaultAdderBehaviorAttribute(params Type[] faults)
{
this.faults = faults;
}
public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
}
public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
{
}
public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
{
foreach (OperationDescription op in contractDescription.Operations)
foreach (Type fault in this.faults)
op.Faults.Add(this.ExposeFault(fault));
}
private FaultDescription ExposeFault(Type fault)
{
string action = fault.Name;
DescriptionAttribute attr = (DescriptionAttribute)Attribute.GetCustomAttribute(fault, typeof(DescriptionAttribute));
if (attr != null) action = attr.Description;
FaultDescription description = new FaultDescription(action);
description.DetailType = fault;
description.Name = fault.Name;
return description;
}
}
There is a problem with FaultAdderBehaviorAttribute and you are right to be worried that fault.Action is null.
For FaultException functionality to work properly you must have non-null actions for each fault.
When you declare the FaultContract on the operation itself you are implicitly using WCF's ability to automatically generate the action string (further details). However, when you use your FaultAdderBehaviorAttribute, any default actions for declared operations have already been generated, and you are failing to provide a valid action.
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);
}
}