Handling Fixed-width Flat Files with .NET Custom Attributes






4.78/5 (11 votes)
Handling Fixed width flat files with .NET custom attributes
Introduction
This article shows how to handle flat files containing fixed width datafields, using .NET custom attributes. It doesn't explain custom attributes, because I think there has already been enough written on that subject.
Background
How many times did you have to exchange data coming from or going to another environment like a mainframe? Many projects I have worked on needed to import or export datafiles in a fixed width format. The code handling these files must be flexible enough to handle changes in the file format, that's why I created the code described in this article.
Using the Code
Suppose your code needs to import a file containing customers with the following layout :
Id PIC(9)
Code PIC(1)
Name PIC(20)
.....
To read the data from the file, you create a customer
class that must derive from a provided base class called StringLayoutUtility
. This base class has two important methods : Parse()
and ToString()
. These two methods will iterate over each property of (your) the (derived) class, having a StringLayoutAttribute
defined. With the position information it will find in the StringLayoutAttribute
, it will know how to parse or build the string
. So on each property, you assign a StringLayoutAttribute
, together with two arguments: the begin and end position of the property in the input-string
as shown below:
[StringLayoutAttribute(0, 8)]
public string Id
{
get { return _Id; }
set { _Id = value; }
}
The Id
field starts at position 0
and ends at position 8
, meaning the Id
field is 9 characters long.
"012345678ACustomerxxx......."
The StringLayoutUtility
class also has a virtual method IsValid()
that can be used to do some business validation after the parsing.
public override bool IsValid()
{
// add your validation logic here
return (!String.IsNullOrEmpty(this.Id) && !String.IsNullOrEmpty(this.Name));
}
Usage
The first code sample provided shows how a string
is parsed, and is later compared with the result of the ToString()
method:
// this simulates input from a FlatFileReader
string fileLineIn = "123456789ADepoorter Stephan WhateverStreet 456AB1000 Brussels" +
"Belgium";
Customer customer = new Customer();
customer.Parse(fileLineIn);
DisplayCustomer(customer);
string fileLineOut = customer.ToString();
Console.WriteLine("customer.Parse() " +
((fileLineIn == fileLineOut.TrimEnd()) ? "==" : "!=") +
" customer.ToString() !" );
The second code sample shows the reading of a file customers.txt, using the FlatFileReader
:
// Sample using the FlatFileReader:
FlatFileReader file = new FlatFileReader("customers.txt", Encoding.GetEncoding(
"iso-8859-1"));
while (file.ParseLine())
{
customer.Parse(file.CurrentLine);
DisplayCustomer(customer);
}
Base Class
public void Parse(string input)
{
if(!String.IsNullOrEmpty(input ) )
{
foreach (PropertyInfo property in GetType().GetProperties())
{
foreach (Attribute attribute in property.GetCustomAttributes(true))
{
StringLayoutAttribute stringLayoutAttribute = attribute as StringLayoutAttribute;
if (null != stringLayoutAttribute)
{
string tmp = string.Empty;
if (stringLayoutAttribute.StartPosition <= input.Length - 1)
{
tmp = input.Substring(stringLayoutAttribute.StartPosition,
Math.Min((
stringLayoutAttribute.EndPosition - stringLayoutAttribute.StartPosition +
1), input.Length - stringLayoutAttribute.StartPosition));
}
switch (_trimInput)
{
case TrimInputMode.Trim :
tmp = tmp.Trim();
break;
case TrimInputMode.TrimStart:
tmp = tmp.TrimStart();
break;
case TrimInputMode.TrimEnd:
tmp = tmp.TrimEnd();
break;
}
property.SetValue(this, tmp, null);
break;
}
}
}
}
}
public override string ToString()
{
string result = string.Empty;
foreach (PropertyInfo property in GetType().GetProperties())
{
foreach (Attribute attribute in property.GetCustomAttributes(false))
{
StringLayoutAttribute stringLayoutAttribute = attribute as StringLayoutAttribute;
if (null != stringLayoutAttribute)
{
string propertyValue = (string)property.GetValue(this, null);
if (stringLayoutAttribute.StartPosition > 0 && result.Length <
stringLayoutAttribute.StartPosition)
result = result.PadRight(stringLayoutAttribute.StartPosition, _paddingChar);
string left = string.Empty;
string right = string.Empty;
if (stringLayoutAttribute.StartPosition > 0)
left = result.Substring(0, stringLayoutAttribute.StartPosition);
if (result.Length > stringLayoutAttribute.EndPosition + 1)
right = result.Substring(stringLayoutAttribute.EndPosition + 1);
if (propertyValue.Length < stringLayoutAttribute.EndPosition -
stringLayoutAttribute.StartPosition + 1)
{
propertyValue = propertyValue.PadRight(stringLayoutAttribute.EndPosition -
stringLayoutAttribute.StartPosition + 1, _paddingChar);
}
result = left + propertyValue + right;
}
break;
}
}
return result;
}
Extensibility
The sample shown was kept very basic on purpose. Often you will need to parse a part of the input data fields, and then depending on what you have read in the first part, parse the rest of the line differently. This can be handled easily, by using base classes to read the first part, and pass these to a factory that will instantiate an appropriate class that will parse the remaining fields of the input line.
Conclusion
I hope to have shown a simple and flexible implementation to read and create files containing fixed width fields, using .NET custom attributes.
History
- October, 2006: Initial version