I had the opportunity to take a SharePoint 2010 class recently. In the class, the labs were mostly a cut and paste affair due to time limitations. Those lab exercises only helps me to become familiar with working in Visual Studio and seeing some of the SharePoint API, but does not really help me engage actively in thinking about what I was actually doing. After the class, I decided to write a F# equivalent of the lab exercises to help me get a deeper understanding and to learn. The class did impress on me that the tooling for SharePoint 2010 development is so much more superior in C# such that I've decided to build only the logic code in F# and retain the C# SharePoint project. This blog post describes my effort in getting that first class exercise working with F# code.
I ended up creating an empty SharePoint 2010 C# project that references a F# library project.
I added two application pages to the SharePoint 2010 project, the first being FarmHierarchy.aspx.
In FarmHierarchy.aspx, I added the following markup between the opening and closing tags of the
<asp:Content>
element that has an ID of Main:
<h2>My Farm</h2> <asp:TreeView ID="farmHierarchyViewer" runat="server" ShowLines="true" EnableViewState="true"></asp:TreeView>
The second application page I created was PropertyChanger.aspx. I added the following markup between the opening and closing tags of the
<asp:Content>
elsement that has an ID of Main:
<h2>Properties:</h2> <asp:Label ID="objectName" runat="server" Text=""></asp:Label><br/><br/> <asp:Panel ID="webProperties" runat="server" Visible="false" BorderColor="Orange" BorderStyle="Dashed" BorderWidth="1"> <asp:Label ID="WebLabel" runat="server" Text="Web Title"></asp:Label> <br/> <asp:TextBox ID="webTitle" runat="server" EnableViewState="true"></asp:TextBox> <asp:Button ID="webTitleUpdate" runat="server" Text="Update"/> <asp:Button ID="webCancel" runat="server" Text="Cancel" /> </asp:Panel> <asp:Panel ID="listProperties" runat="server" Visible="false" BorderColor="Orange" BorderStyle="Dashed" BorderWidth="1"> <asp:Label ID="ListLabel" runat="server" Text="List Properties"></asp:Label> <br/> <asp:CheckBox ID="listVersioning" runat="server" EnableViewState="true" Text="Enable Versioning" /> <br/> <asp:CheckBox ID="listContentTypes" runat="server" EnableViewState="true" Text="Enable Content Types" /> <asp:Button ID="listPropertiesUpdate" runat="server" Text="Update" /> <asp:Button ID="listCancel" runat="server" Text="Cancel"/> </asp:Panel>
The F# code would iterate through the services, Web applications, site collections, and lists in the SharePoint farm, with the details added to nodes in the TreeView control:
module FsLab01 open System open System.Web.UI.WebControls open Microsoft.SharePoint open Microsoft.SharePoint.Administration open System.Web.UI // Convenience methods let nullfunc _ = () // Copied from Clojure let cond clauses = let (_,func) = clauses |> Seq.find (fun (pred,func) -> pred) func() let navListUrl url (id:Guid) = sprintf "%s/_layouts/lab01/PropertyChanger.aspx?type=list&objectID=%s" url <| id.ToString() let navWebUrl url (id:Guid) = sprintf "%s/_layouts/lab01/PropertyChanger.aspx?type=web&objectID=%s" url <| id.ToString() // Recursively add SPWeb & SPList objects let rec addWeb (web:SPWeb) (parentNode:TreeNode) = let node = new TreeNode(web.Title,null,null,navWebUrl web.Url web.ID, "_self") node |> parentNode.ChildNodes.Add [0..(web.Lists.Count-1)] |> Seq.map (fun i -> web.Lists.[i]) |> Seq.iter (fun item -> new TreeNode(item.Title,null,null,navListUrl web.Url item.ID,"_self") |> node.ChildNodes.Add) [0..(web.Webs.Count-1)] |> Seq.map (fun i -> web.Webs.[i]) |> Seq.iter (fun item -> try addWeb item node finally item.Dispose()) // Main function to be called by FarmHierarchy.aspx code to build TreeView control let loadViewer (viewer:TreeView) (farm:SPFarm) = let processSite (webappnode:TreeNode) (site:SPSite) = site.CatchAccessDeniedException <- false try let node = new TreeNode(Text=site.Url) node |> webappnode.ChildNodes.Add addWeb site.RootWeb node finally site.CatchAccessDeniedException <- false let processWebApp (svcNode:TreeNode) (webapp:SPWebApplication) = let node = new TreeNode(Text=webapp.DisplayName ) node |> svcNode.ChildNodes.Add cond [(not webapp.IsAdministrationWebApplication, fun _ -> webapp.Sites |> Seq.iter (processSite node)); (true,nullfunc)] let processService (svc:SPService) = let label = sprintf "FarmService (Type=%s; Status=%s)" (svc.TypeName) (svc.Status.ToString()) let node = new TreeNode(Text=label) node |> viewer.Nodes.Add match svc with | :? SPWebService as websvc -> websvc.WebApplications |> Seq.iter (processWebApp node) | _ -> () viewer.Nodes.Clear() farm.Services |> Seq.iter processService viewer.ExpandAll()
As I was creating loadViewer
method, I was bothered by the if-else-then clauses in the code.
I had a previous blog that talked about this issue when it struck me that what I really wanted
was something similar to the cond
macro in Clojure. Hence the convenience function called cond
in the above F# code.
With the F# code written and packaged as a library, I can now use the above F# function in FarmHierarchy.aspx as follows:
protected void Page_Load(object sender, EventArgs e) { SPFarm thisFarm = SPFarm.Local; FsLab01.loadViewer(farmHierarchyViewer, thisFarm); }
The second part of this lab was to create code to manipulate properties of SharePoint SPWeb
or SPList
objects. The F# code that manipulates the SharePoint object properties and the UI are as follow:
let changeProperty (page:Page) (objectName:Label) (webtitle:TextBox) (listpanel:Panel) (webpanel:Panel) (listVersioning:CheckBox) (listContentTypes:CheckBox) (webUpdateBtn:Button) (listUpdateBtn:Button) (webCancelBtn:Button) (listCancelBtn:Button) = let homeurl baseurl = sprintf "%s/_layouts/lab01/FarmHierarchy.aspx" baseurl let wrapupdates (web:SPWeb) action = web.AllowUnsafeUpdates <- true action() web.AllowUnsafeUpdates <- false let hidepanels () = listpanel.Visible <- false webpanel.Visible <- false let cancel (web:SPWeb) = page.Response.Redirect(homeurl web.Url) let checkNull item = cond [(item=null, fun _ -> objectName.Text <- "Malformed URL" hidepanels() ""); (item<>null, fun _ -> item.ToString())] try let objectType = checkNull page.Request.["type"] let objectId = checkNull page.Request.["objectID"] match objectType with | "web" -> listpanel.Visible <- false webpanel.Visible <- true use web = SPContext.Current.Site.OpenWeb(new Guid(objectId)) // Hook up the events let myupdates _ = web.Title <- webtitle.Text web.Update() webUpdateBtn.Click.Add( fun _ -> try wrapupdates web myupdates page.Response.Redirect(homeurl web.Url) with ex -> objectName.Text <- ex.Message hidepanels() ) webCancelBtn.Click.Add(fun _ -> cancel web) objectName.Text <- sprintf "Web: %s" web.Title cond [(not page.IsPostBack, fun () -> webtitle.Text <- web.Title); (true,nullfunc)] | "list" -> listpanel.Visible <- true webpanel.Visible <- false let web = SPContext.Current.Web let splist = web.Lists.[new Guid(objectId)] let myupdates _ = splist.EnableVersioning <- listVersioning.Checked splist.ContentTypesEnabled <- listContentTypes.Checked splist.Update() listUpdateBtn.Click.Add( fun _ -> try wrapupdates web myupdates page.Response.Redirect(homeurl web.Url) with ex -> objectName.Text <- ex.Message hidepanels()) listCancelBtn.Click.Add(fun _ -> cancel web) cond [(not page.IsPostBack, fun _ -> listVersioning.Checked <- splist.EnableVersioning listContentTypes.Checked <- splist.ContentTypesEnabled)] | _ -> () with ex-> objectName.Text <- ex.Message
I needed to sign my F# code in order it to work in SharePoint. Since the C# SharePoint 2010 already created the key, all I had to do is to
add --keyfile:<Location to keyfile>\key.snk
to the F# build setting's "Other Flags" properties.
In addition, before I can build and deploy, I needed to add the F# library dll to the C# SharePoint project's package.
I can do so by clicking on Package in the SharePoint project and then click on the Advanced button.
I can now call this F# function from PropertyChanger.aspx code as follows:
FsLab01.changeProperty(this.Page, objectName, webTitle, listProperties, webProperties, listVersioning, listContentTypes, webTitleUpdate, listPropertiesUpdate, webCancel,listCancel);
Here's how what FarmHierarchy.aspx would look like :
Here's how what PropertyChanger.aspx would look like if I wanted to modify a SPWeb object:
Here's how what PropertyChanger.aspx would look like if I wanted to modify a SPList object:
1 comment:
Hierarchy data binding with TreeView
Post a Comment