Writing a custom Dynamic Option List using Code Reference

Recently, we have gotten quite some questions regarding the implementation of custom Dynamic Option Lists using C# Code References. In this blog post, we will walk you through the process of writing such a completely custom Dynamic Option List, what is expected and what are the caveats to look out for.

How do Dynamic Option Lists work?

The Dynamic Option List code reference is supposed to fetch and return the available options, taking into account optional Key and Value filters. These filter values can be accessed through the Global Variables collection (i.e. "adamKey" and "adamValue").

The following snippet is the skeleton of your Dynamic Option List code reference, including all of the appropriate Global Variables at your disposal.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
object ICodeReference.Resolve(ReferenceData data)
{
   DynamicOptionCollection adamOptions = data.Manager.GetGlobalVariable<DynamicOptionCollection>("adamOptions");
   int adamPageNumber = data.Manager.GetGlobalVariable<int>("adamPageNumber");
   int adamRecordsPerPage = data.Manager.GetGlobalVariable<int>("adamRecordsPerPage");
   string adamKey = data.Manager.GetGlobalVariable<string>("adamKey");
   string adamValue = data.Manager.GetGlobalVariable<string>("adamValue");

   // Fetch and filter options
   // ...

   return adamOptions;
}

The actual choice on how to populate the adamOptions collection is completely free. In the ADAM Help an example is given using built in ADAM functionality such as search expressions. Search expressions have built in support for things like filtering (using wildcards) and paging, making it very easy to use within your Dynamic Option List code reference. But off course, search expressions can only be used on ADAM objects.

Below is a copy of the code sample for a Dynamic Option List code reference, using ADAM functionality.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
object ICodeReference.Resolve(ReferenceData data)
{
   DynamicOptionCollection adamOptions = data.Manager.GetGlobalVariable<DynamicOptionCollection>("adamOptions");
   int adamPageNumber = data.Manager.GetGlobalVariable<int>("adamPageNumber");
   int adamRecordsPerPage = data.Manager.GetGlobalVariable<int>("adamRecordsPerPage");
   string adamKey = data.Manager.GetGlobalVariable<string>("adamKey");
   string adamValue = data.Manager.GetGlobalVariable<string>("adamValue");

   // Create a search expression for the user groups 
   SearchExpression oExpression = adamOptions.CreateSearchExpression("Id", "Name");
   // Create and load the collection of user groups filtered on adamKey and adamValue. 
   // We also use the adamPageNumber and adamRecordsPerPage variables to implement paging. 
   UserGroupCollection userGroups = new UserGroupCollection(data.App);
   userGroups.Load(oExpression, "Name", adamPageNumber, adamRecordsPerPage, out isLastPage);
   // Add all user groups to the list of options 
   foreach (UserGroup userGroup in userGroups)
   {
      adamOptions.Add(new DynamicOption(userGroup.Id.ToString(), userGroup.Name));
   }
 
   // Indicate whether or not the last page was reached. 
   adamOptions.IsLastPage = isLastPage;
   return adamOptions;
}

Notice the creation of the search expression on line 10. That's where the filtering based on the value of adamKey and adamValue is set up. We'll come back to that later in this post.

When is the code reference called?

It is very important to understand when and where this code reference will be called. Firstly and most obviously, it is called each time the options are requested.

However, one consideration is often being overlooked: the code reference is only supposed to return the options that are compliant with the filters as defined by the adamKey and adamValue Global Variables! Even if you don't want to enable filtering, you're still required to implement it because of the second place where your code reference is called i.e. during validation.

When your object is being validated (e.g. during saving), the dynamic option list field will validate all the selected options. It does so by passing the key of each of the selected options to the code reference. If your code reference returns an option list with exactly one match, then the selected option will be regarded as valid. If the code reference returns an empty option list, or a list with multiple options, the field will be invalid. That is why it is crucial to correctly implement filtering using the value of adamKey (and adamValue) on your custom Dynamic Option List code reference.

Writing a non-ADAM code reference

Now that we know where, when and how the Dynamic Option List's code reference is being called, we will write a completely custom code reference without using any of the built in ADAM functionality. For example, you could be querying data from an external source (e.g. another database, web service, file, ...).
Up next, we'll write a custom code reference that queries an external database.

Querying an external database

Let's assume we want to populate our Dynamic Option List with some data coming out of an external database. For the sake of simplicity, let's pop out Ye Olde DataTable once more.

C#
1
2
3
4
5
6
7
8
9
10
11
12
private DataTable GetData()
{
   string connectionString = "somevalidconnectionstring";
   DataSet ds = new DataSet();
   using (SqlConnection conn = new SqlConnection(connectionString))
   {
      string sql = "SELECT ID, Name FROM tblSOMETABLE";
      SqlDataAdapter adapter = new SqlDataAdapter(sql, conn);
      adapter.Fill(ds);
   }
   return ds.Tables[0];
}

However, we're missing one key component here: the filtering! Remember how I referred to the CreateSearchExpression call on the DynamicOptionListCollection to generate a search expression that takes into account the values of adamKey and adamValue to filter and return the appropriate options. Since we're querying an external non-ADAM database here, we'll have to implement this crucial functionality ourselves. So let's do so and refactor our method a bit so that it can handle filters for key and value.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private DataView GetData(string keyFilter, string valueFilter)
{
   string connectionString = "somevalidconnectionstring";
   DataSet ds = new DataSet();
   using (SqlConnection conn = new SqlConnection(connectionString))
   {
      string sql = "SELECT ID, Name FROM tblSOMETABLE";
      SqlDataAdapter adapter = new SqlDataAdapter(sql, conn);
      adapter.Fill(ds);
   }
   
   if (string.IsNullOrEmpty(keyFilter))
      keyFilter = "%";
   if (string.IsNullOrEmpty(valueFilter))
      valueFilter = "%";
   ds.Tables[0].DefaultView.RowFilter = string.Format("ID like '{0}' and Name like '{1}'", keyFilter, valueFilter);
   return ds.Tables[0].DefaultView;
}

In this sample, I have implemented a rudimentary contains filter supporting wildcards on both the ID and Name column.

Populating the options collection

Next, we simply need to populate the DynamicOptionCollection using the data from our external datasource. It is just a matter of mapping each row to a new DynamicOption object.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
object ICodeReference.Resolve(ReferenceData data)
{
    DynamicOptionCollection adamOptions = data.Manager.GetGlobalVariable<DynamicOptionCollection>("adamOptions");
    int adamPageNumber = data.Manager.GetGlobalVariable<int>("adamPageNumber");
    int adamRecordsPerPage = data.Manager.GetGlobalVariable<int>("adamRecordsPerPage");
    string adamKey = data.Manager.GetGlobalVariable<string>("adamKey");
    string adamValue = data.Manager.GetGlobalVariable<string>("adamValue");

    // Fetch and filter options
    DataView results = GetData(adamKey, adamValue);
    foreach (DataRowView row in results)
    {
        adamOptions.Add(new DynamicOption(row["ID"].ToString(), row["Name"].ToString()));
    }

    adamOptions.IsLastPage = true;
    return adamOptions;
}

That's all there's to it. Your custom Dynamic Option List is ready to use. Note that in this example we haven't implemented paging yet. But using the appropriate Global Variables (i.e. adamPageNumber and adamRecordsPerPage), that's also easy to achieve, if you would require this functionality.

Comments

Friday, 03 June 2011EL ASSAS AKRAM says
Hello,
I would like to add an important information when using an external datasource.
The MSDTC should be activated on the adam server : Enable DTC access
Kind regards,
Leave a comment
You must be logged in to post comments.
Sign in now
 
 
Technical
Business
rss feed