Select your user interface:

Use the AppFxWebService to Add Rows to an Existing Batch

In the previous example, we created a batch from a batch template. We have a batch template named "Sample Constituent Batch Template" that is based on the batch type named "Constituent Batch."

Figure: Batch template

We created a batch from the template. The batch contains two rows from prior activity within the Infinity shell user interface. I am adding a third row to the batch using the shell UI. The third row has been added to the batch UI grid but has not yet been saved.

Figure: Add a third row to the batch

I have turned the web service logging utility by un-commenting the appropriate tag within the Blackbaud CRM web application's Web.config file. This file is located in the application's bbappfx\vroot folder.

Figure: Modify Web.config

I altered the value for the "logrequests" appsettings key. The "logrequests" appsettings key can be used to log requests to the dbo.WSREQUESTLOG table within SQL Server. If the value attribute equals "BatchSaveRequest," then only these types of requests are logged into the table.

Figure: Log batch save requests

Saving the row within the batch to the database yields the following results within the WSREQUESTLOG table in the Blackbaud CRM database on SQL Server:

SELECT TOP 1000 
      [CLIENTAPP]
      ,[REQUESTNAME]
      ,[REQUESTXML]
      ,[REPLYXML]
      ,[ERRORMSG]
   FROM [BBInfinity].[dbo].[WSREQUESTLOG]

Figure: Use WSREQUESTLOG to spy on the web service

The REQUESTXML and REPLYXML columns in the WSREQUESTLOG table are of data type XML.

Figure: WSREQUESTLOG columns

Selecting the value in the REQUESTXML column within the results displays the request XML message sent to the AppFxWebService from the Blackbaud CRM client user interface (shell).

<BatchSaveRequest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="Blackbaud.AppFx.WebService.API.1">
  <ClientAppInfo REDatabaseToUse="BBInfinityPROD2.7.1633.0" ClientAppName="Blackbaud Enterprise CRM" SessionKey="52a33b11-7ed8-4bf7-8fde-cacadd0820b0" TimeOutSeconds="120" />
  <BatchID>b104b77d-a82b-45e3-bda6-f3377756ec1a</BatchID>
  <Rows>
    <BatchDataRow>
      <ID>fa129d43-018a-40bf-aeb2-e07e6b60cd5e</ID>
      <AddRow>true</AddRow>
      <DataFormItem>
        <Values xmlns="bb_appfx_dataforms">
          <fv ID="ISORGANIZATION">
            <Value xsi:type="xsd:string">0</Value>
          </fv>
          <fv ID="KEYNAME">
            <Value xsi:type="xsd:string">Jefferson</Value>
          </fv>
          <fv ID="ADDRESS_ADDRESSTYPECODEID">
            <Value xmlns:q1="http://microsoft.com/wsdl/types/" xsi:type="q1:guid">e5403bad-5b97-4644-aade-ad612a48ddba</Value>
          </fv>
          <fv ID="ADDRESS_COUNTRYID">
            <Value xmlns:q1="http://microsoft.com/wsdl/types/" xsi:type="q1:guid">d81cef85-7569-4b2e-8f2e-f7cf998a3342</Value>
          </fv>
          <fv ID="ADDRESS_ADDRESSBLOCK">
            <Value xsi:type="xsd:string">101 Home Sweet Home Lane</Value>
          </fv>
          <fv ID="ADDRESS_CITY">
            <Value xsi:type="xsd:string">Santa Monica</Value>
          </fv>
          <fv ID="ADDRESS_STATEID">
            <Value xmlns:q1="http://microsoft.com/wsdl/types/" xsi:type="q1:guid">5c72589b-3898-4760-880c-4189cd8c5f7d</Value>
          </fv>
          <fv ID="ADDRESS_POSTCODE">
            <Value xsi:type="xsd:string">90401</Value>
          </fv>
          <fv ID="SEQUENCE">
            <Value xsi:type="xsd:int">1</Value>
          </fv>
          <fv ID="SPOUSE_RELATIONSHIPTYPECODEID" />
          <fv ID="SPOUSE_RECIPROCALTYPECODEID" />
          <fv ID="PRIMARYBUSINESS_RELATIONSHIPTYPECODEID" />
          <fv ID="PRIMARYBUSINESS_RECIPROCALTYPECODEID" />
          <fv ID="EDUCATIONALINVOLVEMENT" />
          <fv ID="TAXDECLARATIONS" />
          <fv ID="SOLICITCODES" />
        </Values>
      </DataFormItem>
      <ExceptionMessageTypeCode>GeneralError</ExceptionMessageTypeCode>
      <ClearUserMessage>false</ClearUserMessage>
      <ClearSystemMessage>false</ClearSystemMessage>
      <IgnoreDuplicate>false</IgnoreDuplicate>
    </BatchDataRow>
  </Rows>
  <DeletedRows />
</BatchSaveRequest>

With a little work we can translate the XML above into the code necessary within a new custom client application. Within the BatchHelper.vb class file created in the previous exercise, a new function is created that accepts a BatchID, a set of values to save which represent a new batch row, and a sequence number of where that row should be placed amongst the other batch rows.

Public Function BatchSaveRequest(ByVal BatchID As System.Guid, ByVal BatchRowValues As Generic.Dictionary(Of String, String), ByVal BatchRowSequence As Integer) As System.Guid

First we create a BatchSaveRequest object.

Dim req As New Blackbaud.AppFx.WebAPI.ServiceProxy.BatchSaveRequest

Then we create a reply object to catch the results of saving the batch row.

Dim reply As New Blackbaud.AppFx.WebAPI.ServiceProxy.BatchSaveReply

Next we make a call to a helper function GetRequestHeader(), which points us to the correct Infinity database.

req.ClientAppInfo = GetRequestHeader()

Next we set the BatchID on the request object to appropriately identify the batch created from the batch template. This ID can be found within the BATCH table in the database.

req.BatchID = BatchID

Next we create a BatchDataRow to represent our batch row and the column values. We also declare an array of the same data type. We are only adding one row, so we have dimensioned the array to hold a single row. We will provide a new GUID for the row ID and designate that this is a new row to be added to the batch.

Dim BatchDataRow As New Blackbaud.AppFx.WebAPI.ServiceProxy.BatchDataRow
Dim BatchDataRows(0) As Blackbaud.AppFx.WebAPI.ServiceProxy.BatchDataRow

BatchDataRow.ID = System.Guid.NewGuid.ToString
BatchDataRow.AddRow = True

We will convert the generic.dictionary of form field values passed into our function into a generic.list of DataFormFieldValues which is used within AddRange a few lines of code below.

Dim DataFormItem As New Blackbaud.AppFx.XmlTypes.DataForms.DataFormItem
Dim DataFormFieldValueList As New Generic.List(Of Blackbaud.AppFx.XmlTypes.DataForms.DataFormFieldValue)

For Each DataFormValueKey In BatchRowValues.Keys
DataFormFieldValueList.Add(New Blackbaud.AppFx.XmlTypes.DataForms.DataFormFieldValue(DataFormValueKey, BatchRowValues(DataFormValueKey)))
Next

We will add a final DataFormFieldValue to represent the row sequence:

DataFormFieldValueList.Add(New Blackbaud.AppFx.XmlTypes.DataForms.DataFormFieldValue("SEQUENCE", BatchRowSequence))

AddRange accepts an IList of dataformfieldvalues. This will provide the DataFormSaveRequest with its payload of form field values to save.

DataFormItem.Values.AddRange(DataFormFieldValueList)

Next we add the DataFormItem that contains our row values to the BatchDataRow and then we add the BatchDataRow to the array and finally add the array to the request object.

BatchDataRow.DataFormItem = DataFormItem
BatchDataRow.ExceptionMessageTypeCode = Blackbaud.AppFx.WebAPI.ServiceProxy.BatchMessageType.GeneralError
BatchDataRow.ClearUserMessage = False
BatchDataRow.ClearSystemMessage = False
BatchDataRow.IgnoreDuplicate = False

BatchDataRows(0) = BatchDataRow
req.Rows = BatchDataRows

Next we call BatchSave using an object of type Blackbaud.AppFx.WebAPI.ServiceProxy.AppFxWebService and catch the reply. We can inspect the reply object for exceptions, if there are any we throw a custom exception containing the errors.

reply = _service.BatchSave(req)

If Not reply.Exceptions Is Nothing Then
If reply.Exceptions.Count > 0 Then
Dim ex As New BatchException("Batch Save Error")
ex.BatchSaveReply = reply
Throw ex
Exit Function
End If
End If
End Function

Now let's see the BatchSaveRequest in action within our custom user interface. We provide the constituent and address information and select the Add Row To Batch button to call BatchSaveRequest (yellow highlight below).

Private Sub cmdAddRowToBatch_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdAddRowToBatch.Click
        Dim SaveRequestGUID As System.Guid
        Dim BatchRowValues As New Generic.Dictionary(Of String, String)
        Dim ISORGANIZATION As System.Int16
        Try
            If Me.cboConstituentType.Text = "Individual" Then
                ISORGANIZATION = 0
            Else
                ISORGANIZATION = 1
            End If

            BatchRowValues.Add(Me.cboConstituentType.Tag.ToString, ISORGANIZATION.ToString)
            BatchRowValues.Add(Me.txtKeyName.Tag.ToString, Me.txtKeyName.Text.ToString)
            BatchRowValues.Add(Me.cboAddressTypeCodeID.Tag.ToString, CType(Me.cboAddressTypeCodeID.SelectedItem, SimpleAddressType).AddressType.ToString)
            BatchRowValues.Add(Me.txtAddressBlock.Tag.ToString, Me.txtAddressBlock.Text.ToString)
            BatchRowValues.Add(Me.txtCity.Tag.ToString, Me.txtCity.Text.ToString)

            BatchRowValues.Add(Me.cboStateID.Tag.ToString, CType(Me.cboStateID.SelectedItem, SimpleState).StateID.ToString)
            BatchRowValues.Add(Me.cboCountryID.Tag.ToString, CType(Me.cboCountryID.SelectedItem, SimpleCountry).CountryID.ToString)
            BatchRowValues.Add(Me.txtPostCode.Tag.ToString, Me.txtPostCode.Text.ToString)

            _batchRowSequence += 1
            SaveRequestGUID = _helper.BatchSaveRequest(_currentBatchID, BatchRowValues, _batchRowSequence)

            ClearBatchRow()
            cmdAddRowToBatch.Enabled = CheckEnableAddRowToBatchButton()

        Catch ex As BatchException
            MsgBox(ex.BatchErrorMessage, MsgBoxStyle.Exclamation)
        Catch ex As Exception
            MsgBox(ex.Message)
        End Try
End Sub

Figure: Adding a new row from our custom user interface

We then can inspect the row within the batch in Blackbaud CRM.

Figure: Access the new row

Figure: The new row