NServiceBus retrying message even though no exception - nhibernate

I got a wierd problem with NServiceBus retying the message X number of times although no exception is thrown in the handler. There is some information out there dealing with the NHibernate session and the NSB ambiant transaction. Since no error is thrown I'm not 100% sure of the problem and thus can't really decide on what to do.
I got NSB configured with Castle Windsor like so:
IWindsorContainer container = new WindsorContainer(new XmlInterpreter());
container.Install(new ContainerInstaller());
container.Install(new UnitOfWorkInstaller(AppDomain.CurrentDomain.BaseDirectory, Castle.Core.LifestyleType.Scoped));
container.Install(new FactoryInstaller(AppDomain.CurrentDomain.BaseDirectory));
container.Install(new RepositoryInstaller(AppDomain.CurrentDomain.BaseDirectory));
Configure.With()
.CastleWindsorBuilder(container)
.FileShareDataBus(Properties.Settings.Default.DataBusFileSharePath)
.MsmqTransport()
.IsTransactional(true)
.PurgeOnStartup(false)
.UnicastBus()
.LoadMessageHandlers()
.ImpersonateSender(false)
.JsonSerializer();
The UnitOfWorkInstaller registers the unit of work (the NHibernate session) like so:
public void Install(IWindsorContainer container, IConfigurationStore store)
{
var fromAssemblyDescriptor = AllTypes.FromAssemblyInDirectory(new AssemblyFilter(_installationPath));
container.Register(fromAssemblyDescriptor
.IncludeNonPublicTypes()
.Pick()
.If(t => t.GetInterfaces().Any(i => i == typeof(IUnitOfWork)) && t.Namespace.StartsWith("Magma"))
.WithService.AllInterfaces()
.Configure(con => con.LifeStyle.Is(_lifeStyleType).UsingFactoryMethod(k => k.Resolve<IUnitOfWorkFactory>().Create())));
}
So each time a message arrives the same unit of work is used for all the repositories. I read that manually rolling back the current transaction results in an error (I don't really know why) and I also know that NSB creates a child container for every transport message and that this child container is disposed after the processing of the message. The problem is that when the child container is disposed the unit of work is disposed this way:
public void Dispose()
{
if (!_isDisposed)
{
DiscardSession();
_isDisposed = true;
}
}
private void DiscardSession()
{
if (_transaction != null && _transaction.IsActive)
{
_transaction.Dispose();
}
if (Session != null)
{
Session.Dispose();
}
}
My handlers are structured like this: (the _unitOfWork is passed as a constructor dependency)
public void Handle(<MessageType> message)
{
using (_unitOfWork)
{
try
{
// do stuff
_unitOfWork.Commit();
}
catch (Exception ex)
{
_unitOfWork.Rollback();
// rethrow so the message stays in the queue
throw;
}
}
}
I found out that if I don't commit the unit of work (which flushes the session and commit the transaction) I got an error saying that the message has retried beyond the max retry count bla bla bla...
So it seems to be linked with the NHibernate session and the way it's created and disposed of but since it's created within the unit of work I can't really use the session factory. I read that I could use the IMessageModule to create and dispose the session but again I don't know if this is the right way to go since I don't understand what's causing the error in the first place.
So to recap:
I'm using a scoped unit of work so that all the handler dependencies using it will share the same instance (thx to the child container, BTW: I've setup the unit of work as transient thinking that the child container will treat all transient object as singleton within that container but I saw that the unit of work wasn't shared so this is why it's setup as scoped)
I'm wrapping my handler in a using(_unitOfWork) { } statement to dispose the unit of work after each processing.
When the unit of work get's disposed, the NHibernate session is also disposed
If I don't explicitly call Commit on the _unitOfWork, the message retries beyond the max retry count and then an error is thrown.
What is causing this behavior? and is the IMessageModule is the answer for this?

I think I narrowed it down a bit... I removed all of the using(_unitOfWork), the _unitOfWork.Commit() and the _unitOfWork.Rollback() and let the NSB TransactionScope do the work of commit or rollback the transaction since the NHibernate's Session was enlisting in the NSB transaction scope.
I also started to use the NHibernate session's transaction (Session.Transaction) instead of grabbing a reference to it through Session.BeginTransaction() and using this one. I've copy/pasted my UoW implementation so you can see the differences (the old code is in comments).
I don't know if my changes account for anything but using the Session's transaction and removing the flushes since it's taking care of within the transaction commit seems to have solved the problem... I don't have to explicitly call the Commit method in order for the message to be successfully processed anymore. Here is my UoW implementation:
public class NHibernateUnitOfWork : INHibernateUnitOfWork
{
//private ITransaction _transaction;
private bool _isDisposed;
private bool _isInError;
public ISession Session { get; protected set; }
public NHibernateUnitOfWork(ISession session)
{
Contract.Requires(session != null, "session");
Session = session;
//_transaction = Session.BeginTransaction();
// create a new transaction as soon as the session is available
Session.BeginTransaction();
_isDisposed = false;
_isInError = false;
}
public void MarkCreated(Object entity)
{
// assert stuff
try
{
Session.SaveOrUpdate(entity);
//Session.Flush();
}
catch (HibernateException ex)
{
HandleError();
throw;
}
}
public void MarkUpdated(Object entity)
{
// assert stuff
try
{
Session.Update(entity);
//Session.Flush();
}
catch (HibernateException ex)
{
HandleError();
throw;
}
}
public void MarkSavedOrUpdated(Object entity)
{
// assert stuff
try
{
Session.SaveOrUpdate(entity);
//Session.Flush();
}
catch (HibernateException)
{
HandleError();
throw;
}
}
public void MarkDeleted(Object entity)
{
// assert stuff
try
{
Session.Delete(entity);
//Session.Flush();
}
catch (HibernateException ex)
{
HandleError();
throw;
}
}
public void Commit()
{
// assert stuff
try
{
//Session.Flush();
//_transaction.Commit();
Session.Transaction.Commit();
}
catch (HibernateException ex)
{
HandleError();
throw;
}
}
public void Rollback()
{
// assert stuff
try
{
//if (!_transaction.WasRolledBack)
//{
// _transaction.Rollback();
//}
Session.Transaction.Rollback();
}
catch (HibernateException ex)
{
HandleError();
throw;
}
}
public void Dispose()
{
if (!_isDisposed)
{
DiscardSession();
_isDisposed = true;
}
}
private void DiscardSession()
{
//if (_transaction != null && _transaction.IsActive)
//{
// _transaction.Dispose();
//}
if (Session != null)
{
try
{
// rollback all uncommitted changes
if (Session.Transaction != null && Session.Transaction.IsActive)
{
Session.Transaction.Rollback();
}
//Session.Clear();
Session.Close();
}
catch (Exception)
{ }
finally
{
Session.Dispose();
}
}
}
private void HandleError()
{
_isInError = true;
//if (_transaction != null && _transaction.IsActive)
//{
// _transaction.Rollback();
//}
if (Session.Transaction != null && Session.Transaction.IsActive)
{
Session.Transaction.Rollback();
}
}
// assert methods
}
Does any of this makes sense? I still don't know what caused the error in the first place but it seems to have to do with disposing the NHibernate Session before the transaction scope finishes.

Related

ASP.NET Core Interception with Castle.DinamicProxy doesn't throw Exception with Async Methods !!! How can I solve this?

I have been creating a project with Aspect Oriented Programming paradigm and
I have an "ExceptionLogAspect" class attribute which is used on business methods to log the errors throwing from them.
public class ExceptionLogAspect : MethodInterception
{
private readonly LoggerServiceBase _loggerServiceBase;
private static byte _risk;
public ExceptionLogAspect(Type loggerService, byte risk)
{
if (loggerService.BaseType != typeof(LoggerServiceBase))
{
throw new System.Exception(AspectMessages.WrongLoggerType);
}
_loggerServiceBase = (LoggerServiceBase)Activator.CreateInstance(loggerService);
_risk = risk;
}
protected override void OnException(IInvocation invocation, System.Exception e)
{
var logDetailWithException = GetLogDetail(invocation);
logDetailWithException.ExceptionMessage = e.Message;
_loggerServiceBase.Error(logDetailWithException);
}
}
This Aspect migrates MethodInterception class that I created with Castle.DinamicProxy package. And OnException method included by MethodInterception logs the exception data.
public abstract class MethodInterception:MethodInterceptionBaseAttribute
{
protected virtual void OnBefore(IInvocation invocation){}
protected virtual void OnAfter(IInvocation invocation){}
protected virtual void OnException(IInvocation invocation, System.Exception e){}
protected virtual void OnSuccess(IInvocation invocation){}
public override void Intercept(IInvocation invocation)
{
var isSuccess = true;
OnBefore(invocation);
try
{
invocation.Proceed();//Business Method works here.
}
catch (Exception e)
{
isSuccess = false;
OnException(invocation, e);
throw;
}
finally
{
if(isSuccess)
OnSuccess(invocation);
}
OnAfter(invocation);
}
}
When I run the code and try-catch block doesn't work for Exception. So catch block isn't called and no messages are logged.
If I turn the business method into a syncronous method, exception will be thrown and data will be logged.
How can I solve this asynchronous method problem?
I tried this solution, it works properly.
Intercept method has to be like this to make this process asynchronous.
Otherwise, this method doesn't work properly for async.
There are some other ways, for example Castle CoreAsync Interceptor, you can find it on Github or NuGet.
https://github.com/JSkimming/Castle.Core.AsyncInterceptor
public override void Intercept(IInvocation invocation)
{
var isSuccess = true;
OnBefore(invocation);
try
{
invocation.Proceed(); //Metodu çalıştır
if (invocation.ReturnValue is Task returnValueTask)
{
returnValueTask.GetAwaiter().GetResult();
}
if (invocation.ReturnValue is Task task && task.Exception != null)
{
throw task.Exception;
}
}
catch (Exception e)
{
isSuccess = false;
OnException(invocation, e);
throw;
}
finally
{
if (isSuccess)
OnSuccess(invocation);
}
OnAfter(invocation);
}

Exception handling on Tasks without Wait()

What is the best approach to handle exception of a Task that does not Wait()? I read a couple of blogs which spoke about using ContinueWith because regular try/catch cannot handle Task exception. Below code does not validate that.
Method 1:
public class Service1 : IService1
{
public string GetData(int value)
{
var a = Task.Factory.StartNew(ThrowException);
return string.Format("You entered: {0}", value);
}
private void ThrowException()
{
try
{
Thread.Sleep(6000);
throw new ArgumentException("Hello from exception");
}
catch (Exception)
{
Trace.WriteLine("Log it");
}
}
}
Method 2:
public class Service1 : IService1
{
public string GetData(int value)
{
var a = Task.Factory.StartNew(ThrowException);
a.ContinueWith(c => { Trace.WriteLine("Log it"); },
TaskContinuationOptions.OnlyOnFaulted);
return string.Format("You entered: {0}", value);
}
private void ThrowException()
{
Thread.Sleep(6000);
throw new ArgumentException("Hello from exception");
}
}
Are Method 1 and Method 2 doing the same thing? Is there a better way to implement this.
Edit: Added code snippet for continuewith.
Both methods work and they are equivalent. Choose what you like most. The continuation based one has the advantage that you can make the error handling into an extension method (or some other central helper).
Are you aware that IIS worker processes can suddenly disappear for many reasons? In that case background work is lost. Or, the work faults but the error handler disappears.
It looks like it will work if all you need is to call methods on the Trace class. However, if you need custom exception handling, I would recommend injecting an exception handler:
private void ThrowException(Action<Exception> handleExceptionDelegate)
{
try
{
// do stuff that may throw an exception
}
catch (Exception ex)
{
if (handler != null)
handleExceptionDelegate(ex);
}
}
Then you could do
Task.Factory.StartNew(() =>
{
ThrowException((ex) =>
{
// Handle Exception
});
});

NHibernate Transaction.Commit automatically closes Session

I have a web application that is using the absolute latest version (3.3) and is using session-by-request session management within a HttpModule, so there are no problems with multiple session conflicts. Unfortunately, I am finding that the session is getting automatically closed immediately after I perform a Transaction.Commit which I only do when I am actually performing a Create, Update or Delete. I am finding this within my NHibernate log.
I know for a fact that I am not doing it, because the only call to the ISession.Close function is being done within my HttpModule.
Yes, of course I can put code in my SessionManager to check for the IsClosed parameter and then use the OpenSession function instead of GetCurrentSession, but should this even be happening? Is there some way that I can prevent this either through my configuration or some attribute that I could set on the Session or Transaction object or is this just one of the new features that I can't find any documentation anywhere on?
Please help.
Brian
I was asked to provide some code, so here is the code for the HttpModule:
public class NhibernateModule : IHttpModule
{
public void Dispose()
{
}
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(context_BeginRequest);
context.EndRequest += new EventHandler(context_EndRequest);
}
public void context_BeginRequest(Object sender, EventArgs e)
{
WebSessionContext.Bind(NhibernateSessionManager.GetContextSession());
}
public void context_EndRequest(Object sender, EventArgs e)
{
ISession session = WebSessionContext.Unbind(NhibernateSessionManager.SessionFactory);
if (session != null)
{
if (session.Transaction != null && session.Transaction.IsActive)
{
session.Transaction.Rollback();
}
else
session.Flush();
session.Close();
}
}
}
}
Next, you will find the original code that I am using within my SessionManager:
public sealed class NhibernateSessionManager
{
private readonly ISessionFactory sessionFactory;
public static ISessionFactory SessionFactory
{
get { return Instance.sessionFactory; }
}
private ISessionFactory GetSessionFactory()
{
return sessionFactory;
}
public static NhibernateSessionManager Instance
{
get { return NestedSessionManager.sessionManager; }
}
public static ISession GetContextSession()
{
ISession session;
if (CurrentSessionContext.HasBind(SessionFactory))
{
session = SessionFactory.GetCurrentSession();
}
else
{
session = SessionFactory.OpenSession();
CurrentSessionContext.Bind(session);
}
return session;
}
private NhibernateSessionManager()
{
if (sessionFactory == null)
{
Configuration configuration;
configuration = new Configuration().Configure(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "web.config"));
log4net.Config.XmlConfigurator.ConfigureAndWatch(new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "web.config")));
//Configuration configuration = new Configuration().Configure();
if (configuration == null)
{
throw new InvalidOperationException("NHibernate configuration is null.");
}
else
{
sessionFactory = configuration.BuildSessionFactory();
if (sessionFactory == null)
throw new InvalidOperationException("Call to BuildSessionFactory() returned null.");
}
}
}
class NestedSessionManager
{
internal static readonly NhibernateSessionManager sessionManager = new NhibernateSessionManager();
}
}
Last, here is a function that is currently causing the session to close immediately after the Transaction.Commit(). Each of the inner functions retrieve the current session and then process a Save call.
public static Int32 AddVideo(VideoFile Video, Int32 UserID, Int16 InstID)
{
log.Debug("Begin AddVideo");
Int32 FileID = 0;
using (ISession Session = NhibernateSessionManager.GetContextSession())
{
using (ITransaction Transaction = Session.BeginTransaction())
{
Video.Created = DateTime.Now;
Video.Modified = DateTime.Now;
FileID = (Int32)Session.Save(Video);
Video.FileID = FileID;
// Need to process through all the categories and insert records into the ivxFileCategories table
// to associate the newly created file with the chosen categories
if (Video.CategoryAssociations != null)
{
log.Info("Number of categories to be associated with the video: " + Video.CategoryAssociations.Count);
for (int i = 0; i < Video.CategoryAssociations.Count; i++)
{
CategoryFileAssociation Assoc = (CategoryFileAssociation)Video.CategoryAssociations[i];
Assoc.FileID = FileID;
AssociationManager.AddCategoryFileTransaction(Assoc);
}
}
// Need to add the default file access for the UserDetail that is creating the new video which will always
// be Admin because the UserDetail creating a file should always have admin access over the file, no matter
// what their default role is.
AssociationManager.AddFileAccessTransaction(FileID, UserID, UserClassConstants.IVXUSERCLASS_ADMIN);
// Need to add the institutional association based on whether the new video was created by a librarian
// or one of the iVidix admins
AssociationManager.AddInstitutionFileTransaction(InstID, FileID);
Transaction.Commit();
}
}
log.Debug("End AddVideo");
return FileID;
}
The session will by disposed in the AddVideo method because you are using the using Statement for the session.
using (ISession Session = NhibernateSessionManager.GetContextSession())
{
}
I would totally recommend striping away the transaction stuff
using (ISession Session = NhibernateSessionManager.GetContextSession())
{
using (ITransaction Transaction = Session.BeginTransaction())
{
...
}
}
and move it into the begin/end request. This way you have a UOW for each request. The reason you session is closing IS because of the using statement.
Your begin request code can then be something along the lines of:-
var session = sessionFactory.OpenSession();
CurrentSessionContext.Bind(session);
session.BeginTransaction();
and your end request:-
var session = CurrentSessionContext.Unbind(sessionFactory);
if (session != null)
{
if (session.Transaction.IsActive)
{
try
{
session.Transaction.Commit();
}
catch
{
session.Transaction.Rollback();
}
}
session.Close();
}
I have this in my global.asax
public static ISessionFactory SessionFactory { get; set; }
and this in my repositories
public ISession Session
{
get
{
return SessionFactory.GetCurrentSession();
}
}
Now I use IOC to pass my sessionFactory to my repository layers, else you will need to pass this in yourself manually.
Committing transaction will end that session.
Move your transaction start to context_BeginRequest and the commit/cleanup in context_EndRequest
I actually don't like session in view pattern and prefer to keep my transactions open as short as possible and rather inject session into the controller. I then perform the transaction in the action or service. I prefer this fine grained control of the transactions and keeps them short lived avoiding any locking issues.

NHibernate Session/Transaction Woes

I'm trying to use a session-per-request pattern and I'm having trouble with getting a record right after it's been saved. The reason for doing that being that I need to get the records that the foreign keys relate to.
Some (simplified) code:
// UnitOfWork Attribute
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
SessionFactory.Begin();
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if (filterContext.Exception == null) {
try {
SessionFactory.Commit();
} catch {
SessionFactory.Rollback();
throw;
}
} else {
SessionFactory.Rollback();
throw filterContext.Exception;
}
}
// Service layer
public void Save(Form form)
{
_repository.Save(form);
var savedForm = _repository.Get(form.Id);
SendEmail(savedForm);
}
// Repository
public void Save(Form form)
{
var session = SessionFactory.CurrentSession;
session.SaveOrUpdate(form);
}
The problem is that when I try to get the record, the transaction hasn't yet been committed, so it just gives me what's already in the session. Am I just going to have to commit the transaction after saving, and open a new transaction for getting it?
Thanks
Update:
I've implemented the Agathas Storefront way of doing things, giving the service layer control over the transactions, i.e.:
public class UnitOfWork : IUnitOfWork
{
public void Commit()
{
var session = SessionFactory.CurrentSession;
using (ITransaction transaction = session.BeginTransaction())
{
try {
transaction.Commit();
} catch {
transaction.Rollback();
throw;
}
}
}
public void Clear()
{
var session = SessionFactory.CurrentSession;
session.Clear();
}
}
Then in the service layer:
public void SaveForm(Form form)
{
_repository.Save(form);
_uow.Commit();
_uow.Clear();
var savedForm = _repository.Get(form.Id);
SendEmail(savedForm);
}
Update 2
OK, I think I've found a suitable solution. I've gone back to the transaction-per-request pattern, and after saving the form I'm now flushing it and then evicting the form from the session in order to force NH to get it from the DB.
// Service layer
public void SaveForm(Form form)
{
_repository.Save(form);
var savedForm = _repository.Get(form.Id);
SendEmail(savedForm);
}
// Repository
public void Save(Form form)
{
var session = SessionFactory.CurrentSession;
session.SaveOrUpdate(form);
session.Flush();
session.Evict(form);
}
Until you flush the session or reduce your transaction scope you won't have an ID because nhibernate will not have inserted the record.
One option, change your id strategy from an identity generated in the database to one that is managed by NHibernate (see options at http://nhibernate.info/doc/nh/en/index.html#mapping-declaration-id-generator) or another option change your Session's flushmode to FlushMode.Auto.

WCF reliability issues

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>