![]() |
Languages »
C# »
Attributes
Beginner
License: The Code Project Open License (CPOL)
Further attributes - method based attributes and data conversion for business objectsBy Malisa NcubeThis article shows how you can use attribute based programming, reflection to perform data conversion on your business objects. |
C# (C# 1.0, C# 2.0, C# 3.0), Architect
|
||||||||||
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
This is a follow-up to the "An attribute based approach to business object validation" article in which i introduced the use attributes and reflection to validate business objects. I also promised to write another article on data converters and method based validators, and here it is.
In this article i will use an example to show how you can validate your business object using a method based attribute and how you can convert data assigned to properties when it is saved and re-convert it when the business object is read. All this will be based on attributes.
Sometimes a need arises, to store data differently from the way it is viewed. For example if you have a field which would have comments you may want to compress them on storage. This will certainly be good for your bandwidth - especially for us here in Africa.
In this example lets use a Candidate object. I'm assuming a company considering interviewees.
I would like you to look closely at the attributes that we have used to decorate the object below. The attributes will enable validation of the business object much easier than having to write some instructions on the presentation layer. Some attributes below will help in converting the data so that it is stored differently from the way it is viewed.
public class Candidate : EntityObject
{
#region Constructors
public Candidate(DataContext dataContext)
{
}
public Candidate()
{
}
#endregion
#region Properties
/// <summary>
/// Property that describes the FirstName of this <see cref=""Candidate"">
/// We ensure that this is unique by using a method based validator.
/// </see></summary>
[Required]
[MethodRule("Unique")]
public int CandidateNo { get; set; }
/// <summary>
/// Property that describes the Title of this <see cref=""Candidate"">
/// This property defaults to "Mr" if the user does not specify a value.
/// </see></summary>
[DefaultValue("Mr")]
public string Title { get; set; }
/// <summary>
/// Property that describes the Firstname of this <see cref=""Candidate"">
/// </see></summary>
[Required]
public string Firstname { get; set; }
/// <summary>
/// Property that describes the Lastname of this <see cref=""Candidate"">
/// </see></summary>
[Required]
public string Lastname { get; set; }
/// <summary>
/// Property that describes the Age of this <see cref=""Candidate"">
/// </see></summary>
[InRange(18, 95)]
[DefaultValue(30)]
public int? Age { get; set; }
/// <summary>
/// Property that describes the RegistrationDate of this <see cref=""Candidate"">
/// We use a method to validate this property. I have placed the Validators
/// in their region below.
/// </see></summary>
[MethodRule("ValidRegistration")]
public DateTime RegistrationDate { get; set; }
/// <summary>
/// Property that describes the CV of this <see cref=""Candidate"">
/// In this attribute we have a DataConverter which should compress the CV
/// text before it is stored.
/// </see></summary>
[DataConversion(typeof(StringZipper))]
public string CV { get; set; }
#endregion
#region Methods
public override string ToString()
{
return string.Format("ObjectID: {0} \nName: {1} \nSurname : {2}" +
"\nAge: {3} \nRegistration Date: {4} \nCV: {5}",
ObjectID, Firstname, Lastname, Age, RegistrationDate, CV);
}
#endregion
#region Method Based Validators
public void ValidRegistration(object sender, ValidateEventArgs e)
{
e.Valid = true;
if (RegistrationDate.Date < DateTime.Parse("01/01/2008 8:00:00"))
{
e.Valid = true;
e.ErrorMessage = "Impossible! The client could not have registered" +
"before we started this business.";
//Set to the minimum date - if you want
e.Property.SetValue(this, DateTime.Parse("01/01/2008 8:00:00"), null);
}
}
public void Unique(object sender, ValidateEventArgs e)
{
if (this.IsNew)
{
// A bit of LINQ to find if we have this candidate
var query = from candidate in DataSettings.DataContext.EntityObjects.OfType<candidate>()
where candidate.CandidateNo == this.CandidateNo
select candidate;
e.Valid = query.Count() <= 1;
e.ErrorMessage = "The Candidate number must be unique.";
}
}
#endregion
}
The collection of attributes included in this object are:
In this example, i'm saving all my objects in a cache-like object, which i call DataSettings.DataContext.EntityObjects, and i use the code below to find an object.
Guid CandidateID = (Guid)dataGridView1[9, e.RowIndex].Value;
Candidate candidate = new Candidate(); //You may decide to use static methods for this
candidate.Load(CandidateID);
if (candidate != null)
{
candidateBindingSource.DataSource = candidate;
cVTextBox.Text = candidate.CV;
}
If we save the data we would have the following.
In this example i would like you to focus on the area where i create a delegate using Delegate.CreateDelegate(typeof(EventHandler)...
public class EntityObject : IEntityObject
{
#region Internal Fields
internal Guid objectID;
public bool IsNew { get; set; }
public bool IsDirty { get; set; }
#endregion
#region Public Properties
/// <summary>
/// The Errors collection to keep the errors. The validation method populates this.
/// </summary>
public readonly List<error> Errors = new List<error>();
public DataContext dataContext;
public Guid ObjectID
{
get
{
return objectID;
}
set
{
objectID = value;
}
}
#endregion
#region Constructors
public EntityObject()
{
if (dataContext == null)
{
if (DataSettings.DataContext == null) DataSettings.DataContext = new DataContext();
this.dataContext = DataSettings.DataContext;
}
//Create unique object identifier
objectID = Guid.NewGuid();
IsNew = true;
IsDirty = false;
}
public EntityObject(DataContext dataContext)
{
if (dataContext == null)
{
if (DataSettings.DataContext == null) DataSettings.DataContext = new DataContext();
this.dataContext = DataSettings.DataContext;
}
else
this.dataContext = dataContext;
}
#endregion
.....
public virtual void Validate(object sender, ValidateEventArgs e)
{
//Initialise the error collection
Errors.Clear();
//Enable calling the OnValidate event before validation takes place
if (this.OnValidate != null) this.OnValidate(this, new ValidateEventArgs());
try
{
foreach (PropertyInfo prop in this.GetType().GetProperties())
{
/* Get property value assigned to property */
object data = prop.GetValue(this, null);
#region Default Value setting
...
#endregion
#region IsRequired Validation
...
#endregion
#region InRange Validation
...
#endregion
#region MethodBasedValidation
/* Check if property value is Method Based Validation */
foreach (object customAttribute in prop.GetCustomAttributes(typeof(MethodRuleAttribute), true))
{
//Create event handler dynamically
EventHandler<validateeventargs> eventHandler =
Delegate.CreateDelegate(typeof(EventHandler<validateeventargs>),
this, (customAttribute as MethodRuleAttribute).ValidationMethod) as
EventHandler<validateeventargs>;
ValidateEventArgs args = new ValidateEventArgs(prop,
string.Format("Value assigned to {0} is invalid.", prop.Name));
eventHandler(this, args); // Execute event handler
if (!args.Valid)
{
Errors.Add(new Error(this, prop.Name, args.ErrorMessage));
}
}
#endregion
#region Data Converters
/* Check if property value is required */
foreach (object customAttribute in prop.GetCustomAttributes(
typeof(DataConversionAttribute), true))
{
Type conversionType = (customAttribute as DataConversionAttribute).ConverterType;
prop.SetValue(this, Converter.Instance(conversionType).Change.ToPersistentType(data), null);
}
#endregion
}
}
catch (Exception ex)
{
//
throw new Exception("Could not validate Object!", ex);
}
finally
{
//Enable calling the OnValidated event after validation has taken place
if (this.OnValidated != null) this.OnValidated(this, new ValidateEventArgs());
}
}
A delegate is a pointer to a method or event handler.
The .NET framework has a very interesting feature which enables us to create delegates at runtime and bind them to event handlers.
The following is the algorithm for a method based validator
The main thing behind the method based validator is the ability to bind the event at runtime to a property, and thanks again to reflection. We can find attributes associated with a property and then we create a delegate which points to the event-handler presented at the attribute parameter. We then invoke that event handler using event arguments, which would then help us to obtain a response from the event handler which is then used to add associated Error to the object.Errors collection of this object.
//Create delegate and map it to ValidationMethod of current property
EventHandler<validateeventargs> eventHandler = Delegate.CreateDelegate(
typeof(EventHandler<validateeventargs>), this,
(customAttribute as MethodRuleAttribute).ValidationMethod) as EventHandler<validateeventargs>;
//Create event arguments which we will inject into handler, and then review the modifications on it
ValidateEventArgs args = new ValidateEventArgs(prop, string.Format("Value assigned to {0} is invalid.", prop.Name));
eventHandler(this, args); // Execute event handler through the delegate
if (!args.Valid) // Read argument after it has been affected by the event handler
{
Errors.Add(new Error(this, prop.Name, args.ErrorMessage));
}
For more information on dynamic delagate creation, consult the MDSN documentation (here).
The data converters enable us to change the data before it is stored. They inherit from the DataConverter class below.
We made the methods virtual so that you can override them for any implementation of conversion. The ToPersistentType(object value) is to enable conversion to storage type while FromPersistentType(object value) converts data back to viewable type.
public class DataConverter
{
public virtual object ToPersistentType(object value)
{
throw new NotImplementedException();
}
public virtual object FromPersistentType(object value)
{
throw new NotImplementedException();
}
}
The class below is one data converter, that may be used to compress string data. You may decide to create another one to compress other data types or encrypt certain data when its stored. That reminds me of a payroll application which i built and the clients did not want the column which stores salary to be readable to database admnistrators.
class StringZipper: DataConverter
{
public override object ToPersistentType(object value)
{
return value == null ? null : (object)Zipper.Compress((string)value);
}
public override object FromPersistentType(object value)
{
return value == null ? null : (object)Zipper.Decompress((string)value);
}
}
Code below shows how you can load the and object within the base class.
public void Load(Guid entityObjectID)
{
EntityObject entityObject = new EntityObject(DataSettings.DataContext);
entityObject = dataContext.Load(entityObjectID);
entityObject.IsNew = false;
entityObject.IsDirty = false;
if (entityObject != null)
{
foreach (PropertyInfo prop in entityObject.GetType().GetProperties())
{
/* Get property value assigned to property */
object data = prop.GetValue(entityObject, null);
#region Data Converters
if ((prop.Attributes == PropertyAttributes.None) && (data != null))
prop.SetValue(this, data, null);
/* Check if property value is required */
foreach (object customAttribute in prop.GetCustomAttributes(typeof(DataConversionAttribute), true))
{
Type conversionType = (customAttribute as DataConversionAttribute).ConverterType;
prop.SetValue(this, Converter.Instance(conversionType).Change.FromPersistentType(data), null);
}
#endregion
}
}
else
{
Console.WriteLine("Could not find object!");
}
}
Published on 05/01/2009, follow-up of Attribute based validation
| You must Sign In to use this message board. | |||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 5 Jan 2009 Editor: Sean Ewington |
Copyright 2009 by Malisa Ncube Everything else Copyright © CodeProject, 1999-2009 Web22 | Advertise on the Code Project |