Sunday, October 26, 2008

Programmatically uploading master pages to master pages gallery in MOSS 2007

We recently had a requirement to set a custom master page to a blog site when blog site is created in Microsoft Office SharePoint Server (MOSS) 2007. There are couple of things we had to consider:
  • blog is a separate website with its own Master Pages gallery
  • the master page must be uploaded to blog's Master Pages gallery as soon as blog website is created
  • uploaded master page must be assigned to blog as soon as blog website is created
  • regular users (non-administrators) should be able to create blog websites

We decided to use "feature-stapling" mechanism in MOSS to be able to get notified when a new blog website is created and to be able to performed the required operations. I will not write about the feature-stapling mechanism in this blog post because it is fully covered in other blog posts (like in: http://www.sharepointnutsandbolts.com/2007/05/feature-stapling.html).

We made a custom master page, put it in a folder on file system of a MOSS server and added one feature property to hold full path to master page.

Out class that will handle master page uploading and assigning inherits from SPFeatureReceiver class and the method that peforms required operations executes in elevated privileges context.

Next listing shows the code that performs above mentioned operations:




private void UploadMasterPage(string pathToBlogMasterPage, SPWeb web)
{
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite blogSite = new SPSite(web.Site.Url))
{
using (SPWeb blogWeb = blogSite.OpenWeb(web.ID))
{

byte[] contents = null;
using (Stream fStream =
File.Open(pathToBlogMasterPage, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
contents = new byte[fStream.Length];
fStream.Position = 0;
fStream.Read(contents, 0, (int)fStream.Length);
fStream.Close();
}

//We need to extract a server relative URL of current web
string webRelativeUrl = web.Site.ServerRelativeUrl + web.Url.Replace(web.Site.Url, "");
string url = webRelativeUrl + "/_catalogs/masterpage/" + BlogCustomMasterpage;

SPFile currentFile = web.Files.Add(url, contents);
web.MasterUrl = web.MasterUrl.Replace(BlogDefaultMasterpage, BlogCustomMasterpage);
web.CustomMasterUrl =
web.CustomMasterUrl.Replace(BlogDefaultMasterpage, BlogCustomMasterpage);
web.Update();
}
}
});
}


NB: pathToBlogMasterPage is extracted from SPFeatureReceiverProperties instance and web is an instance of SPWeb that represents the newly created blog website.

Sunday, October 19, 2008

Paste formatted C# source code into blogger posts

I recently found a very good online tool that allows C# (and other) source code to be pasted into blogger posts as formatted and colorized text: C# code format.

It is very simple to use:

  • copy source code from your IDE (like VisualStudio)
  • paste source code into the source code box on this website: http://www.manoli.net/csharpformat/
  • click on "format my code" button
  • click on the "select all" button
  • copy the HTML code
  • go to your blog post
  • switch to HTML mode (click on "Edit Html" tab)
  • paste copied HTML code

There is one more thing you should do. Either add a link to "csharp.css" file (the file is available for download on the website) to template of your blogger blog or select "embed stylesheet" option before clicking on "format my code" button.

NB: You should be able to use the same tool with other blog engines or any other HTML pages.

Properly disposing SPWeb.Webs items

A while ago I read a great article by Roger Lamb that shows how to properly use and dispose SPSite and SPWeb instances: SharePoint 2007 and WSS 3.0 Dispose Patterns by Example.

One of dispose patterns that he showed is about SPWeb.Webs items disposal. He first showed one of the wrong ways you can use SPWeb.Webs collection:


void WebsLeak()
{
using (SPSite siteCollection = new SPSite("http://moss"))
{
using (SPWeb outerWeb = siteCollection.OpenWeb())
{
foreach (SPWeb innerWeb in outerWeb.Webs)
{
// SPWeb innerWeb leak
}
} // SPWeb object outerWeb.Dispose() automatically called
} // SPSite object siteCollection.Dispose() automatically called
}

Then he showed the right way to handle SPWeb.Webs collection:


void WebsBestPractice()
{
using (SPSite siteCollection = new SPSite("http://moss"))
{
using (SPWeb outerWeb = siteCollection.OpenWeb())
{
foreach (SPWeb innerWeb in outerWeb.Webs)
{
innerWeb.Dispose();
}
} // SPWeb object outerWeb.Dispose() automatically called
} // SPSite object siteCollection.Dispose() automatically called
}

You can see on line 9 that "innerWeb" is disposed as it should be. The thing is that Roger didn't show that innerWeb instance must be used in either "using" block or in "try" block with disposal in "finally" block. If "innerWeb" is not used in "using" block (or in "try & finally") then it may happen that an exception is thrown before innerWeb.Dispose method is called.

I didn't think it is significant and assumed developers will recognize this as a potential leak and will always use "innerWeb" in "using" block. After some time I saw this mistake in source code of web parts being used in production environment and I instructed developers who wrote the code to fix the potential issue. Then I saw the same bug a few days ago on a lecture called "MOSS 2007 for developers - how to avoid mistakes" on a Microsoft conference I attended in Novi Sad.

I thought I should blog about this and point other developers to this issue. Next code shows one of the proper ways SPWeb.Webs should be used and disposed:


void WebsBestPractice()
{
using (SPSite siteCollection = new SPSite("http://moss"))
{
using (SPWeb outerWeb = siteCollection.OpenWeb())
{
SPWebCollection innerWebs = outerWeb.Webs;
if (innerWebs != null && innerWebs.Count > 0)
{
foreach (SPWeb innerWeb in innerWebs)
{
using (innerWeb)
{
//use innerWeb here
}
}
}
} // SPWeb object outerWeb.Dispose() automatically called
} // SPSite object siteCollection.Dispose() automatically called
}

You can see in line 12 that "innerWeb" is used in "using" block. No matter if an exception happens in line 14, the "innerWeb" will be disposed.

Thursday, September 4, 2008

Adding a New Wiki Page link in MOSS 2007

I recently got a request to add a link for new wiki page creation to wiki pages in Microsoft Office SharePoint Server 2007 (MOSS 2007).

If you go to a wiki page in MOSS you will see something like this:

My customer wanted to have another link in the top right corner of wiki pages and to be able to create new wiki pages without going to the wiki pages library (if you go to the wiki pages library directly then you will have an option to add a new wiki page).

In order to make the requested change I followed the next steps:

  • went to the "\12\TEMPLATE\CONTROLTEMPLATES\" folder on MOSS machine

  • opened "DefaultTemplates.ascx" file with a text viewer

  • found template whose ID is "WikiMiniConsole"

  • selected and copied the whole content of the template

  • created a new text file called "WikiMiniConsole.ascx" and opened the file with VisualStudio (I could use any text editor)

  • pasted the copied text (the WikiMiniConsole template) into the new ASCX file

  • added a few lines of code to the top of the file to declare it as ASP.NET control and to register necessary tag prefixes (I copied the lines from "DefaultTemplates.ascx")

  • located the Template_Buttons section and added a link button to that section:
    <asp:hyperlink id="lnkNewWikiPage" runat="server" text="New Wiki Page" navigateurl="/_layouts/CreateWebPage.aspx?List=%7B0A589486%2D5E83%2D4DBF%2D9607%2DFA218150B179%7D&amp;RootFolderUrl=%2FWikiPagesLibrary" cssclass="ms-toolbar"></asp:hyperlink>

  • saved the new ASCX file and deployed it to "\12\TEMPLATE\CONTROLTEMPLATES\" folder on MOSS machine

After that I had to recycle worker threads for MOSS site and finally I got the new link in wiki mini console:



You can download the WikiMiniConsole.ascx that I made from here.