Login | Register   
RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


Use Generics to Create an Audit Trail : Page 3

Building an audit trail into your application provides a mechanism for tracking who updated what when, and the new generics feature in Whidbey will help you build that trail.

Leaving an Audit Trail
Applications with critical business data often require an audit trail. Take invoicing for example. In a perfect world, an application should generate invoices for a business using input data such as time sheets, job reports, or purchase orders. There should be no need for the users to update these documents. But in the real world, there are data input errors, special customer requests, last minute discounts or uplifts, and so on. So users occasionally need to update some invoices. But do you just want any user to be able to update any invoice? (Probably not.)

In addition to building code that controls which users have access to invoicing, you can build features into the application to track which users updated which fields. This provides an auditing mechanism to answer any future questions regarding changes to the invoice.

Though these examples use invoicing, you can apply the concepts similarly to any other business entity where you want to audit changes to the data.

Here I've created an Audit class to collect the set of auditing data. It tracks what field was changed, the old value and new value, the user that made the change, and the date and time that the user made the change.

In Visual Basic .NET "Whidbey":

Public Class Audit Private m_sPropertyName As String Private m_sOriginalValue As String Private m_sNewValue As String Private m_sUserName As String _ = "Deborah" Private m_dtAuditDate As Date End Class

In C# .NET "Whidbey":

public class Audit { string m_sPropertyName=""; string m_sOriginalValue=""; string m_sNewValue =""; string m_sUserName= "Deborah"; DateTime m_dtAuditDate =DateTime.Now; }

Notice that the user name in both examples is hard-coded to my name. In a real application you would want to set the appropriate user's identification information. This may be the username that was used to log into the system or some ID entered into your application.

The constructor for this Audit class sets the properties based on the passed in parameters.

In Visual Basic .NET "Whidbey":

Public Sub New(ByVal sPropertyName As String, _ ByVal sOriginalValue As String, _ ByVal sNewValue As String) m_sPropertyName = sPropertyName m_sOriginalValue = sOriginalValue m_sNewValue = sNewValue m_dtAuditDate = Now End Sub

In C# .NET "Whidbey":

public Audit(string sPropertyName, string sOriginalValue, string sNewValue) { m_sPropertyName = sPropertyName; m_sOriginalValue = sOriginalValue; m_sNewValue = sNewValue; m_dtAuditDate = DateTime.Now; }

You can expose any of the values associated with the Audit object using properties. You may want to consider making the properties read-only so they can be retrieved, but not updated.

In Visual Basic.NET "Whidbey":

Public ReadOnly Property PropertyName() _ As String Get Return m_sPropertyName End Get End Property Public ReadOnly Property AuditDate() _ As Date Get Return m_dtAuditDate End Get End Property

In C# .NET "Whidbey":

public string PropertyName { get {return m_sPropertyName;} } public DateTime AuditDate { get { return m_dtAuditDate ;} }

Each business object could include code that creates Audit objects, but by using a class factory to build the Audit objects you keep that code encapsulated. The class factory generates the Audit objects as needed. In this example, the AuditFactory class builds Audit objects only if the data changed.

In Visual Basic .NET "Whidbey":

Public Class AuditFactory(Of BOType) Public Function Add(ByVal bo As BOType, _ ByVal sPropertyName As String, _ ByVal sNewValue As String) As Audit Dim boPropertyInfo As _ System.Reflection.PropertyInfo = _ bo.GetType.GetProperty(sPropertyName) Dim sOriginalValue As String = _ boPropertyInfo.GetValue(bo, Nothing).ToString If sOriginalValue <> sNewValue Then ' Create an audit entry Dim oAudit As New Audit(sPropertyName, _ sOriginalValue, sNewValue) Return oAudit Else Return Nothing End If End Function End Class

In C# .NET "Whidbey":

public class AuditFactory<BOType> { public Audit Add(BOType bo, string sPropertyName, string sNewValue) { System.Reflection.PropertyInfo boPropertyInfo = typeof(BOType).GetProperty(sPropertyName); string sOriginalValue= (string)boPropertyInfo.GetValue(bo, null); if (sOriginalValue != sNewValue) { Audit oAudit = new Audit(sPropertyName, sOriginalValue, sNewValue); return oAudit; } else { return null; } } }

The class factory uses generics to define the type of business object that will be audited at run time. The Add method in the class factory uses reflection to get the current value of a particular property. It then compares the current value with the new value that was passed in to this method. If the value is changed then an Audit object is created and returned.

Code in the business object keeps the list of audit records. This example uses an Invoice class, though you can use any class.

In Visual Basic .NET "Whidbey":

Public Class Invoice Dim oInvoiceAudit As New List(Of Audit) Dim oAuditFactory As New AuditFactory(Of Invoice) Dim oAudit As Audit Dim m_sInvoiceDescription As String = "" Public Property InvoiceDescription() As String Get Return m_sInvoiceDescription End Get Set(ByVal Value As String) oAudit = oAuditFactory.Add(Me, _ "InvoiceDescription", Value) If oAudit IsNot Nothing Then oInvoiceAudit.Add(oAudit) End If m_sInvoiceDescription = Value End Set End Property End Class

In C# .NET "Whidbey":

public class Invoice { List<Audit> oInvoiceAudit = new List<Audit>(); AuditFactory<Invoice> oAuditFactory = new AuditFactory<Invoice>(); Audit oAudit; string m_sInvoiceDescription = ""; public string InvoiceDescription { get { return m_sInvoiceDescription; } set { oAudit = oAuditFactory.Add(this, "InvoiceDescription", value); if (oAudit != null) { oInvoiceAudit.Add(oAudit); } m_sInvoiceDescription = value; } } }

This code first creates a list of Audit objects. It uses the generic List class to manage the list. It then creates an instance of the AuditFactory, defining that it will create audit records for the Invoice class. An Audit object is declared but not created because the AuditFactory is responsible for creating the Audit objects.

The code for each property in the class then calls the AuditFactory, which compares the original property value and new property value to determine whether to create an Audit object. The property code shown in the examples provide a pattern that you can use to define any other properties of the class.

Generics provide a way to build general code that is specific at run time. Generics give you the best of both worlds: the efficiency of building generic code and the type safety and performance of building strongly-typed code.

The techniques shown in this article only show a small fraction of the power of generics. You can use them in interfaces and in delegates. You can specify constraints to limit the valid data types that your users can use in the generic code. You can define multiple generic types in one class. Plus, the .NET Framework provides a pre-defined set of generic collections.

Deborah Kurata is cofounder of InStep Technologies Inc., a professional consulting firm that focuses on turning your business vision into reality using Microsoft .NET technologies. She has over 15 years of experience in architecting, designing, and developing successful .NET applications. Deborah is the author of several books, including "Doing Objects in Visual Basic 6.0" (SAMS) and "Doing Web Development: Client-Side Techniques" (APress). She is on the INETA Speaker's Bureau and is a well-known speaker at technical conferences.
Comment and Contribute






(Maximum characters: 1200). You have 1200 characters left.



Thanks for your registration, follow us on our social networks to keep up-to-date