Hey everyone,

Sorry this post has been so delayed. I was wiped out last week and a half with a nasty cold that I am still recovering from. I missed my entire Thanksgiving break, curled up under blankets and coughing my lungs up. Anyway, I’m finally back on my feet, albeit completely buried under catch up work and a new project. But as promised, nuts and bolts from KBay!

The Power of Communication

One the best parts of KBay is, IMO, the communication design. Every web method in the service takes a single object parameter and returns a single parameter. These two objects are always called the Request and the Response.

Some of the awesomeness behind this pattern includes:
1) Client side, you can hang on to a request and populated it as needed. No more piles of variables to pass in, just set up the single request object, it’s all nice and self contained.
2) Super easy logging/debugging. At any point in time we can serialize the request/response objects to see what’s in them. Even using a tool like Fiddler you can see a nicely formatted data layout of your parameters.
3) Server side additions will not break functionality. So long as the server side checks the value it wants to use before using it, the client side will continue to function. This was great for us as we had 2+ developers on the project the whole time. If Adam was adding more parameters and functionality server side, I could continue hitting the dev server with out having to rebind or implement any changes.

The Simplest Web Methods Ever

First off, the actual code behind on the web services:

    <WebMethod(Description:="Returns a set of data required at application startup.")> _
    Public Function GetInit(ByVal Request As GetInitRequest) As GetInitResponse
        Return Request.FillRequest()
    End Function

    <WebMethod(Description:="Returns a list of Alert Data based on the filter criteria set in the request.")> _
    Public Function GetAlertData(ByVal Request As GetAlertDataRequest) As GetAlertDataResponse
        Return Request.FillRequest()
    End Function

This code is actually for a different project, but it’s what I had handy. All of the functions follow the same pattern as the ones you can see here. They take a custom Request object and return a custom Response object (both named after the method). Inside the method, they call the Request.FillRequest method.

Where’s the beef?

So let’s keep digging. Here is the GetInit.vb file that contains the functionality for the GetInit method:

Namespace Implementation
    <ServiceManager.AutoCreateSessionId()> _
    Public Class GetInitRequest
        Inherits ServiceManager.BaseRequest(Of GetInitRequest, GetInitResponse)

        Public Overrides Function innerFillRequest() As GetInitResponse
            Dim output As New GetInitResponse

            Dim env = Utility.GetEnvironement("Alerts|Development")
            Dim Statuses As String = Utility.GetEnvironmentSetting("Alerts|Development", "StartupMessage").ToString
            If Statuses <> String.Empty Then
            End If

            Return output
        End Function

        Protected Overrides Function GetLoggingReplacement(ByVal XMLElement As System.Xml.Linq.XElement) As String
            Return Nothing
        End Function
    End Class

    Public Class GetInitResponse
        Inherits ServiceManager.BaseResponse

        Private _Statuses As New List(Of String)
        Public Property Statuses() As List(Of String)
                Return _Statuses
            End Get
            Set(ByVal value As List(Of String))
                _Statuses = value
            End Set
        End Property
    End Class

End Namespace

A couple of things to notice here. First, there are two classes in this file, both the request and the response. This is just for organizational purposes. You’ll also want to note the “servicemanager.AutoCreateSessionId()” attribute on the Request class. We’ll use this again later.

One of the cool things about this system is that it uses generics to move data UP the hierarchy chain. In this case, you can see that we are inheriting from BaseRequest, which allows us to specify the types of the generic.

The “innerFillRequest” method is where the actual functionality of this web method takes place. In the case of GetInit, it grabs a string from the Environment Provider, splits it, and adds it to a member on the Response object.

Below that is the GetInitResponse class, which in this case is pretty simple and just contains a property that exposes a list of strings. The same property that is populated in the Request object’s innerFillRequest method.

Strange Child, Who is your Parent?

While these derived classes show us what the method does, it doesn’t show us how they are called. The web methods called Request.FillRequest, not InnerFillRequest. So lets look at the class that the request inherits from, BaseRequest:

Namespace ServiceManager
    Public MustInherit Class BaseRequest(Of Req As {New, iRequest}, Res As {New, BaseResponse})
        Implements iRequest

        Private mRequestToken As String
        Public Property RequestToken() As String Implements iRequest.RequestToken
                Return mRequestToken
            End Get
            Set(ByVal value As String)
                mRequestToken = value
            End Set
        End Property
        Private mSessionId As Long
        Public Property SessionId() As Long Implements iRequest.SessionId
                Return mSessionId
            End Get
            Set(ByVal value As Long)
                mSessionId = value
            End Set
        End Property

        ''' <summary>
        ''' This procedure forms a response object and deals with any errors that take place while
        ''' that response is being created.
        ''' </summary>
        ''' <typeparam name="Req"></typeparam>
        ''' <typeparam name="Res"></typeparam>
        ''' <param name="Request"></param>
        ''' <param name="RunAction"></param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function FillRequest() As Res
            '// create a place holder for the output response object
            Dim Output As Res = Nothing
            '// note the time we started this service call.
            Dim sw As New Stopwatch : sw.Start()

                '// if this request type implements the AutoCreateSessionIdAttribute AND the user has
                '// not given a session ID then create a session ID.
                Dim Atts = Me.GetType.GetCustomAttributes(GetType(AutoCreateSessionIdAttribute), True)
                If Atts IsNot Nothing AndAlso Atts.Count > 0 Then
                    If Me.SessionId <= 0 Then
                        Me.SessionId = Utility.LogApplicationStartup()
                    End If
                End If

                '// if at this point there is no session ID, throw an exception
                If Me.SessionId <= 0 Then
                    Throw New ApplicationException("Invalid session ID given. Ensure you pass the session ID to every service call.")
                End If

                '// set a GUID token for this request
                If String.IsNullOrEmpty(Me.RequestToken) Then
                    Me.RequestToken = System.Guid.NewGuid.ToString
                End If

                '// log the request XML
                Utility.WriteLogEntry(Me.SessionId, KerryAuditLoggingServices.LogTypes.Verbose, CreateXML(Me))

                '// actually run the request and build a response object.
                Output = innerFillRequest()

                '// note the session ID back to the response object
                Output.SessionId = Me.SessionId
                '// note the request token
                Output.RequestToken = Me.RequestToken

                '// log the response object about to be sent back.
                Utility.WriteLogEntry(Output.SessionId, KerryAuditLoggingServices.LogTypes.Verbose, CreateXML(Output))
            Catch ex As Exception
                '// create a new empty response object to hold the error that was thrown
                Output = New Res()
                Output.Error = ex.ToString

                '// note the session ID if the request isnot empty (the caller may have passed a sessionId in)
                If Me IsNot Nothing Then
                    Output.SessionId = Me.SessionId
                    Output.RequestToken = Me.RequestToken
                End If

                '// log the error
                Utility.WriteLogEntry(Me.SessionId, KerryAuditLoggingServices.LogTypes.HandledException, CreateXML(Output))
                If Output IsNot Nothing Then
                    '// note the amount of time it took to complete this request.
                    Output.CompletedInSeconds = sw.Elapsed.TotalSeconds
                End If
            End Try

            Return Output
        End Function
        Public MustOverride Function innerFillRequest() As Res

        ''' <summary>
        ''' This procedure will take the given object and create an XML string representing the
        ''' public properties of the given object.
        ''' </summary>
        ''' <param name="Obj"></param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Private Function CreateXML(ByVal Obj As Object) As String
            Dim XML As String

            Dim xmlSer = New System.Xml.Serialization.XmlSerializer(Obj.GetType)
            Using ms As New System.IO.MemoryStream(1000)
                xmlSer.Serialize(ms, Obj)
                ms.Seek(0, IO.SeekOrigin.Begin)
                Dim reader As New System.IO.StreamReader(ms)
                XML = reader.ReadToEnd
            End Using

            Dim XMLElements = XElement.Parse(XML)

            Return XMLElements.ToString
        End Function

        ''' <summary>
        ''' This procedure should be modified to remove any tags you do not want to send to the log. 
        ''' Tags that hold byte arrays are good candidates for removal.
        ''' </summary>
        ''' <param name="Source"></param>
        ''' <remarks></remarks>
        Private Sub ReplaceInvalidTags(ByVal Source As XElement)
            Dim Nodes = (From e In Source.Elements Select e).ToArray
            For Each n In Nodes
                Dim LoggingReplacement As String = GetLoggingReplacement(n)
                If String.IsNullOrEmpty(LoggingReplacement) Then
                    n.Value = LoggingReplacement
                End If
        End Sub

        Protected Overridable Function GetLoggingReplacement(ByVal XMLElement As XElement) As String
            If XMLElement.Name.ToString = "SFDCSessionId" Then
                Return "[SFDC Session ID Hidden]"
                Return String.Empty
            End If
        End Function
    End Class
End Namespace

The BaseRequest class is abstract, it must be inherited. But even cooler, it is a generic class. We can define it’s type dynamically. It implements iRequest, which just guarantees it some basic members:

Namespace ServiceManager
    Public Interface iRequest
        Property SessionId() As Long
        Property RequestToken() As String
    End Interface
End Namespace

The “FillRequest” method is the conductor of this train. This is the method that the web services are actually calling. Right off the bat, if starts up a stop watch, so we can get performance indicators off of every web service call.

If you remember back to that GetInitRequest class, we set the AutoCreateSessionIDAttribute. And this is where it gets used. If that attribute is set, then we know that this is an acceptable application start method. So we Log the application start up, which returns the Session ID. The Session ID, from this point on, should always be passed back and forth between the client and server, so that all interactions with this user for this execution of the application are under one ID. If for any reason, this Session ID is not set, we kill the process with an exception. This prevents applications from jumping into web service calls with out following the appropriate chain of processes.

The RequestToken is a feature used for logging. It allows us to tie multiple log entries (say like a request, some server side process logs, and the response) into a single log entity. This is really only of use when viewing the log, so you can collapse multiple log entries into a single interaction.

The “GetLoggingReplacement” is a block we use to replace chunks of text we don’t want to log. For instance, if the request contains large binary blocks (like uploading files) or sensitive information, we can set up string manipulations here to remove those items from the serialized request before it gets logged. This sub is usually overridden by the derived classes to add more method specific replacements.

Until Next Time

The projects continue to pile up. Everyone is trying to wrap stuff up before the holidays and new year, so I’ve got a lot on my plate. As I get more time, I’ll try to put up more on some of the other technical tricks we used inside of Silverlight. But this communication system, in conjunction with our Logging system, Unit tests, and having 2 developers on each project has dramatically helped us cut down on development time, reduce released bugs, and improved user acceptance of the applications.