Category Archives: REST

Beware of changing variable names in SOAP Webservices

When improving upon your SOAP webservices currently in production, it might occur to you to change variable names in method signatures to make the more readable and conform to your coding practices. After these are the small incremental improvements we make to reduce our technical debt. But before doing this, please beware of the ramifications.

A WSDL describing a SOAP webservice is “strongly typed” in the sense that method signatures including method names, variable names and their types, once defined, cannot be changed with breaking backward compatibility. And Since SOAP does not provide an easy way for clients to detect an API change, altering either of the constructs that define your WSDL guarantees a temporal disruption of service due to your current API consumers.

For example, say you have a SOAP webservice exposing this method:

public Report GetReport(string ReportName)

which is defined as part of your WSDL. If you later change this this method to say

public Report GetReport(string reportName)

the service will receive null as the reportName in some client implementations. Others may simply crash since this endpoint is no longer known. If one is spending quite some time working with SOAP based services on a daily basis, it becomes almost of a second nature to regenerate the client proxy based on the published WSDL to ensure consistency otherwise, you get these strange behaviors. However, if you are spending a lot of time in RESTFul services, chances are you are not worrying about these types of problems. In this case, there is a good chance you will rename a webservice API parameter name in the name of improving readability (nothing wrong with that) only to find out that the next morning none of your clients are able to connect to your API.

And no, unit tests will not catch this.

Use case for custom Log4Net MemoryAppender

When developing an enterprise grade desktop or web application, there is always a need to be able to view application logs including activities in one of the views. These activities could include user actions, system warnings or errors, triggered from the client or server.

Server errors or warnings could be polled for, through a WCF or RESTFul API, and stored in an inmemory collection that is bound to some data grid in a view. Log messages generated on the client, either through exceptions, caught and logged somewhere or via actions that require auditing such as changing sensitive system parameters, can also share the same inmemory collection with data retreived from the server.

Log4Net is a popular .NET library used for application logging. The general pattern in using Log4Net is as follows:

1. Get an ILog instance.
2. Call any of the API on the ILog instance to log your message.

Log4Net log messages eventually are persisted to entitities called Appenders. There are FileAppenders, ConsoleAppenders, InMemoryAppenders and many more. An Appender is the destination of a log message.

We can leverage an MemoryAppender to catch application error or warning messages into a collection which can then be bound to a view. The code is as follows:

 public class NotifyingInMemoryLog4NetAppender : MemoryAppender
    {
        private readonly static BindableCollection<LogMessage> LogData = new BindableCollection<LogMessage>();
        private readonly static List<LoggingEvent> CachedEvents = new List<LoggingEvent>();
        private readonly Timer _flushTimer;
        private readonly object _lock  = new object();
        public NotifyingInMemoryLog4NetAppender()
        {
            _flushTimer = new Timer((arg) =>
            {
                lock (_lock)
                {
                    if (CachedEvents.Count > 0)
                    {
                        LogData.AddRange(CachedEvents.Select(Convert));
                        CachedEvents.Clear();
                        
                    }    
                }
            }, null, 5000, 3000);
        }

        public IListExtended Logs
        {
            get { return LogData; }
        }

        private DateTime _lastEventTime = DateTime.Now;
        private readonly TimeSpan _threshold = new TimeSpan(0, 0, 0, 0, 500);

        protected override void Append(LoggingEvent loggingEvent)
        {
            var now = DateTime.Now;
            
            // if we are getting flooded, cache the events and process them later.
            if (now - _lastEventTime < _threshold)
            {
                lock (_lock)
                {
                    CachedEvents.Add(loggingEvent);
                }
            }
            else
            {
                LogData.Add(Convert(loggingEvent));    
            }

            _lastEventTime = DateTime.Now;
        }

        private Severity ConvertFrom(Level level)
        {
            if (level == Level.Error)
                return Severity.Error;
            if (level == Level.Warn)
                return Severity.Warning;
            if (level == Level.Info)
                return Severity.Information;

            return Severity.Trace;
        }

        private LogMessage Convert(LoggingEvent loggingEvent)
        {
            return new LogMessage
            {
                Timestamp = loggingEvent.TimeStamp,
                Message = loggingEvent.RenderedMessage,
                Severity = ConvertFrom(loggingEvent.Level)
            };
            
        }
    }

This custom appender is used as follows:

<log4net debug="true">
    <appender name="InMemory" type="Infrastructure.Dictionary.Facades.NotifyingInMemoryLog4NetAppender, Pidac.Infrastructure.Dictionary ">
       <conversionPattern value="%5level [%thread] (%file:%line) - %message%newline" />
    </appender>
    <appender name="Console" type="log4net.Appender.ConsoleAppender">
      <layout type="log4net.Layout.PatternLayout">
        <!-- Pattern to output the caller's file name and line number -->
        <conversionPattern value="%5level [%thread] (%file:%line) - %message%newline" />
      </layout>
    </appender>
    <appender name="RollingFile" type="log4net.Appender.RollingFileAppender">
      <file value="dosewin.log" />
      <appendToFile value="true" />
      <rollingStyle value="Size" />
      <maxSizeRollBackups value="10" />
      <maximumFileSize value="10MB" />
      <staticLogFileName value="true" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
      </layout>
    </appender>
    <root>
      <!--ALL,DEBUG,INFO,WARN,ERROR,FATAL-->
      <level value="DEBUG" />
      <appender-ref ref="Console" />
      <appender-ref ref="RollingFile" />
      <appender-ref ref="InMemory" />
    </root>
  </log4net>

When you call any of the ILog API’s the Append(LoggingEvent loggingEvent) method in our custom implementation is called. We add this message to a local queue and then subsequently pass this on to the Static LogData structure which could be bound to a view. Note that since we do not really care about preserving the order in which the messages appear, since most datagrids today provide an ability for data to be sorted prior to presentation, a simple List will suffice for our queue.

And that is it.

RESTful Webservice testing using cURL on Windows

Testing RESTful webservices on Windows can be done outside of the browser using an open source command tool called cURL, which can be donwloaded here: http://curl.haxx.se/download.html After downloading the Windows version based on your operation system, remember to add the location of the cURL binary to your system’s path environment variable.

The advantage of cURL over conventional forms such as browser is the ability to automate testing tasks and execute different types of HTTP requests. See the cURL online documentation for details.

Assume you have a RESTFul webservice available locally at http://localhost:8080/controlroomws. This webservice exposes a list of users via /UserList and the details of a specific user can be retrieved via /UserList/username. Through the webservice API, you can also persist users to a back end database using an HTTP POST request.

Say we will like to persist the following user to the back end database via the webservice API:

<user role="ROLE_OPERATOR" loginName="test_login1"></user>

Submitting an HTTP POST using inline XML will not work. For example, running this on the command line will return an exception:

curl -X POST -d "<userList xmlns="urn:user"><user role="ROLE_OPERATOR" loginName="chuck_norris"></user></userList>" -H "Content-Type: application/xml" --basic --user username:password http://localhost:8080/controlroomws/UserList/

as posted in this StackOverflow thread.

To circumvent this problem create an XML file containing your data and
submit to the RESTful service as follows:

curl -X POST -d @userList.xml -H "Content-Type: application/xml" --basic --user username:password http://localhost:8080/commandcontrolws/UserList

To confirm that your data has been persisted to the underlying data store, perform a HTTP GET request that will return the list of users as follows:

curl -X GET --basic --user username:password http://localhost:8080/commandcontrolws/UserList

To return details of the specific user you just added, issue another HTTP GET command:

curl -X GET --basic --user username:password http://localhost:8080/commandcontrolws/UserList/chuck_norris