Use Infinity Web Services from PowerShell

Windows PowerShell has built into it great support for invoking web services. Infinity applications such as Blackbaud Enterprise CRM expose a rich suite of web services that can easily be invoked from PowerShell without the need for any dependencies on the machine where the PowerShell session is running. This article will show how to use the PowerShell New-WebServiceProxy cmdlet with the Infinity AppFxWebService.asmx endpoint.

Why would you want to do this?

PowerShell is Microsoft’s strategic technology for automation, scripting, and management on Windows. PowerShell is quickly becoming the tool of choice for IT professionals who are responsible for configuring, administering, and managing Windows environments and who are looking to maximize the level of automation of their operations. PowerShell has elegant support for combining .Net, classic COM, WMI, and existing command line utilities in a single unified scripting framework that scales from interactive command line use up to complex modularized scripting libraries.

Infinity web services can be invoked from any client programming technology that can utilize WSDL defined SOAP endpoints, including of course the Visual Studio languages of VB.Net and C#. But it might make sense for you to implement a solution using PowerShell instead of a Visual Studio language if you are most comfortable with the PowerShell environment or if the task at hand is in the area that is in PowerShell’s power swing already, perhaps intersecting with other IT operational management tasks that will involve WMI, COM, or other PowerShell cmdlets. For example if you are authoring some kind of operational deployment script that configures an Infinity web server using the PowerShell WebAdministration module and as part of that script you need to invoke some of the Infinity application functionality such as adding an application user to an application System Role.

Prerequisites

This article will focus on the specific mechanics of combining PowerShell with the Infinity web service API, so it is best if you already have a basic familiarity with each of those independent technologies.

To learn more about the Infinity web services API see the documentation on the BBDN web site:

http://www.bbdevnetwork.com/content/introduction-infinity-web-service-apis

To learn more about PowerShell’s support for web services see these articles:

New-WebServiceProxy reference

http://www.jonathanmedd.net/2009/12/powershell-2-0-one-cmdlet-at-a-time-26-new-webserviceproxy.html

http://thepowershellguy.com/blogs/posh/archive/2009/05/15/powershell-v2-get-weather-function-using-a-web-service.aspx

To follow along against a live Infinity system you will also need the following:

  • The full url to the appfxwebservice.asmx endpoint for your system, for example http://localhost/bbappfx/appfxwebservice.asmx
  • A user account with permissions in the Infinity application. Security is enforced at the web services layer just as it is through the interactive GUI so you will need to be granted permission to any of the features that you will be invoking or you will get a permission denied error.

Start with a (simple) Ping

The AppFxWebService endpoint exposes a very simple method named “PingSimple” that takes as input a string and returns that same string with some time information from the server appended. This method is often used to verify that the server is up and running or to do a best-case round trip timing when troubleshooting performance issues. The PingSimple web service is a nice introductory target because it is so simple.

In order to invoke this method from PowerShell the first thing we need to do is create the web service proxy object that will be used to invoke methods against, and for that we use the New-WebServiceProxy cmdlet. (I am going to use a variable named $url to store my url so that if you copy and paste from these examples it will be easy to port to your own live system.)

Step 1 – Create the web service proxy

#change this to the url for your system            
$url="http://paulg2008r2/bbappfx_firebird/appfxwebservice.asmx"            
            
$proxy=New-WebServiceProxy -Uri $url -Class "proxy" -Namespace "bbapi" `
-UseDefaultCredential            

Here I am using the –UseDefaultCredential option so that my integrated Windows Authentication login will be used. You could optionally use the Get-Credential cmdlet along with the –Credential option if you need to supply alternate credentials or if you are hitting a system that only supports basic authentication.

If you are using the command line shell then once you have created the proxy you will get intellisense on the $proxy variable with all of the methods, so for example if you type “$proxy.PingS” and hit TAB the shell will auto-complete to “$proxy.PingSimple(“ with an open paren ready to accept the string parameter to pass to the web method:

clip_image002

Figure 1: Auto-complete of the web service method name

Step 2 – Invoke the method

To invoke the method just enter a string after the open paren, close the paren, and hit enter:

clip_image004

Figure 2: Response from server after calling the PingSimple method

Note that the first time you invoke one of the methods from the proxy there will be a slight delay while the PowerShell runtime does the necessary metadata interpretation and code generation magic that enables this function-like syntax over the underlying SOAP protocol. After the first call all of that metadata is cached and subsequent calls will be bound only by the server response time.

Using the *request / * reply style web service methods

Unlike the PingSimple method, most of the Infinity web service methods have a signature that looks something like this:

VB

    Function WebMethodX(request As WebMethodXRequest) As WebMethodXReply

C#

     WebMethodXReply WebMethodX(WebMethodXRequest request )

where “WebMethodXRequest” is a rich class with properties that define all of the parameters to the method.

For example, the method signature for the CodeTableEntryGetId web service which translates a code table entry such as the Constituent Biographical Title Code “Mr.” into the guid id value for that entry looks like this:

Function CodeTableEntryGetID(CodeTableEntryGetIDRequest As CodeTableEntryGetIDRequest) As CodeTableEntryGetIDReply

Where the request and reply objects are defined as follows (if using VB.Net syntax):

Class CodeTableEntryGetIDRequest
    Inherits ServiceRequest

    Public CodeTableName As String
    Public Description As String
End Class

Class CodeTableEntryGetIDReply

    Public ID As Guid
End Class

The CodeTableEntryGetIdRequest class has two fields which represent the parameters into the web service and the web service method itself only takes a single parameter which is an instance of the CodeTableEntryGetIdRequest class.

This is sometimes called the “document style” or “message style” or “explicit style” pattern because it is explicit that this method accepts a payload as input and returns a payload as output, as opposed to exposing an RPC style syntax where traditional arguments are implicitly mapped to the input payload by the runtime.

This means that the typical call sequence to an Infinity web service would look something like this pseudocode:

  1. Create instance of request object
  2. Set properties on request
  3. Call method and pass in request
  4. Examine properties of returned reply object

The next web service method we will call from PowerShell will use this pattern.

Ping using the request / reply pattern style method

The AppFxWebService exposes a method named “Ping” that, unlike the PingSimple method, follows this request/reply pattern. In order to invoke it we will need to do the following:

  1. Create instance of the PingRequest object
  2. Set properties on the PingRequest object
  3. Call the Ping method and pass in the PingRequest object
  4. Examine properties of returned reply object

You use the New-Object cmdlet to create an instance of the PingRequest class. When we invoked the New-WebServiceProxy cmdLet we specified “bbapi” as the –NameSpace parameter, so the syntax for creating the PingRequest is as follows:

$pingRequest= New-Object bbapi.PingRequest

If you need to know the properties that are available on the request object you can always pipe the object to the Get-Member cmdlet to take a look:

clip_image006

Figure 3: Examining the request properties with Get-Member

You can see that the PingRequest has three properties:

  1. ClientAppInfo
  2. ConnectToDatabase
  3. MessageToEcho

The ClientAppInfo property is of type bbapi.ClientAppInfoHeader. This is another rich class that has its own properties that must be configured, so we need to create an instance of this class using New-Object, and we can examine the properties with Get-Member:

clip_image008

The most important properties are the ClientAppName and the REDatabaseToUse. For our first Ping invocation we will ignore the REDatabaseToUse and just specify the ClientAppName. Then we will assign the header to the ClientAppInfo property of the PingRequest and set the other properties. The full script looks like this:

clip_image010

Figure 4: PowerShell script to invoke the Ping web service

We can see that the reply object has a property named EchoedMessage. If we examine that property we see that the method succeeded and the server replied:

clip_image012

Figure 5: Getting the EchoedMessage value from the PingReply

Get available databases

It is rare but possible that an Infinity endpoint could support multiple different databases from the same url. This possibility is reflected in the API in the fact that almost every web service method requires you to pass in a unique key that identifies the intended target database. Note that the value of this key is not the physical SQL Server database name but rather a key/alias that will be used to lookup the actual SQL Server database name. On my system I have 3 databases configured and thus I have 3 possible keys and I will need to supply one to every web service API call I make if that method will end up touching the database. In the webshell UI on my system I get prompted at login with a menu that presents all 3 keys:

clip_image014

Figure 6: WebShell prompt when the url is configured for multiple databases

The classic ClickOnce shell presents this list as a dropdown:

clip_image016

The value of the key I need for the API will be one of the values listed in the dropdown or on the webshell prompt page. I can use the GetAvailableREDatabases web service method to get this list.

clip_image018

Figure 7: Using GetAvailableREDatabases to get a list of databases serviced by the web service endpoint

Ping and Connect to the database

Now that I know a few specific database keys I can use the Ping method and set the ConnectToDatabase property to $true on the request so that the Ping will not only connect to the web server but also verify connectivity from the web server to the database.

clip_image020

Figure 8: Using ConnectToDatabase=$true with Ping

Get data from the DataListLoad method

Now that we understand the basic pattern of how to call the Infinity web services we can do something a little more useful than Ping. Suppose we want to find out if there are any users in our database that are not System Administrators but are also not in any system roles. Those users would not be able to do anything, so there probably needs to be some cleanup done on those records. Either they should be removed as users or placed into one or more system roles. In the WebShell GUI there is a page located in the Administration\Security functional area that shows a list of all users:

clip_image022

Figure 9: Application Users datalist

The metadata page for this list shows the ID and name of the list, it is a datalist named “Application Users List”:

clip_image024

Figure 10: Metadata for the Application Users List

The following script will get the data from this datalist and it will use the Where-Object cmdlet to filter down to those users that are not sysadmins but who are also not in any system roles:

clip_image026

Figure 11: Script to query the Application Users Datalist

Conclusion

Windows PowerShell is a great tool for calling web services. Infinity applications expose a lot of functionality via web services. It’s not hard to combine the two together in scenarios where it makes sense.

Posted in DevOps | Tagged , , , | Leave a comment

Adapt Infinity Web Service for ESB Integration–Part 6

This post is part 6 in a series on integrating Blackbaud Infinity based applications such as Blackbaud Enterprise CRM (BBEC) into an Enterprise Service Bus (ESB) architecture using a WCF Workflow service hosted in Windows Server AppFabric.

  1. Part 1 – Intro
  2. Part 2 – ESB Message pass-through to BBEC workflow activity
  3. Part 3 – ESB Message translation to BBEC IDs and operations
  4. Part 4 – Deploying to Windows Server AppFabric on IIS
  5. Part 5 – Asynchronous message handling with SQL persistence

This post will cover securing access to the ESB adapter service.  I will show how to ensure that only authorized principals can send a message to the adapter service.

Inbound Security Authentication & Authorization

In part 4 of this series we moved our application from the local ASP.Net development web server to IIS.  In the process we realized we had to deal with the security requirements for the outgoing calls from the adapter service to the Infinity web service.  We used the Trusted Subsystem Model pattern where the account that the ESB adapter service is running under is treated as a trusted subsystem with a set of the least privileges needed to carry out only and exactly the operations that are used by the workflow service.  We placed the account of the workflow service into a BBEC System Role and gave it permissions to only the BBEC features that are used by the workflow.  This took care of the outgoing security from the adapter service into Infinity.

However, up until now we have failed to take into account the incoming security- which is a potentially glaring omission.  In the current implementation of our service up until Part 5 of this series we have a pretty wide open front door to add a new record to BBEC because our IIS application that hosts the ESB adapter is configured to allow Anonymous access.

image

This means that currently anyone who can make an HTTP POST to our endpoint on this server can send a message and the adapter will dutifully forward it on to the BBEC application even though the caller may be completely anonymous.  If we don’t correct this then if we have a mischievous and bored summer intern on staff we can probably look forward to a few prank “Seymore Butz” and “”Amanda Hugginkiss” Constituent records showing up in our database.  Obviously we need to plug this hole.

There are multiple ways we can seal off this service from unauthorized access.  A brute force approach could be to secure the service behind some kind of firewall (hardware or software) that only allows messages to this service from a well known server that is the actual ESB server.  Such hardening of access may be a good idea as a way to implement a Defense In Depth strategy, but I would feel much better if the service itself had its own implementation of proper user authentication and authorization in addition to any external access control.

AppFabric, where are you?

Securing access to the workflow service is an area that Workflow Foundation and AppFabric are surprisingly… how can I say this nicely…. not in the business of helping us out.  According to the AppFabric documentation on MSDN:

The primary goal of the AppFabric security model is to provide a simple, yet effective, mechanism for the majority of AppFabric users. Because of its integration with existing Windows, .NET Framework, IIS, and SQL Server security models, users can leverage existing security knowledge and skill sets to use the AppFabric security model. Specifically it uses Windows, .NET Framework, IIS, and SQL Server security concepts to enforce different levels of security on the WCF and WF applications it manages. Because AppFabric adds only minor enhancements to an already robust integrated Microsoft security picture, its security model is familiar to administrators who are knowledgeable in Microsoft security concepts. This results in a lower long-term total cost of ownership for AppFabric customers. If you are already familiar with these products and technologies, you can easily secure your application by following the guidance in the Security and Protection section.

Allow me to translate in my own words:

When it comes to securing access to the workflow service, go read up on how you do that with WCF because all an AppFabric service is really is just another WCF service.  AppFabric workflow services security punts to all that existing stuff so you get no help from WF or AppFabric beyond what comes with WCF.  Just secure it like you would any other WCF service.

This delegation to existing WCF functionality is something that I can respect because there are some good things about being compatible with existing concepts and implementation.  But I do feel a little disappointed that neither WF or AppFabric have done anything to take the level of abstraction up a notch with WCF authentication in the same way that they have with so much else.  In fact, in some ways it is actually more work to secure a workflow service because you can’t take advantage of the PrincipalPermissionAttribute that would allow you to declaratively decorate a WCF service method implemented in procedural code.  The result is that paradoxically you can use declarative security in procedural WCF code but you can’t use the same easy technique in declarative workflow services.  (Personally this makes me want to put a T in the middle of the WF abbreviation).

Interestingly, there is a project from Microsoft up on CodePlex called the WF Security Pack CTP 1 that includes an activity named “PrinciplePermissionScope” that is exactly the kind of security wrapper we need.  According to the documentation of the "CTP status" activity:

PrincipalPermissionScope

The PrincipalPermissionScope activity enforces authorization within the workflow by performing a principal permission check against a client-provided identity.

After a message is received (via a Receive activity within the scope of the Body), the authenticated client identity is checked against the principal permission values specified in the PrincipalPermissionName and PrincipalPermissionRole arguments. This permission demand is done in the same way as enforced by the PrincipalPermission class or attribute, and therefore it also supports ASP.NET Role Providers in addition to WindowsIdentity.

The following example demonstrates how to use a PrincipalPermissionScope activity to authorize an UpdatePurchaseOrder request submitted by a client who must be an Administrator:

image

The PrincipalPermissionScope is exactly the kind of declarative security mechanism that I would expect to be available with WF, however it is implemented in an open source project on CodePlex, not in the Framework itself as a first class supported feature.  We can hope that a future version of WF will include this activity, but in the meantime I would prefer to stay away from it as long as it remains in CTP status.

We finally get to write some code.

So if the bad news is that there is not a nice clean fully supported first class declarative mechanism for implementing authorization, then the good news is that we finally get to write some .Net code in our adapter implementation.  Securing our service will involve the following:

  1. Authentication – Our service will be configured for Integrated Windows Authentication.  Anonymous access will be denied
  2. Authorization – Our service will check if the caller is a member of a local security group named BBECAdapter_Callers and only accept the message if the calling identity is in that group.

#1 (authentication) is implemented by configuring the web.config file.

#2 (authorization) must be implemented in code by a class that inherits from System.ServiceModel.ServiceAuthorizationManager

Enable Windows Authentication

To enable Windows Authentication for the service edit the web.config system.webServer\Security element:

<system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>

    <security>
      <authentication>
        <windowsAuthentication enabled="true" />
        <anonymousAuthentication enabled="false"/>
      </authentication>
    </security>

  </system.webServer>

and in the system.serviceModel\bindings\basicHttpBinding element set the security mode to “TransportCredentialOnly” and the transport client credential type to “Windows”

<bindings>
      <basicHttpBinding>
        <!--This binding is used as the default for all HTTP / Port 80 requests-->
        <binding name="">

          <!--Security mode TransportCredentialOnly requires Windows Authentication-->
          <security mode="TransportCredentialOnly">
            <transport clientCredentialType="Windows" />
          </security>
          
        </binding>
      </basicHttpBinding>
    </bindings>

Implement ServiceAuthorizationManager

While we are editing the web.config file we can go ahead and add a placeholder pointer to our ServiceAuthorizationManager implementation, which will be named ServiceAuthManager.  Add the <serviceAuthorization> behavior in the system.serviceModel\behaviors\serviceBehaviors\behavior element:

   <behaviors>
      <serviceBehaviors>
        <behavior>
          <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
          <serviceMetadata httpGetEnabled="true"/>
          <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="true"/>

          <sqlWorkflowInstanceStore connectionStringName="BBECBusAdapterInstanceStoreConnection" />

          <serviceAuthorization serviceAuthorizationManagerType="BBECBusAdapter.ServiceAuthManager,BBECBusAdapter" />

        </behavior>
      </serviceBehaviors>
    </behaviors>

In my authorization logic I am going to check if the caller identity is in at least one of three local security groups:

  • Local Administrators Group
    • A local administrator of the web server should probably be allowed to do anything
  • The AS_Administrators group that AppFabric installs by default
    • Members of this group are intended to be administrators of all AppFabric apps, so it makes sense that they should be able to make calls to the adatper
  • A local security group named BBECBusAdapter_Callers
    • This is the group that the ESB calling principal should be placed in

Implementing this check is straightforward.  In the constructor of the ServiceAuthorizationManager I will grab the account SIDs for those three local security groups:

Public Class ServiceAuthManager
    Inherits ServiceAuthorizationManager

    'sid for local AS_Administrators group
    Private _appServerAdministratorsWindowsGroupSid As SecurityIdentifier

    'sid for local Administrtors group 
    Private _builtInAdminsSid As SecurityIdentifier

    'Sid for local security group BBECBusAdapter_Callers
    Private _esbAdapterServiceWorkflowUsers As SecurityIdentifier

    Public Sub New()
        MyBase.New()

        'builtin\Administrators
        _builtInAdminsSid = New SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, Nothing)

        'AS_Administrators
        _appServerAdministratorsWindowsGroupSid = GetAppServerAdministratorsWindowsGroup()

        'BBECBusAdapter_Callers
        _esbAdapterServiceWorkflowUsers = GetESBWorkflowServiceUsersWindowsGroup()


    End Sub

and then in CheckAccessCore I will simply call System.Security.WindowsPrincipal.IsInRole() for each of the three SIDs and return true if the incoming caller principal is in any of the groups.  Before returning False in the case where the caller is not in any role I will write a detailed error message to the Windows Application Event log.

 Protected Overrides Function CheckAccessCore(ByVal operationContext As System.ServiceModel.OperationContext) As Boolean

        If operationContext.ServiceSecurityContext.IsAnonymous Then

            RaiseWebHealthEvent("An anonymous request was made to the workflow service. WorkflowServiceAuthorizationManager.CheckAccesCore will return 'false' to deny access.")

            Return False

        End If

        Dim incomingCallerWinIdentity = operationContext.ServiceSecurityContext.WindowsIdentity
        Dim incomingCallerWinPrincipal = New WindowsPrincipal(incomingCallerWinIdentity)

        'ok if the incoming caller is in the Administrators local security group
        If incomingCallerWinPrincipal.IsInRole(_builtInAdminsSid) Then
            Return True
        End If

        'ok if the incoming caller is in the AS_Administrators local security group
        If _appServerAdministratorsWindowsGroupSid IsNot Nothing Then
            If incomingCallerWinPrincipal.IsInRole(_appServerAdministratorsWindowsGroupSid) Then

                Return True

            End If
        End If

        If _esbAdapterServiceWorkflowUsers IsNot Nothing Then
            'ok if the incoming caller is in the BBECBusAdapter_Callers group
            If incomingCallerWinPrincipal.IsInRole(_esbAdapterServiceWorkflowUsers) Then
                Return True

            End If
        End If


        'build detailed error message, log it, and return false
        Dim sb As New Text.StringBuilder
        sb.AppendLine("A request to the workflow service was made by an unauthorized account. WorkflowServiceAuthorizationManager.CheckAccesCore will return 'false' to deny access.")
        sb.AppendLine("caller WindowsIdentity.Name: " & incomingCallerWinIdentity.Name)
        sb.AppendLine("caller WindowsIdentity.AuthType: " & incomingCallerWinIdentity.AuthenticationType)

        RaiseWebHealthEvent(sb.ToString)


        Return False



    End Function

My solution now has 1 new .vb file in it, so I’ve got 2 .vb files and 3 .xaml files and a web.config, so the ratio of declarative to procedural code is still very high.

image

With the ServiceAuthManager now in place, when I run the WCF Test client I get an “Access Denied” message:

image

I am running on a machine with UAC enabled so I my Windows account is not in the local administrators group.  I am also not in the AS_Administrators group and I haven’t yet created a group called BBEC_BusAdapters yet.  The service is correctly denying me access to send a message to it.  In my implementation I used a Web Health event to log this authorization denial to the Windows Application event log.  The event message reads:

“A request to the workflow service was made by an unauthorized account. WorkflowServiceAuthorizationManager.CheckAccesCore will return ‘false’ to deny access.

caller WindowsIdentity.Name: bbdev\PaulG”

 

image

Having this kind of logging in place can be very helpful when trying to troubleshoot problems with the service.

To be able to successfully call the service I need to grant access to my Windows account by placing it in one of the groups that the ServiceAuthManager recognizes.  The PowerShell script below will create a local security group named BBEC_BusAdapters and add my account to it.

#create the local group named BBEC_BusAdapter            
#see http://blogs.technet.com/b/heyscriptingguy/archive/2010/11/24/use-powershell-to-create-local-windows-groups.aspx            
            
$localComputerName="paulgxps16"            
$groupName="BBECBusAdapter_Callers"            
            
$comp=[adsi]"WinNT://$localComputerName"            
$group=$comp.Create("Group",$groupName)            
$group.SetInfo()            
            
$group.description="Users in this group can call the BBECBusAdapter workflow service"            
$group.SetInfo()            
            
#add my Windows account to the group            
#see http://blogs.technet.com/b/heyscriptingguy/archive/2008/03/11/how-can-i-use-windows-powershell-to-add-a-domain-user-to-a-local-group.aspx            
            
$userName=[System.Security.Principal.WindowsIdentity]::GetCurrent().Name            
$userName= $userName -replace ("\\","/")            
            
$userPath = "WinNT://$userName, user"            
            
$group.PSBase.Invoke("Add",$userPath)            

After running that script I have a new local security group named BBECBusAdapter_Callers:

image

After logging out and logging back in my session now has this group token in my identity and I can successfully call the ESB adapter service from the WCFTestclient.exe.

Where are we?

We have now tightened up the security of our ESB adapter service.  We made the following changes in this post:

  1. Modified web.config to require Windows Authentication
  2. Implemented a standard WCF ServiceAuthorizationManager class that checks that the caller is in at least one of a specific set of local security groups.

Web.config Source Code:

Here is the full web.config file for reference:

<configuration>
  <connectionStrings>
    <add
        name="appfx_connection"
        connectionString="applicationName=ESBAdapt;databaseName=BBInfinity_firebird;url=http://paulgxps16/bbappfx_firebird/appfxwebservice.asmx"
      />

    <add
      name="BBECBusAdapterInstanceStoreConnection"
      connectionString="server=paulgxps16;database=BBECBusAdapterInstanceStore;integrated security=true"
      />

  </connectionStrings>

  <system.web>
    <compilation debug="true" strict="false" explicit="true" targetFramework="4.0" />


    <healthMonitoring>
      <eventMappings>

        <add name="ServiceAuth Events" 
             type="BBECBusAdapter.ServiceAuthWebHealthEvent,BBECBusAdapter" />

      </eventMappings>
      <rules>

        <add name="Event Log ServiceAuth Events" 
             eventName="ServiceAuth Events" 
             provider="EventLogProvider" 
             profile="Critical" />

      </rules>
      
    </healthMonitoring>
    
  </system.web>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
          <serviceMetadata httpGetEnabled="true"/>
          <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="true"/>

          <sqlWorkflowInstanceStore connectionStringName="BBECBusAdapterInstanceStoreConnection" />

          <serviceAuthorization serviceAuthorizationManagerType="BBECBusAdapter.ServiceAuthManager,BBECBusAdapter" />

        </behavior>
      </serviceBehaviors>
    </behaviors>


    <bindings>
      <basicHttpBinding>
        <!--This binding is used as the default for all HTTP / Port 80 requests-->
        <binding name="">

          <!--Security mode TransportCredentialOnly requires Windows Authentication-->
          <security mode="TransportCredentialOnly">
            <transport clientCredentialType="Windows" />
          </security>
          
        </binding>
      </basicHttpBinding>
    </bindings>

    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>

    <security>
      <authentication>
        <windowsAuthentication enabled="true" />
        <anonymousAuthentication enabled="false"/>
      </authentication>
    </security>

  </system.webServer>
  <microsoft.applicationServer>
    <monitoring>
      <default enabled="true" connectionStringName="ApplicationServerMonitoringConnectionString" monitoringLevel="Troubleshooting" />
    </monitoring>
    <persistence>
      <instanceStores>
        <add name="BBECBusAdapterStore" provider="SqlPersistenceStoreProvider" connectionStringName="BBECBusAdapterInstanceStoreConnection" />
      </instanceStores>
    </persistence>
  </microsoft.applicationServer>
</configuration>

ServiceAuthManager source code:

and here is the code in our ServiceAuthManager class:

Imports System.ServiceModel
Imports System.Security.Principal
Imports System.Web.Management


Public Class ServiceAuthManager
    Inherits ServiceAuthorizationManager

    'sid for local AS_Administrators group
    Private _appServerAdministratorsWindowsGroupSid As SecurityIdentifier

    'sid for local Administrtors group 
    Private _builtInAdminsSid As SecurityIdentifier

    'Sid for local security group BBECBusAdapter_Callers
    Private _esbAdapterServiceWorkflowUsers As SecurityIdentifier

    Public Sub New()
        MyBase.New()

        'builtin\Administrators
        _builtInAdminsSid = New SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, Nothing)

        'AS_Administrators
        _appServerAdministratorsWindowsGroupSid = GetAppServerAdministratorsWindowsGroup()

        'BBECBusAdapter_Callers
        _esbAdapterServiceWorkflowUsers = GetESBWorkflowServiceUsersWindowsGroup()


    End Sub

    Private Function GetAppServerAdministratorsWindowsGroup() As SecurityIdentifier

        Dim appServerAdmins = GetAppServerAdministratorsWindowsGroupName()

        If Not String.IsNullOrEmpty(appServerAdmins) Then

            Dim account As New NTAccount(appServerAdmins)
            Try

                Return DirectCast(account.Translate(GetType(SecurityIdentifier)), SecurityIdentifier)

            Catch exNotMapped As IdentityNotMappedException
                'just report and eat the error.
                RaiseWebHealthEvent("Unable to map the App Server Administrators group to an NT account")


            End Try
        End If

        Return Nothing

    End Function
    Private Function GetESBWorkflowServiceUsersWindowsGroup() As SecurityIdentifier

        Dim groupName As String
        groupName = System.Configuration.ConfigurationManager.AppSettings.Get("BBECBusAdapter_AuthorizedWindowsGroup")

        If String.IsNullOrEmpty(groupName) Then
            groupName = "BBECBusAdapter_Callers"
        End If


        Dim account As New NTAccount(groupName)
        Dim identifier As SecurityIdentifier = Nothing

        Try

            identifier = DirectCast(account.Translate(GetType(SecurityIdentifier)), SecurityIdentifier)

        Catch exNotMapped As IdentityNotMappedException
            Dim msg As String = String.Format("Unable to map account {0} specified in the BBECBusAdapter_AuthorizedWindowsGroup app setting value. Error={1}", groupName, exNotMapped.Message)

            RaiseWebHealthEvent(msg)
            'just report and eat the error.

        End Try

        Return identifier


    End Function

    Private Function GetAppServerAdministratorsWindowsGroupName() As String

        'Use the WorkflowInstanceManagementElement setting for AuthorizedWindowsGroup.

        Dim behaviorsSection = TryCast(System.Configuration.ConfigurationManager.GetSection("system.serviceModel/behaviors"), System.ServiceModel.Configuration.BehaviorsSection)

        If behaviorsSection IsNot Nothing Then

            If behaviorsSection.ServiceBehaviors IsNot Nothing Then

                For Each section In behaviorsSection.ServiceBehaviors

                    Dim serviceBehaviorElm = TryCast(section, System.ServiceModel.Configuration.ServiceBehaviorElement)

                    If serviceBehaviorElm IsNot Nothing Then

                        For i = 0 To serviceBehaviorElm.Count
                            Dim item = serviceBehaviorElm.Item(i)

                            Dim wfManInstance = TryCast(item, System.ServiceModel.Activities.Configuration.WorkflowInstanceManagementElement)
                            If wfManInstance IsNot Nothing Then

                                ' RaiseWebHealthEvent("ServiceAuthManager windows group = " & wfManInstance.AuthorizedWindowsGroup)

                                Return wfManInstance.AuthorizedWindowsGroup
                            End If
                        Next


                    End If


                Next
            End If
        End If


        Return "AS_Administrators"


    End Function



    Protected Overrides Function CheckAccessCore(ByVal operationContext As System.ServiceModel.OperationContext) As Boolean

        If operationContext.ServiceSecurityContext.IsAnonymous Then

            RaiseWebHealthEvent("An anonymous request was made to the workflow service. WorkflowServiceAuthorizationManager.CheckAccesCore will return 'false' to deny access.")

            Return False

        End If

        Dim incomingCallerWinIdentity = operationContext.ServiceSecurityContext.WindowsIdentity
        Dim incomingCallerWinPrincipal = New WindowsPrincipal(incomingCallerWinIdentity)

        'ok if the incoming caller is in the Administrators local security group
        If incomingCallerWinPrincipal.IsInRole(_builtInAdminsSid) Then
            Return True
        End If

        'ok if the incoming caller is in the AS_Administrators local security group
        If _appServerAdministratorsWindowsGroupSid IsNot Nothing Then
            If incomingCallerWinPrincipal.IsInRole(_appServerAdministratorsWindowsGroupSid) Then

                Return True

            End If
        End If

        If _esbAdapterServiceWorkflowUsers IsNot Nothing Then
            'ok if the incoming caller is in the BBECBusAdapter_Callers group
            If incomingCallerWinPrincipal.IsInRole(_esbAdapterServiceWorkflowUsers) Then
                Return True

            End If
        End If


        'build detailed error message, log it, and return false
        Dim sb As New Text.StringBuilder
        sb.AppendLine("A request to the workflow service was made by an unauthorized account. WorkflowServiceAuthorizationManager.CheckAccesCore will return 'false' to deny access.")
        sb.AppendLine("caller WindowsIdentity.Name: " & incomingCallerWinIdentity.Name)
        sb.AppendLine("caller WindowsIdentity.AuthType: " & incomingCallerWinIdentity.AuthenticationType)

        RaiseWebHealthEvent(sb.ToString)


        Return False



    End Function


    Private Sub RaiseWebHealthEvent(msg As String)

        Dim evt As New ServiceAuthWebHealthEvent(msg, Me)
        Web.Management.WebBaseEvent.Raise(evt)

    End Sub


End Class


Public Class ServiceAuthWebHealthEvent
    Inherits System.Web.Management.WebBaseEvent

    Public Const SERVICEAUTH_EVENT_BASECODE As Integer = WebEventCodes.WebExtendedBase + 2400000

    Public Sub New(msg As String, source As Object)
        MyBase.New(msg, source, SERVICEAUTH_EVENT_BASECODE)
    End Sub

End Class

Posted in Architecture, workflow | Tagged , | Leave a comment

Adapt Infinity Web Services for ESB Integration–Part 5

This post is part 5 in a series on integrating Blackbaud Infinity based applications such as Blackbaud Enterprise CRM (BBEC) into an Enterprise Service Bus (ESB) architecture using a WCF Workflow service hosted in Windows Server AppFabric.

  1. Part 1 – Intro
  2. Part 2 – ESB Message pass-through to BBEC workflow activity
  3. Part 3 – ESB Message translation to BBEC IDs and operations
  4. Part 4 – Deploying to Windows Server AppFabric on IIS

When we left off in Part 4 we had a “production” adapter service up and running on IIS using AppFabric hosting services.  We saw that by hosting in Windows Server AppFabric we were able to take advantage of the built in logging capabilities of the AppFabric dashboard to monitor and troubleshoot the service.  We were also quite proud of how easy it was to create this service using almost no code in a mostly declarative implementation.

Not Fail – but not total victory – yet…

However, if I was grading the current implementation of this ESB-to-Infinity adapter service on an academic scale I would probably give it a B-, or maybe even a C+, for the following reasons:

  • It isn’t very “Bus-y”, or “Bus-ish”, or “Bus-like” because the ESB message is handled in a synchronous fashion.
  • The service logic is not very robust in the face of errors.  If an error occurs while processing the message we will be depending on the ESB to care that we had an  error and the ESB will need to be sure to send the message again.
  • If for any reason the Infinity web service is down the ESB message will be lost unless the ESB knows to resend it when the web service is back online.

Referring back to the Enterprise Application Integration pattern that inspired this series – the Messaging patern – it is clear that the synchronous nature of our service violates a key aspect of the pattern, which states (emphasis mine):

Use Messaging to transfer packets of data frequently, immediately, reliably, and asynchronously, using customizable formats.

Asynchronous messaging is fundamentally a pragmatic reaction to the problems of distributed systems. Sending a message does not require both systems to be up and ready at the same time. Furthermore, thinking about the communication in an asynchronous manner forces developers to recognize that working with a remote application is slower, which encourages design of components with high cohesion (lots of work locally) and low adhesion (selective work remotely).

Even the diagram reinforces this point with the arrows from the bus to Application B and Application C going in one direction:

The bus sends the message to the apps.  It doesn’t need or want a reply.  That is the fundamental nature of a bus architecture.

Also note the point about not needing both systems to be online at the same time.  It should be pretty clear that in the current implementation of our adapter there is a strong assumption that the Infinity web service is up and running when the ESB message arrives.  While that is usually a pretty good assumption due to the ability to load balance Infinity web servers for redundancy and leverage SQL Server high availability features such as failover clustering, in the real world there can always be issues that cause a system to be temporarily unavailable even if only for a very short time.  To borrow a phrase from our inspirational cloud friends at Netflix – “Chaos Monkey happens”.

In those rare but possible cases where the system is temporarily down we would prefer not to just drop all messages that get sent our way via the bus.

AppFabric and Workflow Foundation to the rescue

Fortunately there are features built into AppFabric and Workflow Foundation that will allow us to address the deficiencies in the current service due to lack of asynchronous processing.  With only a few small tweaks to the service logic we can turn this implementation into an A+ adapter that processes messages asynchronously.  This will let the ESB implementation fire-and-forget a message to our adapter with confidence that once the message is delivered our service will “own” it and make sure the operation is carried out in BBEC even in the face of transient runtime errors or lack of availability of the BBEC service at the time the message arrives at the adapter.

And we are going to do this without writing any code, of course.

To implement the asynchronous version of our service we are going to take advantage of the SQL Persistence features of AppFabric and Workflow Foundation.  The first thing I need to do is configure a SQL Server database to serve as the “workflow instance store” for my service.  There is a “Configure AppFabric” GUI tool that can be used for this, but my tool of choice will instead be the AppFabric Powershell cmdlets.  We need to do 2 things:

  1. Create the workflow persistence store SQL database
  2. Modify the web.config to point to the persistence store
    1. Add a connection string for the store
    2. Add the info for the AppFabric dashboard
    3. Add the WCF workflow behavior

The following PowerShell script will take care of this on my development machine named PAULGXPS16:

image

#Import the AppFabric cmdlets            
Import-Module ApplicationServer            
            
#Import the IIS admin cmdlets            
Import-Module WebAdministration            
            
#create an AppFabric SQL persistence database            
Initialize-ASPersistenceSqlDatabase -Server paulgxps16 -Database BBECBusAdapterInstanceStore -Readers "paulgxps16\AS_Observers" `
-Admins "paulgxps16\AS_Administrators" -Users "BUILTIN\IIS_IUSRS"  -Verbose            
            
            
#The rest of the script will alter the web.config of the BBECBusAdapter web site            
            
#set script context location to the IIS: provdier            
cd 'IIS:\Sites\Default Web Site\BBECBusAdapter'            
            
#make a backup just in case            
copy .\Web.config .\Web.config.backup1            
            
#add a new connection string to point to the AppFabric persistence SQL database            
Add-WebConfiguration -filter /connectionStrings `
-Value @{name="BBECBusAdapterInstanceStoreConnection"; connectionString="server=paulgxps16;database=BBECBusAdapterInstanceStore;integrated security=true"}             
            
#add the instance store info for AppFabric dashboard            
Add-ASAppSqlInstanceStore -Name BBECBusAdapterStore -SiteName "Default Web Site" -VirtualPath BBECBusAdapter -ConnectionStringName BBECBusAdapterInstanceStoreConnection            
            
#add the WCF sql persistence behavior to route workflow persistence to the database this script created            
Set-ASAppSqlServicePersistence -SiteName "Default Web Site" -VirtualPath BBECBusAdapter -ConnectionStringName BBECBusAdapterInstanceStoreConnection            

This script uses the following cmdlets:

The script ends up making the following changes to web.config:

adds a new connection string named BBECBusAdapterInstanceStoreConnection :

  <connectionStrings>
    <add 
        name="appfx_connection"
        connectionString="applicationName=ESBAdapt;databaseName=BBInfinity_firebird;url=http://paulgxps16/bbappfx_firebird/appfxwebservice.asmx"
      />

    <add 
      name="BBECBusAdapterInstanceStoreConnection"
      connectionString="server=paulgxps16;database=BBECBusAdapterInstanceStore;integrated security=true"
      />


adds the <sqlWorkflowInstanceStore> behavior pointing to the connection string

 <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
          <serviceMetadata httpGetEnabled="true"/>
          <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="true"/>

          <sqlWorkflowInstanceStore connectionStringName="BBECBusAdapterInstanceStoreConnection" />

        </behavior>
      </serviceBehaviors>
    </behaviors>

adds the pointer to the instance store to the <microsoft.ApplicationServer> section which is used by the AppFabric dashboard:

  <microsoft.applicationServer>
    <monitoring>
      <default enabled="true" connectionStringName="ApplicationServerMonitoringConnectionString" monitoringLevel="Troubleshooting" />
    </monitoring>
    <persistence>
      <instanceStores>
        <add name="BBECBusAdapterStore" provider="SqlPersistenceStoreProvider" connectionStringName="BBECBusAdapterInstanceStoreConnection" />
      </instanceStores>
    </persistence>
  </microsoft.applicationServer>

With those changes to my web.config I am ready to tweak the workflow logic to take advantage of the persistence store I just created.  The first thing I do is to move the WebServiceScope and its nested ProcessNewConstitMessage activity after the SendResponse activity:

image

And since this service is now going to support a fire-and-forget message where the ESB doesn’t know or care about how we carry out the work I am going to make two changes to the SendResponse activity.  First, I am going to change it so that rather than returning the ID of the newly added Constituent I will just return a simple message indicating that the message was received.  In the property grid for the SendResponse message I edit the Content property and change the message data to be the literal string “ok”:

image

I also check the PersistBeforeSend property to indicate that I want the current state of the service to be persisted before sending a reply.  This will make sure that we only return that “ok” message after we have successfully stored the data in the incoming message in our instance store SQL server database that we just configured.

image

To make my service even more reliable I am going to insert a few Persist activities in my service logic at locations that will be considered “checkpoints” for done work.  This will let the workflow logic pick up where it left off in the event of an error.  The most logical place to put these Persist checkpoints is after doing some work in the logic that we cannot or do not want to re-do.  So I will place a Persist activity after each Infinity web service activity that does a write-operation to the Infinity web service.  I won’t bother to add Persist checkpoints after the activities that do the lookups because if I make a call to translate a Title Code, for example, and then an error occurs it is not really a problem if I later repeat that activity in the workflow once it resumes.  Since I have 3 write-operations in my workflow I add 3 persist activities:

image

Use AppFabric to fix and resume from errors

To demonstrate how our service is now both asynchronous and more robust I am going to intentionally introduce an error and show how I can use the AppFabric dashboard to diagnose the error, correct it, and then resume the workflow from the point where the error occurred.  You will recall that the activity that translates an incoming two letter Address State abbreviation to the corresponding BBEC State ID guid uses a hard-coded value in our workflow service of “United States”.  To simulate an error I am going to edit this Country in BBEC and change it to “United States of America” which will cause the lookup to fail.

image

Let’s see what happens now when I call into the service from the WCF Test client with the logic doomed to fail due to the unexpected name of the country.  Right away I get back an “ok” – indicating that the message was received and no errors occurred.  If this was the ESB making the call then the ESB knows it can move on – the message has been delivered, mission accomplished.

image

But we know it is not ok.  If I search for the test record “Randal Retry” I don’t find it, and a quick check in the AppFabric dashboard confirms that there is a problem:

image

I can see that there is currently one workflow instance suspended.

I can see that there was 1 WCF call completed (the one that returned “ok” with no errors).

I can see that there was 1 workflow instance started and 1 workflow instance failure.

  Drilling in I can see the error:

image

Here once again AppFabric is clearly showing me the error, which in this case is very clear with the message “No country exists with the given description”.  I can of course drill in further and look at the tracked events to confirm that it was the CountryStateGetId activity that caused the error:

image

Get ready to have your mind blown!

Ok, this is where it gets really cool.  Note that the status of the workflow is reported as “Suspended”.  This is because I am using the default AppFabric configuration regarding error handling in a workflow.  The “Abandon and Suspend” option tells AppFabric to freeze the state of the workflow until further notice, allowing us to possibly correct the error and then pick up where the logic left off.

image

While suspended the workflow execution is logically frozen at the last Persist checkpoint, which in our case would be just after receiving and storing the incoming message.  The state of the execution logic, including the values of any workflow arguments or variables in scope, is stored safely in our SQL Server database.  The workflow can remain in this state indefinitely, surviving restarts of the web service or reboots of the web server.

In this scenario it is easy to fix the underlying problem, I just edit the Country in BBEC back to “United States”.  After I do that I can go into the AppFabric dashboard and resume the workflow that is suspended:

image

After resuming, the workflow picks up where it left off, and this time it has no trouble getting the ID of the Address State sent in the ESB message.  The AppFabric dashboard shows that there is no longer an instance suspended and the workflow did indeed complete:

image

And a quick search for our test record confirms that the record went into the BBEC application database with the information provided by the ESB:

image

What did we just do?

Let’s review what we just did:

  • We configured our AppFabric app to use SQL workflow persistence
  • We tweaked our workflow service logic (with no coding Smile) to perform the ESB message handling using asynchronous processing with persistence checkpoints
  • We introduced an error that caused the service to fail – BUT – we were able to identify the cause of the failure, correct it, and then resume the workflow logic from the point of the failure.

Our adapter is still 100% declarative visual workflow.

Our adapter is still built using existing BBEC workflow activities.

Our adapter is still a standalone IIS application requiring no bits deployed on the BBEC web server, communicating exclusively over HTTP(s) via the Infinity web services

And now our adapter is implemented using asynchronous message handling more true to the ESB Messaging pattern with the ability to gracefully handle some class of errors and recover from them, and to report on details of any other errors that may not have a graceful resolution.

And with that I am prepared to upgrade the grade for our ESB adapter service to a well earned “A”.

Posted in Architecture, workflow | Tagged , , | 1 Comment

Adapt Infinity Web Services for ESB Integration-Part 4

This post is part 4 in a series on integrating Blackbaud Infinity based applications such as Blackbaud Enterprise CRM (BBEC) into an Enterprise Service Bus architecture using a WCF Workflow service hosted in Windows Server AppFabric.

Part 1 introduced the concept of using a “middle man” service as an adapter sitting between the Infinity web server and the service bus.

image

Part 2 showed how to use an out-of-the-box Blackbaud workflow activity to invoke the BBEC API that creates a new Constituent record.

image

Part 3 showed how to use other out-of-the-box Blackbaud workflow activities to transform the incoming ESB message and invoke additional BBEC APIs needed to carry out the operation called for by the content of the message.

In this post I will move the application from the development environment into production where our workflow service will be hosted as an IIS AppFabric application.

Prerequisites

I am going to assume the following has already been installed on your Windows Server 2008 R2 (or later) or Windows 7 Pro (or later) IIS server:

  • .Net Framework 4.0 Full
  • Windows Server AppFabric

I am also going to assume you performed the default AppFabric configuration for the Monitoring store and Persistence Store.  See this link for information on installing and configuring AppFabric:

http://msdn.microsoft.com/en-us/library/ff637707.aspx

Also, make sure that SQL Agent is running on the SQL Server where the Monitoring database is located.  The AppFabric dashboard depends on a few SQL Agent jobs to move the data from tables where events are captured to tables that are used for reporting.

Deploy

There are a number of ways to get bits from a dev solution to an IIS server, starting with the simplest technique of an xcopy deploy of the \bin folder and any endpoint resources such as our .xamlx service.  Xcopy deploy is usually clean and easy, but in the latest editions of .Net/IIS/Visual Studio Microsoft has invested in having strong support for the “Web Deploy” tool, so using that tool is the technique I am going to demonstrate here in the interest of following the most contemporary best practice.

The deployment properties are configured on the Package/Publish Web tab of the project properties:

image

Once I have the location and IIS app name configured to my liking I can choose “Build Deployment Package” from the VS Project menu:

image

On the target server if you don’t already have the latest version of the Web Deployment Tool then you can get it from the Web Platform Installer, which you can access from within the IIS Manager application:

image

image

If you have the Web Deploy tool installed then in the IIS Manager you can right-click on the Site node and choose Deploy | Import Application:

image

On the dialog that appears click Next and you will get a chance to edit the appfx connection string that we used to connect to our development Infinity instance.  You can change the connection string to point to your production server.

image

When the wizard completes the service deployment is finished.  You should be able to hit http://{server}/BBECBusAdapter/AddNewConstit.xamlx in a browser and get the auto-generated service documentation page:

image

You can now fire up the WCFTestClient.exe that we have been using and use that url to send a test message just like we have been doing all along against our development server url:

image

image

(Be sure to use a dummy address /name if this is going against a production system! In reality this confirmation step might be something you skip when you go to do a true deployment, we are just doing this here to confirm that our initial deployment is working, and most likely we would do that first against a Test or Staging server where adding a dummy record would be ok.  )

When I try this the first time I get an error in the WCF client tester with the following message:

Access Denied.
The user ‘IIS APPPOOL\DefaultAppPool’ is not an application user in the BBInfinity_firebird database.

image

Fortunately, this is an AppFabric application, so to get more information about this error I can go to the AppFabric Dashboard in IIS Manager:

image

image

The Dashboard shows that in the past minute I have had 1 error.  I can drill into this error and see if there are any clues to the problem:

image

Immediately I am clued into the problem.  I see that the workflow service did receive the incoming message, and the workflow execution got all the way into the WebServiceScope activity.  There is an Error record in the Dashboard, and if I select it I see that the error was generated in the TitleGetId activity which is inside the AddNewConstit Sequence activity.  Clicking on the Errors tab I confirm that the error reported here on the execution of the TitleGetId activity is the same as the one in my error message in the WCF test client:

image

If I was still not sure about exactly where in my workflow this error was being generated I could turn up the level of detail in the error reporting.  I can configure the AppFabric monitoring to use the more verbose “Troubleshooting” level:

image

And now if I reproduce the error I get a greater level of detail:

image

I can see in this more detailed trace that the If activity named “If Title not blank translate it” started executing, and then it scheduled it’s child activity named “TitleGetId” and those were the last events before the error occurred.  Looking back at the workflow definition it is pretty clear where this error occurred:

image

Putting aside the actual cause of the error, what is important to take away from this discussion is that by using AppFabric along with a Workflow Service I get a great head start in troubleshooting my adapter logic.  I didn’t have to build any kind of logging capability into my implementation and yet here I am getting detailed logging of every step in the workflow leading up to my error, and then detailed information about the error as well.  In this simple test case I am just dealing with a one-off error, but you can imagine how useful this would be for some kind of transient error in a real world deployment where 99% of the ESB messages have no problems but there are a handful of errors that need to be tracked down.  The AppFabric Dashboard provides a great way to immediately focus on those errors and begin the troubleshooting process.

To reiterate the point, allow me to quote from this great transcript from Scott Hanselman’s podcast interview with Karandeep Anand from Microsoft about AppFabric:

Scott Hanselman: And we always used to joke
that we wanted to do refactoring via subtraction,
meaning that whenever Microsoft would come out
with something that would do what we needed to do
already then we could just throw away something that
we wrote and start worrying about the business
problem.

Karandeep Anand: That’s true and for any
professional developer to actually go write a lot of that
logic every time, it’s just time consuming and takes
away a big chunk of your productivity towards these
basic repetitive tasks. The interesting add on to that
is it’s not just at the development time that you’re
paying this huge cost, what you’re also doing is once
you have a successful application running, then you
need to go help your operations or your monitoring
teams or whoever’s really managing this app to start
building a lot of instrumentation, a lot of code to spit
out debugging information, tracing information,
logging stuff, and every time you build an app, you
have to that yourself because there is no consistent
framework on an automated thing to give basic
monitoring and troubleshooting information out to your
ops team and that’s one of the other things that we’ve
done really well with .NET 4.0 and what AppFabric
enabled on top of that is once you have an app
running, we automatically are able to dial up and dial
down the troubleshooting levels so you can get basic
health information, other information and all the way
up until full troubleshooting logs just by turning on a
few knobs. So not only did you save a lot of time
during development to not write basic lifecycle
management, you don’t have to invest as much in
writing the basic monitoring and troubleshooting
plumbing in your code anymore because the way
.NET 4.0 is wired and what AppFabric enables on top
of it can help you spread out and reduce your
overhead of writing the monitoring and
troubleshooting code as well.

Scott Hanselman: So that, I see, brings up two
interesting points I think, first is, I always used to find
myself writing logging and instrumentation code but
I’d end up putting it in the database or putting it in a
log file and even now, years later after I’ve left this
banking company, you’ll find OpSkies, Operation
Skies poking around inside of a half dozen log files
trying to figure out what went wrong when they really
wished that they could be inside of IIS manager or
inside of some MMC snap in.

The point about how logging is not just something that impacts developer productivity but also the Ops team productivity is a really good one that is worth taking to heart.  It is very easy to forget that once this adapter thing goes live there is going to be a separate team of people responsible for keeping it up and running and dealing with any issues that occur in production.  At my company we call this team the “Service Delivery Organization”.  Since AppFabric is a standard component of Windows Server it is fully supported by Microsoft and we can expect that the standard Microsoft administration training and certification courses will start to cover the logging features.

Fixing the error

Believe it or not the error we are troubleshooting here is kind of a good thing.  With the clue that the error is happening on the first call we make to the AppFx web service I realize that when I was using the ASP.Net development server I was connecting as my own Windows account to the BBEC API because the ASP.Net dev server runs as a user process launched in my session.  When we deploy to IIS the calls to BBEC will now be made using the account identity of the worker process that is configured for this IIS application, so we need to grant the appropriate permissions to that identity in order for this service to succeed when it calls the API.  Best practice would be to grant the minimal set of permissions, which is this case would be the permissions to call the Individual Spouse Business Add Form, the Edit Lookup Id form, and the Add Phone Form.  Also we will need to grant rights to add new Title and Phone Type code table entries.

I add a System Role to BBEC named “ESBAdapter Service”

image

And then I grant rights to the features used by this workflow service:

image

and the code tables :

image

And then I add the identity of the worker process to this role:

image

It just got real

Having taken care of the security I try the WCF test client again, and this time it goes through no problem and I get the guid ID of the newly added Constituent record.

image

Flipping back to the AppFabric Dashboard, I can see that my call is logged as being successful:

image

and since I still had the logging level at “Troubleshooting” I can drill in and see some deep information about the activities that executed:

image

For example, the IndividualSpouseBusinessAddForm activity emits a custom tracking record that includes the elapsed time in milliseconds that it took to make the call to the corresponding BBEC web service API.  In fact, all of the Infinity activities that make web service calls like this emit such a custom tracking record.  Imagine you are trying to troubleshoot reports that the ESB adapter is running slow.  By turning the logging level up to the Troubleshooting level you can dig in and see exactly how long calls are taking from the adapter to the Infinity web service.  This could really give you a head start in trying to identify where the bottleneck is occurring.

Where are we?

In this post we took our fully developed ESB Adapter and deployed it to IIS AppFabric.

  • We did everything using the built in VS2010 / IIS / AppFabric GUI tools
  • We used the “Import Application” wizard and just had to change a connection string when prompted
  • We used the “Configure WCF/WF Services” wizard to turn up the level of logging detail.
  • We used the IIS AppFabric dashboard to help troubleshoot an error and track it down to the exact activity in the workflow

On the development side we wrote no code really – everything was declarative.

On the deployment side we wrote no code – everything had a wizard or GUI.

If you think about it, we didn’t really do much at all Smile, most everything was done using some kind of point-click mouse driven interaction, from development to deployment.

And yet we have a fully functioning ESB Adapter up and running in IIS, and the guts of it are built on the rock solid scalable foundation of Windows Server, IIS, .Net Framework 4, WCF, WF, and of course the Infinity web service API.

To paraphrase a line from Bad Boys II, “Adapter just got real!”

image

But wait, there’s more…

Even though we have a deployed service up and running there are a few more loose ends we need to take care of.  The next post will continue to refine this ESB adapter.  Stay tuned for part 5…

Posted in Architecture, workflow | Tagged , , | 2 Comments

Adapt Infinity Web Services for ESB Integration–Part 3

This post is part 3 in a series on integrating Blackbaud Infinity based applications such as Blackbaud Enterprise CRM (BBEC) into an Enterprise Service Bus architecture using a WCF Workflow service hosted in Windows Server AppFabric.

Part 1 introduced the concept of using a “middle man” service as an adapter sitting between the Infinity web server and the service bus.

Part 2 showed how to use an out-of-the-box Blackbaud workflow activity to invoke the BBEC API that creates a new Constituent record.

In this post we will start tackling more of the complexity in the translation of the incoming ESB message.  When we left off in Part 2 we were successfully handling the simple string text fields that mapped nicely from the ESB message to the existing BBEC API, so we can scratch those fields off of our list that need to be mapped:

  • First Name
  • Last Name
  • Title (a text string, such as “Dr.”, “Mr.”, “Mrs.”
  • Gender (a single character, one of M,F,O (for other)
  • Organizational Identifier (unique Id defined by the organization, used across systems)
  • Home Phone number
  • Work Phone number
  • Address – Street address
  • Address – City
  • Address – State (a 2 letter string abbreviation)
  • Address – Zip code

We have 6 more fields that we need to deal with in our incoming message, so let’s get started with the first on one the list, the Title code.

There’s an activity for that

The IndividualSpouseBusinessAddForm has a field where the Title code can be specified, but like all Infinity form APIs it expects to be supplied with the record ID guid of the given code table entry.  In our scenario the ESB will be sending a message with just a text string such as “Mr.” or “Mrs.” or “Dr.” so we need to do a lookup to obtain the code table entry ID for whatever is in the Title field of the ESB message.  Fortunately there is an activity for that, located in the Blackbaud.AppFx.Constituent.Catalog.Activities assembly that we are already referencing in our project.  Here is the information you need to add this activity to your workflow toolbox:

Assembly: Blackbaud.AppFx.Constituent.Catalog.Activities

Namespace: Blackbaud.AppFx.Constituent.Catalog.Activities.CodeTables

Activity Class: TitleGetId

According to the Object Browser, this activity “Returns the ID for an entry in the Title code table”, which is exactly what we need.

image

image

Once I have added this activity to my toolbox I can drag it into my sequence activity in the ProcessNewConstitMessage.xaml activity before the form activity:

image

Note that the activity displays some metadata about the Title code table that it does the lookup for:

Code Table Name: Title

Database table: TITLECODE

Description: Returns the Id for the entry with the given Description in a code table.

The little red exclamation point icon indicates that there is a validation error with the activity that I need to take care of.  Hovering over it reveals that there is a required argument I need to supply, namely the code table value that I need the Id for.  I select the TitleGetId activity and then in the Property Grid I can assign the required argument.  This activity has the following arguments:

  • In, Required – CodeTableEntryDescription
    • This is the text value that we want the ID for
  • In –AddEntryIfNotFound
    • If true then if the lookup fails to find a matching existing entry the value of CodeTableEntryDescription will be added as a new entry
  • Out – Result
    • This is the Id value for the code table entry with the given description
  • Out – CodeTableEntryAdded
    • When the AddEntryIfNotFound property is true this argument will indicate if the entry already existed or if it was added on the fly

image

I assign the CodeTableEntryDescription property the newConstitMsg.Title field and I check the AddEntryIfNotFound property to true.  (To keep things simple for now I am going to assume that when the ESB says “this is the title” we should carry that out without question – so we will add the value supplied by the ESB if that value does not already exist as a code table entry in BBEC.  Our willingness to assume the data trustworthiness of the ESB is something we will explore in a future post).  For this scenario I don’t care to know if the entry already existed or if it was added on the fly so I won’t bother to map the CodeTableAdded argument.  But I do care about the result since that is exactly what I am after with this activity, so I map the result out argument to a variable named “titleCodeId” of type System.Guid defined in the root Sequence activity scope of the ProcessNewConstitMessage activity.

image

The last step is to get the value in the titleCodeId variable into the form.  I select the form activity in the designer and go back to the property grid and now I can fill in TITLECODEID with the titleCodeId variable:

image

A quick F5 visit to the WCF Test client and some sample data …

image

allows us to hit a breakpoint in the workflow  and examine the value of the titleCodeId variable before the form activity is invoked:

image

and then letting the workflow exectution continue…

image

confirms that we can scratch Title off of our list!

Before moving on though I want to add one small embellishment to the logic to handle the case where the ESB sends a message with a blank Title field.  Title is not required in BBEC and so I want to treat it as optional.  When the ESB sends a message with a blank Title I don’t need to to the lookup to translate and get the Id, so I will nest the TitleGetId activity inside a native WF4 “If” activity that tests for a blank Title in the incoming message:

image

Only 5 fields left to map.

Gender Code

The next field to tackle is the Gender code.  According to this scenario, the incoming message will supply a single character to represent the gender of the Constituent:

M = male

F= female

O = other (maps to “unknown” in BBEC)

On the IndividualSpouseBusinessAddForm activity the GENDERCODE field expects a value of type IndividualSpouseBusinessAddFormData.GENDERCODEVALUES from the Blackbaud.AppFx.Constituent.Catalog.WebApiClient.AddForms.Constituent namespace.

Here is that enum presented in the VS Object Browser window:

image

We need to translate the single character M,F,O to an instance of that enum. 

Side bar:

Note that the GENDERCODEVALUES enum is actually a hidden type.  In the original release of the Infinity workflow activities all value list fields such as the Gender field were defined in terms of an enum type nested in another class.  Unfortunately, the workflow designer has problems with these nested enums, so a future release of the Infinity workflow library will hide those existing nested enum types and create new enum types that are not nested so that everything plays nice with the workflow designer.  In the meantime we have to implement a quirky workaround that is described here.

First I create a variable named “genderCodeValue” to hold t he translation value.  To pick the variable type I use the “browse a .Net type” dialog and search on GENDERCODE:

image

I locate the enum type that is associated with the IndividualSpouseBusinessAddForm in the IndividualSpouseBusinessAddFormEnums namespace.

As of the Q4 2011 2.92 release I also need to create a dummy variable that references a type in the Blackbaud.AppFx.WebApi assembly as a workaround for the workflow designer problem described in the sidebar section above.

image

I can then assign the genderCodeValue variable to the forms GENDERCODE argument using a CType cast to work around the quirk with the workflow designer and nested enums in the current code base.  The expression editor for the GENDERCODE property makes it a little easier to type in the full type cast expression:

image

Now all I have to do is populate that variable.  The native WF4 Switch activity works well for this because it works like a Select..Case clause in procedural code.  I use newConstitMsg.Gender as the expression to switch on, and for the default case I will assign the genderCodeValue the literal “GENDERCODE.Unknown”

image

I add a case for M:

image

and lastly a case for F:

image

This Switch is equivalent to the following pseudo code:

Select case newConstitMsg.Gender

Case “M”

genderCodeValue=GENDERCODE.Male

Case “F”

genderCodeValue=GENDERCODE.Female

Case Else

genderCodeValue=GENDERCODE.Unknown

End Select

And with that in place we are now mapping the incoming Gender indicator to what the BBEC API expects.  Only 4 fields left to map:

  • First Name
  • Last Name
  • Title (a text string, such as “Dr.”, “Mr.”, “Mrs.”
  • Gender (a single character, one of M,F,O (for other)
  • Organizational Identifier (unique Id defined by the organization, used across systems)
  • Home Phone number
  • Work Phone number
  • Address – Street address
  • Address – City
  • Address – State (a 2 letter string abbreviation)
  • Address – Zip code

Our workflow Sequence now has 3 main activities and looks like this:

image

Address State Code

Next I want to translate the incoming Address State field that the ESB will be sending in the form of a 2 letter abbreviation.  For this I will use the built-in Blackbaud Infinity workflow activity named CountryStateGetId which is in the Blackbaud.AppFx.Workflow.CatalogActivities assembly.  This activity will be in the workflow toolbox in the “Blackbaud Web Services” tab:

image

This activity takes as input a text string name of a country and a text string name of a state and returns the guid ID of the country and the guid id of the state.  The return type is CountryStateGetIdReply from the Blackbaud.AppFx.WebApi.ServiceProxy namespace, so I make a variable named addressStateGetIdReply of type CountryStateGetIdReply:

image

Then I add the CountryStateGetId activity to the workflow after the Gender translation switch activity:

image

Then I configure it in the Property Grid.  I am going to assume the country will be “United States” so I hard code that literal string.  According to the ESB message specification in this scenario the State will be coming in as a 2 letter abbreviation, so I set the “UseStateAbbreviation” property to True:

 

image

and I map the result to my addressStateGetIdReply variable.

I can then select the IndividualSpouseBusinessAddForm activity and set the ADDRESS_STATEID = addressStateGetIdReply.StateID and the ADDRESS.COUNTRYID to addressStateGetIdReply.CountryID

image

And with that in place I can scratch off State from my TODO list of fields, leaving just 3 left to translate.

  • First Name
  • Last Name
  • Title (a text string, such as “Dr.”, “Mr.”, “Mrs.”
  • Gender (a single character, one of M,F,O (for other)
  • Organizational Identifier (unique Id defined by the organization, used across systems)
  • Home Phone number
  • Work Phone number
  • Address – Street address
  • Address – City
  • Address – State (a 2 letter string abbreviation)
  • Address – Zip code

My ProcessNewConstitMessage.xaml now looks like this (collapsing all the activities for clarity)

image

Just 4 activities in a Sequence and 4 variables.

Home / Work Phone Number

The API we are using to add our Constituent record accepts a single phone number along with a code table entry indicating the type of this primary phone.  However, the ESB message is sending two specific phone numbers, one for Home and one for Work.  So we have to make a judgment call on which one to map to the BBEC notion of a “Primary” phone.  In a real world scenario I would consult with the client and then a possible decision would be to implement the following:

If the ESB message Home phone is not blank, it will be the Primary phone.

Otherwise, the Work phone will be the primary phone.

To model this in the workflow I am going to create 4 variables:

primaryPhoneType

primaryPhoneNumber

secondaryPhoneType

secondaryPhoneNumber

and to keep things organized I am going to implement the logic to parse the ESB message in a separate .xaml declarative workflow activity named DeterminePrimaryPhone.xaml

image

This helper activity will take 2 in arguments, homePhoneNumber and workPhoneNumber:

image

and it will have 4 out arguments:

primaryPhoneNumber

primaryPhoneTypeId

secondaryPhoneNumber

secondaryPhoneTypeId

The first thing I will do in this helper activity is go ahead and get the code table id for the “Home” and “Work” phone types.  I add the PhoneTypeGetId activity to my toolbox:

image

image

Getting the Home and Work phone types is basically the same as getting the Title code back in the main workflow. In fact, both TitleGetId and PhoneTypeGetId inherit from the same base class and the only difference is that one looks up an entry in the dbo.TITLECODE table and one looks up an entry in the dbo.PHONETYPECODE table.  So I won’t walk through all the steps to do the mapping of those 2 literal entries.  The end result is that I create 2 variables and use one activity to get the Home id and one to get the Work id and map those to the variables:

image

image

image

Now I can implement the required logic, which was:

If the Home phone is not blank, it will be the Primary phone.

Otherwise, the Work phone will be the primary phone.

This is easily implemented using an If activity:

image

This block is just a few nested If activities with a couple of Assign activities in the body of the if blocks.

Now I can use this helper activity back in my main workflow.  I create 4 variables to hold the 4 out arguments and place the DeterminePrimaryPhone activity I just built into the sequence and map the variables to the outputs and the inputs to the fields in the ESB message:

image

image

With that in place I can map the primaryPhoneNumber and primaryPhoneTypeId variables to the input of the IndividualSpouseBusinessAddForm fields PHONE_NUMBER and PHONE_PHONETYPECODEID:

image

If there are 2 phones provided by the ESB message then I need to add the 2nd phone as a separate step because the IndividualSpouseBusinessAddForm activity only accepts a single primary phone number.  To do this I first need to capture the ID of the newly added record.  The IndividualSpouseBusinessAddForm activity returns a result of type IndividualSpouseBusinessAddFormData which contains the ID of the newly added record (as well as all of the other information that was passed to the form when it was saved).  I create a variable to hold this result named constitAddDataResult and map the Result argument of the form activity to that variable:

image

Next I will use the bulit-in PhoneAddForm activity to add the secondary phone.  This activity wraps the API used by the “Add Phone” form in the BBEC application that looks like this:

image

I add it to my toolbox:

image

image

and then drag it onto the Sequence after the IndividualSpouseBusinessAddForm:

image

(I place it inside an If block that tests if there is a second phone).

Since the Phone entity is a child record this form takes as input a ContextRecordId which is the ID of the Constituent that the phone is to be added to.  So I supply the ContextRecordId from the constitAddDataResult.RecordID and then the number and type from the secondaryPhoneNumber and secondaryPhoneTypeId variables:

image

To try out the Phone logic I hit F5 and enter 111-1111 in the HomeNumber field and 222-2222 in the Work number field and click the Invoke button:image

Flipping over to BBEC and navigating to the record I find that indeed I have 2 phones, a Primary phone of type Home 111-1111 and another phone of type Work 222-2222!

image

And with all of that in place I can scratch off Home and Work phone numbers.

 

  • First Name
  • Last Name
  • Title (a text string, such as “Dr.”, “Mr.”, “Mrs.”
  • Gender (a single character, one of M,F,O (for other)
  • Organizational Identifier (unique Id defined by the organization, used across systems)
  • Home Phone number
  • Work Phone number
  • Address – Street address
  • Address – City
  • Address – State (a 2 letter string abbreviation)
  • Address – Zip code

Organization Identifier (aka User-Defined Constituent Id)

The last field we need to map is the “Organizational Identifier”, which in this scenario is a key used to uniquely identify a Constituent record that is defined external to the BBEC application.  The BBEC “Lookup Id” field on a Constituent is intended for this purpose so we need to assign the Constituent record we are creating the Lookup Id from the incoming Organizational Id (which in the actual message uses an element named “ConstitId”).  In the BBEC application the “Edit Lookup Id” action launches the “Edit Lookup Id” form which is used to do this interactively:

image

image

What we want to do here in our service is use the underlying API that this form uses to carry out this operation programmatically.

A quick search in the VS “Choose Toolbox Items” dialog gets the corresponding workflow activity into my toolbox:

image

image

and then I can drag it onto the designer.  I nest it in an If activity so that if the incoming message does not contain an ID the Constituent record will just keep the default Lookup Id assigned by the BBEC system.

image

In the Property grid I assign the RecordId property to the constitAddDataResult.RecordId and the LOOKUPID field is assigned the value from the incoming ESB message:

image

With that in place my sequence now looks like this:

image

The last thing I want to do is return the newly added Constituent record Id that the BBEC application generated.  I add an Out argument to the ProcessNewConstitMessage.xaml named newConstitId and I add an activity to assign it a value from the result of the add form:

image

Then back in the main .xamlx I just need to map that Out argument to the newConstitId variable that I am using in the SendResponse data:

image 

I can try out a test message with all fields filled in:

image

which will look like this on the wire:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
    <Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://tempuri.org/IService/AddNewConstit</Action>
  </s:Header>
  <s:Body>
    <AddNewConstitMessage xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/BBECBusAdapter">
      <City>Riverside</City>
      <ConstitId>SC937-0176 CEC</ConstitId>
      <FirstName>James</FirstName>
      <Gender>M</Gender>
      <HomePhoneNumber>555-2233</HomePhoneNumber>
      <LastName>Kirk</LastName>
      <State>IA</State>
      <StreetAddress>123 Bridge Street</StreetAddress>
      <Title>Captain</Title>
      <WorkPhoneNumber>NCC-1701</WorkPhoneNumber>
      <ZipCode>52327</ZipCode>
    </AddNewConstitMessage>
  </s:Body>
</s:Envelope>

and now when that xml is sent to our .xamlx service via HTTP the service logic executes and back in the application it all results in this:

image

image

Where are we now?

Our solution now has 4 source files and a web.config file.

image

  1. AddNewConstit.xamlx – the outer workflow service definition
  2. AddNewConstitMessage.vb – the Plain Old .Net Object DTO class
  3. DeterminePrimaryPhone.xaml – helper declarative activity
  4. ProcessNewConstitMessage.xaml – main adapter logic declarative activity
  • We have a service that accepts an incoming xml message
  • The service internally handles all the necessary lookups and translations for data in the message
  • The service makes multiple calls to the Infinity web service to carry out the intent of the message (this is the Splitter pattern)
  • The service uses the built in Infinity workflow activities to translate Title, Phone, and State codes from text values in the incoming message to the required guid id values that the BBEC API needs
  • The service uses the built in Infinity workflow activities to add the Contituent record, add a secondary phone, and update the Lookup Id on the newly added record.
  • We wrote almost no code! Everything is declarative!
    • The message format is defined via a plain class with a simple list of properties
    • The service logic is 100% declarative xaml workflow

Hopefully I am not getting paid by the lines of .Net code I write, because the only .vb code in this project looks like this declaration of the message format in AddNewConstitMessage.vb:

Public Class AddNewConstitMessage

    Property FirstName As String
    Property LastName As String
    Property Gender As String 'single character, M,F, or O
    Property ConstitId As String
    Property Title As String 'text string such as Mr., Dr., Mrs. etc.
    Property HomePhoneNumber As String
    Property WorkPhoneNumber As String
    Property StreetAddress As String
    Property City As String
    Property State As String ' 2 letter abbreviation, for example SC, CA, NC
    Property ZipCode As String

End Class

That is the only code in this project, if you could even call that code.  It’s really just a declaration of a data type.  Everything else here was done using drag-and-drop plus Property Grid editing (visual coding) in the Visual Studio workflow designer.

There are no imperative procedural functions.

There are no concepts of byval/byref parameter intricacies.

Besides the DTO, no class declarations.  No inheritance.  No Interfaces.  I really don’t need to know very much at all from the VB language except just enough to create a few of the expressions such as the If activity condition (and intellisense in the designer helps with that too).

In other words – everything is declarative!  ( Those of us who work a lot with Infinity tend to be big fans of declarative code, you may have noticed).

Up Next ….

I started this series to show how to build a WCF Workflow Service hosted in Windows Server AppFabric, but so far I have been only using the local ASP.Net development server.  AppFabric is a component of IIS, so to start leveraging AppFabric we need to get this service running on  IIS instead of the ASP.Net development server.  The ASP.Net development server works fine…for development… but to make this thing real we need to get into deploying this service into a real AppFabric IIS application.  The next post in this series will dive into getting our workflow service hosted in AppFabric to suitable for production use.  Stay tuned…

Posted in Architecture, workflow | Tagged , , | 3 Comments

Adapt Infinity Web Services for ESB Integration–Part 2

This post is a continuation of Part 1 in this series on integrating an Infinity application such as Blackbaud Enterprise CRM into an Enterprise Service Bus architecture. If you haven’t read part 1 be sure to check that out here before reading this post.

As a reminder, we are creating a .Net 4.0 WCF Workflow Service built in Visual Studio 2010 to be hosted on Windows Server AppFabric.  This service will serve as an adapter layer in between the ESB and the Infinity web service API of the BBEC application web server, as depicted in this diagram:

image

The ESB is going to be delivering a message with the following data:

  • First Name
  • Last Name
  • Title (a text string, such as “Dr.”, “Mr.”, “Mrs.”
  • Gender (a single character, one of M,F,O (for other)
  • Organizational Identifier (unique Id defined by the organization, used across systems)
  • Home Phone number
  • Work Phone number
  • Address – Street address
  • Address – City
  • Address – State (a 2 letter string abbreviation)
  • Address – Zip code

and our mission is to accept this incoming data and execute the necessary logic and lookups to transform this ESB message to a series of BBEC API calls to carry out the intent of the message.  When we left off with Part 1 we had created the skeleton WCF Workflow Service and our solution had 3 files:

  1. AddNewConstsit.xamlx – the declarative WCF workflow service definition
  2. AddNewConstitMessage.vb – the Plain Old .Net Object class that defines the incoming ESB message
  3. ProcessNewConstitMessage.xaml – a declarative workflow activity that will define the logic to adapt the ESB message to the BBEC web service API

image

In this post I will start to build out the ProcessNewConstitMessage.xaml activity where the bulk of the adapter logic takes place.

New Infinity Activity – WebServiceScope

Before diving into the implementation of ProcessNewConstitMessage.xaml I need to introduce a new activity in the Infinity workflow library – the WebServiceScope activity located in the Blackbaud.AppFx.Workflow.CatalogActivities assembly. This activity will show up in the workflow toolbox in the tab labeled “Blackbaud Web Services Activities” which was introduced in the Infinity/BBEC 2.91 Q3 2011 release.

image

All of the built-in Infinity workflow activities that internally use the Infinity web service API must somehow know the connection information such as the url and databasekey to be used with the classes in the Blackbaud.AppFx.WebApi namespace.  To make the Infinity workflow activities easy to use the connection information is not specified on each individual activity but rather pulled from an ambient context.  The original mechanism to provide this ambient context to the workflow activities was to create an instance of the AppFxWebServiceProvider, configure it, and then add it as an extension to the WorkflowApplication before starting the workflow, like this example from the “Blackbaud AppFx Workflow Console application” template in the Infinity SDK

image 

Internally the code inside of the Infinity workflow activities uses the ActivityContext.GetExtension method to obtain a reference to the configured AppFxWebServiceProvider instance.  That works great for a standalone workflow application that is directly creating an instance of the WorkflowApplication class, but how does that work in a WCF workflow service where the WCF framework is managing the instantiation of the workflow host? The original Infinity workflow release also provided a mechanism for configuring the provider connection information via a WCF behavior, for example this is a section of the web.config file from the Blackbaud Workflow Service AppFabric application which uses the WCF behavior to establish the connection information containing the url and database key:

image

The <appFxWorkflowExtensionBehavior> element points to a connection string that is used to configure the AppFxWebServiceProvider.  This allows the tweaking of the target AppFx server url at deploy time by simply changing the connection string in web.config.

Both of those methods for supplying the connection information to workflow activities work well, but starting with the BBEC 2.91 Q3 2011 release there is a new activity that makes setting up the required ambient AppFxWebServiceProvider easier and more flexible.  The WebServiceScope activity allows you to nest any of the Infinity web service activities inside of it as child activities.  The connection information is then configured on the WebServiceScope activity and all child activities within that scope will use that connection information defined by the scope.  When using this activity a single workflow could have different blocks of activities that target different appfx web servers, each with unique connection information.  Also the connection information can be dynamically defined at runtime instead of just statically defined in the web.config.

Since I will be using Infinity web service activities in my workflow service I will use a WebServiceScope activity as the outer-most activity in the body of the workflow service in between the Receive and Send activities.  Then I will nest everything else inside that WebServiceScope.  I start by dragging the WebServiceScope onto the designer:

image

And then I configure it in the property grid.  You can set individual properties such as the url, databasekey, application name, and user credentials or you can specify an AppFxWebServiceProvider connection string in the following format:

applicationName=ESBAdapt;databaseName=BBInfinity;url=http://svr/bbappfx/appfxwebservice.asmx

You can also use one level of indirection and just specify the name of a connection string in the web.config.  All of the properties are InArguments so that they can be set dynamically at runtime, but to keep things simple I am going to use the ConnectionStringName property and just set it to the literal expression “appfx_connection”.

image

In the web.config of my solution I add a connection string with the name “appfx_connection” with the info for my server:

image

This will allow me to change the target Infinity web server by just updating the web.config.

With that in place I am eager to try everything out to make sure the connection string is good, so I temporarily place another new activity in the Blackbaud library as the first child in the WebServiceScope – the Ping activity which will call the Ping web service method on the Infinity web server.  The Ping web service method is a quick way to check that a call can make the trip all the way from the client to the server, pass the required security check, and then optionally test the connection from the web server to the database.  The Ping activity simply wraps up this web service in a workflow activity.

image

The activity requires the “MessageToEcho” argument which is just a text string that will be round-tripped to the server.  I use a literal string “test” and I also set the ConnectToDatabase property to the literal “True” so that the ping will test the connection to the database from the web service:

image

And with that in place I can hit F5 and step through the workflow and see that the service is being called and everything is wired up correctly.  I use the WCF test client to send a dummy message and step through my breakpoints in the workflow debugger:

image

Where are we?

Let’s pause and review where we are so far.  Here is what we have accomplished:

  1. We have a WCF Workflow Service with a .xamlx workflow named AddNewConstit.xamlx.
  2. We have defined the incoming message format using a plain old .Net class that maps directly to the format of the message that the ESB will be sending.
  3. We have configured our WebServiceScope activity so that Infinity workflow activities that we use within the workflow service will have the connection info they need at runtime.
  4. We have used F5 to debug the service using the WCF test client by sending a dummy message – verifying that our harness is working correctly and that calls made from a client to our adapter flow into the workflow and the calls from the workflow are flowing to the Infinity AppFx web service.
Find the BBEC application API needed to carry out the operation

Now we are ready to build the logic to call the actual BBEC API that will add a Constituent record.  The surface area of the BBEC API is quite large due to the fact that every single application feature is available as a web service.  One great way to find the right API is to use the “Feature Feature”, which is the internal slang we use to refer to the set of pages in any Infinity app that present information and metadata about all of the features within the application, including the API information for the feature.  I know that what I am trying to do is add an individual, so in the application I can see that there is a link labeled “Add an Individual” in the Constituent functional are:

image

Clicking the link brings up this form, which seems to have all of the biographical, address, and phone information I need:

image

So now I know what form I need to use.  Next I want to navigate to the “Feature Feature” page for this form.  I am not sure of the name of the form, so first I will find the feature feature page for the task that invokes the form.  I search and find that there are multiple Tasks named “Add an individual” , so I will pick the first one

image

The page for this task tells me that the form it invokes is named “Individual, Spouse, Business Add Form”

image

I click the link in the Feature name column and arrive at the feature feature page for this form:

image

 

On the API tab of this form I can see that there is indeed a workflow activity in the box that I can use to invoke the operation performed by this form:

image

Even though the “Individual, Spouse, Business Add Form” API is not a perfect 1-1 mapping to the incoming ESB message we can still use it as a building block, and to kick things off in our ProcessNewConstitMessage.xaml activity I am going to go ahead and add the activity for that from the BBEC activity library.  I am using the Blackbaud AppFx Workflow Visual Studio extension so on my Visual Studio 2010 Tools menu I can choose “Add Data Form Instance Activities” and browse for that form:

image

A dialog pops up that lets me search for the form:

image

When I select the form I will get a new toolbox tab named “Blackbaud AppFx DataForms” with the activities that work with this form:

image

As a reminder, add and edit form activities come in 4 flavors:

  • Load
    • This activity represents the “DataFormLoad” operation for the form.
    • For AddForms this will load the defaults for the form into a variable
  • Assign Values
    • This activity has a property for each field in the form
    • This activity is initialized with the variable that is loaded by the Load activity
  • Save
    • This activity will call DataFormSave with the assigned values
  • Combo (IndividualAddForm in this example)
    • This activity wraps up the 3 steps of Load, Assign Values, Save into one convenient activity for cases where using 3 explicit steps in the workflow is overkill.

I will use the combo IndividualSpouseBusinessAddForm activity for convenience because I don’t need to directly deal with the lower level load/assign values/save activities separately.  I drag that activity into a Sequence activity at the root of my  ProcessNewConstitMessage.xaml activity:

image

Note that the activity description includes this info from the metadata for this form:

“This dataform template is used to add an individual constituent, spouse (optional) and business (optional).”

Yep, that is what we want.

Once again I am eager to try this thing out, but the first thing I need to do is supply the field values for the form.  So I add an InArgument named newConstitMsg to the ProcessNewConstitMessage activity to take an instance of the incoming ESB message using the AddNewConstitMessage class:

image:

 

Next I map the properties of the form (that I can) to the data in the incoming message.  I will start with the text values I can use directly in the form such as First Name and Last Name and Zip code and City:

image

ADDRESS_POSTCODE = newConstitMsg.ZipCode

FIRSTNAME= newConstitMsg.FirstName

LASTNAME = newConstitMsg.LastName

ADDRESS_CITY= new ConstitMsg.City

I will ignore all of the other information in the incoming message at this point just so that I can try this out and see if at least the text values are working.

I do a quick build on the project and then my ProcessNewConstitMessage.xaml activity will appear in my toolbox:

image

I can then replace the Ping activity I used inside the WebServiceScope with this activity:

image

and I map the workflow service variable named incomingMsg to the newConstitMsg argument of the ProcessNewConstitMessage activity.

I will stop here and try out the workflow by hitting F5 and this time put some good data into the test message in the WCF test client:

image

(by the way, if you are interested in what the message will look like on the wire you can click the Xml tab at the bottom and see the exact format, which might be nice if the ESB client code will not be .Net code and you need to code more directly to the xml)

image

I click the Invoke button and if all goes well I hit might break point:

image

and if no errors happen then flipping over to the app I see that indeed the record was added:

image

What just happened?

Let’s review what we have achieved so far:

  1. We have implemented a WCF Workflow Service with a .xamlx workflow named AddNewConstit.xamlx.
  2. We have defined the incoming message format using a plain old .Net class that maps directly to the format of the message that the ESB will be sending.
  3. We have configured our WebServiceScope activity so that Infinity workflow activities that we use within the workflow service will have the connection info they need at runtime.
  4. We created a declarative .xaml workflow activity named ProcessNewConstitMessage.xaml to contain the logic of the ESB adapter. So far this activity only has a single child item – the BBEC application activity named IndividualSpouseBusinessAddForm which invokes the web service API used by the “Add an Individual” application feature.
  5. We placed that ProcessNewConstitMessage.xaml activity in the body of our workflow service inside the WebServiceScope activity.
  6. We sent a dummy message with some sample data to the service using the WCF test client and a record was added to the system containing the information in the message!!!!

Note the following about how we are invoking this logic:

  • The WCF test client app has no direct connection to the BBEC application database
  • The WCF test client app has no direct connection to the BBEC Infinity web server
  • The WCF test client is sending a message that is not compatible with any existing BBEC API
  • The WCF test client is sending a message to our WCF service over HTTP

Note the following about how much developer effort went into the adapter:

  • So far we have written no procedural code.  The only .Net code we have written is the definition of the class which is just a list of properties – a pure “Data Transfer Object” (DTO) style class to represent the message.
  • We got a good start using the WCF Workflow Service template that comes with Visual Studio 2010.  This gave us a skeleton service with a Receive and Send activity and a place to put our logic in between.
  • We added 2 Blackbaud specific workflow activities:
    • WebServiceScope  – used to configure the Infinity web service connection information for other workflow activities
    • IndividualSpouseBusinessAddForm – used to actually add a record to the BBEC application via the Infinity web service API.
  • All of the existing business logic embodied in that “Add an Individual” form in the application is respected and enforced because ultimately the code in the workflow service is going through the exact same code path that is executed when an end user uses this form.

That last point is very important.  While adding a Constituent record is fairly straightforward, there could easily be some complex business rules involved that are not immediately obvious.  Thinking forward to scenarios that involve things like adding Gift/Donation/Revenue records you can imagine that the underlying BBEC application logic can be quite complex.  The last thing we want to do is burden our ESB adapter with having to understand and re-implement that logic – to do so would not be practically feasible and would likely be a maintenance nightmare.  By calling into the obvious existing API we can rest assured that all of the proper business logic and validation rules will be enforce to keep the BBEC application data free of any corruption.

Our solution is still very simple with only 3 project items an a web.config:

  1. AddNewConstit.xamlx – The WCF declarative workflow service definition
  2. AddNewConstitMessage.vb – The plain old .Net class defining the ESB message DTO
  3. ProcessNewConstitMessage.xaml – a declarative workflow activity containing our adapter logic

image

Most of what we have done has been drag-drop activities onto the workflow designer and then configure those activities in the property grid.

And so far debugging has been very simple – just hit F5 in the IDE and then type some dummy data into the WCF test client application.

Next steps…

We have taken the first baby step in implementing our ESB adapter, but we have more work to do.  Up to this point we are only handling the simple text string fields in the incoming ESB message and we are not handling the other minor complexities such as the need to add multiple phones.  In the next installment in this series we will begin building out more of the workflow logic to accomplish those steps. 

Stay tuned.

 

See also:

Adapt Infinity Web Services for Enterprise Service Bus Integration–Part 1

Windows Server AppFabric Learning Links

Posted in Architecture, workflow | 5 Comments

Adapt Infinity Web Services for Enterprise Service Bus Integration–Part 1

Infinity – a Model Citizen

The Infinity Platform was designed from the start to support making an application such as Blackbaud Enterprise CRM a good citizen in any kind of Enterprise Application Integration  (EAI) scenario.  A core part of this capability of Infinity is the architecture that exposes every single application feature as a web service endpoint.  The Infinity web service APIs map closely to the following EIA patterns from the classicEnterprise Integration Patterns textbook by Hohpe and Woolf:

  • Remote Procedure Invocation (RPC)
    • Infinity web services behave very much like an RPC – each web service call is an atomic operation that succeeds or fails and returns a result.
  • Message
    • Infinity web services are implemented using SOAP where each message represents a stateless atomic operation.  When viewed through the lens of the BizOps endpoints the message oriented nature of the Infinity web service
      APIs becomes clear.
  • Request-Reply
    • This pattern can be thought of as RPC over messaging, which is one way to describe what SOAP is.  The .Net Framework (or whatever client tool/language is being used) usually hides the details of the separate request and reply channels, but under the hood deep down in the HTTP stack those components of the request-reply pattern are present.
    • In an Infinity web service each supported operation is actually modeled with a class to represent the Request and a class to represent the Reply.  For example, in the Blackbaud.AppFx.Server assembly the DataListLoad web service method of the AppFxWebService.asmx endpoint accepts a request message that is defined by a .Net class named “DataListLoadRequest”.  The method returns a reply message defined by a .Net class named “DataListLoadReply”.

The native Infinity web services APIs provide building blocks for the more elaborate patterns that will typically be employed in a multi-application integration scenario.  Using these building blocks a higher-level architecture can be created based on the messaging pattern.  For example, in the diagram below think of Application A  as an instance of Blackbaud Enterprise CRM, with a desire to integrate that instance of BBEC with two other applications that are possibly built on different technology stacks, perhaps even non-Microsoft systems.

This graphic depicts a simple Enterprise Service Bus (ESB) architecture, where applications communicate by placing messages onto a conceptual “bus” rather than by exchanging messages directly with each other.  This is the type of Enterprise Application Integration architecture that CIO’s really get exited about because if implemented correctly this architecture supports the independent deployment and evolution of applications.  For example the diagram below from the MSDN site depicts an example implementation of an ESB where mobile client servers, application servers, email servers, java servers, and mainframe servers are all playing nicely with each other by communicating exclusively through the ESB.  Of course the devil is in the details of how that nice clean blue pipe labeled “Enterprise Service Bus” is actually implemented.

There are many vendors that offer products that would claim to perform that magical blue pipe ESB function, ranging from centralized message orchestration products like Microsoft BizTalk Server to decentralized distributed systems like NServiceBus.  Infinity is not designed to be the bus itself but rather simply just a good citizen in this type of architecture.  Infinity is not the blue pipe – it is designed to plug into the blue pipe.  In the type of ESB architecture depicted in this diagram the scope of the Infinity/BBEC application is limited to just the single Application Server and associated web service plugging into the ESB, highlighted in red below for clarity:

image

Make it easy to plug into the bus

The ease of plugging the native Infinity web services into the specific implementation of the bus will depend on the technologies that make up the bus.  For example, if the bus is built on .Net then it may be a fairly simple matter to use the strongly typed .Net Blackbaud AppFx WebApiClient assemblies as the mechanism for invoking BBEC application logic from within the ESB.  However, in many customer deployments BBEC may not be the first application to plug into the bus and in those scenarios there may already be a pre-defined message format / protocol in place that may not closely align to the native Infinity and BBEC application web services.  In those scenarios, if BBEC is the last app to the ESB party then most likely it is BBEC that needs to be adapted to work well in that existing architecture.  If that is the case then the following patterns can be used to connect the existing ESB to BBEC:

  • Channel Adapter
    • Some code can be written to present a bus friendly API to the existing ESB that internally will make the necessary Infinity web service calls to carry the operation indicated by the ESB delivered message
  • Message Translator
    • If the incoming message is conceptually close to an existing BBEC API (for example the incoming message is intended to add a new Constituent record) then the adapter may be a very simple implementation that just translates from the specific incoming format into the SOAP format needed for BBEC.
    • The ESB message may contain textual codes that require a lookup of the associated ID in BBEC.  For example, a message to add a new Constituent may include a string value for the title code of “Mr.”, but the BBEC Add Constituent API takes the ID of the code table value for “Mr.”  The translator would take perform the ID lookup for the incoming value in the ESB message.
  • Aggregator
    • The ESB may send multiple messages that map to a single logical existing BBEC API.  For example the ESB may have a message to add a Constituent record and 2nd message to add the associated primary address, but BBEC has a single endpoint that accepts Constituent bio and Address information in a single atomic API.  An aggregator pattern can be used to accept the multiple messages and combine them into a single payload for the BBEC API.
  • Splitter
    • This is the inverse of the aggregator pattern.  The ESB may deliver a single message that needs to be broken apart into individual BBEC API calls.  For example maybe it sends one message to add a Constituent that includes Constituent bio information AND the associated Constituency codes.  There is not currently a BBEC API that accepts both Bio and Constituency information, so this would require 2  calls to 2 separate BBEC APIs to carry out the logical operation defined by the ESB message.
Use AppFabric Workflow Services

The patterns defined here could be implemented using any number of technologies.  However, given that Infinity is built on the Microsoft .Net platform it makes a lot of sense to look first at the .Net stack to implement our ESB adapter.  In this post I will demonstrate implementing a simple adapter using a Windows Communication Foundation (WCF) Workflow Service built on the .Net 4.0 Workflow Foundation (WF) and hosted in Windows Server AppFabfric.  If you are not familiar with Workflow Services and Server AppFabric I suggest the following introductory articles:

Visual Design of Workflows with WCF and WF 4

Introducing Windows Server AppFabric whitepaper

Workflow Services MSDN documentation

Windows Server AppFabric product documentation

(Note that in this example I will only be using the application hosting features of AppFabric, I will not be using the caching features, although in an actual implementation of a real BBEC solution that may be something that makes sense to incorporate into the architecture.)

Workflow Services are a good fit for Infinity ESB integration for a number of reasons:

  • The Splitter pattern is easily implemented by defining the incoming message using a WCF “Plain Old C#/VB/.Net Object” (aka POCO, PONO, POVBO) and then implementing the logic using the visual workflow designer
  • The adapter logic can leverage the Infinity Workflow components including the thousands of activities in the BBEC Workflow Activity Library, acting as an External Workflow Application (EWA) to Infinity.
  • The Aggregator pattern, if needed, can leverage the native persistence features of Workflow Services so that chains of incoming messages can reliably be handled even across restarts of the adapter host.
  • The built in health monitoring features of AppFabric server could be very helpful in troubleshooting any issues that arise.  By leveraging that existing logging functionality we can put all of our solution architecture energy into implementing the adapter rather than basic commodity application hosting infrastructure such as logging frameworks.
  • AppFabric, WCF, and WF are all free components included with Windows Server so no additional license is required to use those technologies.

We can implement the adapter as an independent component isolated from the actual BBEC application server.  Since we will be building on Windows Server we can of course opt to host the adapter on the same physical (or virtual) host as the BBEC application.  But we will implement it as its own isolated web application with it’s own virtual directory in IIS with its own web.config and \bin folder.  This independence will allow us to evolve and update the adapter without having to touch any bits on the BBEC application server, and it will position us to be able to move the adapter to a dedicated host or dedicated load balanced hosts at some point if the requirements end up growing to the point where deploying on the same server as BBEC is no longer suitable.

The logical architecture will be something like the diagram below:

image

Note that if implemented as depicted with all communication happening to both the Workflow Service adapter and the BBEC app server over HTTP/HTTPS (port 80/443) there is the added potential to have great flexibility in the deployment, perhaps deploying the BBEC instance on a cloud host provider such as Rackspace or Amazon EC2, or perhaps the inverse with the bus and other applications being deployed in the cloud with connecting to an on-premise BBEC installation.  The point is that using WCF services as the adapter interface coupled with the existing BBEC web service interface provides for great flexibility.

Example: Add a new Constituent Record

Imagine that we are integrating Blackbaud Enterprise CRM into an ESB architecture where the service bus will send a message when a new Constituent record is to be created in BBEC.  The message payload will contain the following information:

  • First Name
  • Last Name
  • Title (a text string, such as “Dr.”, “Mr.”, “Mrs.”
  • Gender (a single character, one of M,F,O (for other)
  • Organizational Identifier (unique Id defined by the organization, used across systems)
  • Home Phone number
  • Work Phone number
  • Address – Street address
  • Address – City
  • Address – State (a 2 letter string abbreviation)
  • Address – Zip code

There is not a single existing BBEC web service endpoint that takes this exact information in this format.  The “Individual Add Form” API (catalog Id 1986f0cf-efb6-48b3-afde-950b57562434) is very close, for example browsing to the Bizop soap.asmx endpoint for this API we see that the SaveData method will create a new Constituent record,

image

but if we drill into the expected payload by clicking the link on that method name for this API we see that the Address State input is expected to be the GUID ID of a STATE record in the BBEC database, where as the ESB is going to be sending a 2 letter abbreviation.  Also the “Add Individual” BBEC API only accepts a single line item phone record which can include the phone type, but our ESB is going to be sending 2 specific phone numbers, Home and Work.  And while the BBEC web service expects the Gender codes to be the strings “Male”, “Female” or “Unknown” the ESB is only going to be providing the single character “M”, “F”, or “O”. So we are going to need to implement a small amount of adapter logic to bridge these minor differences in message format and content.

image

Walkthrough – Implementing the adapter as a Workflow Service

In the rest of this post I will walk through implementing a WCF Workflow Service hosted in AppFabric that will perform the adapter functionality needed for this scenario of adding a Constituent record.  To get started in Visual Studio 2010 choose “WCF Workflow Service Application” in the Workflow template section of the new project dialog.  I called my project “BBECBusAdapter”.

image

This template will create a project with a single .declarative xamlx workflow service project item in it.  This xamlx is going to be where we implement the adapter logic so I rename it from the default “service1.xamlx” to “AddNewConstit.xamlx”.

Before digging into the .xamlx definition I want to go ahead and define the incoming message based on the specification above.  For this I will use a “Plain Old VB Object”  so I add a new class project item named AddNewConstitMessage.vb

image

This class just defines the format of the message that our service will accept from the ESB:

Public Class AddNewConstitMessage

    Property FirstName As String
    Property LastName As String
    Property Gender As String 'single character, M,F, or O
    Property ConstitId As String
    Property Title As String 'text string such as Mr., Dr., Mrs. etc.
    Property HomePhoneNumber As String
    Property WorkPhoneNumber As String
    Property StreetAddress As String
    Property City As String
    Property State As String ' 2 letter abbreviation, for example SC, CA, NC
    Property ZipCode As String

End Class

Now that I have the incoming message defined I can open up the AddNewConstit.xamlx in the designer and change the ReceiveRequest activity Content property so that the message type will be AddNewConstitMessage, and I will map it to a workflow variable named “incomingMsg”:

image

For the reply message I am going to keep things simple and simply return the BBEC defined Constituent record unique identifier guid that is assigned when the record is created.  So I edit the SendResponse activity content property to by of type System.Guid and map it to a workflow variable named newConstitId:

image

At this point I have defined the basic shape of my workflow service:

image

Before going any further I can actually hit F5 in the VS IDE and try out this shell service just to make sure everything is wired up ok.  I hit F5 (or “Start Debugging” on the Debug menu) and the WCF Test Client utility will launch pointing to the debug url for this service.  This allows me to actually fill in a sample message using a property grid and invoke the AddNewConstit.xamlx service interactively by filling in some dummy data into the message fields:

image

I can click the “Invoke” button and I will hit a breakpoint that I set on the ReceiveActivity:

image

Single stepping to the SendResponse activity allows me to examine the contents of the incomingMsg variable in the debug immediate window:

image

So now I have a wireframe for the adapter that is actually working, and my only chore is to implement the internal logic to take this incoming message and make the necessary calls to the BBEC web service API to carry out the intent of the message.

Implement the message handling logic in a workflow activity

I like to build up my workflows by creating small standalone activities that I can then combine at a higher level.  So to get started I am going to create a new .xaml activity named “ProcessNewConstitMessage” that will contain the logic to process the incoming message from the bus.

image

At this point my solution now had 3 files:

  1. AddNewConstsit.xamlx – the declarative WCF workflow service definition
  2. AddNewConstitMessage.vb – the Plain Old .Net Object class that defines the incoming ESB message
  3. ProcessNewConstitMessage.xaml – a declarative workflow activity that will define the logic to adapt the ESB message to the BBEC web service API

image

Stay tuned for part 2…

So far this post has shown how to get started implementing a framework for adapting Infinity web services to an ESB via the use of an intermediary WCF Workflow Service acting as an adapter.  In the next post in this series I will dig into the implementation of the ProcessNewConstitMessage.xaml and show how the Infinity workflow activity library can be used to connect this adapter to the logic contained in the BBEC application API.  If you are already familiar with authoring workflows with Infinity then you can probably take what has been shown in this post and run with it.  If you are not familiar with Infinity workflow yet then you may want to do some background reading of the introductory “Hello Workflow” post that walks through a simple use of the Infinity workflow library.

Posted in Architecture, workflow | Tagged , , , | 7 Comments