Thursday, June 11, 2009

F# and WCF

I recently had to implement some self hosted workflows on WF that leverages WCF for inter-process communication.  I’ve found the book Learning WCF by Michele Bustamante helpful for learning and implementing WCF parts of the code and Pro WF by Bruce Bukovics useful for implementing the workflows.  I became curious on how  to implement WCF in F#.  I suspect that WCF services in F# is probably one more likely scenarios for me to introduce F# usage in the corporate environment.  

For the following sample code, I took the last example with DataGrid and stripped out the portion that retrieves a list of Person from the database and implemented it as a WCF hosted on a separate executable.  There are many other blogs on F# and WCF including those by Ray Vernagus, Nick Holmes and Ted Neward.  This blogs were immensely helpful in aiding me to build my own example.

The blog entry by Ray Vernagus seemed to indicate that I needed to define my data contract for Person record with DataContract attribute.  It did not seem like that is necessary in this simple scenario.  However, when I ran my example client code, I did get the following error:

stdin(0,1): error FS0082: Could not resolve this reference. Could not locate the assembly "PersonService.XmlSerializers.dll". Check to make sure the assembly exists on disk. If this reference is required by your code, you may get compilation errors. (Code=MSB3245)
stopped due to error

The client code still works and subsequent runs does not generate this error.  I was unable to track down what caused this error message and what impact it has on the application.  Even after I added the DataContract attributes to Person type, I still get the same error message.  However, I did not see any impact to the client application due to this error.  I got this error with version 1.9.6.2 of the F# compiler.  My next step would be to try this out with VS2010 beta version of F# compiler.

Here’s the WCF server code that serves up a list of Person:


#light

namespace WCF.Test

open System
open System.Data.Common
open System.Data.Sql
open System.Data.SqlClient
open System.ServiceModel
open System.Runtime.Serialization

type Person =
{FirstName:string;
LastName:string;
Email:string;
PhoneNumber:string}

module DB =
let connString = @"Data Source=.\SQLEXPRESS;Initial Catalog=AdventureWorks;Integrated Security=True;"
let conn = new SqlConnection(connString)
let query () =
seq { use conn = new SqlConnection(connString)
do conn.Open()
use comm = new SqlCommand("SELECT top 50 * FROM Person.Contact",conn)
use reader = comm.ExecuteReader()
while reader.Read() do
yield ({FirstName = reader.GetString 3;
LastName = reader.GetString 5;
Email = reader.GetString 7;
PhoneNumber = reader.GetString 9}) }

[<ServiceContract()>]
type IPersonService = interface
[<OperationContract()>]
abstract GetPersons: unit -> Person array
end

[<ServiceBehavior(Name="PersonService",InstanceContextMode=InstanceContextMode.Single)>]
type PersonService() =
interface IPersonService with
member v.GetPersons () =
Console.WriteLine("Retrieving people list...")
DB.query() |> Seq.to_array


do
Console.WriteLine("PersonService")
let serviceType = typeof<PersonService>
let address = new Uri("http://localhost:28888/PersonService")
let host = new ServiceHost(serviceType,[|address|])
host.Open()
Console.WriteLine("Press <ENTER> to terminate the host application")
Console.ReadLine() |> ignore
host.Close()

Here’s the configuration file for the WCF service:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!-- here is the base address of our service -->
<appSettings>
<add key ="baseAddress" value="http://localhost:28888/PersonService"/>
</appSettings>
<system.serviceModel>
<services>
<service name="WCF.Test.PersonService" behaviorConfiguration="PersonServiceBehaviors">
<endpoint address="http://localhost:28888/PersonService"
binding="basicHttpBinding"
contract="WCF.Test.IPersonService"/>
<endpoint contract="IMetadataExchange" binding="mexHttpBinding" address="mex" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="PersonServiceBehaviors" >
<serviceMetadata httpGetEnabled="true" />
<serviceDebug httpHelpPageEnabled="true"
includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>

Here’s the client code:


#light
#I @"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0"
#I @"C:\WINNT\Microsoft.NET\Framework\v2.0.50727"
#r @"WindowsBase.dll"
#r @"PresentationCore.dll"
#r @"PresentationFramework.dll"
#r @"C:\Program Files\WPF Toolkit\v3.5.40320.1\WPFToolkit.dll"
#r @"c:\dev\webservice\PersonService.dll" // Generated with wsdl tool

open Microsoft.Windows.Controls
open System
open System.Windows
open System.Windows.Controls

let service = new PersonService()
let persons = service.GetPersons()

let win = new Window(Title="Test DataGrid w/WCF")

let datagrid = DataGrid()
datagrid.HeadersVisibility <- DataGridHeadersVisibility.Column
datagrid.ItemsSource <- persons


win.Content <- new ScrollViewer(Content=datagrid)
win.Show()



2 comments:

Don Syme said...

You can safely ignore the error about the Serializers DLL - it only occurs once and we will be suppressing it in a future release of F#

Anonymous said...
This comment has been removed by a blog administrator.