Logging All Exceptions in ASP.NET and WCF

I find it useful to log all uncaught exception centrally in ASP.NET web applications and WCF services. In ASP.NET web applications, it is relatively easy – just put logging code in Global.asax.cs like this:

        protected void Application_Error(object sender, EventArgs e)
        {
            var ex = Server.GetLastError();
            if (ex is HttpUnhandledException && ex.InnerException != null)
                ex = ex.InnerException;

            var httpException = ex as HttpException;
            if (httpException != null && httpException.GetHttpCode() == 404)
            {
                // Log this
                Uri urlReferrer = null;
                try
                {
                    urlReferrer = Request.UrlReferrer;
                }
                catch (Exception)
                {
                    // ignored
                }
                Log.WarnFormat("{0} (Client address={1}, UrlReferrer={2})", ex.Message, Request.UserHostAddress, urlReferrer);
            }
            else
                Log.Error(ex.Message, ex);
        }

One thing to note is that uncaught exceptions are wrapped in an HttpUnhandledException – that is why I extract the inner exception. Another thing to note is that you may not want to log 404 not found or as here, log it with a different level.

With WCF, it is (as always) a little more complicated. Putting code in Application_Error in Global won’t work. You have to write one or two classes that implement System.ServiceModel.Dispatcher.IErrorHandler and System.ServiceModel.Description.IServiceBehavior, and do some configuration work. There are different ways to do this. See e.g. http://codeidol.com/csharp/wcf/Faults/Error-Handling-Extensions/. But here is my simplistic solution:

using System;
using System.Collections.ObjectModel;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using log4net;

namespace MyNamespace
{
    public class ErrorHandler : IErrorHandler, IServiceBehavior
    {
        private static readonly ILog Log = LogManager.GetLogger(typeof(ErrorHandler));

        #region IErrorHandler Members

        public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
        {
            var faultException = new FaultException("Error caught in API error handler. Please see API server log for details.");
            var messageFault = faultException.CreateMessageFault();
            fault = Message.CreateMessage(version, messageFault, faultException.Action);
        }

        public bool HandleError(Exception error)
        {
            Log.Error(error.Message, error);
            return false;
        }

        #endregion

        #region IServiceBehavior Members

        // This behavior modifies no binding parameters.
        public void AddBindingParameters(
            ServiceDescription description,
            ServiceHostBase serviceHostBase,
            Collection<ServiceEndpoint> endpoints,
            BindingParameterCollection parameters
            )
        {
        }

        // This behavior is an IErrorHandler implementation and must be applied to each ChannelDispatcher.
        public void ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
        {
            foreach (ChannelDispatcher chanDisp in serviceHostBase.ChannelDispatchers)
            {
                chanDisp.ErrorHandlers.Add(this);
            }
        }

        public void Validate(ServiceDescription description, ServiceHostBase serviceHostBase)
        {
        }

        #endregion
    }

    public class ErrorHandlerBehavior : BehaviorExtensionElement
    {
        protected override object CreateBehavior()
        {
            return new ErrorHandler();
        }

        public override Type BehaviorType
        {
            get { return typeof(ErrorHandler); }
        }
    }
}

In ProvideFault, which executes on the request thread, I return a generic error to the client (for security reasons). It is logged in HandleError, which executes after the response has been sent.

Here is part of web.config to use this error handler:

  </system.serviceModel>
    <extensions>
      <behaviorExtensions>
        <add name="ErrorHandler" type="MyNamespace.ErrorHandlerBehavior, MyNamespace" />
      </behaviorExtensions>
    </extensions>
    <behaviors>
      <serviceBehaviors>
        <behavior name="MyBehavior">
          <!-- ... -->
          <ErrorHandler/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
Advertisements

System.ServiceModel.ServiceActivationException when EnsureCertificateCanDoKeyExchange (netTcpBinding only)

This was a strange problem. I had a service with two endpoints, one with basicHttpBinding and one with netTcpBinding, both using transport security with certificates. If I tried to browse the service using IE I got a ServiceActivationException, and looking at the event log I was this:

“The certificate ‘SERIALNUMBER=…, CN=.., OU=Test, DC=Nod1, DC=Services, C=SE’ must have a private key that is capable of key exchange. The process must have access rights for the private key.”

I had checked that IIS APPPOOL\DefaultAppPool had access (full control) to the private key (using Certificate Manager, Manage Private Keys…) (This was Windows Server 2008 R2 with IIS 7.5 and .NET Framework 3.5.)

The strangest thing is that when I removed the netTcp endpoint, leaving only basicHttp, I didn’t get this error! From what I could see, they were both configured exactly the same.

It turned out to be a problem with the private key after all. Previously, when I imported the certificate (including private key) from a .p12 file using Windows Explorer, it ended up in Current User and I then moved it to Local Machine using the certificate console. That was obviously not the right way to do it. Instead, I had to open the certificate console, right click on Local Machine/Personal and choose All Tasks->Import… That imports the certificate to the right place. (You still must assign access to the private key to IIS APPPOOL\DefaultAppPool.)

Securing a WCF Service in Windows Azure with SSL

It is easy to find articles on how to create https (SSL) endpoints in Windows Azure services, e.g. this one. But I didn’t find information on how to configure the actual service, so I had to experiment. Here is a summary of what is needed with .NET Framework 4.

Http Endpoint

With .NET Framework 4, you can skip declaring your service and endpoints in the configuration file. Default is an endpoint with basicHttpBinding. But you you probably want to enable metadata publishing using a service behavior:

    <behaviors>
      <serviceBehaviors>
        <behavior>
          <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
          <serviceMetadata httpGetEnabled="true" />
          <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>

Https Endpoint

To secure your service with SSL (https), you must create a binding configuration, a service definition and an endpoint using the binding configuration like this:

    <bindings>
      <basicHttpBinding>
        <binding name="SecureBasic">
          <security mode="Transport" />
        </binding>
      </basicHttpBinding>
    </bindings>
    <services>
      <service name="Namespace.TestService">
        <endpoint binding="basicHttpBinding" bindingConfiguration="SecureBasic" name="basicHttp" contract="Namespace.ITestService" />
      </service>
    </services>

To publish meta data, you would want to change httpGetEnabled=”true” to httpsGetEnabled=”true”:

<serviceMetadata httpsGetEnabled="true" />

Http and Https Endpoints

If you, for testing purposes, want to have both http and https bindings, you add two endpoints:

<endpoint binding="basicHttpBinding" bindingConfiguration="" name="basicHttp" contract="Namespace.ITestService" />
<endpoint binding="basicHttpBinding" bindingConfiguration="SecureBasic" name="basicHttpSecure" contract="Namespace.ITestService" />

To publish metadata on both endpoints, modify serviceMetadata like this:

<serviceMetadata httpsGetEnabled="true" httpGetEnabled="true" />

Using a WCF Custom Instance Provider

I recently developed a couple of WCF services that I wanted to share some common data, which I stored to and read from an XML file using standard serialization. (I didn’t want the trouble of setting up a database and writing code to access it.) And I didn’t want to load and save this file for every service call. So what I did was to create a custom instance provider that (a) loaded data from the file each time a service was instantiated and saved each time it was released and (b) passed a reference to the shared data to the created service instance.

First I created a class that implemented IInstanceProvider:

public class MyInstanceProvider : IInstanceProvider
{
    private static List<Case> caseList = null;

    #region IInstanceProvider Members

    public object GetInstance(InstanceContext instanceContext)
    {
        if (caseList == null)
            caseList = Load();
        return new TheService(logger, caseList);
    }

    public void ReleaseInstance(InstanceContext instanceContext, object instance)
    {
        Save();
        if (instance is IDisposable)
            ((IDisposable)instance).Dispose();
    }

    #endregion
}

To be able to use this instance provider, I then created a custom attribute using this code:

public class CustomInstancingBehaviorAttribute : Attribute, IContractBehaviorAttribute, IContractBehavior
{
    #region IContractBehaviorAttribute Members

    public Type TargetContract
    {
        get { return typeof(ITheService); }
    }

    #endregion

    #region IContractBehavior Members

    public void AddBindingParameters(ContractDescription description, ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection parameters)
    {
        return;
    }

    public void ApplyClientBehavior(ContractDescription description, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        return;
    }

    public void ApplyDispatchBehavior(ContractDescription description, ServiceEndpoint endpoint, DispatchRuntime dispatch)
    {
        dispatch.InstanceProvider = new MyInstanceProvider();
    }

    public void Validate(ContractDescription description, ServiceEndpoint endpoint)
    {
        return;
    }

    #endregion
}

The important method above is ApplyDispatchBehavior that specifies that my instance provider should be used instead of the default one.

I could then use this attribute with my service implementation:

[ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Single)]
[CustomInstancingBehavior]
public class TheService : ITheService
{
    private Logger logger;
    private List<EcosCase> caseList;

    public TheService()
    {
    }

    public TheService(Logger logger, List<Case> caseList)
    {
        this.caseList = caseList;
    }

    …
}

The reason for specifying ConcurrencyMode.Single is that I did not want to write synchronization code to mitigate the risk that multiple threads could update the list simultaneously.

System.ServiceModel.CommunicationException: The socket connection was aborted.

I recently encountered this misleading error from WCF:

System.ServiceModel.CommunicationException: 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. Local socket timeout was ’00:00:59.9090000′. —>  System.IO.IOException: The read operation failed, see inner exception. —>  System.ServiceModel.CommunicationException: 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. Local socket timeout was ’00:00:59.9090000′. —>  System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host.

I got this when calling a WCF service (S1), which in turned called another WCF service (S2). It turned out that the root cause was a bad schema. The S2 operation GetData returned a string:

<wsdl:message name="NaPersonPostXMLWS_getData_OutputMessage">
  <wsdl:part name="getDataReturn" type="xsd:string" />
</wsdl:message>

This string was in fact an XML document of a known schema, which caused the problem. Here is an extract of the schema:

<xs:complexType name="CivilstandTYPE">
    <xs:sequence>
        <xs:element name="CivilstandKod" nillable="true" minOccurs="0"/>
        …

Note that the CivilstandKod element has no type. Therefore, XSD.exe generated a field of type object:

public partial class CivilstandTYPE
{
    private object civilstandKodField;
    …

S2 returned valid XML:


<Civilstand>
  <CivilstandKod>OG</CivilstandKod>
</Civilstand>

But when S1 deserialized this, I discovered that civilstandKodField became an instance of type XmlNode[], i.e. an array of XmlNodes, and civilstandKodField[0].Value was “OG”. When S1 was about to return the response, it had to serialize the object structure again to XML, and obviously didn’t know what to do with this field, which generated the error.

Changing the schema to

<xs:complexType name="CivilstandTYPE">
    <xs:sequence>
        <xs:element name="CivilstandKod" type="CivilstandKodTYPE" nillable="true" minOccurs="0"/>
        …

and regenerating the proxy classes fixed the problem!