Deborah's Developer MindScape






         Tips and Techniques for Web and .NET developers.

Archive for OOP

August 27, 2009

Building an Alarm Class

Filed under: C#,OOP,VB.NET @ 12:21 pm

The specifics of this class demonstrate how to build an alarm. You can use this class to build an alarm clock application, or to add an alarm feature into your application, similar to the Reminder feature in Outlook.

OR you can just use this class as an example of raising events from a business object or displaying sounds asynchronously.

Build the Alarm class as follows.

In C#:

// Declare a delegate
public delegate void AlarmWentOffHandler(object sender, EventArgs e);

public class Alarm
{
    // Declare an event
    public event AlarmWentOffHandler AlarmWentOff;

    public DateTime? AlarmTime { get; set; }
    private Timer AlarmTimer { get; set; }
    public int SnoozeInterval { get; set; }

    public Alarm() : this(null, 5)
    {  }

    public Alarm(DateTime? timeForAlarm): this(timeForAlarm, 5)
    {  }

    public Alarm(DateTime? timeForAlarm, int minutesToSnooze)
    {
        // Set the properties
        AlarmTime = timeForAlarm;
        SnoozeInterval = minutesToSnooze;

        // Start the timer
        AlarmTimer = new Timer() {Interval = 10000,
                                  Enabled = true};
        AlarmTimer.Tick += CheckAlarm;
    }

    public void Snooze()
    {
        // Reset the alarm by the snooze interval
        AlarmTime = DateTime.Now.AddMinutes(SnoozeInterval);

        // Reset the timer
        AlarmTimer.Enabled = true;
    }

    protected virtual void OnAlarmWentOff(EventArgs e)
    {
        if (AlarmWentOff != null)
            AlarmWentOff(this, e);
    }

    private void CheckAlarm(object sender,
                            EventArgs e)
    {
        // Check whether alarm time has been reached
        if (AlarmTime <= DateTime.Now)
        {
            // Disable the timer
            AlarmTimer.Enabled = false;

            // Raise the event
            OnAlarmWentOff(new EventArgs());
        }
    }
}

In VB:

Public Class Alarm
    Event AlarmWentOff(ByVal sender As Object, ByVal e As EventArgs)

    Private _AlarmTime As DateTime?
    Public Property AlarmTime() As DateTime?
        Get
            Return _AlarmTime
        End Get
        Set(ByVal value As DateTime?)
            _AlarmTime = value
        End Set
    End Property

    Private _AlarmTimer As Timer
    Private Property AlarmTimer() As Timer
        Get
            Return _AlarmTimer
        End Get
        Set(ByVal value As Timer)
            _AlarmTimer = value
        End Set
    End Property

    Private _SnoozeInterval As Integer
    Public Property SnoozeInterval() As Integer
        Get
            Return _SnoozeInterval
        End Get
        Set(ByVal value As Integer)
            _SnoozeInterval = value
        End Set
    End Property

    Public Sub New()
        ‘ Set Defaults
        Me.New(Nothing, 5)
    End Sub

    Public Sub New(ByVal timeForAlarm As DateTime?)
        ‘ Set Defaults
        Me.New(timeForAlarm, 5)
    End Sub

    Public Sub New(ByVal timeForAlarm As DateTime?, _
                   ByVal minutesToSnooze As Integer)
        ‘ Set the properties
        AlarmTime = timeForAlarm
        SnoozeInterval = minutesToSnooze

        ‘ Start the timer
        AlarmTimer = New Timer With {.Interval = 10000, _
                                     .Enabled = True}
        AddHandler AlarmTimer.Tick, AddressOf CheckAlarm
    End Sub

    Public Sub Snooze()
        ‘ Reset the alarm by the snooze interval
        AlarmTime = Now.AddMinutes(SnoozeInterval)

        ‘ Reset the timer
        AlarmTimer.Enabled = True
    End Sub

    Private Sub CheckAlarm(ByVal sender As Object, _
                           ByVal e As EventArgs)
        ‘ Check whether alarm time has been reached
        If AlarmTime <= Now Then
            ‘ Disable the timer
            AlarmTimer.Enabled = False

            ‘ Raise the event
            RaiseEvent AlarmWentOff(Me, New EventArgs())
        End If

    End Sub
End Class

The C# code begins by declaring a delegate for its event. This is not required in the VB code since the delegate is handled for you.

Within the class, the C# and VB code declare an event called AlarmWentOff. This is the event that is generated when the alarm goes off.

The class has three properties:

  • AlarmTime: Time that the alarm should go off.
  • AlarmTimer: A private property that manages the timer for checking the alarm time.
  • SnoozeInterval: The alarm can optionally be set to snooze. This is the amount of snooze time in minutes.

The C# code uses auto-implemented properties and the VB code uses the expanded property syntax. (VB is getting auto-implemented properties in VB 10.)

The constructors for the class optionally define the alarm time and minutes to snooze. The constructor then starts the timer that goes off every 10 seconds. (This can be adjusted as desired. Or the timer interval could be defined as a property of this Alarm class.)

The class has one public method: Snooze. The Snooze method adds the snooze interval amount to the AlarmTime to reset the alarm. It also ensures that the timer in enabled.

Each time the timer goes off, the code checks the current time against the alarm time. If the alarm time has been reached, the code disables the timer and raises the AlarmWentOff event.

This class can be used whenever you need to add an alarm, notification, or reminder to your application. Here is an example of how this class is accessed.

NOTE: It uses a .wav file for the alarm and plays it using the built-in SoundPlayer.

Start by adding this to the top of your code file.

In C#:

using System.Media;

In VB:

Imports System.Media

Then add the following code somewhere in your user interface.

In C#:

Alarm myAlarm;
private void SetTheAlarm()
{
    // Create an alarm object
    myAlarm = new Alarm(DateTime.Now.AddMinutes(1), 1);
    myAlarm.AlarmWentOff +=  AlarmWentOff;
}

private void AlarmWentOff(object sender, EventArgs e)
{
    var player = new SoundPlayer("myFile.wav");
    player.PlayLooping();
    DialogResult replay =
        MessageBox.Show("Wake up" +
                             Environment.NewLine +
                             "Yes to wake up." +
                             Environment.NewLine +
                             "No to snooze.",
                        "Alarm Clock",
                        MessageBoxButtons.YesNo,
                        MessageBoxIcon.Exclamation);
    player.Stop();
    if (replay != DialogResult.Yes)
        myAlarm.Snooze();
}

In VB:

Dim myAlarm As Alarm
Private Sub SetTheAlarm()
    ‘ Create an alarm object
    myAlarm = New Alarm(Now.AddMinutes(1), 1)
    AddHandler myAlarm.AlarmWentOff, AddressOf AlarmWentOff
End Sub

Private Sub AlarmWentOff(ByVal sender As Object, ByVal e As EventArgs)
    Dim player As New SoundPlayer("myFile.wav")
    player.PlayLooping()
    Dim replay As DialogResult = _
        MessageBox.Show("Wake up" & _
                             Environment.NewLine & _
                             "Yes to wake up." & _
                             Environment.NewLine & _
                             "No to snooze.", _
                        "Alarm Clock", _
                        MessageBoxButtons.YesNo, _
                        MessageBoxIcon.Exclamation)
    player.Stop()
    If replay <> DialogResult.Yes Then
        myAlarm.Snooze()
    End If
End Sub

This code defines a variable that references the Alarm class. In a SetTheAlarm method, the class creates an instance of the class, passing in an alarm time and snooze interval.

For testing, this is set to 1 minute from the current time and the snooze is set to one minute. In most cases, you would want to get the alarm time from the user.

When the alarm goes off, this code plays a .wav file using the SoundPlayer provided in .NET. Be sure to change the .wav file name to the directory and file name of a .wav file on your system.

The PlayLooping method of the SoundPlayer is used to play the sound continuously. This method is asynchronous, so the code will continue to the next line.

The example displays a message box, allowing the user to cancel the alarm or snooze the alarm. In either cause, the code calls the Stop method of the SoundPlayer to stop the .wav file. If the user requested to snooze, it calls the alarm’s Snooze method.

Enjoy!

July 21, 2009

Building a Business Object Base Class

Filed under: C#,Data Binding,OOP,VB.NET @ 1:49 pm

If you are building applications in .NET to manage data for a business, you are most likely creating business object classes. Depending on the business, these classes could include Customer, Product, Order, Invoice, PurchaseOrder, Employee, TimeCard and so on.

A simple sample Customer class is shown here.

There are some features that all business objects need to support. For example:

  • Entity State: The business object needs to track whether it is new, updated, or deleted so it can make the appropriate change to the database.
  • Validation: Is the current data defined for the business object valid?
  • Save: Saves the changes to the database.

Notice that data retrieval functionality is not included in this list. That is because in most cases, you may never retrieve a single business object. Rather, you would retrieve a set of them. For example, all active orders or all customers with overdue invoices. So the retrieve functionality is not included in the class that manages a single object. (Most on this in a later post.)

Instead of repeating this common functionality in each business object, it makes sense to build a base class. Each business object can then inherit from this base class to share this common functionality.

So let’s get started.

In C#:

using System;
using System.ComponentModel;

public abstract class BoBase :
        IDataErrorInfo,
        INotifyPropertyChanged
{

}

In VB:

Imports System.ComponentModel

Public MustInherit Class BOBase
    Implements IDataErrorInfo
    Implements INotifyPropertyChanged

End Class

The class is abstract (MustInherit in VB) to indicate that it is meant to be a base class and not to be used on its own. An abstract class cannot be instantiated directly, so no other code can create an instance of the class.

The class then implements two interfaces:

  • IDataErrorInfo: This interface provides error information that the user interface can use to report validation errors to the user. It works well with the ErrorProvider control provided in WinForms and supports ASP.NET and WPF features.
  • INotifyPropertyChanged: This interface ensures that the user interface is notified when a property value changes, keeping your business object and user interface in sync.

We’ll look at the implementation of these interfaces shortly.

First, define the valid set of entity states.

In C#:

protected internal enum EntityStateType
{
    Unchanged,
    Added,
    Deleted,
    Modified
}

In VB:

Protected Friend Enum EntityStateType
    Unchanged
    Added
    Deleted
    Modified
End Enum

The Enum is declared Protected because the entity state should only be accessible from the business object itself. However, Internal (Friend in VB) was added so that related objects (such as Order and OrderLineItem) could reference the related object state.

The business object state is retained using an EntityState property.

In C#:

protected EntityStateType EntityState { get; private set; }

In VB:

Private _EntityState As EntityStateType
Protected Property EntityState() As EntityStateType
    Get
        Return _EntityState
    End Get
    Private Set(ByVal value As EntityStateType)
        _EntityState = value
    End Set
End Property

This property is protected, but the setter is private. So the business objects that inherit from this class can read the entity state, but only the base class can set the value.

Additional properties provide a way to get the entity’s state in an easier fashion. These properties are not required, but they make the base class a little easier to use.

In C#:

[BindableAttribute(false)]
[BrowsableAttribute(false)]
public bool IsDirty
{
    get { return this.EntityState != EntityStateType.Unchanged; }
}

[BindableAttribute(false)]
[BrowsableAttribute(false)]
public bool IsNew
{
    get { return this.EntityState == EntityStateType.Added; }
}

In VB:

<BindableAttribute(False)> _
<BrowsableAttribute(False)> _
Public ReadOnly Property IsDirty() As Boolean
    Get
        Return Me.EntityState <> EntityStateType.Unchanged
    End Get
End Property

<BindableAttribute(False)> _
<BrowsableAttribute(False)> _
Public ReadOnly Property IsNew() As Boolean
    Get
        Return Me.EntityState = EntityStateType.Added
    End Get
End Property

The IsDirty and IsNew properties are public, so they can be accessed from anywhere. They are marked with two attributes:

  • Bindable: Defines whether the property should be used for binding. In this case the value is false because the user interface should not be able to bind to this property.
  • Browsable: Defines whether the property should be displayed in the Properties window. Again, the value is false.

Two other properties handle the validation.

In C#:

[BindableAttribute(false)]
[BrowsableAttribute(false)]
public bool IsValid
{
    get { return (ValidationInstance.Count == 0); }
}

protected Validation ValidationInstance { get; set; }

In VB:

<Bindable(False)> _   
<BrowsableAttribute(False)> _
Public ReadOnly Property IsValid() As Boolean
    Get
        Return (ValidationInstance.Count = 0)
    End Get
End Property

Private _ValidationInstance As Validation
Protected Property ValidationInstance() As Validation
    Get
        Return _ValidationInstance
    End Get
    Private Set(ByVal value As Validation)
        _ValidationInstance = value
    End Set
End Property

Again, the public property is marked with the Browsable and Bindable attributes. The ValidationInstance property retains an instance of the Validation class for the business object. The code for the Validation class is here.

The constructor creates an instance of the Validation class.

In C#:

protected BoBase()

   ValidationInstance = new Validation();
}

In VB:

Protected Sub New()
    ValidationInstance = New Validation
End Sub

The following is the implementation of IDataErrorInfo.

In C#:

#region IDataErrorInfo Members

[BrowsableAttribute(false)]
[BindableAttribute(false)]
public string Error
{
    get { return ValidationInstance.ToString(); }
}

[BrowsableAttribute(false)]
[BindableAttribute(false)]
public string this[string columnName]
{
    get { return ValidationInstance[columnName]; }
}

#endregion

In VB:

#Region " Properties required by the IDataErrorInfo"

    <Bindable(False)> _   
    <BrowsableAttribute(False)> _
    Public ReadOnly Property [Error]() As String _
           Implements IDataErrorInfo.Error
        Get
            Return ValidationInstance.ToString
        End Get
    End Property

    <BrowsableAttribute(False)> _
    <Bindable(False)> _
    Default Protected ReadOnly Property Item(ByVal columnName _ 
           As String) As String _ 
           Implements IDataErrorInfo.Item
        Get
            Return ValidationInstance.Item(columnName)
        End Get
    End Property

#End Region

The Error property uses the overridden ToString method of the validation class to return the full list of validation errors.

The Item property provides access to the validation errors given a property name. This property is implemented as the class indexer in C#.

The following in the implementation of INotifyPropertyChanged.

In C#:

#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion

In VB:

#Region " Events required by INotifyPropertyChanged"
    Public Event PropertyChanged(ByVal sender As Object, _
        ByVal e As System.ComponentModel.PropertyChangedEventArgs) _
        Implements INotifyPropertyChanged.PropertyChanged
#End Region

This interface only defines a single event. This event should be raised whenever the data is changed.

Since every business object will have unique requirements for the save operation, the SaveItem method is not implemented. Rather it is defined as abstract.

In C#:

public abstract Boolean SaveItem();

In VB:

Public MustOverride Function SaveItem() As Boolean

Defining an abstract (MustOverride in VB) SaveItem method ensures that every business object has a SaveItem method.

Finally, since the EntityState property setter is private, the base class needs a method to set the entity state.

In C#:

protected internal void SetEntityState(EntityStateType newEntityState)
{
    switch (newEntityState)
    {
        case EntityStateType.Deleted:
        case EntityStateType.Unchanged:
        case EntityStateType.Added:

            this.EntityState = newEntityState;
            break;

    default:
            if (this.EntityState == EntityStateType.Unchanged)
                this.EntityState = newEntityState;
            break;
    }
}

protected internal void SetEntityState(EntityStateType newEntityState, string propertyName)
{
    SetEntityState(newEntityState);
    if (PropertyChanged != null)
        PropertyChanged(this,
              
new PropertyChangedEventArgs(propertyName));
}

In VB:

Protected Friend Sub SetEntityState(ByVal dataState As EntityStateType)
    SetEntityState(dataState, Nothing)
End Sub

Protected Friend Sub SetEntityState( _
ByVal newEntityState As EntityStateType, ByVal propertyName As String)
    Select Case newEntityState
        Case EntityStateType.Deleted, _
             EntityStateType.Unchanged, _
             EntityStateType.Added

            Me.EntityState = newEntityState

        Case Else
            If Me.EntityState = EntityStateType.Unchanged Then
                Me.EntityState = newEntityState
            End If
    End Select

    If Not String.IsNullOrEmpty(propertyName) Then
        Dim e As New PropertyChangedEventArgs(propertyName)
        RaiseEvent PropertyChanged(Me, e)
    End If
End Sub

The SetEntityState method has two overloads. The first is used when changing the entity state in general and the second is used when changing the entity state because a specific property is changed.

For example, when setting an object as Unchanged, Added, or Deleted, it does not matter which property was changed. But when a particular property is changed, the code must also raise the PropertyChanged event.

In this case, the C# and VB code was implemented differently. In the C# code, the code to set the entity state is in the first overload. The second overload then calls the first and then raises the event.

In the VB code, the first overload simply calls the second overload. The second overload then sets the entity state and then raises the event as appropriate. You can use either technique in either language.

That’s it! You now have a base class that can be used with any business object class. If you have any other common functionality, you can add it to this base class.

Here is an example of how you use this base class.

In C#:

public class Customer : BoBase
{

    private string _LastName;
    public string LastName
    {
        get { return _LastName; }
        set
        {
            if (_LastName == null || _LastName != value)
            {
              string propertyName = "LastName";
              _LastName = value;

              // Validate the last name
              ValidationInstance.ValidateClear(propertyName);
              ValidationInstance.ValidateRequired(propertyName, value);

              SetEntityState(EntityStateType.Modified, propertyName);
            }
        }
    }

    public override Boolean SaveItem()
    {
        // TODO: Add code here
    }

}

In VB:

Public Class Customer
    Inherits BoBase

    Private _LastName As String
    Public Property LastName() As String
        Get
            Return _LastName
        End Get
        Set(ByVal value As String)
            If _LastName Is Nothing OrElse _LastName <> value Then
              Dim propertyName As String = "LastName"
              _LastName = value

              ‘ Validate the last name
              ValidationInstance.ValidateClear(propertyName)
              ValidationInstance.ValidateRequired(propertyName, value)

              SetEntityState(EntityStateType.Modified, propertyName)
            End If
        End Set
    End Property

    Public Overrides Function SaveItem() As Boolean
        ‘ TODO: Add code here
    End Function
End Class

Notice how the class statement includes the syntax to inherit from BoBase. The LastName property uses the ValidationInstance defined in the base class to validate the value. It also sets the entity state when the last name is changed.

Enjoy!

July 16, 2009

Validation Class

Filed under: C#,OOP,VB.NET @ 6:15 pm

A common requirement in most applications is to validate the data entered by the user. This is such a common requirement, that it makes sense to build a reusable Validation class. This post details the beginnings of a .NET Validation class.

The class was originally designed to validate business object properties. But it could also be used to directly validate fields in your user interface if you are not yet using business objects.

In C#:

using System;
using System.Collections.Generic;
using System.Text;

namespace InStepLibrary
{
    public class Validation
    {

    }
}

In VB:

Imports System.Text

Public Class Validation

End Class

This class uses a StringBuilder, hence the need for the System.Text imports.

The Validation class has three properties.

In C#:

public int Count
{
    get
    {
        return ValidationList.Count;
    }
}

public string this[string propertyName]
{
    get
    {
        if (ValidationList.ContainsKey(propertyName))
            return ValidationList[propertyName];
        else
            return null;
    }
}

private Dictionary<String, String> ValidationList { get; set; }

In VB:

Public ReadOnly Property Count() As Integer
    Get
        Return ValidationList.Count
    End Get
End Property

Public ReadOnly Property Item(ByVal propertyName As String) As String
    Get
        If ValidationList.ContainsKey(propertyName) Then
            Return ValidationList.Item(propertyName)
        Else
            Return Nothing
        End If
    End Get
End Property

Private _ValidationList As Dictionary(Of String, String)
Private Property ValidationList() As Dictionary(Of String, String)
       Get
        Return _ValidationList
    End Get
    Set(ByVal value As Dictionary(Of String, String))
        _ValidationList = value
    End Set
End Property

The ValidationList property retains a private Dictionary of validation errors. The key of the dictionary is the property name and the value is the error text. For example, the “LastName” property may have a validation error such as “Last Name is required.”

The Count property returns the number of properties with validation errors by returning the count of the ValidationList items.

The Item property provides access to the validation errors given a property name. This property is implemented as the class indexer in C#.

The constructor ensures that a new instance of the ValidationList Dictionary is created.

In C#:

public Validation()
{
    // Create the list to contain the validation errors
    ValidationList = new Dictionary<String, String>();
}

In VB:

Public Sub New()
    ‘ Create the list to contain the validation errors
    ValidationList = New Dictionary(Of String, String)
End Sub

The ToString method is overwritten in the Validation class to build a single string containing all of the validation errors.

In C#:

public override string ToString()
{
    StringBuilder sb = new StringBuilder();

    foreach (string k in ValidationList.Keys)
        sb.AppendLine(k + ": " + ValidationList[k]);

    return sb.ToString();
}

In VB:

Public Overrides Function ToString() As String
    Dim sb As New StringBuilder

    For Each k As String In ValidationList.Keys
        sb.AppendLine(k & ": " & ValidationList(k))
    Next
    Return sb.ToString
End Function

This method uses the StringBuilder class to build up the potentially large string of errors.

When a validation error is added to the list, it is added for a particular property. If the property already has a validation error, additional validation errors are appended to it, separated by semi-colons (;). An AddValidationError method handles this logic.

In C#:

private void AddValidationError(string propertyName, string message)
{
    // If the property already has a message, append this message
    if (ValidationList.ContainsKey(propertyName))
    {
        string existingMessage = ValidationList[propertyName];

        if (!existingMessage.Contains(message))
            // Append the new message to the existing message
            ValidationList[propertyName] += "; " + message;
    }
    else
        // Add the message to the validation list
        ValidationList.Add(propertyName, message);
}

In VB:

Private Sub AddValidationError(ByVal propertyName As String, _
         ByVal message As String)

    ‘ If the property already has a message, append this message
    If ValidationList.ContainsKey(propertyName) Then
        Dim existingMessage As String = ValidationList(propertyName)

        If Not existingMessage.Contains(message) Then
            ‘ Append the new message to the existing message
            ValidationList(propertyName) &= "; " & message
        End If
    Else
        ‘ Add the message to the validation list
        ValidationList.Add(propertyName, message)
    End If
End Sub

A ValidateClear method clears any existing validation errors for a property. This method should be called before performing any new validation on the property. For example, the user leaves the Last Name field empty. The validation is performed and a validation error entry is created in the ValidationList. Then the user enters a value into the last name field. The original validation error must be cleared before revalidating the value.

In C#:

public void ValidateClear(string propertyName)
{
    // If the Property doesn’t have any messages, this is done
    if (ValidationList.ContainsKey(propertyName))
        // Otherwise, remove the entry
        ValidationList.Remove(propertyName);
}

In VB:

Public Sub ValidateClear(ByVal propertyName As String)
    ‘ If the Property doesn’t have any messages, this is done
    If ValidationList.ContainsKey(propertyName) Then
        ‘ Otherwise, remove the entry
        ValidationList.Remove(propertyName)
    End If
End Sub

The ValidateClear method uses the Remove method of the Dictionary to remove any Dictionary entry for the property.

That’s it for the basics of the Validation class. The only thing that is left to do is build all of the methods to perform the types of validation that your application requires.

For example, a common requirement is to ensure that a property is not left blank.

In C#:

public bool ValidateRequired(string propertyName,
                             string value)
{
    string newMessage = String.Empty;

    if (String.IsNullOrEmpty(value))
    {
        newMessage =
           String.Format("{0} is required, please enter a valid value",
                                    propertyName);
        // Add the message to the validation list
        AddValidationError(propertyName, newMessage);
        return false;
    }
    else
        return true;
}

In VB:

Public Function ValidateRequired(ByVal propertyName As String, _
                                 ByVal value As String) As Boolean
    Dim newMessage As String = String.Empty

    If String.IsNullOrEmpty(value) Then
      newMessage = _
        String.Format("{0} is required. Please enter a valid value.", _
                                  propertyName)
      ‘ Add the message to the validation list
      AddValidationError(propertyName, validationMessage)
      Return False
    Else
      Return True
    End If

End Function

This function performs the validation, adding a validation error as appropriate.

Another common requirement is that a property not exceed a maximum length.

In C#:

public Boolean ValidateLength(string propertyName,
                                string value, int maxLength)
{
    String newMessage = String.Empty;

    if (!String.IsNullOrEmpty(value) && value.Length > maxLength)
    {
        newMessage = String.Format("{0} has a maximum size of {1}",
                                 propertyName, maxLength);
        // Add the message to the validation list
        AddValidationError(propertyName, newMessage);
        return false;
    }
    else
        return true;
}

In VB:

Public Function ValidateLength(ByVal propertyName As String, _
         ByVal value As String, ByVal maxLength As Integer) As Boolean
    Dim sMessage As String = String.Empty

    If Not String.IsNullOrEmpty(value) AndAlso _
                             value.Length > maxLength Then
        sMessage = String.Format("{0} has a maximum size of {1}.", _
                                propertyName, maxLength)
        ‘ Add the message to the validation list
        AddValidationError(propertyName, sMessage)
        Return False
    Else
        Return True
    End If
End Function

You get the idea. You can add any number of these to perform whatever validation your application requires.

Some additional suggestions:

  • ValidateAlphaNumeric
  • ValidateDirectory
  • ValidateEnum
  • ValidateFileExists
  • ValidateMinLength
  • ValidateNonZero
  • ValidateNoSpaces
  • ValidateNumeric

You can create any validation method you need for your application. Just add code to perform the validation and then call AddValidationError as appropriate.

You call these validation methods from your business objects as shown below.

NOTE: This code assume you have a ValidationInstance variable that is declared in your class as a new instance of the Validation class.

In C#:

private string _LastName;
public string LastName
{
    get { return _LastName; }
    set
    {
        if (_LastName == null || _LastName != value)
        {
            string propertyName = "LastName";
            _LastName = value;

            // Validate the last name
            ValidationInstance.ValidateClear(propertyName);
            ValidationInstance.ValidateRequired(propertyName, value); 
        }
    }
}

In VB:

Private _LastName As String
Public Property LastName() As String
    Get
        Return _LastName
    End Get
    Set(ByVal value As String)
        If _LastName Is Nothing OrElse _LastName <> value Then
            Dim propertyName As String = "LastName"
            _LastName = value

            ‘ Validate the last name
            ValidationInstance.ValidateClear(propertyName)
            ValidationInstance.ValidateRequired(propertyName, value) 
        End If
    End Set
End Property

Or you can call these validation methods directly from your user interface as shown below.

NOTE: This code assume you have a ValidationInstance variable that is declared in your form as a new instance of the Validation class.

In C#:

private void textBox1_Validating(object sender, CancelEventArgs e)
{
    string propertyName  = "Last Name";
    validationInstance.ValidateClear(propertyName);
    validationInstance.ValidateRequired(propertyName, textBox1.Text);
    validationInstance.ValidateLength(propertyName, textBox1.Text, 20);

    if (validationInstance.Count > 0)
        errorProvider1.SetError(textBox1,
                        validationInstance[propertyName]);
    else
        // Clear the validation error
        errorProvider1.SetError(textBox1, String.Empty);
}

In VB:

Private Sub TextBox1_Validating(ByVal sender As Object, _
             ByVal e As System.ComponentModel.CancelEventArgs) _
             Handles TextBox1.Validating
    Dim propertyName As String = "Last Name"
    validationInstance.ValidateClear(propertyName)
    validationInstance.ValidateRequired(propertyName, TextBox1.Text)
    validationInstance.ValidateLength(propertyName, TextBox1.Text, 20)

    If validationInstance.Count > 0 Then
        ErrorProvider1.SetError(TextBox1, _
                        validationInstance.Item(propertyName))
    Else
        ‘ Clear the validation error
        ErrorProvider1.SetError(TextBox1, String.Empty)
    End If
End Sub

Hope this helps you encapsulate all of your validation logic into a reusable class.

Enjoy!

July 10, 2009

Populating a Business Object from a DataTable

Filed under: C#,Data,OOP,VB.NET @ 3:43 pm

Most business applications have business objects such as customer, order, or invoice. Often, the data access layer (DAL) provides the data and your code needs to use that data to manually populate a business object.

This post describes how to manually populate a business object from a DataTable. It uses the Customer class defined here.

The code is first shown in both VB and C#. It is then described in detail below.

In C#:

public static List<Customer> Retrieve()
{
   DataTable dt = Dac.ExecuteDataTable("CustomerRetrieveAll", null);

    List<Customer> customerList = new List<Customer>();

    foreach (DataRow dr in dt.Rows)
    {
        customerList.Add(new Customer()
                    {
                        CustomerId = (int)dr["CustomerId"],
                        LastName = (string)dr["LastName"],
                        FirstName = (string)dr["FirstName"]
                    });
    }

    return customerList;
}

In VB:

Public Shared Function Retrieve() As List(Of Customer)

    Dim dt As DataTable = Dac.ExecuteDataTable( _
                   
"CustomerRetrieveAll", nothing)

    Dim customerList As New List(Of Customer)

    For Each dr As DataRow In dt.Rows
       customerList.Add(New Customer With _
                    {.CustomerId = CType(dr("CustomerID"), Integer), _
                     .LastName = dr("LastName").ToString, _
                     .FirstName = dr("FirstName").ToString}) 
    Next

    Return customerList
End Function

The Retrieve method is public and static (shared in VB). It is public so it can be called from the user interface code. It is static/shared because it does not use or retain any state. This allows the function to be called without creating an instance of the class containing the function.

This function calls the ExecuteDataTable method from here, which returns a DataTable. It then creates a new list and adds a new customer to the list for each row in the DataTable.

The resulting list of customers is returned. The user interface code can then use this list to bind to a control such as a grid or combo box. You can also search, sort, filter, or work with this list using LINQ or Lambda expressions.

NOTE: If the fields in the DataTable match the properties of the business object, you could use reflection to map the fields to the properties. Though reflection does have a performance hit, it provides a more general solution that could be used by any business object.

Enjoy!

July 6, 2009

Zodiac Signs: DateRange Class

Filed under: C#,OOP @ 4:56 pm

This entry details the implementation of the DateRange class from this example in C#:

public class DateRange
{
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }

    // Constructor
    public DateRange(DateTime startdate, DateTime enddate)
    {
        this.StartDate = startdate;
        this.EndDate = enddate;
    }
}

This class uses the automatically implemented properties feature that was introduced to C# in .NET 3.5 to define the two DateTime properties.

The constructor in this example creates a DateRange with an appropriate start date and end date. Though this class was created for the Zodiac Signs example, it could be used anywhere a DateRange is needed.

Enjoy!

Zodiac Sign: ZodiacSigns Class

Filed under: C#,LINQ,OOP @ 4:53 pm

This entry details the implementation of the ZodiacSigns class from this example in C#:

public class ZodiacSigns : List<ZodiacSign>
{

}

Constructor

The following is the constructor defined in the ZodiacSigns class:

public ZodiacSigns()
{
    InitializeCollection();
}

This constructor ensures that the collection of zodiac signs is initialized when an instance of this class is created.

Methods

The following are the methods in the ZodiacSigns class:

public string FindSign(DateTime desiredDate)
{
    // Find the name of the Zodiak sign with the date within the ranges
    var query = from z in this
                from d in z.DateRanges
                where (desiredDate >= d.StartDate) &&
                       
(desiredDate <= d.EndDate)
                select z.Name;
    string name = query.FirstOrDefault();
    return name;
}

The FindSign method uses LINQ to find the date within the defined ranges. It returns the name of the appropriate sign.

private void InitializeCollection()
{
    // This could potentially read all of these from a file.
    // NOTE: This data may not be accurate
    this.Add(new ZodiacSign("Rat",
        new List<DateRange> {
            new DateRange(new DateTime(1996, 2,19),
                new DateTime(1997, 2,6)),
            new DateRange(new DateTime(2008, 2,7),
                new DateTime(2009, 2,25))}));
    this.Add(new ZodiacSign("Ox",
        new List<DateRange> {
            new DateRange(new DateTime(1997, 2,7),
                new DateTime(1998, 2,27)),
            new DateRange(new DateTime(2009, 2,26),
                new DateTime(2010, 2,13))}));
    this.Add(new ZodiacSign("Tiger",
        new List<DateRange> {
            new DateRange(new DateTime(1998, 2,28),
                new DateTime(1999, 2,15)),
            new DateRange(new DateTime(2010, 2,14),
                new DateTime(2011, 2,2))}));
    this.Add(new ZodiacSign("Rabbit",
        new List<DateRange> {
            new DateRange(new DateTime(1999, 2,16),
                new DateTime(2000, 2,4)),
            new DateRange(new DateTime(2011, 2,3),
                new DateTime(2012, 1,22))}));
    this.Add(new ZodiacSign("Drago",
        new List<DateRange> {
            new DateRange(new DateTime(2000, 2,5),
                new DateTime(2001, 2,23)),
            new DateRange(new DateTime(2012, 1,23),
                new DateTime(2013, 2,9))}));
    this.Add(new ZodiacSign("Snake",
        new List<DateRange> {
            new DateRange(new DateTime(2001, 1,24),
                new DateTime(2002, 2,11)),
            new DateRange(new DateTime(2013, 2,10),
                new DateTime(2014, 1,30))}));
    this.Add(new ZodiacSign("Horse",
        new List<DateRange> {
            new DateRange(new DateTime(2002, 2,12),
                new DateTime(2003, 1,31)),
            new DateRange(new DateTime(2014, 1,31),
                new DateTime(2015, 2,18))}));
    this.Add(new ZodiacSign("Sheep",
        new List<DateRange> {
            new DateRange(new DateTime(2003, 2,1),
                new DateTime(2004, 2,21)),
            new DateRange(new DateTime(2015, 2,19),
                new DateTime(2016, 2,7))}));
    this.Add(new ZodiacSign("Monkey",
        new List<DateRange> {
            new DateRange(new DateTime(2004, 1, 22),
                new DateTime(2005, 2, 8)),
            new DateRange(new DateTime(2016, 2, 8),
                new DateTime(2017, 1, 27))}));
    this.Add(new ZodiacSign("Rooster",
        new List<DateRange> {
            new DateRange(new DateTime(2005, 2, 9),
                new DateTime(2006, 1, 28)),
            new DateRange(new DateTime(2017, 1, 28),
                new DateTime(2018, 2, 15))}));
    this.Add(new ZodiacSign("Dog",
        new List<DateRange> {
            new DateRange(new DateTime(2006, 1, 29),
                new DateTime(2007, 2, 17)),
            new DateRange(new DateTime(2018, 2, 16),
                new DateTime(2019, 2, 4))}));
    this.Add(new ZodiacSign("Pig",
        new List<DateRange> {
            new DateRange(new DateTime(2007, 2, 18),
                new DateTime(2008, 2, 6)),
            new DateRange(new DateTime(2019, 2, 5),
                new DateTime(2020, 2, 21))}));
}

The InitializeCollection method does exactly what it sounds like … creating the set of date ranges associated with each zodiac sign. This code takes advantage of the list initializers now available in C#.

Enjoy!

Zodiac Sign: ZodiacSign Class

Filed under: C#,OOP @ 4:40 pm

This entry details the implementation of the ZodiacSign class from this example in C#:

public class ZodiacSign
{
    // Properties
    public string Name { get; set; }
    public List<DateRange> DateRanges { get; set; }

    // Constructor
    public ZodiacSign(string name, List<DateRange> dateranges)
    {
        this.Name = name;
        this.DateRanges = dateranges;
    }
}

This example uses the automatically implemented properties feature that was introduced to C# in .NET 3.5 to define the two properties: Name and DateRanges.

The constructor in this example creates a zodiac sign with an appropriate name and set of date ranges.

Enjoy!

Applying OOP to Simple Situations: Chinese Zodiac Signs

Filed under: C#,OOP,VB.NET @ 4:34 pm

Here is the story defining the simple use case for this application:

  1. The user picks a date between 2/19/1996 and 2/5/2019.
  2. The system displays the appropriate Chinese zodiac sign (Monkey, Dog, Rat, etc)

Seems simple enough. So how to implement this …

Defining the Classes

The first step in using OOP with a simple situation is the same as with any application … define the "nouns”.

These are the first nouns I came up with:

  • Zodiac sign
  • Date range

The next step is to think through each of these nouns and determine which make sense as classes for building the code to support this feature.

Zodiac Sign

This feature needs to work with a zodiac sign, so a ZodiacSign class makes sense.

In addition, this feature needs to retain the set of zodiac signs. So a ZodiacSigns (plural) class is also needed to track the list of ZodiacSign instances.

Date Range

Each zodiac sign is associated with a date range, so a DateRange class makes sense to track the dates.

Defining the Properties and Methods

The next step is to define what data that each class retains (called properties in OOP) and what functionality that the class provides (called methods in OOP).

After reviewing the nouns, three classes were defined:

ZodiacSign

This class provides the definition of a single Chinese zodiac sign. The properties for this class include:

  • Name: Name of the sign such as “dog” or “monkey”.
  • DateRanges: Set of date ranges associated with the sign.

ZodiacSigns

This class manages the list of all zodiac signs. To leverage the .NET Framework List features, this class can inherit from the built in generic List class.

This class has two methods:

  • InitializeCollection: Builds the list with the set of zodiac signs and date ranges.
  • FindSign: Given a date, finds the sign.

DateRange

This class has two simple properties:

  • Start Date: First date of the date range.
  • End Date: End date of the date range.

Using the Classes

The user interface portion of the application creates an instance of the ZodiacSigns class and calls the FindSign method as needed.

Enjoy!

MasterMind: Peg Class

Filed under: OOP,VB.NET @ 3:07 pm

This entry describes the Peg class from this example in further detail:

Public Class Peg

End Class

Properties

The properties of the class are as follows:

Private _Column As Integer
”’ <summary>
”’ Gets the column of this peg.
”’ </summary>
”’ <value></value>
”’ <returns></returns>
”’ <remarks></remarks>
Public Property Column() As Integer
    Get
        Return _Column
    End Get
    Private Set(ByVal value As Integer)
        _Column = value
    End Set
End Property

The Column property defines the column of the board that contains this peg. The setter is private because once the peg is created, its location cannot be moved.

Private _Correct As Boolean?
”’ <summary>
”’ Gets or sets whether the peg is in a correct position.
”’ </summary>
”’ <value></value>
”’ <returns></returns>
”’ <remarks></remarks>
Public Property Correct() As Boolean?
    Get
        Return _Correct
    End Get
    Friend Set(ByVal value As Boolean?)
        _Correct = value
    End Set
End Property

The Correct property defines whether this peg denotes a correct answer. A correct answer requires that the peg be of the same color and column position as the correct answer.

Private _PegColor As Color?
”’ <summary>
”’ Gets or sets the peg color.
”’ </summary>
”’ <value></value>
”’ <returns></returns>
”’ <remarks></remarks>
Public Property PegColor() As Color?
    Get
        Return _PegColor
    End Get
    Set(ByVal value As Color?)
        _PegColor = value
    End Set
End Property

The PegColor property defines the color of this peg as defined by the user.

Private _Row As Integer
”’ <summary>
”’ Gets the row containing the peg.
”’ </summary>
”’ <value></value>
”’ <returns></returns>
”’ <remarks></remarks>
Public Property Row() As Integer
    Get
        Return _Row
    End Get
    Private Set(ByVal value As Integer)
        _Row = value
    End Set
End Property

The Row property defines the row of the board that contains this peg. The setter is private because once the peg is created, its location cannot be moved.

Constructor

The following is the constructor defined in the Peg class:

”’ <summary>
”’ Constructs a new instance in a specific position.
”’ </summary>
”’ <param name="columnIndex"></param>
”’ <param name="rowIndex"></param>
”’ <remarks></remarks>
Public Sub New(ByVal columnIndex As Integer, ByVal rowIndex As Integer)
    Me.Column = columnIndex
    Me.Row = rowIndex
    Me.PegColor = Nothing
End Sub

Download the sample code (that is currently only in VB) from here.

Enjoy!

MasterMind: MasterMind Class

Filed under: OOP,VB.NET @ 3:00 pm

This entry describes the MasterMind class from this example in further detail:

Public Class MasterMind

End Class

Properties

The properties of the class are as follows:

Private _Answer As List(Of Color)
”’ <summary>
”’ Gets the answer.
”’ </summary>
”’ <value></value>
”’ <returns></returns>
”’ <remarks></remarks>
Public Property Answer() As List(Of Color)
    Get
        Return _Answer
    End Get
    Private Set(ByVal value As List(Of Color))
        _Answer = value
    End Set
End Property

The Answer property is a list of correct colors. This property has a Private setter because the answer itself is managed internal to the MasterMind class.

Private _Board As List(Of Peg)
”’ <summary>
”’ Gets or sets the board for the game.
”’ </summary>
”’ <value></value>
”’ <returns></returns>
”’ <remarks></remarks>
Public Property Board() As List(Of Peg)
    Get
        Return _Board
    End Get
    Private Set(ByVal value As List(Of Peg))
        _Board = value
    End Set
End Property

The Board property is a list of Pegs. This property also has a Private setter because the board is managed internal to the MasterMind class.

Private _CurrentTurn As Integer
”’ <summary>
”’ Gets or sets the number of the current turn.
”’ </summary>
”’ <value></value>
”’ <returns></returns>
”’ <remarks>
”’ The user cannot go back to any prior turn.
”’ </remarks>
Public Property CurrentTurn() As Integer
    Get
        Return _CurrentTurn
    End Get
    Private Set(ByVal value As Integer)
        _CurrentTurn = value
    End Set
End Property

The CurrentTurn property counts the number of guesses. This property also has a Private setter because it is managed internal to the MasterMind class.

Private Shared _GameColors As List(Of Color)
”’ <summary>
”’ Gets the game colors.
”’ </summary>
”’ <value></value>
”’ <returns></returns>
”’ <remarks></remarks>
Public Shared Property GameColors() As List(Of Color)
    Get
        If _GameColors Is Nothing Then
            _GameColors = New List(Of Color)
            _GameColors.Add(Color.Red)
            _GameColors.Add(Color.Yellow)
            _GameColors.Add(Color.Green)
            _GameColors.Add(Color.Purple)
            _GameColors.Add(Color.Blue)
            _GameColors.Add(Color.Black)
            _GameColors.Add(Color.Orange)
            _GameColors.Add(Color.Pink)
        End If
        Return _GameColors
    End Get
    Private Set(ByVal value As List(Of Color))
        _GameColors = value
    End Set
End Property

The GameColors property defines the set of available peg colors. These are hard-coded into the application, but could instead be settable by a game configuration feature (which is not implemented in this sample). Because the set of colors is the same for every game (at least in this implementation), they are defined using a Shared property.

Private _NumberOfRows As Integer = 8
”’ <summary>
”’ Gets the number of rows that should be allowed in the game.
”’ </summary>
”’ <value></value>
”’ <returns></returns>
”’ <remarks></remarks>
Public Property NumberOfRows() As Integer
    Get
        Return _NumberOfRows
    End Get
    Private Set(ByVal value As Integer)
        _NumberOfRows = value
    End Set
End Property

The NumberOfRows property defines the number of rows in the game. This implementation defines a default value of 8 rows. This could instead by set by the user with a configuration feature (which is not implemented in this sample).

Private _NumberOfHoles As Integer = 4
”’ <summary>
”’ Gets the number of holes in each row that should be allowed in the game.
”’ </summary>
”’ <value></value>
”’ <returns></returns>
”’ <remarks></remarks>
Public Property NumberOfHoles() As Integer
    Get
        Return _NumberOfHoles
    End Get
    Private Set(ByVal value As Integer)
        _NumberOfHoles = value
    End Set
End Property

The NumberOfHoles property defines the number of holes in each row in the game. This implementation defines a default value of 4 holes. This could instead by set by the user with a configuration feature (which is not implemented in this sample).

Constructors

The following are the constructors defined in the MasterMind class:

”’ <summary>
”’ Construct the game with the default holes and rows
”’ </summary>
”’ <remarks></remarks>
Public Sub New()
    ‘ Initialize the game
    InitializeGame()
End Sub

”’ <summary>
”’ Construct the game with a defined number of holes and rows.
”’ </summary>
”’ <param name="holesPerRow"></param>
”’ <param name="rows"></param>
”’ <remarks></remarks>
Public Sub New(ByVal holesPerRow As Integer, ByVal rows As Integer)
    Me.NumberOfHoles = holesPerRow
    Me.NumberOfRows = rows

    ‘ Initialize the game
    InitializeGame()
End Sub

When a new instance of the game is created, the InitializeGame method is called to set up the board and define an answer.

Methods

The following are the MasterMind class methods:

#Region " CreateAnswer"
    ”’ <summary>
    ”’ Creates the answer for the game
    ”’ </summary>
    ”’ <remarks></remarks>
    Private Sub CreateAnswer()
        Dim maxColorIndex As Integer = GameColors.Count – 1
        Dim colorIndex As Integer

        ‘ Clear the answers
        Answer = New List(Of Color)

        ‘ Set up for random numbers
        Dim randomColor As New Random()

        ‘ Build the answer
        For hole As Integer = 0 To NumberOfHoles – 1
            colorIndex = randomColor.Next(0, maxColorIndex)

            Answer.Insert(hole, GameColors(colorIndex))
        Next hole

    End Sub
#End Region

The CreateAnswer method uses the Random .NET Framework class to create a valid answer. Notice that this is a private method and cannot be called directly.

#Region " CreateBoard"
    ”’ <summary>
    ”’ Creates the board for the game.
    ”’ </summary>
    ”’ <remarks></remarks>
    Private Sub CreateBoard()
        ‘ Create the board as a list of potential pegs
        Board = New List(Of Peg)

        Dim peg As Peg

        ‘ Build a control for each hole in the board.
        For rowIndex As Integer = 0 To NumberOfRows – 1
            For pegIndex As Integer = 0 To NumberOfHoles – 1
                ‘ Create a peg
                peg = New Peg(pegIndex, rowIndex)

                ‘ Add it to the board
                Board.Add(peg)
            Next
        Next

    End Sub
#End Region

The CreateBoard method builds the structure of the board with the defined number of rows and holes. Notice that this is a private method and cannot be called directly.

#Region " InitializeGame"
    ”’ <summary>
    ”’ Initialize the game.
    ”’ </summary>
    ”’ <remarks></remarks>
    Private Sub InitializeGame()
        ‘ Create the board
        CreateBoard()

        ‘ Create the answer
        CreateAnswer()

        ‘ Initialize the try
        CurrentTurn = 1
    End Sub
#End Region

The InitializeGame method creates the board and defines the answer. It is also a private method that is called when the game is constructed.

#Region " ProcessGuess"
    ”’ <summary>
    ”’ Processes the user’s guess.
    ”’ </summary>
    ”’ <remarks></remarks>
    Public Function ProcessGuess() As Boolean
        ‘ Row is 0-based; turns are 1-based
        Dim row As Integer = CurrentTurn – 1
        Dim allCorrect As Boolean = True

        ‘ Get the pegs for the row
        Dim rowPegs = Board.Where(Function(p) p.Row = row)

        For Each p As Peg In rowPegs
            If p.PegColor = Answer(p.Column) Then
                ‘ This answer is correct
                p.Correct = True
            Else
                ‘ This answer is incorrect
                p.Correct = False
                allCorrect = False
            End If
        Next

        ‘ Increment the turn
        CurrentTurn += 1

        Return allCorrect
    End Function
#End Region

The ProcessGuess method is a public method that is called by the UI each time the user is finished with a row and wants to submit it as a guess.

Download the sample code (that is currently only in VB) from here.

Enjoy!

« Previous PageNext Page »

© 2018 Deborah's Developer MindScape   Provided by WPMU DEV -The WordPress Experts   Hosted by Microsoft MVPs