It’s been a while since I last took a serious look at F#. Back then I did a simple random fractal terrain generator which, even though the algorithm is simple, I found challenging to do. Nevertheless, functional programming is just one of those areas that I keep returning to. This time around I want to use the event receiver for versioning attachments in SharePoint lists to get familiar with object-oriented F#. Of course, translating a C# class to an F# class, the result will look like C# with different syntax and better type inference. The point here is to use classes in F# as a way to expose functionality to other .NET languages. In a real-world F# application the core logic would likely not be object-oriented.
namespace Dk.Bugfree open System open System.Globalization open Microsoft.SharePoint type public ListAttachmentVersioningEventReceiver() = inherit SPItemEventReceiver() member private r.CustomVersion = "CustomVersion" member private r.ShadowLibrary = "ShadowLibrary" // override ItemAdded : properties:SPItemEventProperties -> unit override r.ItemAdded properties = base.ItemAdded properties r.SetCustomVersionLabel properties.ListItem r.CreateSnapshot properties // override ItemUpdated : properties:SPItemEventProperties -> unit override r.ItemUpdated properties = base.ItemUpdated properties let item = properties.ListItem if r.RollbackHappened item then r.RestoreSnapshot properties r.SetCustomVersionLabel item r.CreateSnapshot properties else r.CreateSnapshot properties r.SetCustomVersionLabel item // member private CreateSnapshot : properties:SPItemEventProperties -> unit member private r.CreateSnapshot properties = use site = properties.OpenWeb() let item = properties.ListItem let shadowLibrary = site.Lists.[r.ShadowLibrary] :?> SPDocumentLibrary let path = String.Format("Versions/{0}/{1}", item.ID, r.GetOfficialVersionLabel(item)) let shadowFolder = r.CreateFolderPath shadowLibrary path item.Attachments |> Seq.cast |> Seq.iter (fun fileName -> let existingFile = item.ParentList.ParentWeb.GetFile(item.Attachments.UrlPrefix + fileName) let newFile = shadowFolder.Files.Add(fileName, existingFile.OpenBinaryStream()) newFile.Item.Update()) // member private RollbackHappened : item:SPListItem -> bool member private r.RollbackHappened item = let culture = CultureInfo.InvariantCulture let currentVersion = Single.Parse(r.GetOfficialVersionLabel(item), culture) let lastVersion = Single.Parse(r.GetCustomVersionLabel(item), culture) currentVersion > lastVersion + 1.0f // member private RestoreSnapshot : properties:SPItemEventProperties -> unit member private r.RestoreSnapshot properties = let item = properties.ListItem let restoreVersion = r.GetCustomVersionLabel item r.EventFiringEnabled <- false item.Attachments |> Seq.cast |> Seq.map (fun fileName -> unbox<string> fileName) |> Seq.toList |> Seq.iter (fun fileName -> item.Attachments.Delete(fileName)) use site = properties.OpenWeb() let path = String.Format("Versions/{0}/{1}", item.ID, restoreVersion) let shadowLibrary = site.Lists.[r.ShadowLibrary] :?> SPDocumentLibrary let source = r.CreateFolderPath shadowLibrary path source.Files |> Seq.cast |> Seq.iter (fun file -> let unboxedFile = unbox<SPFile> file item.Attachments.Add(unboxedFile.Name, unboxedFile.OpenBinary())) item.SystemUpdate false r.EventFiringEnabled <- true // member private CreateFolderPath : list:SPDocumentLibrary -> path:string -> SPFolder member private r.CreateFolderPath list path : SPFolder = r.CreateFolderPathRecursive list.RootFolder (path.Split [|'/'|] |> Array.toList) // member private CreateFolderPathRecursive : folder:SPFolder -> pathComponents:string list -> SPFolder member private r.CreateFolderPathRecursive folder pathComponents = match pathComponents with | [] -> folder | head :: tail -> try let existingFolder = folder.SubFolders.[head] r.CreateFolderPathRecursive existingFolder tail with :? ArgumentException -> let newFolder = folder.SubFolders.Add head r.CreateFolderPathRecursive newFolder tail // member private SetCustomVersionLabel : item:SPListItem -> unit member private r.SetCustomVersionLabel item = r.EventFiringEnabled <- false item.[r.CustomVersion] <- r.GetOfficialVersionLabel item item.SystemUpdate false r.EventFiringEnabled <- true // member private GetCustomVersionLabel : item:SPListItem -> string member private r.GetCustomVersionLabel item = item.[r.CustomVersion] :?> string // member private GetOfficialVersionLabel : item:SPListItem -> string member private r.GetOfficialVersionLabel item = item.Versions.[0].VersionLabel
A couple of things to note about the F# implementation: first, it hardly specifies any types. They’re inferred by the compiler. Where type names do appear, it’s mainly because they’re required to unbox elements of an IEnumerable collection. Secondly, F# has flexible self identifiers. Methods must explicitly specify the name of the this reference in C# and use it when accessing members. Thirdly, arguments to general .NET methods are passed as a tuple value, i.e., as comma-delimited arguments surrounded by parenthesis.