Example: Consume the Blackbaud AppFx Web Service with a .NET WinForms Client Application

In this code sample, we will use Visual Studio 2010 along with the Microsoft .NET Framework version 4.0 to create a custom user interface that communicates with a hosted Altru application. We will use the API to retrieve a list of events from the database. All communication with Infinity-based applications such as Blackbaud CRM, Altru, and ResearchPoint utilize the Blackbaud AppFx Web Service either directly through web service calls to AppFxWebService.asmx or through one of the various middleware "wrappers" that call the AppFxWebService.asmx on our behalf. The Blackbaud.AppFx.WebAPI.dll is one such wrapper that comes with the Infinity SDK. Microsoft .NET software developers are free to use this assembly to ease communication to the AppFxWebService.asmx. The Blackbaud.AppFx.WebAPI.dll has a dependency on .NET Framework 4.0, so we will use that version as well as Visual Studio 2010.

This example assumes you have the Infinity SDK installed for your version of Altru or other Infinity application such as Blackbaud CRM.

The entire code sample can be found here: CustomEventManagerWebAPIDll.zip.

The code sample includes other popular API calls to leverage features such as the ability to:

Step 1 -  Create a new Windows Forms application.

In this example, we will use Visual Studio to create a Windows Forms (WinForm) application as our user interface. We will use Windows Forms as the user interface to display the results of our call to the web service. We will add the necessary Infinity SDK assembly (DLL) references to the client. A reference to the Blackbaud.AppFx.WebAPI.dll will serve as a proxy to the AppFxWebService.asmx. We will communicate Infinity features, such as data list, data form, search list, code tables, etc, within a hosted Altru instance. We will name our project CustomEventManager.

We could use either Visual Studio 2008 or 2010 to build the client application. However, since we will set references to Infinity SDK assemblies and those assemblies have dependencies on .NET Framework 4.0, we need to utilize .NET Framework 4.0 and Visual Studio 2010. To summarize, in this sample, we will use Visual Studio 2010 and .NET Framework version 4.0.

We add a list view, button, and label to the provided Form1.vb WinForm. The list view displays a list of events that we retrieve using the AppFxWebService.asmx. The button calls on a specific web service operation named DataListLoad to retrieve the data from the Event Calendar Event List data list and place the retrieved rows into the list view. The label identifies the list view to the end user.

Step 2 -  We drag a list view control onto the WinForm along with a button and a label control.

Step 3 -  Add a reference to the Blackbaud.AppFx.WebAPI.dll wrapper and the Blackbaud.AppFx.XmlTypes.dll.

Step 4 -  After adding a reference to the Blackbaud.AppFx.WebAPI.dll to your project, you can call any methods exposed by the web service. Installing the Infinity SDK places this DLL into a folder named SDK\DLLReferences on your local machine. In addition to the Blackbaud.AppFx.WebAPI.dll, you also need a reference to Blackbaud.AppFx.XmlTypes.dll and System.Web.Services.dll.

Step 5 -   Proxy class: Blackbaud.AppFx.WebAPI.ServiceProxy.AppFxWebService.

The AppFx Web Service class is a client proxy class used to communicate with the BlackbaudAppFxWebService.asmx web service. The class is organized within the Blackbaud.AppFx.WebAPI.ServiceProxy namespace. The proxy class maps parameters to XML elements and then sends the SOAP messages over a network. In this way, the proxy class frees you from communicating with the web service at the SOAP level and allows you to invoke Web service methods in any development environment that supports SOAP and web service proxies.

Within the code for the WinForm, let's declare a variable of type Blackbaud.AppFx.WebAPI.ServiceProxy.AppFxWebService to represent your proxy for the communication mechanism to Infinity application.

'To cut down on the length of the class names you could import the Blackbaud.AppFx.WebAPI namespace
'Within variable declaration, class names shown in full for learning purposes. 
'Imports Blackbaud.AppFx.WebAPI
Public Class Form1

    'The Blackbaud.AppFx.WebAPI.dll is used to access the Infinity application such as 
    ' Blackbaud Enterprise CRM (BBEC), Altru, and Research Point (RP) and handles communication with the 
    ' AppFxWebService.asmx on your behalf.  If you use this dll to access the Infinity application 
    ' you do NOT need a web service reference to AppFxWebService.asmx.  
    'Declare a variable of type Blackbaud.AppFx.WebAPI.ServiceProxy.AppFxWebService to represent
    ' your proxy for the communication mechanism to Infinity application.
    Private _appFx As Blackbaud.AppFx.WebAPI.ServiceProxy.AppFxWebService

Step 6 -  Initialize our application.

The Blackbaud.AppFx.WebAPI.ServiceProxy.AppFxWebService type includes a Credentials property that gets or sets security credentials for XML Web service client authentication.

For hosted Altru and ResearchPoint scenarios, the custom client application resides outside of the domain of the hosted IIS Web Server. In this case, the API developer needs a user name and password to authenticate against the hosted Windows Active Directory. Microsoft .NET developers can provide an object of type the System.Net.NetworkCredential the provides credentials for password-based authentication schemes such as basic, digest, NTLM, and Kerberos authentication.

Note: For a broader discussion about authentication and authorization related to Infinity Web APIs, see Authentication and Authorization.

Continuing with our code, just below the variable declaration for the proxy, we declare a variable to represent our credentials for authentication against the hosted Altru IIS Web Server.

Private _myCred As System.Net.ICredentials

ClientAppInfoHeader will be used to hold the client application name that identifies your custom client software for auditing purposes within the Infinty database. It also holds a database identifier to help point to the correct database.

Private _clientAppInfoHeader As Blackbaud.AppFx.WebAPI.ServiceProxy.ClientAppInfoHeader

When the form loads, we will call InitializeAppWebService.

Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load

        InitializeAppFxWebService()

End Sub

Initialize web service will instantiate our proxy variable (_appFx) as well as retrieve the credentials for authentication and set the Credentials property on the proxy. See the definition for GetNetworkCredentials below.

Private Sub InitializeAppFxWebService()

        Try
            'Display hourglass during appfx web service calls
            Cursor.Current = Cursors.WaitCursor
            Cursor.Show()

            'Instantiate the proxy to the Infinity application
            _appFx = New Blackbaud.AppFx.WebAPI.ServiceProxy.AppFxWebService

            'Grab the network credentials.  See GetNetworkCredentials() for details.
            _myCred = GetNetworkCredentials()

            'Set the credentials for the service proxy.
            _appFx.Credentials = _myCred

In the code below, note the setting the URL property on the proxy. For Altru, a link to the endpointhelp.html is available via the Resources for Developers link in the top right corner of the branding/splash/candy store home page. For details about how to obtain the URL to the AppFxWebService.asmx, see Application Features.

	     'Be sure to store the url to the AppFxWebService.asmx in a configuration 
            ' file on your client.  Don't hard code the url.
            'AppFxWebService.asmx is the single SOAP endpoint for accessing all 
            ' application features.
            'Grab the url from the Blackbaud AppFX Server HTTP Endpoint Reference 
            ' (endpointhelp.html).  For Altru, a link to the endpointhelp.html is 		  		available via the Resources for Developers
            'link in the top right corner of the branding splash/candystore page
            _appFx.Url = "https://altrurig01bo3.blackbaudhosting.com/5740Altru_9a731bb7-0e50-48e3-b8c3-e03e16a5ac15/appfxwebservice.asmx"

Next we instantiate a new ClientAppInfoHeader and provide a name for our custom client. This name is logged into the database for all database operations performed using the API.

		'Provide the ClientAppName which will be logged in the Infinity database 			and 
            ' used for auditing purposes.
            'Use a client application name that identifies your custom client software 
            ' from the web shell and any other client user interfaces.
            _clientAppInfoHeader = New 
Blackbaud.AppFx.WebAPI.ServiceProxy.ClientAppInfoHeader
            _clientAppInfoHeader.ClientAppName = "CustomEventManager"

After providing a URL to the Altru instance, we need to provide an identifier for the database key. Before you can consume features with the API, a developer should provide a key to identify the database. Two options are available to obtain a database key:

  1. Manually obtain a database key, store the database key in a config file, and retrieve the database key at run time.

  2. Retrieve a list of databases associated with the Infinity application at run time and present the list to let the end user pick the appropriate database.

The ClientAppInfoHeader's REDatabaseToUse property refers to a "database key" that points to a SQL Server instance and database. Unlike non-hosted installations of Blackbaud CRM, Altru, and ResearchPoint, hosted clients use a GUID that refers to the database key within Blackbaud Hosting's SiteManager on the web server side. For hosted Altru clients, the GUID is used to obtain this database key on the server side. The GUID can be found by locating the databaseName querystring value at the end of the webshell's URL. See the sample below.

https://altrurig01bo3.blackbaudhosting.com/5740Altru_9a731bb7-0e50-48e3-b8c3-e03e16a5ac15/webui/webshellpage.aspx?databaseName=10399621-5E99-4916-8625-2703496B1C41
_clientAppInfoHeader.REDatabaseToUse = "10399621-5E99-4916-8625-2703496B1C41"

	Catch ex As Exception
            MsgBox(ex.Message.ToString)

        Finally
            'Hide hourglass after api call
            Cursor.Current = Cursors.Default
            Cursor.Show()
        End Try
End Sub

A private function is used to return the network credentials. While we hard code the credentials below, in the real world, these credentials and the URL to the AppFxWebService.asmx should be secured and configurable.

Private Function GetNetworkCredentials() As System.Net.ICredentials

        Dim securelyStoredUserName, securelyStoredPassword As String
        securelyStoredUserName = "SomeUserName"
        securelyStoredPassword = "SomePassword"

        '**** Providing Credentials
        'System.Net.NetworkCredential implements System.NET.ICredentials
        'For Altru, the appropriate domain name is configured on the Altru IIS server by Blackbaud Hosting.
        'Typically the developer does not need to provide the domain name.  This holds true for BBEC instances, as well. 
        Dim NetworkCredential As New System.Net.NetworkCredential(securelyStoredUserName, securelyStoredPassword)

        Return NetworkCredential

 End Function

Step 7 -  Provide property values for the form's button and label.

On the WinForm designer, provide a value of btnGetEvents for the Name property and a value of Get Events for text property for the button. When the end user clicks the button, we will use the API to retrieve a list of events.

Provide a value of lblEvents for the Name property and a value of Events for the text property of the label.

Step 8 -  Provide a Name property value for the ListView.

Select the list view within the WinForm and provide a Name property value of "lvEvents."

Step 9 -  Provide a Text property value for the WinForm.

Select the WinForm and provide a Text property value of "AppFxWebService Custom Event Manager."

Your Form1 WinForm should look something like this:

Step 10 -  Code up the Get Events button.

Provide the following code for the Get Events button. We call LoadEvents() to retrieve a list of events from a data list, and we place the list into a ListView control on the WinForm.

Private Sub btnGetEvents_Click(sender As System.Object, e As System.EventArgs) Handles btnGetEvents.Click
        LoadEvents()
End Sub

    Private Sub LoadEvents()

        'Display hourglass during appfx web service calls
        Cursor.Current = Cursors.WaitCursor
        Cursor.Show()

To obtain data from a data list, we package up a request of type DataListLoadRequest.

        Dim Req As New Blackbaud.AppFx.WebAPI.ServiceProxy.DataListLoadRequest

Some data lists utilize a filter to narrow down the rows returned by the data list. Each parameter in the filter is represented by a form field. Declare a variable named fvSet of type DataFormFieldValueSet to represent the set of form field values that can be passed to the request to represent the filter for the data list.

        Dim fvSet As New Blackbaud.AppFx.XmlTypes.DataForms.DataFormFieldValueSet

A DataFormItem represents a form field for a parameter on a filter. We will pass one parameter value for our filter.

        Dim dfi As New Blackbaud.AppFx.XmlTypes.DataForms.DataFormItem

A few lines down within this code, we finally make a call to the API to retrieve the data list through the DataListLoad operation. We will receive a reply from the webservice of type DataListLoadReply.

        Dim Reply As New Blackbaud.AppFx.WebAPI.ServiceProxy.DataListLoadReply

Let's prepare the request. We set the ClientAppInfo property on the request that provides the custom client application name and database key.

        Req.ClientAppInfo = _clientAppInfoHeader

Provide the name of the feature as part of the request. Use the metadata features such as the Data List Search task to find the appropriate feature name. For third-party Altru developers, Blackbaud Professional Services needs to permission the API developer with rights to view the feature metadata features. This enables the developer to obtain the feature name or system record ID (GUID). Provide the name of the data list through the DataListName property.

        Req.DataListName = "Event Calendar Event List"

        'As an alternative to the DataListName in the request, you could also use the 		  DataListID/System Record ID.
        'Provide the system record id of the feature.
        'Req.DataListID = New System.Guid("cf479f2c-5657-42ca-8ed6-edde97c6a9ac")

If your data list has filters, you may need to provide a value for one or several of the form fields that serve as the parameters for the data list's filter. Try out the feature's filter within the application to get a feel for each of the data list's parameters.

Tip: To view the FormField tags that declare the parameters for the data list, see the <Parameters> tag within the datalistspec' s XML. Or see the Filters tab on the data list metadata page. For third-party Altru developers, Blackbaud Professional Services needs to permission the API developer with rights to view the feature metadata features.

fvSet (Blackbaud.AppFx.XmlTypes.DataForms.DataFormFieldValueSet) holds a collection of type  Blackbaud.AppFx.XmlTypes.DataForms.DataFormFieldValue. Each Blackbaud.AppFx.XmlTypes.DataForms.DataFormFieldValue represents a form field. For data lists, form fields are used as parameters. The appropriate filter values can be determined by looking at the form fields within the parameters portion of the Data List Spec.

Add an item to the collection of the DataFormFieldValueSet. Use the form field's/parameter's FieldID value for the DataFormFieldValue.ID and a value through the DataFormFieldValue.Value. The DataFormFieldValue's ID property represents the FieldID attribute within <FormField> element of the XML Data List Spec.

        fvSet.Add(New Blackbaud.AppFx.XmlTypes.DataForms.DataFormFieldValue With {.ID = "DATEFILTER", .Value = "0"})

        'The DataFormItem is used to hold the set of form fields.
        dfi.Values = fvSet

        'DataFormItem is passed to the request
        Req.Parameters = dfi

        'Max rows acts as a governor to limit the amount of rows retrieved by the datalist
        Req.MaxRows = 500

If you plan to inspect the metadata values that originated from DataListSpec XML doc, such as the FieldID or the IsHidden attributes of the OutputField element, then set IncludeMetaData to True. Further down in the code within the DisplayDataListReplyRowsInListView procedure, we will inspect the metadata in the reply and use that metadata to format the UI grid, hide certain columns, etc.

        Req.IncludeMetaData = True

        Try

The Blackbaud API utilizes a request-response pattern. The pattern consists of request-response pairs. An operation is called on the proxy, such as DataListLoad. The request is passed to the operation. A reply is received from the proxy.  Each request and reply type is tailored to the operation. Let's assume we usE the Blackbaud.AppFx.WebAPI.dll to communicate to an Altru instance. Since we need to "load" a data list up with data, we will call the proxy's DataListLoad operation.

To grab data from a data list, we package up a request (DataListLoadRequest) that includes the name or ID of the data list, pass the request to the DataListLoad operation, and receive a response (DataListLoadReply).

            Reply = _appFx.DataListLoad(Req)

            DisplayDataListReplyRowsInListView(Reply, lvEvents)

            
        Catch exSoap As System.Web.Services.Protocols.SoapException
            'If an error occurs we attach a SOAP fault error header.
            'You can check the proxy.ResponseErrorHeaderValue to get rich
            'error information including a nicer message copared to the raw exception 			message.
            Dim wsMsg As String
            If _appFx.ResponseErrorHeaderValue IsNot Nothing Then
                wsMsg = _appFx.ResponseErrorHeaderValue.ErrorText
            Else
                wsMsg = exSoap.Message
            End If

        Catch ex As Exception
                        MsgBox(ex.ToString)
        Finally
            'Hide hourglass after api call
            Cursor.Current = Cursors.Default
            Cursor.Show()
        End Try
    End Sub

Private Sub DisplayDataListReplyRowsInListView(Reply As Blackbaud.AppFx.WebAPI.ServiceProxy.DataListLoadReply, ListView As System.Windows.Forms.ListView)
        Try
            'Display hourglass during appfx web service calls
            Cursor.Current = Cursors.WaitCursor
            Cursor.Show()

            With ListView
                .View = View.Details
                .FullRowSelect = True
                .Clear()
            End With

Since we set Req.IncludeMetaData = True in the request, we will receive metadata values that originate from DataListSpec XML doc, such as the FieldID or the IsHidden attributes of the OutputFieldelement.

            If (Reply.Rows IsNot Nothing) Then
                For Each f As Blackbaud.AppFx.XmlTypes.DataListOutputFieldType In Reply.MetaData.OutputDefinition.OutputFields
                    If f.IsHidden = True Then
                        ListView.Columns.Add(f.FieldID, f.Caption, 0)
                    Else
                        ListView.Columns.Add(f.FieldID, f.Caption)
                    End If
                Next

                For Each row As Blackbaud.AppFx.WebAPI.ServiceProxy.DataListResultRow In Reply.Rows
                    ListView.Items.Add(New ListViewItem(row.Values))
                Next

                ListView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize)
                For Each f As Blackbaud.AppFx.XmlTypes.DataListOutputFieldType In Reply.MetaData.OutputDefinition.OutputFields
                    If f.IsHidden = True Then
                        ListView.Columns(f.FieldID).Width = 0
                    End If
                Next
            End If

        Catch ex As Exception
            MsgBox(ex.Message.ToString)

        Finally
            'Hide hourglass after api call
            Cursor.Current = Cursors.Default
            Cursor.Show()
        End Try
    End Sub
End Class

Step 11 -  Let's test the code.

Ensure you have URL, database key, and valid credentials on for the Infinity application. For a hosted Altru instance, contact your Professional Services technical representative for the credentials. If everything goes as planned, any events should appear when you click the Get Events button.

As a reference, here is the code sample in its entirety:

To cut down on the length of the class names you could import the Blackbaud.AppFx.WebAPI namespace
'Within variable declaration, class names shown in full for learning purposes. 
'Imports Blackbaud.AppFx.WebAPI
Public Class Form1

    'The Blackbaud.AppFx.WebAPI.dll is used to access the Infinity application such as 
    ' Blackbaud Enterprise CRM (BBEC), Altru, and Research Point (RP) and handles communication with the 
    ' AppFxWebService.asmx on your behalf.  If you use this dll to access the Infinity application 
    ' you do NOT need a web service reference to AppFxWebService.asmx.  
    'Declare a variable of type Blackbaud.AppFx.WebAPI.ServiceProxy.AppFxWebService to represent
    ' your proxy for the communication mechanism to Infinity applicaiton.
    Private _appFx As Blackbaud.AppFx.WebAPI.ServiceProxy.AppFxWebService
    Private _myCred As System.Net.ICredentials
    Private _clientAppInfoHeader As Blackbaud.AppFx.WebAPI.ServiceProxy.ClientAppInfoHeader



    Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load

        InitializeAppFxWebService()

    End Sub

    Private Sub InitializeAppFxWebService()

        Try
            'Display hourglass during appfx web service calls
            Cursor.Current = Cursors.WaitCursor
            Cursor.Show()

            'Instantiate the proxy to the Infinity application
            _appFx = New Blackbaud.AppFx.WebAPI.ServiceProxy.AppFxWebService

            'Grab the network credentials.  See GetNetworkCredentials() for details.
            _myCred = GetNetworkCredentials()

            'Set the credentials for the service proxy.
            _appFx.Credentials = _myCred

            'Be sure to store the url to the AppFxWebService.asmx in a configuration 
            ' file on your client.  Don't hard code the url.
            'AppFxWebService.asmx is the single SOAP endpoint for accessing all 
            ' application features.
            'Grab the url from the Blackbaud AppFX Server HTTP Endpoint Reference 
            ' (endpointhelp.html).  For Altru, a link to the endpointhelp.html is 		  		available via the Resources for Developers
            'link in the top right corner of the branding splash/candystore page
            _appFx.Url = "https://altrurig01bo3.blackbaudhosting.com/5740Altru_9a731bb7-0e50-48e3-b8c3-e03e16a5ac15/appfxwebservice.asmx"

            'Provide the ClientAppName which will be logged in the Infinity database 			and 
            ' used for auditing purposes.
            'Use a client application name that identifies your custom client software 
            ' from the web shell and any other client user interfaces.
            _clientAppInfoHeader = New Blackbaud.AppFx.WebAPI.ServiceProxy.ClientAppInfoHeader

            _clientAppInfoHeader.ClientAppName = "CustomEventManager"

            _clientAppInfoHeader.REDatabaseToUse = "10399621-5E99-4916-8625-2703496B1C41"

        Catch ex As Exception
            MsgBox(ex.Message.ToString)

        Finally
            'Hide hourglass after api call
            Cursor.Current = Cursors.Default
            Cursor.Show()
        End Try
    End Sub

    Private Function GetNetworkCredentials() As System.Net.ICredentials

        Dim securelyStoredUserName, securelyStoredPassword As String
        securelyStoredUserName = "TripOt"
        securelyStoredPassword = "UG@R1cht"

        '**** Providing Credentials
        'System.Net.NetworkCredential implements System.NET.ICredentials
        'For Altru, the appropriate domain name is configured on the Altru IIS server by Blackbaud Hosting.
        'Typically the developer does not need to provide the domain name.  This holds true for BBEC instances, as well. 
        Dim NetworkCredential As New System.Net.NetworkCredential(securelyStoredUserName, securelyStoredPassword)

        Return NetworkCredential

    End Function



    Private Sub btnGetEvents_Click(sender As System.Object, e As System.EventArgs) Handles btnGetEvents.Click
        LoadEvents()
    End Sub

    Private Sub LoadEvents()

        'Display hourglass during appfx web service calls
        Cursor.Current = Cursors.WaitCursor
        Cursor.Show()

        Dim Req As New Blackbaud.AppFx.WebAPI.ServiceProxy.DataListLoadRequest
        Dim fvSet As New Blackbaud.AppFx.XmlTypes.DataForms.DataFormFieldValueSet
        Dim dfi As New Blackbaud.AppFx.XmlTypes.DataForms.DataFormItem
        Dim Reply As New Blackbaud.AppFx.WebAPI.ServiceProxy.DataListLoadReply

        Req.ClientAppInfo = _clientAppInfoHeader

        Req.DataListName = "Event Calendar Event List"

        fvSet.Add(New Blackbaud.AppFx.XmlTypes.DataForms.DataFormFieldValue With {.ID = "DATEFILTER", .Value = "0"})

        'The DataFormItem is used to hold the set of form fields.
        dfi.Values = fvSet

        'DataFormItem is passed to the request
        Req.Parameters = dfi

        'Max rows acts as a governor to limit the amount of rows retrieved by the datalist
        Req.MaxRows = 500

        Req.IncludeMetaData = True

        Try

            Reply = _appFx.DataListLoad(Req)

            DisplayDataListReplyRowsInListView(Reply, lvEvents)



        Catch exSoap As System.Web.Services.Protocols.SoapException
            'If an error occurs we attach a SOAP fault error header.
            'You can check the proxy.ResponseErrorHeaderValue to get rich
            'error information including a nicer message copared to the raw exception 			message.
            Dim wsMsg As String
            If _appFx.ResponseErrorHeaderValue IsNot Nothing Then
                wsMsg = _appFx.ResponseErrorHeaderValue.ErrorText
            Else
                wsMsg = exSoap.Message
            End If

        Catch ex As Exception

            MsgBox(ex.ToString)
        Finally
            'Hide hourglass after api call
            Cursor.Current = Cursors.Default
            Cursor.Show()
        End Try
    End Sub

    Private Sub DisplayDataListReplyRowsInListView(Reply As Blackbaud.AppFx.WebAPI.ServiceProxy.DataListLoadReply, ListView As System.Windows.Forms.ListView)
        Try
            'Display hourglass during appfx web service calls
            Cursor.Current = Cursors.WaitCursor
            Cursor.Show()

            With ListView
                .View = View.Details
                .FullRowSelect = True
                .Clear()
            End With

            If (Reply.Rows IsNot Nothing) Then
                For Each f As Blackbaud.AppFx.XmlTypes.DataListOutputFieldType In Reply.MetaData.OutputDefinition.OutputFields
                    If f.IsHidden = True Then
                        ListView.Columns.Add(f.FieldID, f.Caption, 0)
                    Else
                        ListView.Columns.Add(f.FieldID, f.Caption)
                    End If
                Next

                For Each row As Blackbaud.AppFx.WebAPI.ServiceProxy.DataListResultRow In Reply.Rows
                    ListView.Items.Add(New ListViewItem(row.Values))
                Next

                ListView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize)
                For Each f As Blackbaud.AppFx.XmlTypes.DataListOutputFieldType In Reply.MetaData.OutputDefinition.OutputFields
                    If f.IsHidden = True Then
                        ListView.Columns(f.FieldID).Width = 0
                    End If
                Next
            End If

        Catch ex As Exception
            MsgBox(ex.Message.ToString)

        Finally
            'Hide hourglass after api call
            Cursor.Current = Cursors.Default
            Cursor.Show()
        End Try
    End Sub
End Class

But Wait, There's More!

The entire .NET code sample can be found here and includes other popular API calls to leverage features such as the ability to: