In the first article in this series, Information-Rich Programming With F# 3.0, I provided a brief overview of one of the most interesting features of F# 3.0: the new type providers. In addition, I developed a complete example with one of the options to establish a quick connection to a SQL database. This second and final article on information-rich programming with F# 3.0 shows how to consume old but still popular WSDL services, newer OData services, and other kinds of data sources around the Web.
Consuming a WSDL Web Service with a Type Provider
Web Service Description Language (WSDL) is an XML-based standard format for describing network services. It was a part of the original implementation of Web services, prior to the wide adoption of REST. It is a bit old school, but still widely used.
If you want to make calls with a typed language (such as many .NET languages), you have to use a tool to generate code that interprets the XML to provide type information and generate .NET code to make it easy for developers to make the Web Service calls with typed requests and responses. However, F# 3.0 includes a type provider prepared to do that job (without needing to use the Add Service Reference feature that might be familiar to some .NET developers who used WSDL in the past): the WsdlService (Microsoft.FSharp.Data.TypeProviders.WsdlService)
provider.
If you're working in an F# project, you must add the following references before using the WsdlService
:
System.ServiceModel
andSystem.Runtime.Serialization
in Assemblies | Framework. In addition, if you plan to use LINQ, don't forget to add bothSystem.Dat
a andSystem.Data.Linq
in Assemblies | Framework.FSharp.Data.TypeProviders
in Assemblies | Extensions.
If you prefer the F# script and you want to work in the FSI window, the following lines will have the same effect. Note that you will want to execute them if you want to test code in the FSI window related to the WsdlService
type provider.
#r "System.Data" #r "System.Data.Linq" #r "System.Runtime.Serialization " #r "System.ServiceModel " #r "FSharp.Data.TypeProviders"
I've chosen a simple weather Web Service for this example. I just want to make a call to the GetCityForecastByZIP
Web Service method. You can inspect the entire WSDL for the Web Service by visiting http://wsf.cdyne.com/WeatherWS/Weather.asmx?WSDL (see Figure 1).

If you take a look at the XML portion of the WSDL service definition that specifies the request and the response for that method, it is easy to see that the WsdlService
type provider can supply the necessary information to infer types from there:
<s:element name="GetCityForecastByZIP"> <s:complexType> <s:sequence> <s:element minOccurs="0" maxOccurs="1" name="ZIP" type="s:string"/> </s:sequence> </s:complexType> </s:element> <s:element name="GetCityForecastByZIPResponse"> <s:complexType> <s:sequence> <s:element minOccurs="0" maxOccurs="1" name="GetCityForecastByZIPResult" type="tns:ForecastReturn"/> </s:sequence> </s:complexType> </s:element> <s:complexType name="ForecastReturn"> <s:sequence> <s:element minOccurs="1" maxOccurs="1" name="Success" type="s:boolean"/> <s:element minOccurs="0" maxOccurs="1" name="ResponseText" type="s:string"/> <s:element minOccurs="0" maxOccurs="1" name="State" type="s:string"/> <s:element minOccurs="0" maxOccurs="1" name="City" type="s:string"/> <s:element minOccurs="0" maxOccurs="1" name="WeatherStationCity" type="s:string"/> <s:element minOccurs="0" maxOccurs="1" name="ForecastResult" type="tns:ArrayOfForecast"/> </s:sequence> </s:complexType> <s:complexType name="ArrayOfForecast"> <s:sequence> <s:element minOccurs="0" maxOccurs="unbounded" name="Forecast" nillable="true" type="tns:Forecast"/> </s:sequence> </s:complexType> <s:complexType name="Forecast"> <s:sequence> <s:element minOccurs="1" maxOccurs="1" name="Date" type="s:dateTime"/> <s:element minOccurs="1" maxOccurs="1" name="WeatherID" type="s:short"/> <s:element minOccurs="0" maxOccurs="1" name="Desciption" type="s:string"/> <s:element minOccurs="1" maxOccurs="1" name="Temperatures" type="tns:temp"/> <s:element minOccurs="1" maxOccurs="1" name="ProbabilityOfPrecipiation" type="tns:POP"/> </s:sequence> </s:complexType> <s:complexType name="temp"> <s:sequence> <s:element minOccurs="0" maxOccurs="1" name="MorningLow" type="s:string"/> <s:element minOccurs="0" maxOccurs="1" name="DaytimeHigh" type="s:string"/> </s:sequence> </s:complexType> <s:complexType name="POP"> <s:sequence> <s:element minOccurs="0" maxOccurs="1" name="Nighttime" type="s:string"/> <s:element minOccurs="0" maxOccurs="1" name="Daytime" type="s:string"/> </s:sequence> </s:complexType>
The following lines show the basic usage of the WsdlService
type provider to make a synchronous call to the GetCityForecastByZIP
method and display the description for the weather forecast (for example, Mostly Cloudy). Notice that in this case, it isn't necessary to make additional configurations for security, endpoints, and channels.
module WsdlConnectivity = open System open System.Runtime.Serialization open System.ServiceModel open Microsoft.FSharp.Data.TypeProviders [<Literal>] let WeatherServiceAddress = "http://wsf.cdyne.com/WeatherWS/Weather.asmx?WSDL" type WeatherService = WsdlService<WeatherServiceAddress> let weatherServiceClient = WeatherService.GetWeatherSoap() let zipCode = "89509" let forecastResult = weatherServiceClient.GetCityForecastByZIP zipCode // It's OK, it's Desciption instead of Description (a bug in the WSDL) printfn "%s: %s" zipCode forecastResult.ForecastResult.[0].Desciption let ReadAnyKey() = Console.WriteLine("Press any key to continue") Console.ReadKey() |> ignore ReadAnyKey()
This code is easy to understand. WeatherService
is the type abbreviation (or alias) for the WsdlService
type provider with the specified Web service WSDL definition address. In this case, the Web Service WSDL definition address is defined as a literal constant (notice the literal attribute).
The GetWeatherSoap
static method gets a simplified data context for the WSDL service. weatherServiceClient
is of type WeatherService.ServiceTypes.SimpleDataContextTypes.WeatherSoapClien
t. Because the type provider already established the connection and generated the necessary types, when you enter "WeatherService,"
IntelliSense will display the available methods, such as GetWeatherSoap
and the ServiceTypes
with all the types required for the service as nested types (see Figure 2).

If you enter the following line:
let forecastResult = weatherServiceClient.
you will notice how IntelliSense displays all the Web service methods that you can call in two versions: synchronous and asynchronous (see Figure 3):
GetCityForecastByZIP
GetCityForecastByZIPAsync
GetCityWeatherByZIP
GetCityWeatherByZIPAsync
GetWeatherInformation
GetWeatherInformationAsync

In the previous example, the code calls the GetCityForecastByZIP
Web service method with a synchronous execution and displays the ZIP code and the returned weather forecast description with printfn
. Once you write the line that defines forecastResult
, the type inference mechanism knows that forecastResult
is of type
WeatherService.ServiceTypes.ws.cdyne.com.WeatherWS.ForecastReturn: let forecastResult = weatherServiceClient.GetCityForecastByZIP zipCode
Thus, after you write "forecastResult,"
IntelliSense will display the different fields available for the response. To keep the example simple, I haven't added exception handling and I assume I'll have a successful response from the Web Service. The response returns an array of forecast results. I also assume there is at least one item (see Figure 4). This way, you focus on the code and you can consume a complex Web Service without having to use additional tools or generate new code for your project. You focus on creating your F# algorithms with the results of the Web service calls.

If you execute the code, you will see the ZIP code with the description of the weather forecast displayed on the console after the Web service call is successful:
89509: Mostly Cloudy Press any key to continue
Making Asynchronous Calls to a WSDL Web Service with a Type Provider
The previous example made calls to the GetCityForecastByZIP
Web service method with synchronous execution. If you want to retrieve the weather forecast description for dozens of ZIP codes, it is a good idea to take advantage of the asynchronous Web service methods that the WsdlService
type provider generates (the asynchronous methods end with the Async
suffix).
The following lines show a more complex usage of the WsdlService
type provider to launch multiple asynchronous calls to the GetCityForecastByZIPAsync
method in parallel, and display the description for the weather forecasts for all the ZIP codes defined in the zipCodes
string list.
module WsdlConnectivity = open System open System.ServiceModel open System.Runtime.Serialization open Microsoft.FSharp.Data.TypeProviders [<Literal>] let WeatherServiceAddress = "http://wsf.cdyne.com/WeatherWS/Weather.asmx?WSDL" type WeatherService = WsdlService<WeatherServiceAddress> let weatherServiceClient = WeatherService.GetWeatherSoap() let zipCodes = [ "89509"; "90042"; "90404"; "91101"; "89510"; "90043"; "90405"; "91103"; "89511"; "90044"; "90501"; "91104"; "89512"; "90045"; "90502"; "91105"; "89523"; "90046"; "90503"; "91106"; "89701"; "90047"; "90504"; "91107"; "89703"; "90048"; "90505"; "91108"; "89704"; "90049"; "90601"; "91201"; "89705"; "90056"; "90602"; "91202"; "89706"; "90057"; "90603"; "91203"; "89801"; "90058"; "90604"; "91204"; "89803"; "90059"; "90605"; "91205"; "89815"; "90061"; "90606"; "91206"; "89820"; "90062"; "90620"; "91207"; "89821"; "90063"; "90621"; "91208"; "89822"; "90064"; "90623"; "91214"; "89823"; "90065"; "90630"; "91301"; "89824"; "90066"; "90631"; "91302"; "89825"; "90067"; "90638"; "91303"; "89826"; "90068"; "90640"; "91304"; "89828"; "90069"; "90650"; "91306"; "89830"; "90071"; "90660"; "91307"; "89831"; "90077"; "90670"; "91311"; "89832"; "90201"; "90680"; "91316"; "89833"; "90210"; "90701"; "91320"; "89834"; "90211"; "90703"; "91321"; "89835"; "90212"; "90704"; "89883"; "90220"; "90706" ] zipCodes |> List.map (fun zipCode -> async { let! forecastResult = Async.AwaitTask <| weatherServiceClient.GetCityForecastByZIPAsync zipCode // It's OK, it's Desciption instead of Description (a bug in the WSDL) do Console.WriteLine("{0}: {1}", zipCode, forecastResult.Body.GetCityForecastByZIPResult.ForecastResult.[0].Desciption) }) |> Async.Parallel |> Async.Ignore |> Async.RunSynchronously let ReadAnyKey() = Console.WriteLine("Press any key to continue") Console.ReadKey() |> ignore ReadAnyKey()
The code is easy to understand. The zipCode
s feed List.map
, which launches an asynchronous block that uses Async.AwaitTask
to wait for the asynchronous operation before going on with the next statement. Thus, the results will appear on the console after the service call returns with the result. However, the key advantage is that Async.Parallel
creates an asynchronous computation that queues and executes all the provided asynchronous computations with a fork/join pattern. Thus, the code maximizes throughput in Web service calls that have some latency and provides results as soon as each Web service call finishes executing (See Figure 5).

The Web service methods generated by the WsdlService
type provider that end with the Async
suffix simplify the generation of asynchronous computations in F#, and make it easy to execute many Web service methods in parallel. Of course, it is necessary to add exception handling and to check the results of the Web service call before displaying them. In this example, I simply want to ensure you understand why the WsdlService
type provider provides the methods with the Async
suffix and how you can use it with asynchronous computations.
Consuming an OData Web Service with a Type Provider
Many modern Web Services use the Open Data Protocol (OData) Web protocol to expose information. OData is ideal when you need to query and update data, and therefore, F# 3.0 also includes a type provider prepared to interact with an OData Web Service, the ODataService (Microsoft.FSharp.Data.TypeProviders.ODataService)
provider.
If you're working in an F# project, you must add the following references before using the ODataService
:
FSharp.Data.TypeProviders
in Assemblies | Extensions.- If you plan to use LINQ, don't forget to add both
System.Data
andSystem.Data.Linq
in Assemblies | Framework.
If you prefer the F# script and you want to work in the FSI window, the following lines will have the same effect. Take into account that you will have to execute them if you want to test code in either the FSI window or any F# script related to the ODataService
type provider.
#r "System.Data" #r "System.Data.Linq" #r "FSharp.Data.TypeProviders"
I've chosen a simple OData service from the International .NET Association (INETA) for this example. I simply want to make a call to query the presentations to find the first one that contains a specific word in the abstract. You can inspect the exposed collections by visiting http://live.ineta.org/InetaLiveService.svc/ (see Figure 6).
