Introduction
Every enterprise application typically has one or several major search forms that are used by multiple types of users. These forms are usually built to support advanced searching of the key objects in the application, such as orders, customers, transactions, events, etc., using a number of different criteria. Most commonly, the user interface for entering such criteria consists of a set of controls like text boxes, drop down lists or multiple-selection list boxes, to specify one or more of the criteria values. While working fine for simple search forms, this approach may fall short for the more advanced search forms when it comes to flexibility and user friendliness, which we will discuss in the following section.
In this article, we will describe a more powerful and flexible approach that will help you design advanced search forms. We will also show you an example of the implementation that allows you to use this approach with little to no coding, which can help you easily turn any search form into an advanced and powerful search form.
Problems with the Traditional Approach
Now, let’s discuss what the problems are with the traditional approach, where we use regular controls for entering search criteria.
Null values. Conventionally, no value in a search criteria control means that we don’t want to search by this field. Depending on the control, it could be either an empty text box, no selection in the list box, or a special blank item in a drop down list, which may also read “Any <field label>” or something. However, there may be cases where we specifically want to search for objects that have no value in a certain field. Those familiar with SQL will recognize it as the IS NULL
clause. In this case, we would have to get creative and handle it differently based on the control we are using. For a text field, for example, we can make users enter a special value such as EMPTY
or BLANK
, which we would handle specially in the code. For a drop down list or a list box, we would have to add an extra item to the list with a similar text, which again would be handled differently in the code. Ideally, we want to be consistent and use the same special values for all such criteria and at the same time make sure that there could never be a real value in the corresponding fields that would coincide with these special values.
Inversed criteria. This is another pretty common case where we want to exclude those objects from the search that have a certain field equal to the value (or one of the values) that we specified in the search criteria for that field. In SQL terms, this is known as the NOT
operator. If we want to support both including and excluding such objects in our search form, we would have no other way but to add a flag to each such field, which would indicate whether we want to include or exclude these values.
Other standard operators. There are a number of other standard operators for each data type that you sometimes need to support. For text field criteria, for instance, you may want to distinguish between the case when the value should be exactly equal to the specified criteria and the case when it should contain the criteria as a substring. Some developers let users enter an *
as part of the criteria and check the presence of an *
to distinguish between these cases. Another example is when you want to allow searching by a numeric range or a period of time. The common way to go about it is to add two controls for the “From
” and “To
” criteria and let users populate either or both of them.
Nonstandard operators. There may be other nonstandard operators that you may offer to search by, which may not require any additional values. For example, if you allow searching by dates, you may want to have a special value “Last 30 Days” to search for recent events or transactions. You can add it as special criteria, but you want to make sure that the user cannot enter any custom dates when they select that.
Design Pattern for Search Criteria
In order to address all the above mentioned problems in a clean and consistent way, you may want to consider the following design pattern.
Each of the search criteria will consist of an operator that has a list of predefined values based on the type of the criteria, and one or two input fields that provide values for the selected operator as needed. Selecting an operator would either hide all value fields if the operator requires no values, such as “Is Null
” or “Last 30 Days
”, or display one or two value fields next to the operator, such as “Equals
” for one value or “Between
” for two values.
Each operator may also be left blank with both value fields hidden to indicate that this field should not be filtered by. Any operator can also have an inverse operator such as “Is Null
” and “Is Not Null
”. The operators may be different for single value criteria (e.g. “Equals
”) and multi-value criteria (“Is One Of
”).
As you can see, a separate operator “Is Null
” will allow consistently and unambiguously specifying that we want to search for objects where the given field has no value. Additionally, you can specify any number of inverse operators or any other standard or nonstandard operators. This will allow you to design powerful and advanced search forms for your application. Mind you, that you don’t have to use operators for every single search field on your form. Search fields with no operator will be always visible and just imply one of the operators (typically “Equals
” or “Is One Of
”) when the field as populated.
As great as it looks, you may be wondering how much coding it will take in each form to implement a design like this. Well, we have good news for you. Read on to learn about Xomega Framework that implements most of the plumbing to support this design for any search form.
Implementation Example
In order to help you better understand our implementation example of this design pattern, you may need a quick background overview of the framework that implements it.
Xomega Framework is a powerful open-source .NET framework for building multi-tier ASP.NET, WPF or Silverlight applications. One of the key presentation layer concepts in Xomega Framework is a data object, which serves as a data model for the form, as per the well known MVC design pattern. Data objects consist of different types of data properties, which can be easily and declaratively bound to UI controls in the XAML or ASPX. What’s most notable about Xomega data properties is that in addition to a single value, they can support multiple values and also some metadata about the property, such as visibility or editability, which gets automatically reflected in the state of the bound control.
So, let’s take a look at how all of this helps to implement our design pattern for the search criteria. To support this design, the framework defines an OperatorProperty
that can be associated with one or two other data properties that will hold the actual values for the criteria.
OperatorProperty
extends from EnumProperty
, which allows it to use a cached list of predefined operators. Each operator in the list may be configured with different additional attributes, such as how many values it requires, whether it is defined for multi-value or single value criteria, what types of properties it operates on (e.g. text, numeric or date/time), etc.
The OperatorProperty
uses these additional attributes of each operator to display only the operators that are applicable to the current search field and also hides or shows the corresponding value fields as appropriate.
How to Use the Implementation Example
First of all, you need to download the latest version of Xomega Framework from CodePlex and add a reference to the Xomega.Framework.dll from your Visual Studio project.
Next you want to load the list of operators into the Xomega look-up cache during the application startup in App.xaml.cs. You can hardcode it, but the easiest way is to embed the attached operators.xml file and load it into the look-up cache during the startup.
private void Application_Startup(object sender, StartupEventArgs e)
{
Assembly asm = GetType().Assembly;
LookupCache.Get(LookupCache.Global).LoadFromXml(
asm.GetManifestResourceStream(
asm.GetName().Name + ".operators.xml"), false);
}
Defining your criteria object is a piece of cake. All you do is extend the base DataObject
, declare constants for property names, and initialize data properties with these names. For the OperatorProperty
you set the enumeration type to be “operators
”, which is how we named it in the XML. The following code demonstrates a criteria object with one operator and two date/time properties for the From
and To
values of the date/time range.
public class MyCriteriaObj : DataObject
{
public const string Criteria = "Criteria";
public const string Criteria2 = "Criteria2";
public const string CriteriaOperator = "CriteriaOperator";
protected override void Initialize()
{
DateTimeProperty crit = new DateTimeProperty (this, Criteria);
DateTimeProperty crit2 = new DateTimeProperty (this, Criteria2);
OperatorProperty op = new OperatorProperty(this, CriteriaOperator);
op.EnumType = "operators";
}
}
You can explicitly associate the OperatorProperty
with the corresponding value properties (see the commented lines), but the framework also uses a naming convention to retrieve that by default. If you don’t want to support certain operators, you can easily add a custom function to filter them out from the list of available operators, as demonstrated by the last commented line.
The last thing you need to do is to bind your search criteria controls in the XAML to the criteria object data properties. All you have to do for that is to set the xom:Property.Name
dependency property on the criteria controls to point to your data property names. You can use the static
constants in WPF for compile time validations or just hardcode the property name in Silverlight, which doesn’t support the x:Static
construct in XAML. The following snippet demonstrates this Xomega property binding.
<Grid Name="pnlCriteria"
xmlns:xom="clr-namespace:Xomega.Framework;assembly=Xomega.Framework"
xmlns:l="clr-namespace:MyNamespace;assembly=MyAssembly">
<Label Grid.Row="0" Grid.Column="0" Name="lblCriteria">Criteria:</Label>
<ComboBox Grid.Row="0" Grid.Column="1" Name="ctlCriteriaOperator"
xom:Property.Label="{Binding ElementName=lblCriteria}"
xom:Property.Name="CriteriaOperator"/>
<TextBox Grid.Row="0" Grid.Column="2" Name="ctlCriteria"
xom:Property.Name="{x:Static l:MyCriteriaObj.Criteria}"/>
<TextBox Grid.Row="0" Grid.Column="3" Name="ctlCriteria2"
xom:Property.Name="{x:Static l:MyCriteriaObj.Criteria2}"/>
</Grid>
If you run your application now, your combo box will be showing the right list of operators and selecting an operator will show or hide criteria fields as appropriate. The values from the criteria controls will be automatically propagated to the corresponding data properties, so you don’t have to access those controls in the code at all.
The ASPX markup for a corresponding ASP.NET page would look pretty similar to the following snippet.
<asp:Panel runat="server" ID="pnlCriteria">
<asp:Label runat="server" ID="lblCriteria">Criteria:</Label>
<asp:DropDownList runat="server" ID="ctlCriteriaOperator"
Property="CriteriaOperator" LabelID="lblCriteria"/>
<asp:TextBox runat="server" ID="ctlCriteria" Property="<%# MyCriteriaObj.Criteria %>"/>
<asp:TextBox runat="server" ID="ctlCriteria2" Property="<%# MyCriteriaObj.Criteria2 %>"/>
</asp:Panel>
As you can see, designing and building powerful search criteria objects is very straightforward with Xomega Framework to the point where all the enumerations, data objects and ASP.NET or XAML controls can be automatically generated from an XML-based model of your domain and service layers. This video demonstrates how the whole search form with advanced criteria can be generated in just a few simple steps.
To learn more, you can download and play with a sample application from CodePlex.
Also, to learn more about Xomega Framework read our articles Take MVC to the Next Level in .Net and Cascading Selection the MVC Way.
Conclusion
In this article, we have discussed some of the problems with the traditional approach to designing search forms. You learned about a design pattern that allows addressing these problems and building powerful advanced search forms in a clean and consistent way. You also learned an implementation example for this design pattern for WPF and Silverlight applications, which requires minimal coding to build advanced search forms.
We would love to hear any comments or questions from you about this.
History
- 1/21/11: First version of the article.
- 6/6/11: Updated to mention ASP.NET support and point to the latest release at CodePlex.
- 12/2/12: Updated the sample app.