Custom Data Binding in Web Tests
We have had a few questions about being able to data bind web tests to either formats that we do not support or to something other than a table, such as a select statement. This post should walk you through one way of creating custom data binding. The method provided in this post is to create one class which will manage the data and then create a web test plug-in which will add the data into the web test context. To use this example, you would need to set your connection string and modify the sql statement.
Here is the class which manages the data. This class is a singleton which will be called from the plug-in. The initialize method will make the call to your data source. In this example, I am just creating a select statement against a database. The GetSqlSelectStatement is the method which will return the SQL statement to be executed. The plug-in will fetch data by calling GetNextRow. This method will return a set of Name, Value pairs. The name is the column name and the value is the column value. Each time the method is called, it will get the current row of the dataset and then advance the current position counter. If the end of the dataset is reached, it will start from the beginning again. To customize this class, you would usually only need to change the SQL statement return from GetSqlSelectStatement.
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Data;
using System.Globalization;
using System.Data.OleDb;
namespace CustomBinding
{
public class CustomDS
{
//singleton instance
private static readonly CustomDS m_instance = new CustomDS();
//keep track of next row to read
private int m_nextPosition = 0;
//Is the Datasource initialized
private bool m_initialized = false;
//Datasource object
private DataSet m_datasource;
//Data table object
private DataTable m_dataTable;
//object for locking during initialization and reading
private static readonly object m_Padlock = new object();
#region Constructor
//constructor
private CustomDS()
{
}
#endregion
#region Properties
public bool Initialized
{
get
{
return m_initialized;
}
}
public static CustomDS Instance
{
get
{
return m_instance;
}
}
#endregion
#region public Methods
public void Initialize(string connectionString)
{
lock (m_Padlock)
{
if (m_datasource == null && Initialized == false)
{
//load the data
//create adapter
OleDbDataAdapter da = new OleDbDataAdapter(GetSqlSelectStatement(), connectionString);
//create the dataset
m_datasource = new DataSet();
m_datasource.Locale = CultureInfo.CurrentCulture;
//load the data
da.Fill(m_datasource);
m_dataTable = m_datasource.Tables[0];
//set the manager to initialized
m_initialized = true;
}
}
}
public Dictionary<String, String> GetNextRow()
{
if (m_dataTable != null)
{
//lock the thread
lock (m_Padlock)
{
//if you have reached the end of the cursor, loop back around to the beginning
if (m_nextPosition == m_dataTable.Rows.Count)
{
m_nextPosition = 0;
}
//create an object to hold the name value pairs
Dictionary<String, String> dictionary = new Dictionary<string, string>();
//add each column to the dictionary
foreach (DataColumn c in m_dataTable.Columns)
{
dictionary.Add(c.ColumnName, m_dataTable.Rows[m_nextPosition][c].ToString());
}
m_nextPosition++;
return dictionary;
}
}
return null;
}
private string GetSqlSelectStatement()
{
return "Select * from Customers";
}
#endregion
}
}
The next class is the Web Test Plug-in. Web Test plug-ins are called once for each iteration of the web test. This plug-in first checks to see if the data source has been initialized. If it has not been initialized, it will call the initialize method of the CustomDS class. The initialize method only takes the connection string. This plug-in checks to see if a context parameter was set on the web test that indicates what the connection string should be. If no connection string was set, it will use the default string defined in this class. After the data is initialized, it calls GetNextRow to get the data for the current iteration. Once it gets the data, it adds each column to the context. Now the data is available to use in the web test.
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.VisualStudio.TestTools.WebTesting;
namespace CustomBinding
{
public class CustomBindingPlugin : WebTestPlugin
{
string m_ConnectionString = @"Provider=SQLOLEDB.1;Data Source=dbserver;Integrated Security=SSPI;Initial Catalog=Northwind";
public override void PostWebTest(object sender, PostWebTestEventArgs e)
{
}
public override void PreWebTest(object sender, PreWebTestEventArgs e)
{
//check to make sure that the data has been loaded
if (CustomDS.Instance.Initialized == false)
{
//look to see if the connection string is set as a context parameter in the web test
//if it is not set use the default string set in this plugin
if (e.WebTest.Context.ContainsKey("ConnectionString"))
{
m_ConnectionString = (string)e.WebTest.Context["ConnectionString"];
}
CustomDS.Instance.Initialize(m_ConnectionString);
}
//add each column to the context
Dictionary<string, string> dictionary = CustomDS.Instance.GetNextRow();
foreach (string key in dictionary.Keys)
{
//if the key exists, then update it. Otherwise add the key
if (e.WebTest.Context.ContainsKey(key))
{
e.WebTest.Context[key] = dictionary[key];
}
else
{
e.WebTest.Context.Add(key, dictionary[key]);
}
}
}
}
}
After adding these classes to you project, you can then set the web test plug-in on the web test. You can do this by clicking the Set Web Test Plug-in button on the web test toolbar. If you want or need to have multiple plug-ins, you will need to use a coded web test. You can have multiple plug-ins in coded test but not the declarative test.
The way to access a value from the plug-in is to surround the variable with {{…}}. If you wanted to bind one of the query string parameters to a column called CustomerName, you would set the value of the query string parameter to {{CustomerName}}
Here are the same samples in VB:
Imports System
Imports System.Collections.Generic
Imports System.Collections.Specialized
Imports System.Data
Imports System.Globalization
Imports System.Data.OleDb
Namespace CustomBinding
Public Class CustomDS
'singleton instance
Private Shared ReadOnly m_instance As CustomDS = New CustomDS()
'keep track of next row to read
Private m_nextPosition As Integer = 0
'Is the Datasource initialized
Private m_initialized As Boolean = False
'Datasource object
Private m_datasource As DataSet
'Data table object
Private m_dataTable As DataTable
'object for locking during initialization and reading
Private Shared ReadOnly m_Padlock As Object = New Object()
'constructor
Private Sub New()
End Sub
Public ReadOnly Property Initialized() As Boolean
Get
Return m_initialized
End Get
End Property
Public Shared ReadOnly Property Instance() As CustomDS
Get
Return m_instance
End Get
End Property
Public Sub Initialize(ByVal connectionString As String)
SyncLock (m_Padlock)
If (m_datasource Is Nothing) And (Initialized = False) Then
'load the data
'create adapter
Dim da As OleDbDataAdapter = New OleDbDataAdapter(GetSqlSelectStatement(), connectionString)
'create the dataset
m_datasource = New DataSet()
m_datasource.Locale = CultureInfo.CurrentCulture
'load the data
da.Fill(m_datasource)
m_dataTable = m_datasource.Tables(0)
'set the manager to initialized
m_initialized = True
End If
End SyncLock
End Sub
Public Function GetNextRow() As Dictionary(Of String, String)
If Not m_dataTable Is Nothing Then
'lock the thread
SyncLock (m_Padlock)
'if you have reached the end of the cursor, loop back around to the beginning
If m_nextPosition = m_dataTable.Rows.Count Then
m_nextPosition = 0
End If
'create an object to hold the name value pairs
Dim dictionary As Dictionary(Of String, String) = New Dictionary(Of String, String)()
'add each column to the dictionary
For Each c As DataColumn In m_dataTable.Columns
dictionary.Add(c.ColumnName, m_dataTable.Rows(m_nextPosition)(c).ToString())
Next
m_nextPosition += 1
Return dictionary
End SyncLock
End If
Return Nothing
End Function
Private Function GetSqlSelectStatement() As String
Return "Select * from Customers"
End Function
End Class
End Namespace
Imports System
Imports System.Collections.Generic
Imports System.Text
Imports Microsoft.VisualStudio.TestTools.WebTesting
Namespace CustomBinding
Public Class CustomBindingPlugin
Inherits WebTestPlugin
Dim m_ConnectionString As String = "Provider=SQLOLEDB.1;Data Source=dbserver;Integrated Security=SSPI;Initial Catalog=Northwind"
Public Overrides Sub PreWebTest(ByVal sender As Object, ByVal e As PreWebTestEventArgs)
'check to make sure that the data has been loaded
If CustomDS.Instance.Initialized = False Then
'look to see if the connection string is set as a context parameter in the web test
'if it is not set use the default string set in this plugin
If e.WebTest.Context.ContainsKey("ConnectionString") Then
m_ConnectionString = e.WebTest.Context("ConnectionString").ToString()
End If
CustomDS.Instance.Initialize(m_ConnectionString)
End If
'add each column to the context
Dim dictionary As Dictionary(Of String, String) = CustomDS.Instance.GetNextRow()
For Each key As String In dictionary.Keys
'if the key exists, then update it. Otherwise add the key
If e.WebTest.Context.ContainsKey(key) Then
e.WebTest.Context(key) = dictionary(key)
Else
e.WebTest.Context.Add(key, dictionary(key))
End If
Next
End Sub
Public Overrides Sub PostWebTest(ByVal sender As Object, ByVal e As PostWebTestEventArgs)
'do nothing
End Sub
End Class
End Namespace