Thursday, May 31, 2007

This is not another MAC ad

Do you want to know what Steve Jobs thinks about the Mac ads? Have a look at this entertaining and unique joint interview with the leaders of the long time rivals.

VIDEO: Steve Jobs and Bill Gates Highlight Reel


Wednesday, May 30, 2007

Why use SPJobDefinition in SharePoint Resources deployment

Recently Matthias Glubrecht and other readers of my post SharePoint Resources, Types, Use and Deployment asked me why do I use a custom SPJobDefinition to deploy resx files to the App_GlobalResources folder. The reason is simple (I remember when I asked Vincent Rothwell the same question and he was kind to explain this to me), the event receiver events (FeatureActivated etc.) are only invoked on the server where the feature gets activated not on all farm servers.

In other words if we need to make modifications to the objects stored in the content or configuration databases of the SharePoint farm, we can use directly the event receiver event handlers. The situation is a bit different when we need to modify objects outside of the SharePoint database, such as the files system, files or the resources (resx) files. In order to invoke the update on all front-web servers need to create a custom SPJobDefinition. One exclusion of this rule is the web.config modification class, which synchronizes web application configuration files via the SharePoint DOM.

Another benefit of using SPJobDefinition is that the timer service runs with a higher privileged account, where as the event receiver event handlers are invoked in the context of the current user.


Wednesday, May 16, 2007

Creating Dynamic Breadcrumbs in SharePoint ASPX Pages

If you already played with custom layouts pages in SharePoint you probably noticed that the breadcrumbs are missing. In a recent post Vincent Rothwell explains how to enable the breadcrumbs in ASPX pages placed in the LAYOUTS folder of SharePoint. Since the stock layouts pages use the SPXmlContentMapProvider, which in turn uses the layouts.sitemap file located in the _app_bin folder, if we add entries for the newly added layouts pages we'll get a rudimentary breadcrumb fixed to one level under the site node. The breadcrumb is in this format: site > custom page. This is one good solution and works fine if this format is satisfactory for your project.

However this is not helping us if we need a dynamic breadcrumb, which will change to represent the logical structure of the site. For example if the ASPX page handles a list item in a specific way, I would like the breadcrumb to properly display the breadcrumbs. For example site > list > folder > item > custom page.

One way to go is to create a custom SiteMapProvider, but this requires changes in the master pages and a bit more code. I was looking for a solution, which works with the standard applications.master page and can be used to modify the breadcrumb of any layouts page.

Another solution is to attach to the SiteMapResolveEventHandler. This is not a secret and there are many samples for regular ASPX pages (Raj thanks for pointing this out!), but in SharePoint context there are several twists, which I would like to illustrate with this sample.

First let's attach our event handler to the provider and do some parameter validation.

private SPWeb web;

private SPListItem currentItem;

protected override void OnInit(EventArgs e)



SiteMap.Providers["SPXmlContentMapProvider"].SiteMapResolve +=

new SiteMapResolveEventHandler(provider_SiteMapResolve);

string listId = Request["listId"];

string itemId = Request["itemId"];

if (string.IsNullOrEmpty(listId) && string.IsNullOrEmpty(itemId))


throw new Exception("Invalid item ID or list ID.");




web = SPControl.GetContextWeb(Context);

SPList list = web.Lists.GetList(new Guid(listId), true);

currentItem = list.GetItemById(int.Parse(itemId));



Since the SiteMapResolve event applies to all pages we want to make sure that we detach the event handler:

protected override void OnUnload(EventArgs e)



//detach from the static SiteMapResolve event and restore localization mode

SiteMap.Providers["SPXmlContentMapProvider"].EnableLocalization = true;

SiteMap.Providers["SPXmlContentMapProvider"].SiteMapResolve -=

new SiteMapResolveEventHandler(provider_SiteMapResolve);


Because this event gets fired on all pages that subscribe to the event, we want to make sure that our code alters only our page. This is why we add the IsSamePage function, which can apply different criteria to determine whether our page is currently loading.

/// <summary>

/// Determines whether the two contexts are equal and, therefore,

/// whether the SiteMap.SiteMapResolve event should be fired.

/// </summary>

protected virtual bool IsSamePage(HttpContext context1, HttpContext context2)


//by default, the contexts are considered the same if they

//map to the same file and the same listid/itemid

return ((Server.MapPath(context1.Request.AppRelativeCurrentExecutionFilePath) ==

Server.MapPath(context2.Request.AppRelativeCurrentExecutionFilePath)) &&

(context1.Request.QueryString == context2.Request.QueryString));


Now lets do the real work and modify the breadcrumb. In this sample I only add the name of the list, which contains the item and the name of the item itself. A logical extension is to add the folders in the path of an item, but once you get the idea you can change the code the way it fits your needs. The event handler should return the last node in the tree. If an exception occurs, we just ignore all changes.

/// <summary>

/// Overrides the breadcrumbs with a new one in the format

/// [site]>[repository list]>[process folder]>[page title].

/// </summary>

SiteMapNode provider_SiteMapResolve(object sender, SiteMapResolveEventArgs e)


if (!IsSamePage(Context, e.Context) || currentItem == null)


e.Provider.EnableLocalization = true;

return e.Provider.CurrentNode;


SiteMapNode pageTitleNode;

SiteMapNode listNode;



// Trun off localization, so that the custom nodes do not get overwritten

e.Provider.EnableLocalization = false;

// Clone some node to get a valid node object and modify it.

listNode = e.Provider.RootNode.ChildNodes[0].Clone();

listNode.Url = currentItem.ParentList.DefaultViewUrl;

listNode.Title = currentItem.ParentList.Title;

listNode.ChildNodes = new SiteMapNodeCollection();

// Add as many new nodes as you need here

pageTitleNode = new SiteMapNode(e.Provider, Guid.NewGuid().ToString());

pageTitleNode.Title = Title.Text;


pageTitleNode.ParentNode = listNode;


catch (Exception ex)


return e.Provider.CurrentNode;


return pageTitleNode;


There are two tricks in this code that I want to point out. The first one is that we cannot add a new root (breadcrumb) node to the provider since the property is read only and our page does not have an entry in layouts.sitemap. This is why we use any of the page nodes defined in the layouts.sitemap file (thus eliminating the need to alter layouts.sitemaps). Since the default format is site > custom page, we get the site node automatically. What we need to do next is to modify the leaf node and extend it with as many node levels as we need. The second caveat is the line where we disable provider localization. This is needed, so that the modified text does not get overwritten.

In the next lines we overwrite the URL and the text for the first node with the URL and the name of the list, which contains the item. Next we add an additional node to display the name of the item.

To make things even easier take out the class from the code-behind file and compile it in a separate assembly, then reference this class from any "List Item" page to get the same breadcrumb behavior.

And finally a link to the source code.


Monday, May 14, 2007

Disable size limits for wsp (cab) files

Couple of days ago we added a big zip file to our SharePoint deployment and out of the sudden the solution started behaving strangely. After some trial and error Robin found out that every time we cross the 360KB limit we have a problem. Wait, 360KB this rings the bell, but it was such a long time ago. What was it? You guessed it right the default limit for file size of makecab.exe is 360KB the size of 5.25 inch diskette. Talk about legacy support... To disable all kinds of limitations I added these lines to my DDF files and everything worked fine again.

.Set CabinetFileCountThreshold=0
.Set FolderFileCountThreshold=0
.Set FolderSizeThreshold=0
.Set MaxCabinetSize=0
.Set MaxDiskFileCount=0
.Set MaxDiskSize=0

You may wonder why would I need a zip file in my solution, but that's another post in the works. Stay tuned. Dovizhdane!