Business Operations Services (BizOps)

For examples that use Business Operations Services, see Business Operations Services (BizOps) API.

Business Operations Services is a wrapper API for AppFxWebService targeted to all developers.

Blackbaud CRM has THOUSANDS of features and the number keeps growing. In each release of Blackbaud CRM, the number of features continues to grow. And don't forget the custom features that you can author with the Infinity SDK! And don't forget that an end user can extend the list of features by authoring a user-defined data list. Certain types of these features can be addressed through a certain type SOAP web service called a BizOp. The nice thing about BizOps is that they are strongly typed web services. If you know which Infinity feature you need access, you can add a reference to the ASMX and begin to code. They are granular, and each BizOp web service represents a single Infinity feature. If you have a small application that only needs access to a few features, you can add the necessary references to the ASMX/WSDL and begin to program. Below is a listing of features that can be addressed through a BizOp:

Add a Web Reference to a BizOp SOAP Endpoint

Making a reference to a business operation service is quite easy. The catch is that you have to know ahead of time which feature you want to automate. You can locate the web service for the feature in a couple ways. First, you can navigate to the Blackbaud AppFx Server HTTP Endpoint Reference web page and click the Business Operations Services link.

Next, you select the database key that directs you to the Blackbaud Infinity database that houses your Infinity features.

After you select the database key, you must select the Infinity feature type /category.

Finally, assuming you know the name of the Infinity feature, you can select. For my example, I am looking for the "Individual Search" search list. So I click the Record Searches link, and I am presented with a listing of all the Record Search web services.

Within my browser, I press CTRL+F to begin my search. I search for "Individual Search" and click the SOAP link to obtain the URL to the web service endpoint.

Within my web browser, I grab the web service URL that points me to the appropriate SOAP web service:

http://localhost/BBInfinityPROD/vpp/bizops/db[BBINFINITYPROD2.7.1633.0]/searchlists/4685952f-6964-486c-9acd-5560a8a30862/soap.asmx

Since I use Visual Studio.NET 2008, I can create a web reference and supply the URL to the soap.asmx:

As you can see, I now have the Individual Search web reference added to my project along with references to the Constituent Summary View, Phone Add, and Phone List BizOp SOAP endpoints.

A second way to find the feature and its associated SOAP endpoint is to use the Go To feature to review the metadata for an Infinity feature. API information is included with the feature metadata. You can utilize the shell to find the appropriate feature and then view its metadata, which includes the URL to the BizOp web service. For more information, see application features.

One advantage of BizOps is the lack of dependence to Infinity .NET assemblies. This is useful if you use a non-Windows development platform to access Infinity features. Here you can see the references for the BizOp project. Note the absence of any Infinity .NET assemblies.

Here is a piece of code that searches for a constituent by a lookup ID using BizOps. It utilizes a search list called "Individual Search" to find the constituent and then makes a call to the Constituent Summary view data form to retrieve summary information for the searched constituent. A façade class called Person.vb is used to ease access to the BizOp web services.

Form1 vb.net WinForm

Private Sub btnRetrieve_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnRetrieve.Click
        btnRetrieve.Enabled = False
        btnAddPhone.Enabled = False
        Try

            'New up a Person object which makes a call 
            'to the BizOp webservice on our behalf 
            Dim Person As New InfinityAPIBizOpDemo.Person(formLog, credentials)
            Person.Search(txtLookupID.Text)

            constituentID = Person.ConstituentID
            btnAddPhone.Enabled = True

        Catch ex As Exception
            txtLog.AppendText(ex.ToString())
        Finally
            btnRetrieve.Enabled = True
        End Try
End Sub

Person.vb

Imports System
Imports System.Net
Namespace InfinityAPIBizOpDemo
    Public Class Person

        Private constitID As Guid = Guid.Empty
           Private credentials As ICredentials

           Public Sub Search(ByVal lookupId As String)

            logger(String.Format("Retrieve: {0}", lookupId))

            'new up a SearchCriteria object from the IndividualSearch Web Reference
            Dim searchCriteria As IndividualSearch.SearchCriteria = New IndividualSearch.SearchCriteria()

            'Here's the search criteria
            searchCriteria.LOOKUPID = lookupId

            'Set flags to ensure we only get individuals
            searchCriteria.INCLUDEINACTIVE = False
            searchCriteria.INCLUDEORGANIZATIONS = False
            searchCriteria.INCLUDEGROUPS = False
            searchCriteria.INCLUDEDECEASED = False
            'Set this to avoid one row for each address
            searchCriteria.ONLYPRIMARYADDRESS = True


            'New up a searchrequest and pass in the searchcriteria
            Dim searchRequest As IndividualSearch.SearchRequest = New IndividualSearch.SearchRequest()
            searchRequest.Criteria = searchCriteria

            'New up a search service... preparing to make the call to the web service
            Dim searchService As IndividualSearch.SearchService = New IndividualSearch.SearchService()
            'Pass the credentials to the search service
            searchService.Credentials = credentials


            logger("Running search...")
            'Using the search request object as part of the call to the search service
            'Use the search reply to catch the results/reply of the call to the web service
            Dim searchReply As IndividualSearch.SearchReply = searchService.Search(searchRequest)

            ' Make sure search was successful 
            If (searchReply.StatusOK) Then
                logger("Successful")
            Else
                logger("Failed.")
                logger(String.Format("Code: {0}", searchReply.StatusCode))
                logger(String.Format("Message: {0}", searchReply.StatusMessage))
                Return
            End If

            'Check count of rows, bail if it isn't 1.  
            'Retrieve constituent ID if we only have 1 row
            If (searchReply.Rows.Length <> 1) Then
                logger(String.Format("Unexpected number of rows returned: {0}", searchReply.Rows.Length))
                Return
            Else
                constitID = searchReply.Rows(0).ID
            End If

            If (ConstituentID.Equals(Guid.Empty)) Then
                logger("No constituent ID returned.")
                Return
            End If

            'Use returned ID to load "Constituent Summary Profile View Form"
            Dim getViewReq As ConstituentSummary.GetViewDataRequest = New ConstituentSummary.GetViewDataRequest()
            getViewReq.RecordID = ConstituentID.ToString()

            Dim viewService As ConstituentSummary.ViewRecordService = New ConstituentSummary.ViewRecordService()
            viewService.Credentials = credentials

            logger("Sending get view request.")
            Dim getViewReply As ConstituentSummary.GetViewDataReply = viewService.GetViewData(getViewReq)

            If (getViewReply.StatusOK) Then
                logger("Successful")
            Else
                logger("Failed.")
                logger(String.Format("Code: {0}", getViewReply.StatusCode))
                logger(String.Format("Message: {0}", getViewReply.StatusMessage))
                Return
            End If

            'Display a few fields from the view form
            logger(String.Format("Address: {0}", getViewReply.ViewData.ADDRESS))
            logger(String.Format("E-mail: {0}", getViewReply.ViewData.EMAILADDRESS))
            logger(String.Format("Spouse: {0}", getViewReply.ViewData.RELATEDCONSTITUENT))
            logger(String.Format("Primary business: {0}", getViewReply.ViewData.PRIMARYBUSINESS))
            logger(String.Format("Primary education: {0}", getViewReply.ViewData.PRIMARYEDUCATION))

            logger("Retrieve done.")
        End Sub

        Public ReadOnly Property ConstituentID() As Guid
            Get
                Return constitID
            End Get
        End Property

    End Class
End Namespace

BizOps UML Sequence Diagram

Finally, here is a UML Sequence diagram that shows the various layers of the objects used and the interactions between the objects over time. Sequence diagrams can describe how a new piece of software that interacts with an Infinity application should behave. During the design phase, architects and developers can use the diagram to force out the system's object interactions, thus fleshing out overall system design. Since we added four web references to our .NET project, .NET was kind enough to create web service proxy classes. Note how the Person façade object talks to the web service proxies that call the BizOp web services that in turn make calls to the AppFxWebService.

Pros of BizOps

Cons of BizOps

The source code for this sample can be found here: InfinityAPIBizOpDemo.zip.

For more background information about Infinity Web APIs, see:

Introduction to the Infinity Web Service APIs

Or a for a more in depth discussion, see:

API Overview