Tuesday, March 8, 2011

FileHelpers is my new best friend

I have gained countless tools and information over the years from Scott Hanselman’s blog . My new favorite tool is now Marcos Meli's FileHelpers . It is great tool for dealing with delimited or fixed length field messages. Despite the name, it is extremely useful in dealing with messages that are memory-based as well. I wish I had this tool years ago, it would have saved my countless hours of development! In particular, dealing with problems in data or record length changes is a teeth-gnashing, prototypically “bejabbers” experience.

As a longtime C++ developer, the only feature I missed in .NET is the ability to have structures/union of character arrays to deal with fixed length field messages. In C++, this feature relies on the ability to directly access memory. FileHelpers gives me a similar capability using reflection and attributes to accomplish this task. You adorn a class and fields with FileHelpers specific attributes and it knows how to do the rest. The FileHelpers documentation is superb, unlike many open source project, so I’ll just include a brief excerpt of the documentation here to whet your appetite.

[FixedLengthRecord()]   
 public class Customer   
 {   
     [FieldFixedLength(5)]   
     public int CustId;   
        
     [FieldFixedLength(20)]   
     [FieldTrim(TrimMode.Right)]   
     public string Name;   
}

As cool as reflection is, performance is always a potential concern. FileHelpers is a great illustration of how .NET can create highly-performant code. Many times developers, including myself, only think of reflection as a way to access methods, properties, and fields dynamically. Using Reflection.Emit, as FileHelpers does, is a mechanism to allow code to be generated at runtime. Combined with caching, dynamic code generation allows excellent performance.

To work with FileHelpers I create a wrapper around usage of the fixed length record.


using System;
using System.Collections.Generic;
using System.Text;
using FileHelpers;
    /// <summary>
    /// This generic class provides convenience functions for the FileHelpers library.
    /// This class implements the Facade design pattern by encapsulating the FileHelpers
    /// engine and providing a simple interface to that functionality.
    /// </summary>
    /// <typeparam name="T">the class type representing a message. This type must use the 
    /// FixedLengthRecord attribute</typeparam>
    public class FixedLengthMessageParser2<T>  where T:class
    {
        FileHelperEngine engine;
        /// <summary>
        /// Initializes a new instance of the <see cref="FixedLengthMessageParser&lt;T&gt;"/> class.
        /// It creates the file helper object.
        /// </summary>
        public FixedLengthMessageParser2()
        {
            engine = new FileHelperEngine(typeof(T));
        }
        /// <summary>
        /// Parses the specified aggregate data.
        /// </summary>
        /// <param name="aggregateData">The aggregate data.</param>
        /// <param name="records">The records found to be contained in the data</param>
        /// <param name="resultText">Results of parsing. Empty string if 
        /// successful, or exception details otherwise</param>
        /// <returns>True if able to parse, False otherwise.</returns>
        public bool Parse(string aggregateData, out T[] records, out string resultText)
        {
            records = null;
            resultText = String.Empty;
            try
            {
                records = (T[])engine.ReadString(aggregateData);
            }
            catch (Exception ex)
            {
                resultText = "entry " + typeof(T) + " [" + aggregateData
                    + "]couldn't be read due to error " + ex.ToString();
                return false;
            }
            return true;
        }
 
        /// <summary>
        /// Provides a string representation of a record.
        /// </summary>
        /// <param name="msg">The MSG received as input.</param>
        /// <returns>String representation of a record.</returns>
        public string ReturnToString(T msg)
        {
            string retVal;
            retVal = engine.WriteString(msg.AsEnumerable());
            retVal = retVal.Substring(0, retVal.Length - 2); //trim end of line characters
            return retVal;
        }
 
 
        /// <summary>
        /// Parses a single record
        /// </summary>
        /// <param name="singleData">A single data record.</param>
        /// <param name="fixedLengthRecord">The fixed length record.</param>
        /// <param name="resultText">Results of parsing. Empty string if 
        /// successful, or exception details otherwise</param>
        /// <returns></returns>
        public bool ParseSingle(string singleData, out T fixedLengthRecord, out string resultText)
        {
            T [] records = null;
            fixedLengthRecord = null;
            resultText = String.Empty;
            try
            {
                records = (T[])engine.ReadString(singleData);
                if (records.Length == 0)
                {
                    resultText  = "entry " + typeof(T) + " [" + singleData
                       + "]couldn't be read due to empty data set";
                    return false;
 
                }
                fixedLengthRecord = records[0];
            }
            catch (Exception ex)
            {
                resultText = "entry " + typeof(T) + " [" + singleData
                    + "]couldn't be read due to error " + ex.ToString();
                return false;
            }
            return true;
        }
    }

No comments:

Post a Comment