This post is part of series:
Part 4 – Strongly typed SharePoint list operations using the repository pattern in F#
Part 3 – Bringing together repository and tree structure in business layer
Part 2 – Creating and working with a custom tree data structure in C#
Part 1 – Strongly typed SharePoint list operations using the repository pattern
As an experiment, I wanted to re-implement the SharePoint repository pattern from Part 1 in F#.
open System open System.Xml.Linq open Microsoft.SharePoint open Microsoft.SharePoint.Taxonomy type LegalDocument = { Title: string ApprovalStatus: string Locations: seq<string> Level: int ProcessHierarchy: Map<Guid, string> DocumentId: string ParentDocumentId: string Url: Uri } type LegalDocumentRepository() = let LinkFilename = "LinkFilename" let FileLeafRef = "FileLeafRef" let ModerationStatus = "_ModerationStatus" let Location = "Location_Legal" let ProcessHierarchyLevel = "ProcessHierarchyLevel" let ProcessHierarchyLegal = "ProcessHierarchyLegal" let DocumentId = "_dlc_DocId" let ParentDocumentId = "Parent_x0020_Document_x0020_Id" let xn s = XName.Get s member __.Query (list: SPList) (caml: XElement): SPListItemCollection = let query = SPQuery( ViewFields = ([LinkFilename; ModerationStatus; Location; ProcessHierarchyLevel; ProcessHierarchyLegal; DocumentId; ParentDocumentId] |> List.fold (fun acc fld -> acc + XElement(xn "FieldRef", XAttribute(xn "Name", xn fld)).ToString()) ""), Query = caml.ToString(), ViewAttributes = @"Scope=""RecursiveAll""") list.GetItems query member this.GetApprovedDocuments (list: SPList) = let caml = XElement(xn "Where", XElement(xn "Eq", XElement(xn "FieldRef", XAttribute(xn "Name", ModerationStatus)), XElement(xn "Value", XAttribute(xn "Type", xn "ModStat"), "Approved"))) this.Query list caml |> this.Map member __.Map items = let levelMapper o = let s = o |> string match (s |> String.IsNullOrEmpty) with | true -> 0 | false -> let v = s.Split [|':'|] match v.Length with | 0 -> 0 | _ -> v.[0] |> int let locationMapper (o: obj) = o :?> TaxonomyFieldValueCollection |> Seq.map (fun f -> f.Label) let processHierarchyMapper (o: obj) = let t = o :?> TaxonomyFieldValueCollection t |> Seq.map (fun f -> Guid(f.TermGuid), f.Label) |> Map.ofSeq let parentDocumentIdMapper o = let s = o |> string match (String.IsNullOrEmpty s) with | true -> "" | false -> s.Trim() let urlPrefix = items.List.ParentWeb.Url + "/" items |> Seq.cast<SPListItem> |> Seq.map (fun i -> { Title = i.[LinkFilename] |> string ApprovalStatus = i.[ModerationStatus] |> string Locations = locationMapper i.[Location] Level = levelMapper i.[ProcessHierarchyLevel] ProcessHierarchy = processHierarchyMapper i.[ProcessHierarchyLegal] DocumentId = i.[DocumentId] |> string ParentDocumentId = parentDocumentIdMapper i.[ParentDocumentId] Url = Uri (urlPrefix + i.Url) }) [<EntryPoint>] let main args = use siteCollection = new SPSite "http://intranet" use site = siteCollection.OpenWeb "Legal" let list = site.Lists.["LegalDocuments"] let repository = LegalDocumentRepository() let documents = repository.GetApprovedDocuments list documents |> Seq.iter (printfn "%A") 0
In C#, the cast from string to LINQ to XML type is implicit. In F#, casts are generally explicit so we require the small xn helper function. Using a record type over a regular LegalDocument class has the benefit that all members must be explicitly set on creation or the compiler will issue an error. This comes in handy when modifying LegalDocument during the application life-cycle as the compiler will make sure we update all places where it’s used.