The Greater Helsinki Area F# User Group - F# & Azure - FI EN

Azure Blob- and Table Storage

Let's use the WorkerRole from previous tutorials and also Windows Azure Storage -library. (Earlier version of this tutorial used Fog-library but it didn't work well with the latest Azure SDKs.)

First task is to add connection strings to Azure storage. It is done like this:

Open the deployment-project file ServiceDefinition.csdef and add there settings: TableStorageConnectionString and BlobStorageConnectionString. In the Azure emulator -environment these can be empty.

File content attribute name="FSharpAzure" refers to the name of the solution and schemaVersion="2013-10.2.2" refers to the used AzureSDK, here 2.2, but 2.3 would be schemaVersion="2014-01.2.3"and 2.4 would be schemaVersion="2014-06.2.4".

<?xml version="1.0" encoding="utf-8"?>
<ServiceDefinition name="FSharpAzure" 
xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition"
schemaVersion="2013-10.2.2">
  <WorkerRole name="WorkerRole1" vmsize="ExtraSmall">
    <Endpoints>
      <InputEndpoint name="Endpoint1" protocol="http" port="80" localPort="80" />
    </Endpoints>
    <Imports>
      <Import moduleName="Diagnostics" />
    </Imports>
    <ConfigurationSettings>
      <Setting name="TableStorageConnectionString" />
      <Setting name="BlobStorageConnectionString" />
    </ConfigurationSettings>    
  </WorkerRole>
</ServiceDefinition>

Modify also the files ServiceConfiguration.Cloud.cscfg and ServiceConfiguration.Local.cscfg to contain the corresponding settings:

<?xml version="1.0" encoding="utf-8"?>
<ServiceConfiguration serviceName="FSharpAzure" 
xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration"
osFamily="4" osVersion="*" schemaVersion="2013-10.2.2">
  <Role name="WorkerRole1">
    <Instances count="1" />
    <ConfigurationSettings>
      <Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" 
      value="UseDevelopmentStorage=true" />
      <Setting name="TableStorageConnectionString" value="UseDevelopmentStorage=true" />
      <Setting name="BlobStorageConnectionString" value="UseDevelopmentStorage=true" />
    </ConfigurationSettings>
  </Role>
</ServiceConfiguration>

For emulator, it is OK to use value="UseDevelopmentStorage=true", but in the production this connection-string should be something else. For this, there is tutorial in the net.

Using F-Sharp script-files

Using these codes in interactive-environment works, but execution of the actual Azure-calls won't. So you may add to beginning of the file:

1: 
2: 
3: 
4: 
5: 
#if INTERACTIVE
#r "../packages/Fog.0.1.4.1/Lib/Net40/Fog.dll"
#r "System.Data.Services.Client.dll"
#r "Microsoft.WindowsAzure.StorageClient.dll"
#endif

Note that NuGet may have fetched you different version of the component than in this example. So if Visual Studio underlines the line with red, and says the file doesn't exist, this may be the result of you having newer version of the component, in another path. Another commonly used practice in F# is to add one file of type Script File (*.fsx) to the project. This runs only in interactive-style scripting but will be excluded from the actual compilation. These #r:s works also there and project's .fs-files could be loaded like this:

1: 
2: 
#load "MyLogics.fs"
open MyLogics

Caching and settings

This code works with Blob and Table -storages. Open the logics file and add on this code to it:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
module MyLogics

open System
open Microsoft.WindowsAzure.ServiceRuntime
open Microsoft.WindowsAzure.Storage

open System.Collections.Concurrent
let dict = ConcurrentDictionary() //for caching

let fetchSetting = RoleEnvironment.GetConfigurationSettingValue >> CloudStorageAccount.Parse

We use memoize for caching. Memoize/memoization caches the whole function call and not only input parameters like normal caching.

Azure Blob Storage

Blob-storage is a simple data storage e.g. for files. Add this code under the previous:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
let blobConnection (container:string) = 
    let account = fetchSetting "BlobStorageConnectionString"
    let client = account.CreateCloudBlobClient()
    client.GetContainerReference(container.ToLower())

let uploadBlobToContainer containerName blobName (item:string) = 
    let memoize f = fun x -> dict.GetOrAdd(Some x, lazy (f x)).Force()
    let container = memoize(fun cont -> blobConnection cont) containerName
    async {
        let! ok = container.CreateIfNotExistsAsync() |> Async.AwaitTask
        let blob = container.GetBlockBlobReference(blobName)
        let enc = System.Text.Encoding.ASCII.GetBytes(item)
        use ms = new System.IO.MemoryStream(enc, 0, enc.Length)
        do! blob.UploadFromStreamAsync ms |> Async.AwaitIAsyncResult |> Async.Ignore
    }

let addToBlob = uploadBlobToContainer "testContainer" "testBlob"

open System
open FSharp.Data
let demoData =
    let ``scientist of the day `` = 
        FreebaseData.GetDataContext().Commons.Computers.``Computer Scientists``
        |> Seq.skip DateTime.Now.DayOfYear //
        |> Seq.head
    "Scientist of the day: " + ``scientist of the day ``.Name

Now you may call this from the WorkerRole.fs file's method wr.OnStart():

1: 
MyLogics.demoData |> MyLogics.addToBlob

When you start the program (F5), you may find from Server Explorer (not Solution Explorer) that (after refresh) there has been appeared a blob which you can double-click to open, and there is the blob-list that you can again double-click to open the data itself:

Azure Table Storage

Azure Table Storage is NoSQL-minded data storage.

Here is a code sample to use it:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
let ``Azure dvd table`` = "Dvd"

[<Measure>]
type stars

open Microsoft.WindowsAzure.Storage.Table

type MyDvdEntity(partitionKey, rowKey, name, rating) = 
    inherit TableEntity(partitionKey, rowKey)
    new(name, rating) = MyDvdEntity("defaultPartition", System.Guid.NewGuid().ToString(), name, rating)
    new() = MyDvdEntity("", 0<stars>)
    member val Name = name with get, set
    member val Rating = rating with get, set

let tableConnection tableName = 
    let account = fetchSetting "TableStorageConnectionString"
    let client = account.CreateCloudTableClient()
    client.GetTableReference(tableName)

let doAction tableName operation = 
    let memoize f = fun x -> dict.GetOrAdd(Some x, lazy (f x)).Force()
    let table = memoize(fun tb -> tableConnection tb) tableName
    async {
        let! created = table.CreateIfNotExistsAsync() |> Async.AwaitTask
        return! table.ExecuteAsync(operation) |> Async.AwaitTask
    }

let addDvd name rating = 
    let dvd = MyDvdEntity(
                PartitionKey = "myPartition",
                RowKey = System.Guid.NewGuid().ToString(),
                Name = name,
                Rating = rating
              )
    dvd, dvd
    |> TableOperation.Insert //Insert, Delete, Replace, etc.
    |> doAction ``Azure dvd table``
    |> Async.RunSynchronously

let updateDvd dvd = 
    dvd |> TableOperation.Replace |> doAction ``Azure dvd table`` |> Async.RunSynchronously

let deleteDvd dvd = 
    dvd |> TableOperation.Delete |> doAction ``Azure dvd table`` |> Async.RunSynchronously

...and corresponding calls to WorkerRole.fs to OnStart-method before base.OnStart, e.g.:

1: 
2: 
3: 
let dvd, result = MyLogics.addDvd "Godfather" 4<stars>
dvd.Rating <- 5<MyLogics.stars>
let result2 = MyLogics.updateDvd dvd

The actual Table Storage query language is very limited and the new Table Service Layer doesn't support LINQ so the queries has to be done with TableOperation.Retrieve-method, or constructing separate TableQuery and execute it with ExecuteQuery-method.

Back to the menu

namespace System
namespace Microsoft
namespace Microsoft.WindowsAzure
namespace System.Collections
namespace System.Collections.Concurrent
val dict : keyValuePairs:seq<'Key * 'Value> -> System.Collections.Generic.IDictionary<'Key,'Value> (requires equality)

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.dict
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = System.String

Full name: Microsoft.FSharp.Core.string
union case Option.Some: Value: 'T -> Option<'T>
val async : AsyncBuilder

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
Multiple items
type Async
static member AsBeginEnd : computation:('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)
static member AwaitEvent : event:IEvent<'Del,'T> * ?cancelAction:(unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate)
static member AwaitIAsyncResult : iar:IAsyncResult * ?millisecondsTimeout:int -> Async<bool>
static member AwaitTask : task:Task<'T> -> Async<'T>
static member AwaitWaitHandle : waitHandle:WaitHandle * ?millisecondsTimeout:int -> Async<bool>
static member CancelDefaultToken : unit -> unit
static member Catch : computation:Async<'T> -> Async<Choice<'T,exn>>
static member FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg:'Arg1 * beginAction:('Arg1 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * beginAction:('Arg1 * 'Arg2 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * arg3:'Arg3 * beginAction:('Arg1 * 'Arg2 * 'Arg3 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromContinuations : callback:(('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T>
static member Ignore : computation:Async<'T> -> Async<unit>
static member OnCancel : interruption:(unit -> unit) -> Async<IDisposable>
static member Parallel : computations:seq<Async<'T>> -> Async<'T []>
static member RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:CancellationToken -> 'T
static member Sleep : millisecondsDueTime:int -> Async<unit>
static member Start : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions * ?cancellationToken:CancellationToken -> Task<'T>
static member StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>>
static member StartChildAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions -> Async<Task<'T>>
static member StartImmediate : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartWithContinuations : computation:Async<'T> * continuation:('T -> unit) * exceptionContinuation:(exn -> unit) * cancellationContinuation:(OperationCanceledException -> unit) * ?cancellationToken:CancellationToken -> unit
static member SwitchToContext : syncContext:SynchronizationContext -> Async<unit>
static member SwitchToNewThread : unit -> Async<unit>
static member SwitchToThreadPool : unit -> Async<unit>
static member TryCancelled : computation:Async<'T> * compensation:(OperationCanceledException -> unit) -> Async<'T>
static member CancellationToken : Async<CancellationToken>
static member DefaultCancellationToken : CancellationToken

Full name: Microsoft.FSharp.Control.Async

--------------------
type Async<'T>

Full name: Microsoft.FSharp.Control.Async<_>
static member Async.AwaitTask : task:System.Threading.Tasks.Task<'T> -> Async<'T>
namespace System.Text
type Encoding =
  member BodyName : string
  member Clone : unit -> obj
  member CodePage : int
  member DecoderFallback : DecoderFallback with get, set
  member EncoderFallback : EncoderFallback with get, set
  member EncodingName : string
  member Equals : value:obj -> bool
  member GetByteCount : chars:char[] -> int + 3 overloads
  member GetBytes : chars:char[] -> byte[] + 5 overloads
  member GetCharCount : bytes:byte[] -> int + 2 overloads
  ...

Full name: System.Text.Encoding
property System.Text.Encoding.ASCII: System.Text.Encoding
System.Text.Encoding.GetBytes(s: string) : byte []
System.Text.Encoding.GetBytes(chars: char []) : byte []
System.Text.Encoding.GetBytes(chars: char [], index: int, count: int) : byte []
System.Text.Encoding.GetBytes(chars: nativeptr<char>, charCount: int, bytes: nativeptr<byte>, byteCount: int) : int
System.Text.Encoding.GetBytes(s: string, charIndex: int, charCount: int, bytes: byte [], byteIndex: int) : int
System.Text.Encoding.GetBytes(chars: char [], charIndex: int, charCount: int, bytes: byte [], byteIndex: int) : int
namespace System.IO
Multiple items
type MemoryStream =
  inherit Stream
  new : unit -> MemoryStream + 6 overloads
  member CanRead : bool
  member CanSeek : bool
  member CanWrite : bool
  member Capacity : int with get, set
  member Flush : unit -> unit
  member GetBuffer : unit -> byte[]
  member Length : int64
  member Position : int64 with get, set
  member Read : buffer:byte[] * offset:int * count:int -> int
  ...

Full name: System.IO.MemoryStream

--------------------
System.IO.MemoryStream() : unit
System.IO.MemoryStream(capacity: int) : unit
System.IO.MemoryStream(buffer: byte []) : unit
System.IO.MemoryStream(buffer: byte [], writable: bool) : unit
System.IO.MemoryStream(buffer: byte [], index: int, count: int) : unit
System.IO.MemoryStream(buffer: byte [], index: int, count: int, writable: bool) : unit
System.IO.MemoryStream(buffer: byte [], index: int, count: int, writable: bool, publiclyVisible: bool) : unit
static member Async.AwaitIAsyncResult : iar:System.IAsyncResult * ?millisecondsTimeout:int -> Async<bool>
static member Async.Ignore : computation:Async<'T> -> Async<unit>
namespace Microsoft.FSharp.Data
module Seq

from Microsoft.FSharp.Collections
val skip : count:int -> source:seq<'T> -> seq<'T>

Full name: Microsoft.FSharp.Collections.Seq.skip
val head : source:seq<'T> -> 'T

Full name: Microsoft.FSharp.Collections.Seq.head
Multiple items
type MeasureAttribute =
  inherit Attribute
  new : unit -> MeasureAttribute

Full name: Microsoft.FSharp.Core.MeasureAttribute

--------------------
new : unit -> MeasureAttribute
Multiple items
type Guid =
  struct
    new : b:byte[] -> Guid + 4 overloads
    member CompareTo : value:obj -> int + 1 overload
    member Equals : o:obj -> bool + 1 overload
    member GetHashCode : unit -> int
    member ToByteArray : unit -> byte[]
    member ToString : unit -> string + 2 overloads
    static val Empty : Guid
    static member NewGuid : unit -> Guid
    static member Parse : input:string -> Guid
    static member ParseExact : input:string * format:string -> Guid
    ...
  end

Full name: System.Guid

--------------------
System.Guid()
System.Guid(b: byte []) : unit
System.Guid(g: string) : unit
System.Guid(a: int, b: int16, c: int16, d: byte []) : unit
System.Guid(a: uint32, b: uint16, c: uint16, d: byte, e: byte, f: byte, g: byte, h: byte, i: byte, j: byte, k: byte) : unit
System.Guid(a: int, b: int16, c: int16, d: byte, e: byte, f: byte, g: byte, h: byte, i: byte, j: byte, k: byte) : unit
System.Guid.NewGuid() : System.Guid
val set : elements:seq<'T> -> Set<'T> (requires comparison)

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.set
static member Async.RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:System.Threading.CancellationToken -> 'T

Creative Commons -copyright Tuomas Hietanen, 2014, thorium(at)iki.fi, Creative Commons