Thursday, March 09, 2006

Doing remote procedure calls using WSE web service attachments

Note this is not an original idea. I'm sure I saw someone describe something similar on a blog a while back. I couldn't find the original source so I ended up re-implementing the same idea. WSE has a nice feature, web service attachments, that allows you to attach a binary file to a web service call. Now it doesn't take a big leap of imagination to see how you could use this to create a kind of remote procedure call. You'd serialize your parameters at the client and attach them to your standard call function. At the server end you retrieve the attachment, de-serialize your parameters, do whatever, serialize the return argument and return the call. Back at the client you de-serialize the return argument and it's done. Simple. Since the application I'm currently working on (the Civil Aviation Authority's Aircraft Register) describes all its service calls as interfaces it also means we can easily generate the client code. Now, you're thinking; "why doesn't he just use remoting?". That's a good point but this does give us a few benefits. We only have to maintain a single web method. We can use all that good stuff in WSE like security and routing. We could also implement compression easily by compressing the byte stream before we attach it. But most importantly we can still say to our managers, 'of course we're still using web services' so that they can live their SOA dream without it messing up our application design:) OK, now for an example. Let's use the canonical Add function, so our 'database layer' code looks like this (sorry they make me use VB.NET here... urgh):
Public Class MathService
    Implements AIS.Domain.Mock.IMathService

    Public Function Add(ByVal a As Integer, ByVal b As Integer) As Integer Implements Domain.Mock.IMathService.Add
        Return a + b
    End Function

End Class
Notice that it implements IMathService, our service interface. Our service client looks like this:
Public Class MathService
    Implements AIS.Domain.Mock.IMathService

    Private INTERFACE_TYPE As Type = GetType(AIS.Domain.Mock.IMathService)

    Public Function Add(ByVal a As Integer, ByVal b As Integer) As Integer Implements Domain.Mock.IMathService.Add

        Dim genericService As New AIS.Service.Generic.Client.GenericService
        Dim result As Object() = genericService.GenericFunction(INTERFACE_TYPE, "Add", New Object() {a, b})
        Return result(0)

    End Function

End Class
This also implements IMathService. It just takes the parameters and calls GenericService.GenericFunction passing in the interface, the method name, "Add" in this case and the parameters. GenericFunction looks like this:
Public Function GenericFunction(ByVal interfaceType As Type, ByVal methodName As String, ByVal parameters() As Object) As Object()

    Dim formatter As New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
    Dim parameterStream As New System.IO.MemoryStream

    Dim serviceParameter As New serviceParameter(interfaceType.FullName, methodName, parameters)

    formatter.Serialize(parameterStream, serviceParameter)

    Dim returnStream As System.IO.Stream
    returnStream = InvokeAttachmentFunction(parameterStream)
    Return formatter.Deserialize(returnStream)

End Function
It wraps the interface name, method name and parameters into a ServiceParameter object, serializes it and passes it to InvokeAttachmentFunction, which looks like this:
Private Function InvokeAttachmentFunction(ByVal parameterStream As System.IO.Stream) As System.IO.Stream

    Dim proxy As New GenericServiceProxy
    proxy.Url = m_configuration.ProxyConfiguration.ServerUrl

    ' Create a new DimeAttachment class, and add the stream to that
    Dim attachment As New attachment("text/plain", parameterStream)
    proxy.RequestSoapContext.Attachments.Add(attachment)

    ' make the call
    proxy.GenericAttachmentFunction()

    ' retrieve the return attachment
    ' test for attachments
    If proxy.ResponseSoapContext.Attachments.Count = 0 Then
        Throw New ApplicationException("No attachments detected")
    End If

    ' return the object stream
    Return proxy.ResponseSoapContext.Attachments(0).Stream

End Function
This creates a new GenericServiceProxy (generated by the WSDL.exe tool), attaches the parameter stream to the request, calls the web service, gets the returned attachment and returns it. Our web service looks like this:
<WebMethod()> _
Public Sub GenericAttachmentFunction()

    ' check there's at least one attachnent
    If RequestSoapContext.Current.Attachments.Count = 0 Then
        Throw New ApplicationException("No attachments detected")
    End If

    ' deserialize the attachment stream to a serviceParameter object
    Dim parameterStream As System.IO.Stream = RequestSoapContext.Current.Attachments(0).Stream
    Dim formatter As New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
    Dim serviceParameter As serviceParameter = formatter.Deserialize(parameterStream)

    ' invoke the given method on the given interface with the given parameters
    Dim result() As Object = Invoke(serviceParameter.InterfaceFullName, serviceParameter.MethodName, serviceParameter.Parameters)

    ' serialize the result to a stream
    Dim resultByteStream As New System.IO.MemoryStream
    formatter.Serialize(resultByteStream, result)

    ' create an attachment with the stream
    Dim attachment As New attachment("text/plain", resultByteStream)
    ResponseSoapContext.Current.Attachments.Add(attachment)

End Sub

Private Function Invoke(ByVal interfaceFullName As String, ByVal methodName As String, ByVal parameters() As Object) As Object()

    Dim service As Object = ServiceProvider.GetServiceInstance(interfaceFullName)

    Dim serviceType As Type = ServiceProvider.GetServiceInterfaceType(interfaceFullName)
    Dim method As System.Reflection.MethodInfo = serviceType.GetMethod(methodName)
    Dim result As Object = method.Invoke(service, parameters)
    Return New Object() {result}

End Function
The Invoke function calls a method GetServiceInstance on a class ServiceProvider. This returns an instance of a service provider of the given interface type defined in a config file. I'll go into this in another post. So once we've got our concrete instance we can simply use reflection to call the right method. Now have a look at the GenericAttachmentFunction. First it checks that an attachment was attached. Next it de-serializes the parameters to a ServiceParameter object which gives the interface and method that we want to call and the parameters to pass to it. Then we call Invoke as above which returns us an object array as a return value. The return value is then serialized, attached to the response and the function returns.

Tuesday, March 07, 2006

Steve Yegge's whirlwind language tour

This blog by Steve Yegge, a programmer at Amazon, is an excellent diatribe comparing programming languages. No mention of C#, my professional tool (when I'm not forced to use VB.NET - ugh), but I guess a lot of the comments about Java also apply. I'm inspired to have another look at Ruby after reading it.

Wednesday, March 01, 2006

A nice pattern for databinding domain objects

I'm currently working on a windows form application. We've tried to follow good OO principles in designing our business domain classes; encapsulating the business rules, only allowing valid instances of a domain class to be created. We also really like windows form databinding. It's so much easier than writing lots of code to shunt your object's properties back and forth to the form's controls. So, what's the problem. Well, say we have a person class with a name property. The business rules say that the name property can't be an empty string, but how do we create a new person if we can't first bind a blank name to a text box? I've got a nice solution with the use of the 'Builder' pattern. Each domain class has a nested Builder class that is bound to the form instead of an instance of the class itself. The builder allows empty values for required properties so that initially the form has blank fields to be filled in. When the user clicks 'OK', the builder's CreatePerson method is called that returns an instance of the domain class. Because the builder is a nested type it has access to private shared methods that can contain business logic in the domain class, this enforces encapsulation. Validation of the entered fields can be done on the CreateInstance method. Here's the Person class with the nested Builder. Note that the DateOfBirth and Age properties are related via a business rule and that the business rule is also used in the Builder:
Public Class Person

    Private m_name As String
    Private m_age As Integer
    Private m_dateOfBirth As DateTime

    Public Sub New(ByVal name As String, ByVal dateOfBirth As DateTime)
        Me.Name = name
        Me.DateOfBirth = dateOfBirth
    End Sub

    Public Property Name() As String
        Get
            Return m_name
        End Get
        Set(ByVal Value As String)
            If Value = String.Empty Then
                Throw New ValidationException("Name cannot be an empty string")
            End If
            m_name = Value
        End Set
    End Property

    Public Property DateOfBirth() As DateTime
        Get
            Return m_dateOfBirth
        End Get
        Set(ByVal Value As DateTime)
            m_dateOfBirth = Value
            m_age = CalculateAgeFromDob(m_dateOfBirth)
        End Set
    End Property

    Private Shared Function CalculateAgeFromDob(ByVal dateOfBirth As DateTime) As Integer
        Return ((DateTime.Now().Subtract(dateOfBirth).TotalDays) / 360) - 1
    End Function

    Private Shared Function CalculateDobFromAge(ByVal age As Integer, ByVal dateOfBirth As DateTime) As DateTime
        Return New DateTime(DateTime.Now.Year - age, dateOfBirth.Month, dateOfBirth.Day)
    End Function

    Public Property Age() As Integer
        Get
            Return m_age
        End Get
        Set(ByVal Value As Integer)
            m_age = Value
            m_dateOfBirth = CalculateDobFromAge(m_age, m_dateOfBirth)
        End Set
    End Property

    Public Overrides Function ToString() As String
        Return m_name & " " & m_age.ToString() & " " & m_dateOfBirth.ToShortDateString()
    End Function

    Public Class Builder

        Private m_name As String
        Private m_age As Integer
        Private m_dateOfBirth As DateTime

        Public Sub New()
            '
            ' initialise to starting values
            '
            m_name = ""
            m_age = 0
            m_dateOfBirth = DateTime.Now
        End Sub

        Public Function GetPerson() As Person
            Return New Person(m_name, m_dateOfBirth)
        End Function

        Public Property Name() As String
            Get
                Return m_name
            End Get
            Set(ByVal Value As String)
                m_name = Value
            End Set
        End Property

        Public Property DateOfBirth() As DateTime
            Get
                Return m_dateOfBirth
            End Get
            Set(ByVal Value As DateTime)
                m_dateOfBirth = Value
                m_age = CalculateAgeFromDob(m_dateOfBirth)
            End Set
        End Property

        Public Property Age() As Integer
            Get
                Return m_age
            End Get
            Set(ByVal Value As Integer)
                m_age = Value
                m_dateOfBirth = CalculateDobFromAge(m_age, m_dateOfBirth)
            End Set
        End Property

    End Class

End Class

And here's the interesting bits of the form...
Public Class MainForm
    Inherits System.Windows.Forms.Form

    Private m_person As Person
    Private m_personBuilder As Person.Builder

    Private Sub BindPerson(ByVal person As Person)

        m_person = person
        Me.m_nameTextBox.DataBindings.Add("Text", m_person, "Name")
        Me.m_dateOfBirthTextBox.DataBindings.Add("Text", m_person, "DateOfBirth")
        Me.m_ageTextBox.DataBindings.Add("Text", m_person, "Age")

    End Sub

    Private Sub BindPersonBuilder(ByVal personBuilder As Person.Builder)

        m_personBuilder = personBuilder
        Me.m_nameTextBox.DataBindings.Add("Text", m_personBuilder, "Name")
        Me.m_dateOfBirthTextBox.DataBindings.Add("Text", m_personBuilder, "DateOfBirth")
        Me.m_ageTextBox.DataBindings.Add("Text", m_personBuilder, "Age")

    End Sub

#Region " Windows Form Designer generated code "

    Public Sub New(ByVal personBuilder As Person.Builder)
        MyBase.New()
        InitializeComponent()
        BindPersonBuilder(personBuilder)
    End Sub

    Public Sub New(ByVal person As Person)
        MyBase.New()

        'This call is required by the Windows Form Designer.
        InitializeComponent()

        'Add any initialization after the InitializeComponent() call
        BindPerson(person)
    End Sub

    ....

#End Region

    Private Sub m_showPersonButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles m_showPersonButton.Click

        Try
            m_person = m_personBuilder.GetPerson()
            MessageBox.Show(m_person.ToString())
        Catch ex As ValidationException
            MessageBox.Show(ex.Message)
        End Try

    End Sub

End Class

Wednesday, February 15, 2006

A neat way of getting around some OR mapping issues with temporary Domain classes.

I recently discovered a nice pattern for dealing with self joins, and many-to-many joins in a Data Access Layer. Here's the problem. In your domain design you have an class with a collection of itself. This is a common pattern for tree structures.
class MyEntity
{
   MyEntityCollection _myEntities;

   MyEntityCollection MyEntities
   {
       get{ retrun _myEntities; }
   }
}
To do a similar thing in a relational database one would normally have two tables, the entity table and a table to map the relationships between the entities.
create table MyEntity(
   id int
)

create table MyEntityMap(
   from_id int,
   to_id int
)
Now, how do we recreate object map in memory from the relational data. My solution is to use a temporary domain class to represent the map table. It has a reference to the entities on both ends of the relationship and a method to wire them up.
class MyEntityMap
{
   MyEntity _fromEntity;
   MyEntity _toEntity;

   public void WireUp()
   {
       _fromEntity.MyEntities.Add(_toEntity);
   }
}
The MyEntityMapCollection class has a method WireUp() that calls WireUp() on every MyEntityMap object.
class MyEntityMapCollection
{
   ArrayList _myEntityList = new ArrayList();

   ...

   public void WireUp()
   {
      foreach(MyEntity myEntity in _myEntityList)
      {
         myEntity.WireUp();
      }
   }
}
Then in our data access layer we can simply get all the MyEntity objects from the database, storing pointers to them in an object store, then get all the MyEntityMap objects. Each MyEntityMap object gets the pointers for the correct objects from the object store. The last task is to call WireUp() on the MyEntityMapCollection. Here's the DAL code.
public MyEntity GetEntityMap()
{
   MyEntityCollection myEntities = SelectAllEntitiesStoredProcedureWrapper();
   MyEntityMapCollection myMap = SelectAllEntityMapStoredProcedureWrapper();
   myMap.WireUp();

   // some method to get the root MyEntity
   return GetRootEntity(myEntities);
}

Friday, February 03, 2006

Seriously hard core debugging

This is a great blog: If broken it is, fix it you should. She goes really deep into dot net debugging tools and there's some great advice about common application problems. One of my favorite bug bears is ASP Session State. I've seen quite a few applications that fall into the trap of overusing the Session State and then having huge issues with scalability. One such was a mission critical £100 million billing and provisioning system for one of the worlds top cable companies. The original developers had put everything in session state and it simply wouldn't scale. This was classic ASP so they ended up putting an expensive cisco sticky router in front of it, but even that didn't solve all the problems. I remember having a lengthy discussion at the time with someone who said that all these problems will be solved with ASP.NET because you can move the session state handling off to SQL Server. My point was that if you're writing an enterprise application with a SQL server back end, why not maintain your application state by restoring your domain model directly via your Data acces layer on each page hit. That way you'll have real control over state and you wont hit all the scalability issues that Tess goes into in her blog.

Wednesday, February 01, 2006

A VS Macro to count projects classes and functions

I was playing around writing Macros for VS 2003 recently, exploring the CodeModel API and came up with this little solution profiler that counts the number of projects, classes and functions in the current solution. Microsoft has a load of VS 2005 automation samples you can download here
Option Explicit On 

Imports EnvDTE
Imports System.Diagnostics

Public Module SolutionProfiler

    Private Const newline = vbLf
    Private m_textDocument As TextDocument
    Private m_editPoint As EditPoint
    Private m_indent As Integer = 0
    Private m_writeOn As Boolean = False

    Private m_numberOfProjects As Integer = 0
    Private m_numberOfClasses As Integer = 0
    Private m_numberOfFunctions As Integer = 0
    Private m_linesOfCode As Integer = 0

    Public Sub ProfileSolution()

        DTE.ItemOperations.NewFile("General\Text File")
        m_textDocument = DTE.ActiveDocument.Object("TextDocument")
        m_editPoint = m_textDocument.StartPoint.CreateEditPoint()

        Dim solution As Solution = DTE.Solution

        For Each project As Project In solution.Projects
            WriteLine(project.Name)
            WriteProject(project)
        Next

        WriteSummary()

    End Sub

    Private Sub WriteSummary()

        m_writeOn = True
        WriteLine(String.Format("Number of Projects  = {0}", m_numberOfProjects))
        WriteLine(String.Format("Number of Classes   = {0}", m_numberOfClasses))
        WriteLine(String.Format("Number of Functions = {0}", m_numberOfFunctions))
        WriteLine(String.Format("Number of LOK       = {0}", m_linesOfCode))

    End Sub

    Private Sub WriteProject(ByVal project As Project)

        m_numberOfProjects += 1

        Dim codeModel As CodeModel = project.CodeModel
        If Not codeModel Is Nothing Then
            TabIn()
            For Each childElement As CodeElement In codeModel.CodeElements
                If TypeOf childElement Is CodeClass Then
                    WriteClass(childElement)
                End If
                If TypeOf childElement Is CodeEnum Then
                    WriteLine("Enum: " & childElement.FullName)
                End If
                If TypeOf childElement Is CodeInterface Then
                    WriteLine("Interface: " & childElement.FullName)
                End If
            Next
            TabOut()
        End If

    End Sub

    Private Sub WriteClass(ByVal codeClass As CodeClass)

        If codeClass Is Nothing Then
            Return
        End If

        m_numberOfClasses += 1

        WriteLine("Class: " & codeClass.FullName)

        TabIn()
        WriteLine("Properties")
        TabIn()
        For Each member As CodeElement In codeClass.Members
            If TypeOf member Is CodeProperty Then
                WriteProperties(member)
            End If
        Next
        TabOut()
        WriteLine("Functions")
        TabIn()
        For Each member As CodeElement In codeClass.Members
            If TypeOf member Is CodeFunction Then
                WriteFunction(member)
            End If
        Next
        TabOut()
        TabOut()

    End Sub

    Private Sub WriteProperties(ByVal codeProperty As CodeProperty)

        If codeProperty Is Nothing Then
            Return
        End If

        WriteLine(codeProperty.Name)

    End Sub

    Private Sub WriteFunction(ByVal codeFunction As CodeFunction)

        If codeFunction Is Nothing Then
            Return
        End If

        m_numberOfFunctions += 1
        WriteLine(codeFunction.Name)

        Dim startPoint As TextPoint = codeFunction.StartPoint
        Dim endPoint As TextPoint = codeFunction.EndPoint

        Dim lines As Integer = endPoint.Line - startPoint.Line
        m_linesOfCode += lines

    End Sub

    Private Sub TabIn()
        m_indent += 1
    End Sub

    Private Sub TabOut()
        m_indent -= 1
    End Sub

    Private Sub WriteLine(ByVal line As String)

        If m_writeOn Then
            m_editPoint.Insert(New String(vbTab, m_indent) & line & newline)
        End If

    End Sub

End Module


Wednesday, January 25, 2006

PDC 05 Downloads

You can now download any of the PDC 05 sessions free from here.

Thursday, January 19, 2006

The official Microsoft GUI Guidelines

I've been having a lot of heated discussions recently around Windows forms interfaces. Although I feel that I've got quite a good instictive grasp of GUI design I sometimes find it hard to justify why we should do things in a particular way. I wish I'd read this first!

Friday, January 13, 2006

LINQ

I've recently been getting very excited about Microsoft's LINQ project. It brings SQL style declarative query programming into the .net framework and makes it a first class feature of C# and VB.NET. The overview by Don Box and Anders Hejlsberg is here and there's an excellent overview of the current state of the object/relational problem and how LINQ will help by Ted Neward here. There's also a great Channel 9 video of Anders Hejlsberg explaining LINQ.

Monday, October 31, 2005

Passing complex object graphs through web services

The web method must be attributed as SoapRpcMethod and all the types that occur in the object graph must be defined by using BOTH XmlInclude AND SoapInclude attributes:
 _
Public Function GetPersons() As Person()
    ' code to return person
End Function
The client proxy class must have SoapInclude attributes for all types in the object graph:

System.ComponentModel.DesignerCategoryAttribute("code"), _
System.Web.Services.WebServiceBindingAttribute(Name:="PersonServiceSoap", [Namespace]:="http://tempuri.org/Mike.ObjectWS.Server/PersonService"), _ 
SoapInclude(GetType(Person)), _
SoapInclude(GetType(Nurse)), _ 
SoapInclude(GetType(Doctor))> _
Public Class PersonService    
Inherits System.Web.Services.Protocols.SoapHttpClientProtocol
    ' implementation
End Class
When you generate a proxy class using WSDL.exe (via Visual Studio too) it will generate dummy versions of all the classes described in the WSDL file. You need to delete these and make sure your proxy class has a reference to your domain objects so that it deserializes the soap message as the correct types. Passing object graphs through web services puts limitations on your object design. Rocky Lhotka discusses this here This is because of the way objects are serialized by web services. You have to have a default constructor and all properties have to be gettable and settable. This is unfortunate because it means you can’t do good object oriented design. An alternative is to use the binary serializer and then pass the resulting byte array using web services, but then you loose the interoperability that web services give you.