Geeks With Blogs
Welcome to the Lair...
| Home |

The project I am currently on encompasses a large push to clean-up and standardize the client's Reporting library.

Currently, there are around 70 odd reports that were created and/or worked-on by an assortment of consultants and employees over the span of several years.  This process was very organic, responding to the needs of the moment without any over-arcing design philosophy or guidance.

As a result, there is very little consistency in how these reports are structured, or how the settings are defined for parameters which should be globally common.

After spending about a month searching through these reports and making changes manually (a process involving both the ReportDesigner interface and editing of the raw XML), I began to wonder if there was a tool that could eliminate the bulk of this tedious busy work by pinpointing which reports required clean-up and automatically making the appropriate changes.

After posting my thoughts on the MSDN forums, I discovered that some of this functionality is supposed to be available as part of the SQL Server 2008 R2. However, the client's reporting infrastructure uses SQL Server 2005, and there are no plans to upgrade anytime soon.

So, as the saying "When you want something done right..." goes, I decided to take a crack at building an application that would both satisfy my immediate "clean-up" needs, and also put in place a framework that I could eventually hook-into for things like regression testing or driving website content.

This effort has been a great learning experience, involving a lot of trial and error as I've wrestled to understand the RDL schema and build a set of classes that would both present a simple interface for changing reports programmatically, and offer a degree of rudimentary state-managment.

To start, I've created a hierarchy of wrapper classes that correspond to the hierarchy of simple objects generated by deserializing a report from XML. These wrapper classes manage the icky plumbing of modifying the underlying report structure, but themselves present a clean, object-oriented interface for doing so.

Underpinning each of these wrapper classes is the following generic base class:

using System;
using System.ComponentModel;
using System.Collections.Generic;
 
namespace ReportMaintenanceUtility
{
      public abstract class GenericBase<T> : IStateManagement<T>, INotifyPropertyChanged
      {
            public abstract bool EqualTo(T other);
 
            //creates a copy of the underlying deserialized (seed) object
            public abstract object CreateSeedCopy();
 
 
            #region STATE MANAGEMENT
 
            protected T _copy = default(T);
            public abstract T CreateDeepCopy();
            protected void CopyEventHandlers(GenericBase<T> other)
            {
                  this.PropertyChanged = other.PropertyChanged;
            }
 
            protected bool _isDirty = false;
            [Bindable(false)] public bool IsDirty
            {
                  get { return this._isDirty; }
            }
           
            //first method called in the property setter of all derived classes...
            public void PrepStateChange()
            {
                  if (!this._isDirty)
                  {
                        this._copy = this.CreateDeepCopy();
                  }
            }
 
            //called once SaveState() is successfully executed...
            protected void ClearState()
            {
                  this._copy = default(T);
                  this._isDirty = false;
            }
 
            //methods specific to each derived class which encompass modifying the underlying
            //seed object to match the state of the current object in preparation for
            //serializing the report back to XML or returning the object to its pre-modified state
            public abstract List<ChangeLog> SaveState();
            public abstract void RestoreState();
 
            #endregion
 
           
            #region EVENT NOTIFICATION HANDLING
 
            public event PropertyChangedEventHandler PropertyChanged;
            protected void NotifyPropertyChanged(String info)
            {
                  if (this.PropertyChanged != null)
                  {
                        this.PropertyChanged(this, new PropertyChangedEventArgs(info));
                  }
            }
            protected void PropertyChangedListener(object sender, PropertyChangedEventArgs args)
            {
                  if (this != sender)
                  {
                        this.PrepStateChange();
                        this._isDirty = true;
                        NotifyPropertyChanged(args.PropertyName);
                  }
            }
            #endregion
      }
}
 
Each wrapper class derives from this generic base class.  It is designed to ensure that any state change to any object in the hierarchy is communicated back up the chain and that an original, unmodified version of each affected object is saved before any change is actually made.  Additionally, this abstract base class mandates that each derived class must implement methods to modify the underlying seed object once saved, or to restore itself to its original state if not.
 
Here is one of the simpler wrapper classes, which corresponds to the deserialized ParameterValueType.
using System;
using System.Collections.Generic;
using System.ComponentModel;
 
namespace ReportMaintenanceUtility
{
      public class ParameterValue : GenericBase<ParameterValue>
      {
            #region PRIVATE MEMBERT(S)
 
            private string                      _label            = string.Empty;
            private string                      _value            = string.Empty;
            private ParameterValueType    _seed = null;
 
            #endregion
 
 
            #region PUBLIC PROPERTIES
 
            public string Label
            {
                  get { return this._label; }
                  set
                  {
                        this.PrepStateChange();
                        base.NotifyPropertyChanged("Label");
                        this._label = value;
                        this._isDirty = true;
                  }
            }
            public string Value
            {
                  get { return this._value; }
                  set
                  {
                        this.PrepStateChange();
                        base.NotifyPropertyChanged("Value");
                        this._value = value;
                        this._isDirty = true;
                  }
            }
 
            #endregion
 
 
            #region CONSTRUCTOR(S)
 
            private ParameterValue(ParameterValue pv)
            {
                  if (pv == null)
                  {
                        throw new ArgumentNullException();
                  }
                  else
                  {
                        this._seed = (ParameterValueType)pv.CreateSeedCopy();
                        base.CopyEventHandlers(pv);
                        this.Initialize();
                  }
            }
 
            public ParameterValue(ParameterValueType pvt)
            {
                  if (pvt == null)
                  {
                        throw new ArgumentNullException();
                  }
                  else
                  {
                        this._seed = pvt;
                        this.Initialize();
                  }
            }
 
            protected override void Initialize()
            {
                  for (int i = 0; i < this._seed.ItemsElementName.Length; i++)
                  {
                        switch (this._seed.ItemsElementName[i])
                        {
                              case ItemsChoiceType32.Label:
                                    this._label = this._seed.Items[i].ToString();
                                    break;
                              case ItemsChoiceType32.Value:
                                    this._value = this._seed.Items[i].ToString();
                                    break;
                        }
                  }
            }
 
            #endregion
 
 
            #region     INTERFACE IMPLEMENTATION(S)
 
            public override bool EqualTo(ParameterValue other)
            {
                  if (other == null)
                  {
                        return false;
                  }
                  else
                  {
                        return (this._label == other.Label && this._value == other.Value);
                  }
            }
 
            public override List<ChangeLog> SaveState()
            {
                  List<ChangeLog> log = new List<ChangeLog>();
 
                  if (this._isDirty)
                  {
                        int idx = -1;
                        List<ItemsChoiceType32> elements = new List<ItemsChoiceType32>(this._seed.ItemsElementName);
 
                        idx = elements.IndexOf(ItemsChoiceType32.Label);
                        if (idx > -1)
                        {
                              this._seed.Items[idx] = this._label;
                        }
 
                        idx = elements.IndexOf(ItemsChoiceType32.Value);
                        if (idx > -1)
                        {
                              this._seed.Items[idx] = this._value;
                        }
                  }
 
                  this.ClearState();
                  return log;
            }
 
            public override void RestoreState()
            {
                  if (this._copy != null)
                  {
                        this._label = this._copy._label;
                        this._value = this._copy._value;
                  }
                  this.ClearState();
            }
 
            public override ParameterValue CreateDeepCopy()
            {
                  if (this._copy != null)
                  {
                        return this._copy;
                  }
                  return new ParameterValue(this);
            }
 
            public override object CreateSeedCopy()
            {
                  ParameterValueType pvt = new ParameterValueType();
                  List<ItemsChoiceType32> elements = new List<ItemsChoiceType32>(this._seed.ItemsElementName);
                  List<object> items = new List<object>(this._seed.Items);
 
                  pvt.ItemsElementName = elements.ToArray();
                  pvt.Items = items.ToArray();
 
                  return pvt;
            }
 
            #endregion
      }
}
I'm still in the process of getting everything up and running, but so far the basic concept seems strong and the prototype is working.  As my efforts progress, I'll try to update this site with additional details...
Posted on Thursday, July 23, 2009 12:41 PM | Back to top


Comments on this post: A state-aware generic base class...

# re: A state-aware generic base class...
Requesting Gravatar...
Take a look at the CSLA library, it deals with a lot of this type of work, including n-level undo. It also has nice libraries to take a 1 tier client/server app and easily split it into an n-tier app.
Left by Jason Coyne on Jul 23, 2009 3:26 PM

Your comment:
 (will show your gravatar)


Copyright © MorkothsLair | Powered by: GeeksWithBlogs.net