Example: Create an Import Handler

Overview

This sample serves as a demonstration for coding an import handler. This sample extends the capability of the Food Item Add Batch. Our import handler will check specific columns within each row of imported data. Specifically, we will check the Name and Description columns and capitalize the data within each of these columns as it is imported. Within the BatchTypeSpec's ImportHanderstag, a static parameter list provides a list of columns to be capitalized to the import handler code which performs the capitalization.

Prerequisites

The Food Item Add Batch batch is part of the food bank customization. When the batch is committed, the food items are added into the USR_FOODITEM table. See tables. This example assumes familiarity with Batch Types. Building an import handler requires a UI Model assembly project. This example assumes the Food Item Add Batch batch type was created.

If you are unfamiliar with UI Models or creating a UI Model assembly project, review the following topics before continuing:

Tip: You can find The Food Item Add Batch batch BatchTypeSpec sample within the Blackbaud.CustomFx.FoodBank.Catalog project's \Batch Types\Food Item\FoodItemAdd.Batch.xml spec file. You can find the import handler code within the Blackbaud.CustomFx.FoodBank.UIModel project within the \Batch\Food Item\FoodItemCapitalizeImportHandler.vb class file. Be sure to download and review the sample within the latest food bank source code download.

Step 1 -  Add import handler class to the UI Model project.

I'll start by adding a class file named "FoodItemCapitalizeImportHandler.vb" to the Batch\Food Item\ folder within the UI Model project. The import handler class's job is to check each row of imported data. For each row processed, the BeforeRowImport function performs the dirty work of actually capitalizing the imported data.  

Figure: The Import Handler class file contains the code to capitalize food item names and descriptions

Step 2 -  Add assembly references within the UIModel project.

I then set references to the following assemblies:

I utilized Blackbaud.AppFx.DataHygene.UIModel.dll to help capitalize the Name and Description columns of the import file. I utilize Blackbaud.AppFx.Platform.Catalog.dll so the import handler can return an object of type Blackbaud.AppFx.Platform.Catalog.ImportProcessHandlerResult. Last but certainly not least is the fact that the import handler will need to inherit from Blackbaud.AppFx.Platform.Catalog.ImportProcessHandler which also resides within Blackbaud.AppFx.Platform.Catalog.dll.

Assuming you have the SDK installed, you can find both assemblies within your SDK install folder under <SDK install location>\SDK\DLLReferences.

Note: For information about how to install the SDK and set up your local development environment, see Blackbaud CRM SDK Developer Environment Setup.

Step 3 -  Add Imports statements.

With the newly added FoodItemCapitalizeImportHandler.vb class file open, I added some Imports and Inherits statements. The listing below describes the how the classes within these referenced namespaces will be utilized. The completed class file can be found at the bottom of this page.

Namespace Description

Blackbaud.AppFx.Platform.Catalog

The ImportProcessHandler base class is organized within this namespace along with the ImportProcessHandlerResult class. When the import occurs, classes that inherit from this base class are called upon to handle the import.
System.Text This namespace holds the StringBuilder class that is used to hold store error messages that may result form the capitalization process.
Blackbaud.AppFx.Server This popular namespace holds the RequestContext class that encapsulates information about a request and provides access to server utility classes and properties. It also holds the RequestSecurityContext class that allows a request to indicate that the request is in the context of another operation and the security should be applied in that context. Objects created from these classes are passed to the capitalization process.
System.Globalization This namespace holds the CultureInfo class. The CultureInfo.CurrentCulture property gets the System.Globalization.CultureInfo that represents the culture used by the current thread. This is used to format capitalization failed exception messages that could occur during the capitalization process.
Blackbaud.AppFx.DataHygiene.UIModel This namespace holds the CapitalizationHandler class responsible for capitalizing import data. The CapitalizationHandler.ApplyCapitalization function accepts in a string to be capitalized.

Tip: You can find the Food Item Add Batch BatchTypeSpec sample within the Blackbaud.CustomFx.FoodBank.Catalog project's \Batch Types\Food Item\FoodItemAdd.Batch.xml spec file. You can find the import handler code within the Blackbaud.CustomFx.FoodBank.UIModel project within the \Batch\Food Item\FoodItemCapitalizeImportHandler.vb class file. Be sure to download and review the sample within the latest food bank source code download.

Step 4 -  Add Inherits statement.

Within our class, let's add the Inherits statement. We are inheriting from the ImportProcessHandler base class. At this point, the code looks like this:

Figure: Imports and Inherits statements within our import handler class

Step 5 -  Add ImportHandlers element to the BatchTypeSpec.

Next we will add the ImportHandlers element that allows custom import handlers to import files to our batch type. The ImportHandlers element contains an ImportHandler child element that identifies the code by AssemblyName and ClassName attribute values. ImportHandler may also include an optional StaticParameters element, which is a fixed value to pass into the import handler code. We will use a public variable within our code to catch the value of the a single static parameter named "CapitalizationFields."

Within the FoodItemAdd.Batch.xml file that reside within the Blackbaud.CustomFx.FoodBank.Catalog project, I added the following XML below the existing WebEventHandlerstag:

Figure: Add the ImportHandlers tag to the BatchTypeSpec

In the figure above, notice the Param tag. Note how I have set the value to a comma delimited string of text. In this way, I am telling my import handler code which columns from the import file to capitalize.

Step 6 -  Add code to manage capitalization field list.

Next, I will add some variables to help manage the capitalization process. First, I will add a public string variable named CapitalizationFields which will hold the parameter value established within the spec. At run time, the value of "NAME, DESCRIPTION" will be passed from the spec to the import processor by this variable. Next I will add a Boolean variable named _shouldCapitalize to determine whether to capitalize the import data and a string array named capitalFields to hold a list of the fields established by the CapitalizationFields parameter.

Finally, I added a private sub routine named PrepCapitalizationFields() to split the incoming CapitalizationFields into the capitalFields string array. I call PrepCapitalizationFields() within the BeforeFileImport() function which I use to initialize my code. BeforeFileImport is called by the system prior to each row being imported.

Step 7 -  Provide BeforeRowImport logic.

By overriding the BeforeRowImport function from the inherited ImportProcessHandler class, we are able to provide an algorithm which will be called for each row within the import stream. The function contains two parameters: importRowNumber typed as an integer and a ByRef importBatchRow typed as a DataFormItem. A DataFormItem represents a series of values for a data form; in other words, it represents the data for the import row. Below is a screen shot of a breakpoint while debugging the ImportHandler. You can see the values within the DataFormItem highlighting the row data being imported. For instructions, see Debugging.

Each time an import row is read from the input stream, we call ApplyCapitalization which in turn calls the Blackbaud.AppFx.DataHygiene.UIModel.CapitalizationHandler's ApplyCapitalization function that modifies the DataFormItem/batch import row by capitalizing the designated columns. If any exceptions arise, we append the error messages to an object of type StringBuilder called nonCriticalErrors. If we do not have any errors, we call the base class's BeforeRowImport passing the capitalized data which returns an object of type ImportProcessHandlerResult. If we did have errors, we instantiate a new object of type ImportProcessHandlerResult passing in a NonCriticalError enum value for the Blackbaud.AppFx.Platform.Catalog.ImportProcessHandlerResult.Results public enum, and the string builder of errors into the object's constructor.

Note: For the complete code sample, see Import Handler Code below.

Import Handler Code

Here is the completed class file:

Imports Blackbaud.AppFx.Platform.Catalog
Imports System.Text
Imports Blackbaud.AppFx.Server
Imports System.Globalization
Imports Blackbaud.AppFx.DataHygiene.UIModel
 
Public Class FoodItemCapitalizeImportHandler
	Inherits ImportProcessHandler
	Public CapitalizationFields As String
	Private _shouldCapitalize As Boolean = True
	Private capitalFields As String() = Nothing
 
	Private Sub PrepCapitalizationFields()
		If Not String.IsNullOrEmpty(CapitalizationFields) Then
			capitalFields = Split(CapitalizationFields, ",")
		End If
	End Sub
 
	Public Overrides Function BeforeFileImport() As Platform.Catalog.ImportProcessHandlerResult
		PrepCapitalizationFields()
		Return MyBase.BeforeFileImport()
	End Function
	
	'By overriding the BeforeRowImport function from the inherited ImportProcessHandler class,
	'we are able to provide an algorithm which will be called for each row within the import stream.
	'The function contains two parameters: importRowNumber typed as an integer and
	'a ByRef importBatchRow typed as a DataFormItem.
	'A DataFormItem represents a series of values for a data form; in other words,
	'it represents the data for the import row.
	<System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")> _
	Public Overrides Function BeforeRowImport(ByVal importRowNumber As Integer, _
						  ByRef importBatchRow As XmlTypes.DataForms.DataFormItem) As ImportProcessHandlerResult
		Dim nonCriticalErrors As New StringBuilder()
		Try
			' Each time an import row is read from the input stream, we call ApplyCapitalization
			' which in turn calls the Blackbaud.AppFx.DataHygiene.UIModel.CapitalizationHandler's
			' ApplyCapitalization function which will modify the DataFormItem/batch import row
			' by capitalizing the appropriate columns.
			importBatchRow = ApplyCapitalization(importBatchRow, _shouldCapitalize, Me.GetRequestContext, Me.GetRequestSecurityContext)
		Catch ex As Exception
			' If any exceptions arise, we append the error messages to an object of type StringBuilder called nonCriticalErrors.
			nonCriticalErrors.AppendLine(ex.Message)
		End Try
		If nonCriticalErrors.Length = 0 Then
			' If we did have errors we instantiate a new object of type ImportProcessHandlerResult
			' passing in a NonCriticalError enum value for the Blackbaud.AppFx.Platform.Catalog.ImportProcessHandlerResult.Results public enum,
			' and the string builder of errors into the object's constructor.
			Return MyBase.BeforeRowImport(importRowNumber, importBatchRow)
		Else
			' If we do not have any errors, we call the base class' BeforeRowImport passing
			' the capitalized data which returns an object of type ImportProcessHandlerResult.
			Return New ImportProcessHandlerResult(ImportProcessHandlerResult.Results.NonCriticalError, nonCriticalErrors.ToString)
		End If		
	End Function
 
	<System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotCatchGeneralExceptionTypes")> _
	Friend Function ApplyCapitalization(ByVal importBatchRow As XmlTypes.DataForms.DataFormItem, ByVal alsoCapitalize As Boolean, ByVal requestContext As RequestContext, ByVal requestSecurityContext As RequestSecurityContext) As XmlTypes.DataForms.DataFormItem
		If CapitalizationFields IsNot Nothing AndAlso alsoCapitalize Then
			For Each value As String In capitalFields
				If FieldExists(importBatchRow, value) AndAlso alsoCapitalize Then
					Try
						If alsoCapitalize Then
							Dim capitalValue As String = CapitalizationHandler.ApplyCapitalization(CStr(importBatchRow.Values(value).Value), requestContext, requestSecurityContext)
							If Not String.IsNullOrEmpty(capitalValue) Then
								capitalValue = RTrim(LTrim(capitalValue))
							End If
							importBatchRow.SetValueIfNotNull(value, capitalValue)
						End If
					Catch ex As Exception
						Throw New Exception(String.Format(CultureInfo.CurrentCulture, My.Resources.Content.CapitalizationFailed, value, CStr(importBatchRow.Values(value).Value)))
					End Try
				End If
			Next
		End If
		Return importBatchRow
	End Function
 
	Friend Shared Function FieldExists(ByVal row As XmlTypes.DataForms.DataFormItem, ByVal field As String) As Boolean
		Return row.TryGetValue(field, New XmlTypes.DataForms.DataFormFieldValue())
	End Function
End Class