Third Party Feeds: Network Q/Datafirst Integration

5 minute read

Network Q is the used car brand of Vauxhall that offers the consumer piece of mind when buying a used car. Vauxhall, a UK based marque owned by General Motors (GM) allows Vauxhall dealers to list their Network Q qualifying vehicles on their website. In my previous post: Third Party Stock/Inventory Data Feeds I talked about how important and how much choice there is now when displaying your vehicles online, with the Network Q website, its free for dealers to list their vehicles. This integration saves the dealer manually uploading the vehicles one by one though the admin area and can be fully automated by taking the stock from their DMS (Dealer Management System) such as Kerridge/ADP, Pinnacle etc.

The vehicles need to undergo a set of multi point checks and meet some other requirements such as age and mileage.

Vauxhall use a company called Datafirst who are based in France to manage the Network Q website. Datafirst offer a webservice API (Application Programming Interface) for you to upload the stock. Unlike most third party websites that accept feeds the API the Datafirst provide allows the updates to be made in real time. From a developer’s perspective this is far better to work with than CSV (Comma Separated Values) file. The biggest problem with CSV files is that they normally get transferred via FTP (File Transfer Protocol) which makes it difficult to determine whether the transfer succeeded or not.

The Datafirst API is session based and a number of calls need to be made in a particular order.

The main decision you need to make is how the webservice gets called. Normally I would have built an SSIS (SQL Server Integration Services) package to deal with this by utilising the webservice and xml tasks. But due to a number of reasons, such as deployment etc. I opted for a simple console application that is run from Windows scheduled task manager.

The 4 steps required are:

  1. Initialise Session
  2. Send Vehicle Data
  3. Send Vehicle Photos
  4. Finalise Session

Something I recommend is that you put good error checking at every stage plus a catch all to catch unhandled errors. Is not uncommon to find that Datafirst’s service is unavailable from time to time. The error checking will alert you by email so you can rerun or notify someone of the failure.

I do this by adding the following piece of code:

   1: AppDomain currentDomain = AppDomain.CurrentDomain;
   2: currentDomain.UnhandledException += new UnhandledExceptionEventHandler(UnhandledExceptionHandler);
   4: static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args)
   5:         {
   6:             StringBuilder sb = new StringBuilder();
   8:             Exception ex = ((Exception)(args.ExceptionObject));
   9:             sb.Append("****** Unhandled Exception ******<br/><br/>");
  10:             sb.Append("<br/>ExceptionType: " + ex.GetType().FullName);
  11:             sb.Append("<br/>HelpLine: " + ex.HelpLink);
  12:             sb.Append("<br/>Message: " + ex.Message);
  13:             sb.Append("<br/>Source: " + ex.Source);
  14:             sb.Append("<br/>StackTrace: " + ex.StackTrace);
  15:             sb.Append("<br/>TargetSite: " + ex.TargetSite);
  17:             Exception ie = ex;
  18:             while (!((ex.InnerException == null)))
  19:             {
  20:                 ie = ie.InnerException;
  21:                 sb.Append("<br/><br/>****** Inner Exception ******<br/><br/>");
  22:                 sb.Append("<br/>ExceptionType: " + ie.GetType().Name);
  23:                 sb.Append("<br/>HelpLine: " + ie.HelpLink);
  24:                 sb.Append("<br/>Message: " + ie.Message);
  25:                 sb.Append("<br/>Source: " + ie.Source);
  26:                 sb.Append("<br/>StackTrace: " + ie.StackTrace);
  27:                 sb.Append("<br/>TargetSite: " + ie.TargetSite);
  28:             }
  30:             Utilities.SendEmail(Utilities.Preferences.ErrorEmails, sb.ToString());
  31:         }

The next thing to do is set up the objects we need to pass over.  First off we create a base class with only one method that serialises the object.

   1: public abstract class RequestBase
   2:     {
   3:         public string SerialiseToString()
   4:         {
   5:             XmlSerializer xs = new XmlSerializer(this.GetType());
   6:             XmlWriterSettings xws = new XmlWriterSettings();
   7:             xws.Encoding = new UTF8Encoding(false);
   8:             string xml = string.Empty;
  10:             using (MemoryStream ms = new MemoryStream())
  11:             {
  12:                 using (XmlWriter xw = XmlWriter.Create(ms, xws))
  13:                 {
  14:                     xs.Serialize(xw, this);
  15:                     xw.Close();
  16:                 }
  17:                 xml = Encoding.UTF8.GetString(ms.ToArray());
  18:             }
  20:             return xml;
  21:         }
  22:     }


Then the objects, here is the vehicle request object that takes a collection of vehicles.

   1: [Serializable]
   2: [XmlRoot("REQUEST")]
   3: public class VehicleRequest : RequestBase
   4: {
   5:     [XmlAttribute("NAME")]
   6:     public string NAME { get; set; }
   7:     [XmlAttribute("SESSID")]
   8:     public string SESSID { get; set; }
  10:     [XmlArrayAttribute("UVSTOCK")]
  11:     [XmlArrayItemAttribute("USEDVEH")]
  12:     public List<Vehicle> Vehicles { get; set; }  
  13: }

Once all the objects are specified and marked-up with the relevant attributes we need to send them over to the webservice.  Now, normally it would be easy to create a proxy class and call the webservice direct from code, but the way that Datafirst work is just to receive POX (Plain Old Xml) via HTTP requests and responses.  To send the data then we need to us the HttpWebRequest from the System.Net namespace like so:

   1: public static string PostRequest(string xml, string uri)
   2: {
   3:     Uri address = new Uri(uri);
   4:     string responseXml = string.Empty;
   6:     HttpWebRequest request = WebRequest.Create(address) as HttpWebRequest;
   7:     request.Method = "POST";
   8:     request.ContentType = "text/xml";
  10:     byte[] byteData = UTF8Encoding.UTF8.GetBytes(xml.ToString());
  12:     request.ContentLength = byteData.Length;
  14:     using (Stream postStream = request.GetRequestStream())
  15:     {
  16:         postStream.Write(byteData, 0, byteData.Length);
  17:     }
  19:     // Get response   
  20:     using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
  21:     {
  22:         StreamReader reader = new StreamReader(response.GetResponseStream());
  23:         responseXml = reader.ReadToEnd();
  24:     }
  25:     UpdateSession(responseXml);
  27:     return responseXml;
  28: }

Now you might be wondering how do the images get transmitted.  Well, we need to encode the images in base64.

   1: internal static string ConvertImageToBase64(string path)
   2: {
   3:     byte[] byteArray = null;
   5:     using(FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
   6:     {
   7:         byteArray = new byte[fs.Length];
   8:         fs.Position = 0;
   9:         fs.Read(byteArray, 0, (int)fs.Length);
  10:     }
  12:     return Convert.ToBase64String(byteArray);
  13: }

It’s recommended due to the amount of vehicle images that can be transferred to only send over modified or new images, so you’ll need to update the database with a sent flag or similar. Alternatively, what I did that works well is to set the creation time of image to some time in the past and the next time you look for the images only get images without that date.  This is extremely useful if this application was to be deployed to a computer within a dealership or you don’t want to mess around with databases.

So there you have it. Not difficult but different to how other people do it, and in my opinion a lot better then the CSV/FTP model I tend to come across.