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.