|
As the author of the SqlDataSource and ObjectDataSource controls, I often get asked
what is the best way to insert the current date into a query. Every single time
I give the simple answer: Write a custom parameter! The next question is always:
How hard is it? Once again I have a simple answer: It's literally just one line
of actual code.
Writing simple parameters is easy, so let's start with that. The key to writing
a custom System.Web.UI.WebControls.Parameter is the Evaluate() method that you must
override. This role of this method is to evaluate the state of the parameter and
return the value that this parameter represents. In this case, the value is always
DateTime.Now, which is what makes this parameter so easy.
using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace ControlBuilderFaq.Samples {
public class TodayParameter : Parameter {
protected override object Evaluate(HttpContext context, Control control) {
return DateTime.Now;
}
}
}
Now we can use this parameter in a data source:
<asp:SqlDataSource ID="SqlDataSource1" runat="server"
InsertCommand="INSERT INTO [MyTable] ([Field1], [Field2], [PostedDate]) VALUES (@Field1, @Field2, @PostedDate)">
<InsertParameters>
<asp:Parameter Name="Field1" />
<asp:Parameter Name="Field2" />
<cbf:TodayParameter Name="PostedDate" />
</InsertParameters>
</asp:SqlDataSource>
That's really all you need to do. Now you can use the new parameter anywhere you
used to have some custom code. If you want fancier behavior, you're going to need
a fancier parameter. For example, what if the parameter has some additional properties?
Thankfully, that's not too hard to do either, but there are definitely a few things
you need to keep in mind. The main thing that's a bit tricky is the cloning behavior
that all parameters must have in order to work properly in the designer tool, but
we'll save that for the end.
To make our parameter more interesting, let's add a new property indicating which
day you really want: Yesterday, Today, or Tomorrow. Anyone who has written a control
before will find this a familiar task. Parameters have ViewState, just like controls.
using System;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace ControlBuilderFaq.Samples {
public enum DaySetting {
Today = 0,
Yesterday = 1,
Tomorrow = 2,
}
public class DayParameter : Parameter {
[DefaultValue(DaySetting.Today)]
public DaySetting DaySetting {
get {
object o = ViewState["DaySetting"];
if (o != null) {
return (DaySetting)o;
}
return DaySetting.Today;
}
set {
ViewState["DaySetting"] = value;
}
}
protected override object Evaluate(HttpContext context, Control control) {
switch (DaySetting) {
case DaySetting.Today:
return DateTime.Now;
case DaySetting.Yesterday:
return DateTime.Now.AddDays(-1);
case DaySetting.Tomorrow:
return DateTime.Now.AddDays(1);
}
throw new InvalidOperationException("Unknown day setting.");
}
}
}
Now, along with having renamed the parameter to DayParameter, we can use its new
property.
<cbf:DayParameter Name="PostedDate" DaySetting="Yesterday" />
The next problem you'll run into is when you try to use this parameter in the designer.
Some very strange things happen at design time when it detects a custom parameter.
First, a somewhat unfortunate behavior is that it won't let you create parameters
of the new type with any fancy user interface. The developer is restricted to typing
them manually in source view. The second issue is that when you try to even just
edit the InsertParameters of the SqlDataSource, it appears as though the new parameter
has disappeared!
This is where the cloning behavior come into play. The parameter design time behavior
has to be able to clone a parameter in its entirety to preserve its settings in
case the user cancels an edit operation. Since our parameter hasn't implemented
custom cloning behavior, it is inheriting the base class's cloning behavior, which
is to return a regular Parameter, and not a DayParameter. There are two basic steps
you have to follow to do this:
- Implement a new constructor that will perform the cloning.
- Override the Clone() method to call the new constructor.
public class DayParameter : Parameter {
public DayParameter() {
}
protected DayParameter(DayParameter original)
: base(original) {
DaySetting = original.DaySetting;
}
protected override Parameter Clone() {
return new DayParameter(this);
}
...
}
Now you can use this parameter in the designer and write your app with less code
on each page.
One thing to beware of is that the parameter editing dialog (launched when you edit
one of the "Query" properties on a SqlDataSource or edit one of the Parameters collections
on an ObjectDataSource) isn't all that friendly to custom parameters. Although it
will always preserve a custom parameter, it's not friendly about letting
you edit them. Due to what appears to be a bug, you have to follow a work around
to be able to edit the new parameter. You can only edit custom parameters when "Show
advanced properties" is enabled, but that option isn't available on custom parameters.
You have to first go to a standard parameter, then switch to advanced properties,
and then go back to the new DayParameter. At that point you can use the property
grid to edit the parameter's properties.
Using parameters to encapsulate common logic for parts of queries is one of the
best features of data source controls. Any time you notice code being duplicated
in your data source's Selecting, Inserting, Updating, or Deleting events to handle
specific types of values, you should consider packaging up that code into a custom
parameter.
- Eilon
|