Tuesday, September 23, 2008

A more complicated SharePoint web part examples in F# with RSS Viewer and Feed List

I still up to my old tricks with learning new Microsoft Technologies by using F# as the implementation language. I find that by using a different programming language than the one in the book forces me to think through the author's implementation while I transliterate the code into F#. Other bonuses include learning to program in F# and in the process, I often trigger errors while learning the new technology that serendipitously provide me opportunites to explore the technologies in depth.

Lately, I've been exploring SharePoint by going through the book Inside Microsoft Windows SharePoint Services 3.0 by Ted Pattison and Daniel Larson and working through the code examples in Chapter 4 of the book, specifically the RSS Viewer Web Part and the Feed List Web Part example.

In the previous blog, I mentioned that I had to write a SPListCollectionAdapter in C# to wrap SPListCollection so I can use it like standard sequences in F#. I figured out a F# workaround for it so that I don't have to switch back and forth to resolve the problem. In the implementation of the F# solution, I found myself getting really annoyed by the SharePoint libray designers in not implementing the IEnumerable interface in SPBaseCollection so that I don't need to write the adapter in the first place. The second major issue with the SPBaseCollection is that it did not specify and overridable Item property and left it to the implementation class to arbitrarily define the access to the item in the collection list. I have no idea why the SharePoint library designers did this. So instead of writing the following function that can be take any subclass of SPBaseCollection:


let toSeq (splist: #SPBaseCollection) =
seq { for i in 0 .. (splist.Count-1) -> splist.get_Item(i)}

I'm now forced to write an adapter for each of the SPBaseCollection subclasses as shown in the following:

    let SPListToSeq (splist:SPListCollection) =
seq { for i in 0 .. (splist.Count-1) -> splist.get_Item(i)}

let SPWebToSeq (splist:SPWebCollection) =
seq { for i in 0 .. (splist.Count-1) -> splist.get_Item(i)}

In the implementation and testing process, I ran into problems and SharePoint showed some very unhelpful error messages. I found it invaluable to change SharePoint Web.config debug settings as outline in Jesse's SharePoint Blog. Combining that with a custom file logger, I managed to identify all my bugs and resolved the issues.

Here's a screenshot of the implemented RSS Viewer Web Part implemented in F# with the feed url pointed to F# Planet...

Here's a screenshot of the implemented Feed List Web Part that is connected to the RSS Viewer Web Part

In the F# implementation, probably the most notable change is implementation of AddLists member function in FeedListWebPart class as getSPLists in the example F# code. Instead of iterating through each SPList in the collection and using the if statement to determine whether we should add the SPList to the newly created collection, the F# version takes the entire list and pipleline it through a series of filter function to get the file list. F# helped me to think of operations at the granularity of the list level instead of at the items level. Here's the implementation in F#:


#light
namespace DemoWebParts

open System
open System.Collections.Generic
open System.ComponentModel
open System.Data
open System.IO
open System.Net
open System.Reflection
open System.Web
open System.Web.UI
open System.Web.UI.WebControls
open System.Web.UI.WebControls.WebParts
open System.Xml
open System.Xml.Xsl
open Microsoft.SharePoint.WebControls
open Microsoft.SharePoint
open Microsoft.SharePoint.Utilities

// This is a utility debugging tool that I've used to dump deugging info to a file.
// I can simple call it as Log.dump "Error messages"
module Log =

let dump (msg:string) =
using (File.AppendText(@"c:\logs\sharepoint.log"))
(fun writer -> writer.WriteLine(msg))

module SPCollectionUtility =
// This is what I would like to do but could not due to
// the design of SPBaseCollection
(*
let toSeq (splist: #SPBaseCollection) =
seq { for i in 0 .. (splist.Count-1) -> splist.get_Item(i)}
*)

let SPListToSeq (splist:SPListCollection) =
seq { for i in 0 .. (splist.Count-1) -> splist.get_Item(i)}

let SPWebToSeq (splist:SPWebCollection) =
seq { for i in 0 .. (splist.Count-1) -> splist.get_Item(i)}

module WebPartResources =
let GetNamedResource (reference:obj) (filename:string) =
use stream = filename |> Assembly.GetExecutingAssembly().GetManifestResourceStream
use reader = new StreamReader(stream)
reader.ReadToEnd()

let GetNamedResourceStream (reference:obj) (filename:string) =
filename
|> Assembly.GetExecutingAssembly().GetManifestResourceStream

type RenderMode =
| Full
| Titles

module RenderModeUtility =

// Helper function for RenderMode
let label mode =
match mode with
| Full -> "Full"
| Titles -> "Titles"

let setmode label =
match label with
| "Full" -> Full
| "Titles" -> Titles
| _ -> failwith "value must be Full or Titles to be RenderMode"


type RssViewEditorPart() = class
inherit EditorPart()

let mutable (txtXmlUrl:TextBox) = null

let mutable (lstHeadlineMode:RadioButtonList) = null


do base.Title <- "RSS View Custom Editor"

override this.CreateChildControls() =
txtXmlUrl <- new TextBox(Width=new Unit("100%"),
TextMode=TextBoxMode.MultiLine,
Rows = 3)

// Add controls to the radio button list
lstHeadlineMode <- new RadioButtonList()
(RenderModeUtility.label RenderMode.Full) |> lstHeadlineMode.Items.Add
(RenderModeUtility.label RenderMode.Titles) |> lstHeadlineMode.Items.Add

new LiteralControl("Feed Url:<br/>") |> this.Controls.Add

txtXmlUrl |> this.Controls.Add
match this.WebPartManager.Personalization.Scope with
| PersonalizationScope.User -> txtXmlUrl |> this.Controls.Add
| _ -> ()

new LiteralControl("Headline Mode:<br/>") |> this.Controls.Add
lstHeadlineMode |> this.Controls.Add


override this.SyncChanges() =
this.EnsureChildControls()
let targetPart = this.WebPartToEdit :?> RssViewWebPart
let SelectedMode = targetPart.HeadlineMode
let item = lstHeadlineMode.Items.FindByText(SelectedMode)
item.Selected <- true
txtXmlUrl.Text <- targetPart.XmlUrl

override this.ApplyChanges() =
this.EnsureChildControls()
let targetPart = this.WebPartToEdit :?> RssViewWebPart
targetPart.XmlUrl <- txtXmlUrl.Text
targetPart.HeadlineMode <- lstHeadlineMode.SelectedValue
true

end
and RssViewWebPart() = class
inherit WebPart()

let mutable (xmlUrl:string) = null
let mutable headlineMode = RenderMode.Full
let mutable (exceptionDetail:string) = null
let mutable (xmlstream:Stream) = null

(*
// Did not need to implement this...
interface IWebEditable with
member this.WebBrowsableObject
with get() = box this
override this.CreateEditorParts () =
let parts = new List<EditorPart>(1);
parts.Add(new RssViewEditorPart(ID=(this.ID+"_rssViewEditor")))
new EditorPartCollection(base.CreateEditorParts(),parts)
*)


override this.CreateEditorParts () =
let parts = new List<EditorPart>(1);
let part =new RssViewEditorPart(ID=(this.ID+"_rssViewEditor"))
parts.Add(part)
new EditorPartCollection(base.CreateEditorParts(),parts)

member this.Redirect () =
this.Context.Response.Redirect(this.XmlUrl)

[<Personalizable(PersonalizationScope.Shared);WebBrowsable(false)>]
member this.XmlUrl
with get() = xmlUrl
and set value = xmlUrl <- value

[<Personalizable(PersonalizationScope.User);WebBrowsable(false)>]
member this.HeadlineMode
with get() = RenderModeUtility.label headlineMode
and set value = headlineMode <- (RenderModeUtility.setmode value)

override this.Verbs
with get() =
let verbs = new List<WebPartVerb>()
verbs.Add(new WebPartVerb(this.ID + "_ClientSideRssOpenerVerb",
sprintf "window.open('%s','RSSXML')" this.XmlUrl,
Text = "Open RSS Feed"))
verbs.Add(
let handler = new WebPartEventHandler(fun _ _ ->
match String.IsNullOrEmpty(this.XmlUrl) with
| false -> this.Redirect()
| true -> ())

new WebPartVerb(this.ID + "_ServerSideRssOpenerVerb",
handler,
Text = "View RSS Source Feed 3.0"))


new WebPartVerbCollection(base.Verbs, verbs )


override this.OnInit (e:EventArgs) =
base.OnInit(e)

this.HelpUrl <-
(this.GetType(), "help.html")
|> this.Page.ClientScript.GetWebResourceUrl

this.HelpMode <- WebPartHelpMode.Modeless

override this.OnPreRender (e:EventArgs) =
base.OnPreRender(e)

if (String.IsNullOrEmpty(this.XmlUrl)) then ()
elif this.WebPartManager.DisplayMode.AllowPageDesign then
new LiteralControl("No display while in design mode.")
|> this.Controls.Add
else
try
let req = new Uri(this.XmlUrl) |> WebRequest.CreateDefault
req.Credentials <- CredentialCache.DefaultCredentials
req.Timeout <- 10000 // 10 seconds

let beginHandler = new BeginEventHandler(fun _ _ callback state ->
req.BeginGetResponse(callback,state))

let successHandler = new EndEventHandler(fun result ->
try
xmlstream <- req.EndGetResponse(result).GetResponseStream()
with wex ->
exceptionDetail <- wex.Message)

let errorHandler = new EndEventHandler(fun _ ->
let errmsg = sprintf "The request timed out while waiting for %s" this.XmlUrl
new Label(Text=errmsg) |> this.Controls.Add)

new PageAsyncTask(beginHandler,successHandler,errorHandler,null,true)
|> this.Page.RegisterAsyncTask

with :? System.Security.SecurityException ->
let errmsg = "Permission denied - please set trust level to WSS_Medium."
new LiteralControl(errmsg)
|> this.Controls.Add


override this.RenderContents (writer:HtmlTextWriter) =
base.RenderContents(writer)

if exceptionDetail <> null then
writer.Write(exceptionDetail)
elif (String.IsNullOrEmpty(this.XmlUrl) || xmlstream = null) then ()
else
let transformer = new XslCompiledTransform()
let xslt = match headlineMode with
| Full -> "RSS.xslt"
| Titles -> "RssTitles.xslt"
use res = WebPartResources.GetNamedResourceStream this xslt
new XmlTextReader(res) |> transformer.Load

try
use input = new XmlTextReader(xmlstream)
use output = new XmlTextWriter(writer.InnerWriter)
(input,output) |> transformer.Transform
with e ->
writer.Write(e.Message)
if xmlstream <> null then
xmlstream.Close()
xmlstream.Dispose()


[<ConnectionConsumer("Xml URL Consumer",AllowsMultipleConnections=false)>]
member this.SetConnectionInterface (provider:IWebPartField) =
provider.GetFieldValue(new FieldCallback(fun providedUrl ->

if providedUrl = null then ()
else
let urls = (providedUrl :?> String).Split([|','|])
this.XmlUrl <- urls |> Array.to_list |> List.hd))

end


type FeedListWebPart() = class
inherit WebPart()

let mutable xmlurl = ""

let spfilters = [SPListTemplateType.Categories;
SPListTemplateType.MasterPageCatalog;
SPListTemplateType.WebPageLibrary;
SPListTemplateType.WebPartCatalog;
SPListTemplateType.WebTemplateCatalog;
SPListTemplateType.UserInformation;
SPListTemplateType.ListTemplateCatalog;]


let rec getSPLists (web:SPWeb) =
Seq.append
// e.g. foreach (SPList list in web.Lists)
//(new SPListCollectionAdapter(web.Lists)
(SPCollectionUtility.SPListToSeq web.Lists
|> Seq.filter (fun x -> x.AllowRssFeeds)
|> Seq.filter (fun x -> x.EnableSyndication)
|> Seq.filter (fun x -> List.map ((<>) x.BaseTemplate) spfilters
|> List.fold_left (&&) true)
|> Seq.filter (fun x ->
x.DoesUserHavePermissions(SPBasePermissions.ViewListItems)))


// e.g. foreach (SPWeb subweb in web.Webs)
//(new SPWebCollectionAdapter(web.Webs)
(SPCollectionUtility.SPWebToSeq web.Webs
|> Seq.filter (fun w ->
w.DoesUserHavePermissions(SPBasePermissions.ViewListItems))
|> Seq.map getSPLists |> Seq.concat)


override this.CreateChildControls() =
base.CreateChildControls()

let list = SPContext.Current.Web |> getSPLists

let view = new SPGridView(AutoGenerateColumns=false)
this.Controls.Add(
["Title";"ItemCount"]
|> List.iter (fun x ->
new BoundField(DataField=x,HeaderText=x)
|> view.Columns.Add)

view.Columns.Add(
let button = new CommandField(HeaderText="Action",
SelectText="Show RSS",
ShowSelectButton=true)
button.ControlStyle.Width <- new Unit(75.0)
button)

view.SelectedIndexChanged.Add(fun x ->
xmlurl <- view.SelectedValue.ToString())
view
)

if this.Page.IsPostBack = false then
let table = new DataTable()
["Title";"ItemCount";"XmlUrl";"ID"]
|> List.iter (fun x -> table.Columns.Add(x) |> ignore)

// Need to build DataRows & DataTable...
let buildrow (list:SPList) =
let row = table.NewRow()
row.["Title"] <- list.Title
row.["ItemCount"] <- list.ItemCount.ToString()
row.["ID"] <- list.ID
let url = sprintf "%s/_layouts/listfeed.aspx?List=%s" list.ParentWebUrl (list.ID.ToString())
row.["XmlUrl"] <-
this.Page.Request.Url.GetLeftPart(UriPartial.Authority) +
SPUtility.MapWebURLToVirtualServerURL(list.ParentWeb,url)
table.Rows.Add(row)

Seq.iter buildrow list
view.DataKeyNames <- [|"XmlUrl"|]
view.DataSource <- table
view.DataBind()


[<WebBrowsable(true);
Category("Configuration");
Personalizable(PersonalizationScope.User);
DefaultValue("");
WebDisplayName("Xml Url");
WebDescription("F# RSS Feed XML URL")>]
member this.XmlUrl
with get() = xmlurl
and set value =
if String.IsNullOrEmpty(xmlurl) then
let uri = new Uri(value)
xmlurl <- uri.AbsolutePath
else
xmlurl <- null

interface IWebPartField with
member this.Schema
with get() =
TypeDescriptor.GetProperties(this).Find("XmlUrl",false)

member this.GetFieldValue (callback:FieldCallback) =
(this :> IWebPartField).Schema.GetValue(this)
|> callback.Invoke


[<ConnectionProvider("XmlUrl Provider")>]
member this.GetConnectionInterface() = this :> IWebPartField

end

[<Assembly: System.Reflection.AssemblyVersion("1.0.0.0")>]
[<Assembly: System.Security.AllowPartiallyTrustedCallersAttribute>]
do()

Sunday, September 14, 2008

Building a Simple Hello World SharePoint Web Parts with F#

I'm working through the examples in the book Inside Microsoft Windows SharePoint Services 3.0 by Ted Pattison and Daniel Larson and wanted to build a simple hello world web part in F# before attempting the more complicated RssViewWebPart as explained in the book. I start by generating the configuration files DemoWebParts.webpart, feature.xml, and elements.xml.


DemoWebParts.webpart file

<webParts>
<webPart xmlns="http://schemas.microsoft.com/WebPart/v3">
<metaData>
<type name="DemoWebParts.HelloWorldWebPart" />
<importErrorMessage>Cannot import this Web Part.</importErrorMessage>
</metaData>
<data>
<properties>
<!-- standard Web Part properties -->
<property name="ChromeType" type="chrometype">Default</property>
<property name="Title" type="string">Demo Hello World Web Part</property>
<property name="Description" type="string">Simple example to demonstrate F# in SharePoint</property>
<property name="CatalogIconImageUrl" type="string">
/_layouts/images/msxmll.gif
</property>
<property name="AllowConnect" type="bool">True</property>
<property name="ExportMode" type="exportmode">All</property>

<!-- custom Web Part properties -->
<property name="XmlUrl" type="string">
http://blogs.msdn.com/MainFeed.aspx?Type=AllBlogs
</property>
</properties>
</data>
</webPart>
</webParts>

feature.xml file

<Feature
Id="973F298A-96D3-4d7c-B07A-56925FA13CFF"
Title="Demo Webpart using F#"
Description="Demo webpart using F# based on Chapter 4 of Inside Windows SharePoint Services (Pattison/Larson)"
Hidden="FALSE"
Scope="Site"
ImageUrl="actionssettings.gif"
xmlns="http://schemas.microsoft.com/sharepoint/">
<ElementManifests>
<ElementManifest Location="elements.xml"/>
</ElementManifests>
</Feature>

elements.xml file

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Module Name="DemoWebParts" Path="dwp"
List="113" Url="_catalogs/wp" RootWebOnly="true">

<File Url="HelloWebPart.webpart" Type="GhostableInLibrary" >
<Property Name="Group" Value="F# Demo Web Parts"
</File
</Module>
</Elements>

The F# source file DemoWebParts.fs contains just a single class HelloWorldWebPart that inherits from WebPart.


DemoWebParts.fs


#light

namespace DemoWebParts

open System.Web.UI
open System.Web.UI.WebControls.WebParts

type HelloWorldWebPart() =
class
inherit WebPart()

override this.CreateChildControls () =
base.CreateChildControls()
new LiteralControl("Hello, world!") |> this.Controls.Add

end

[<Assembly: System.Reflection.AssemblyVersion("1.0.0.0")>]
[<Assembly: System.Security.AllowPartiallyTrustedCallersAttribute>]
do()

I need to strong name the created assembly. The following are the command line parameters I used to generate the dll file.

sn.exe -k DemoWebParts.snk

fsc -a --keyfile DemoWebParts.snk -I "C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\ISAPI" -r "Microsoft.SharePoint.dll" DemoWebPart.fs

I then copy all the files to the bin folder of the SharePoint website.

I then need to configure the web.config of the SharePoint site to add a new entry to the SafeControls node in the web.config in order to register the assembly and namespace as safe.


<SafeControl Assembly="DemoWebParts, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b89a761d055b9ae3" Namespace="DemoWebParts" TypeName="*" Safe="True" />

The assembly value can be easily pulled by using Lutz Roeder's .NET Reflector tool, which apparently has been acquired by Redgate.

After you copied the dll file along with the configuration files, you then want to navigate to the New Web Part Gallery page which is located at http:///_layouts/NewDwp.aspx.

Select the new web part and click on Populate Gallery Button.

Voila...a F# webpart working in SharePoint!

Tuesday, September 09, 2008

Exploring SharePoint 2007 Object Model with F#

I have been busy with other things that detracted from continued efforts in working with F# and WPF. While I've been busy, I found out that F# 1.9.6 has been released. After quick perusal of F# 1.9.6 release notes, I realized my previously posted codes will break during compilation. Two immediate items that I noticed are:

  • IEnumerable.* are deleted
  • base is now a keyword

I haven't had time to scour my previously posted F# code and correct it to work with F# 1.9.6 so be forewarned if you're trying to compile my previously posted F# code with the new F# compiler. Hopefully, sometime in the future I'll be able to correct the posted code so it compiles and runs with the new F# compiler.

Lately, I have been exploring other pieces of Microsoft technologies such SharePoint 2007, InfoPath 2007, Windows Workflow Foundation, and Excel Services with the goal of crafting a strategy on how to best leverage these technologies in a corporate environment. I needed a way to get up to speed quickly in the SharePoint environment and wanted a way to interactively explore the Windows SharePoint Services (WSS) object model.

I immediately thought of using F# interactive as way to explore WSS object model. I fired up the new F# 1.9.6 interactive shell and wanted to follow the example codes in the book Inside Microsoft Windows SharePoint Services 3.0 by Ted Pattison and Daniel Larson.

Before I could try out the examples in the aforementioned book, I had to created a MOSS 2007 system in a Virtual PC environment based on the instructions by Tony Zink in his post How to Create a MOSS 2007 VPC Image: The Whole 9 Yards .

One problem that I ran into while trying out the examples from the book is that I'm unable to iterate through SPListCollection. SPListCollection does not implement IEnumerable and I do not know an equivalent foreach capability in F#. As a workaround, I implemented the SPListCollectionAdapter as described by Asfar Sadewa in his blog entry linq-ing splistcollection. After implementing this adapter, I can now iterate through SPListCollection as shown in the following example:


Exploring WSS Object Model with F#

#light
#I @"C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\ISAPI"
#r @"Microsoft.SharePoint.dll"
#r @"c:\dev\F#\SharePoint\SharePointUtility.dll"

open Microsoft.SharePoint
open SharePoint.Utility

let path="http://localhost/"
let collection = new SPSite(path)
let site = collection.RootWeb
let lists = SPListCollectionAdapter(site.Lists)
Seq.iter (fun (x:SPList) -> printf "%s\n" x.Title) lists

I was highly encouraged by this initial success. I next tried to implement a simple hello world SharePoint feature as shown in the following code:


Building Test Hello World Sharepoint Feature

#light
namespace HelloWorld

open System
open Microsoft.SharePoint

// From Chapter 1 of Inside Microsoft Windows SharePoint Services 3.0 by Ted Pattison & Daniel Larson
type FeatureReceiver() =
class
inherit SPFeatureReceiver()


override this.FeatureInstalled _ = ()
override this.FeatureUninstalling _ = ()

override this.FeatureActivated (properties:SPFeatureReceiverProperties) =
let site = properties.Feature.Parent :?> SPWeb
site.Properties.["OriginalTitle"] <- site.Title
site.Properties.Update()
site.Title <- "Hello World"
site.Update()

override this.FeatureDeactivating (properties:SPFeatureReceiverProperties) =
let site = properties.Feature.Parent :?> SPWeb
site.Title <- site.Properties.["OriginalTitle"]
site.Update()
end

I was delighted that this worked flawlessly in SharePoint 2007. It looks like I can go back to using some F# in exploring SharePoint 2007.