The ASP.NET do and don’ts serie : episode 4 – If you retrieve server controls in your code-behind do it the safe way !

When we are developping with databound controls, a common action is to retreive controls (like dropdownlists) in an objectdatasource’s “Inserting” event.

Here is a specific example :

In the InsertItemTemplate of a ListView:


<asp:ObjectDataSource runat="server" ID="MyDataSource"
 // ... />

<asp:DropDownList runat="server"
       ID="MyDDL"
       DataSourceID="MyDataSource"
       DataTextField="TheText"
       DataValueField="TheValue"/>

Do:


protected void TheDataSource_Inserting(object sender, ObjectDataSourceMethodEventArgs e)
{
    if (MyListView.InsertItem == null)
       return;

     DropDownList myDDL= MyListView.InsertItem.FindControl("MyDDL") as DropDownList;
     if (myDDL== null)
         return;
     if (string.IsNullOrEmpty(myDDL.SelectedValue))
         return;

    e.InputParameters["TheWantedValue"] = myDDL.SelectedValue;
 }

Don’t:


protected void TheDataSource_Inserting(object sender, ObjectDataSourceMethodEventArgs e)
{
    e.InputParameters.Add("TheWantedValue", ((DropDownList)MyListView.InsertItem.FindControl("MyDDL")).SelectedValue);
}

This for an obvious reason : you can’t always be sure that a problem won’t pop up.

A random problem can occur anywhere, whether the dropdownlist population or something else

So, be safe and protect yourself !

Incoming search terms:

  • objectdatasourcemethodeventargs dropdownlist

The ASP.NET do and don’ts serie : episode 3 – use the validator designed for your needs !

This post is relative to the first episode of the “ASP.NET do and don’ts serie”

ASP.NET contains several validation controls. Be sure to use the good ones for your needs.

Imagine you have the following dropdownlist

<asp:DropDownList runat="server" ID="MyDDL"
      // datasource params
     AppendDataBoundItems="true">
     <asp:ListItem Text="select a value" Value="-1" Selected="True" />
</asp:DropDownList>

Do:


<asp:RequiredFieldValidator runat="server" ID="MyDDLValidator" ControlToValidate="MyDDL" ErrorMessage="Selection required" InitialValue="-1" />

Don’t:


<asp:CompareValidator runat="server" ID="MyDDLValidator"
 ValueToCompare="-1" ControlToValidate="MyDDL"
 Operator="NotEqual" ErrorMessage="Selection required" />

Bring a bit of the Subsonic power to Linq to sql by adding automatic audit and logical delete fields

[Update]
You cand find the code to do the same with entity framework here
[/Update]

Recently I had to work on a project using Linq to sql  and I was suprised to see that Linq to SQL has no built-in auditing ability.

There is no support for logical deletion neither.

With the SubSonic’s ActiveReccord, all these features are built in (see the documentation page).

So, I decided to add this ability for the project, because I doesn’t want to change all the existing request to add these informations.

In my BLL layer, I have a manager object for my DAL Linq objects, so by Example if I have an “User” Object in my DAL layer, I have an “UserManager” object in my BLL layer.

All my managers objects inherit a “ManagerBase” Object, which contains only a “DataContext” Property, which can be used in the child classes.

So my first approach was to add a “SubmitChanges”  method in that class and call it from my child classes instead of directly call the one from the DataContext.

In my SubmitChanges method I add the needed logic before calling the one from the DataContext.

But I realized that when there are more than one developper working on an application, it can be difficult to ensure that everybody use the same technique. A person may not use the base class, another may forget to use the base class method, or other things..

Then I investigated the DataContext object with Reflector, and I noticed that the SubmitChanges(void) method directly call the SubmitChanges(ConflictMode) one


public void SubmitChanges()
{
 this.CheckDispose();
 this.SubmitChanges(ConflictMode.FailOnFirstConflict);
}

And if I take a look at the generated DataContext, I see that the DataContext is a partial class


public partial class MyContext: System.Data.Linq.DataContext
{
 // ...
}

So, I decided to add a new file in my DAL layer and extend the DataContext

partial class MyContext
{
// ...
}

Then override the SubmitChanges(ConflictMode) DataContext method


public override void SubmitChanges(System.Data.Linq.ConflictMode failureMode)
 {
SetSystemFields(); // notice this method call !
base.SubmitChanges(failureMode);
 }

In the SetSystemFields method, I retrieve the pending changes


private void SetSystemFields()
 {
ChangeSet changeset = this.GetChangeSet();

// ...
}

Then I retrieve the user identity in order to be able to set the CreatedBy and ModifiedBy properties of my data objects


System.Security.Principal.WindowsIdentity identity = System.Security.Principal.WindowsIdentity.GetCurrent();
 string userName = "Unknow";
 if (identity != null)
 {
userName = identity.Name;
 }

Then I loop over the objects that have to be inserted


int maxInserts = changeset.Inserts.Count;
for (int i = 0; i < maxInserts; i++)
{

// ...

}

and I use reflection to get the audit properties and set their values


object entity = changeset.Inserts[i];
 Type objectType = entity.GetType();
 PropertyInfo[] properties = objectType.GetProperties();

foreach (PropertyInfo property in properties)
 {
switch (property.Name.ToUpper())
{
case SystemFields.CreatedOn:
case SystemFields.ModifiedOn:
{
property.SetValue(entity, DateTime.Now, null);
} break;
case SystemFields.CreatedBy:
case SystemFields.ModifiedBy:
{
property.SetValue(entity, userName, null);
} break;
case SystemFields.IsDeleted:
{
property.SetValue(entity, false, null);
} break;
default:
break;
}
 }

Remark : “SystemFields” is a struct I defined with the audit column names but you may as well make it come from a config file.


private struct SystemFields
{
 public const string CreatedOn = "CREATEDON";
 public const string ModifiedOn = "MODIFIEDON";
 public const string CreatedBy = "CREATEDBY";
 public const string ModifiedBy = "MODIFIEDBY";
 public const string IsDeleted = "ISDELETED";
}

I do the same treatment for the update and deletes (if I want to enable them).

Now everytime that the DataContext’s SubmitChanges method is called, the audit fields are automaticaly set!

Here is the full partial class for the DataContext

using System;
using System.Data.Linq;
using System.Reflection;

namespace MyNamespace.DAL
{
 partial class MyContext
 {
  /// <summary>
  /// system fields for automatic audit and logical delete
  /// </summary>
  private struct SystemFields
   {
        public const string CreatedOn = "CREATEDON";
        public const string ModifiedOn = "MODIFIEDON";
        public const string CreatedBy = "CREATEDBY";
        public const string ModifiedBy = "MODIFIEDBY";
        public const string IsDeleted = "ISDELETED";
  }

public override void SubmitChanges(System.Data.Linq.ConflictMode failureMode)
{

SetSystemFields();

base.SubmitChanges(failureMode);
}

private void SetSystemFields()
{
ChangeSet changeset = this.GetChangeSet();

// retrieve the user name
System.Security.Principal.WindowsIdentity identity = System.Security.Principal.WindowsIdentity.GetCurrent();
string userName = "Unknow";
if (identity != null)
{
userName = identity.Name;
}

// insert
int maxInserts = changeset.Inserts.Count;
for (int i = 0; i < maxInserts; i++)
{
object entity = changeset.Inserts[i];
Type objectType = entity.GetType();
PropertyInfo[] properties = objectType.GetProperties();
foreach (PropertyInfo property in properties)
{
switch (property.Name.ToUpper())
{
case SystemFields.CreatedOn:
case SystemFields.ModifiedOn:
{
property.SetValue(entity, DateTime.Now, null);
} break;
case SystemFields.CreatedBy:
case SystemFields.ModifiedBy:
{
property.SetValue(entity, userName, null);
} break;
case SystemFields.IsDeleted:
{
property.SetValue(entity, false, null);
} break;
default:
break;
}
}
}

// update
int maxUpdates = changeset.Updates.Count;
for (int i = 0; i < maxUpdates; i++)
{
object entity = changeset.Updates[i];
Type objectType = entity.GetType();
PropertyInfo[] properties = objectType.GetProperties();
foreach (PropertyInfo property in properties)
{
switch (property.Name.ToUpper())
{
case SystemFields.ModifiedOn:
{
property.SetValue(entity, DateTime.Now, null);
} break;
case SystemFields.ModifiedBy:
{
property.SetValue(entity, userName, null);
} break;
default:
break;
}
}
}

// deletes (logical deletes)
int maxDeletes = changeset.Deletes.Count;
for (int i = 0; i < maxDeletes; i++)
{
object entity = changeset.Deletes[i];
Type objectType = entity.GetType();
PropertyInfo[] properties = objectType.GetProperties();
foreach (PropertyInfo property in properties)
{
switch (property.Name.ToUpper())
{
case SystemFields.ModifiedOn:
{
property.SetValue(entity, DateTime.Now, null);
} break;
case SystemFields.ModifiedBy:
{
property.SetValue(entity, userName, null);
} break;
case SystemFields.IsDeleted:
{
property.SetValue(entity, true, null);
} break;
default:
break;
}
}
}
}
}
}

Incoming search terms:

  • linq audit inserts

The ASP.NET do and don’ts serie : episode 2 – the navigation controls

There are many controls you can use for the navigation in ASP.NET.

Be sure that you use the good ones for the good uses.

Let say we want a link on a page to redirect on an other page

Do:

in the aspx markup


<asp:HyperLink runat="server" ID="NavigateToOtherPage" NavigateUrl="~/otherpage.aspx" Text="Go to the other page" />

or even better if you just want to redirect on a page


<a href="otherpage.aspx">Go to the other page</a>

Don”t:

in the aspx markup


<asp:LinkButton runat="server" ID="NavigateToOtherPage" Text="Go to the other page" onclick="RedirectToOtherPage" />

and in the code behind


protected void RedirectToOtherPage(object sender, EventArgs e)
{
     Response.Redirect("~/otherpage.aspx");
}