Resolve 500 Internal Server Errors

If you receive a 500 (Internal Server Error) when running an application in IIS (SharePoint or otherwise), what can you do to resolve it? The ULS logs won’t show anything because the error happens before SharePoint or your web application has a chance to log an error. However, IIS has its own trace logging which can be easily turned on.

  1. Open IIS and click on the node for the site returning the error
  2. In the Actions pane on the right, click Failed Request Tracing…
  3. Check Enable
  4. In the center pane, click the Failed Request Tracing Rules icon
  5. In the Actions pane on the right, click Add…
  6. In the wizard that opens, click next to select all content
  7. Then check Status codes(s) and enter 500, or any other error codes you want to trace
  8. Click Next to select all trace providers, and the Finished
  9. You will now see logs get created for failed request in the following folder:C:\inetpub\logs\FailedReqLogFiles
  10. Clicking on one of the log files will display the following information. In this case we had a duplicate handler <add> element in the web.config – very easy to diagnose and correct with tracing enabled!

Deleting TFS Work Items

When setting up team projects in TFS sometimes it’s helpful to create a few test issues or bugs to make sure any customizations you have made function correctly. However, you will quickly notice there is no option to delete these work items from the UI or administrative console. It can be done though via the Command Prompt. Here’s how:

  1. Open a Visual Studio Command Prompt window
  2. Enter the following command:
    witadmin destroywi /collection:<team project collection URL> /id:<work item id>

For more information on using the Work Item Type Administration console utility see: http://msdn.microsoft.com/en-us/library/dd312129.aspx

Permission Work Item Types in TFS

When tracking work items for a development project you may want to capture issues reported by business users. However, giving business users access to TFS can lead to a lot of bugs getting reported that are really just issues that need to be triaged, broken down, or better categorized. One issue may be resolved by fixing 3 different bugs, 2 tasks being performed, and perhaps a test case developed. An issue can then be turned into a bug once the development team has reviewed it, or several different types of work items and related back to the original issue. But how can we ensure business users only enter issues, not bugs?

You might think work item types can be assigned permissions by user or groups. However, TFS currently doesn’t support this. There is a way though to still prevent a group of users from creating work items by type.

  1. Create a global group for business users and add them as members:

  2. Install the TFS Power Tools for VS if you haven’t already.
    http://visualstudiogallery.msdn.microsoft.com/c255a1e4-04ba-4f68-8f4e-cd473d6b971f
  3. The Power Tools install the Process Editor which allows you to edit work item types. Open each WIT for your project and go to the workflow tab:
  4. Select the first transition and set Not to the business user group you just created. The prevents the first workflow step from executing if the current user is in the group.
  5. Now users in the business group cannot save work items of type Bug:

Of course, this approach merely prevents users from creating work item types they don’t have access to. We still need to train business users to enter issues into TFS since the error message is not very informative. And of course security trimming would be nice but at least we have the bare minimum of access control by item type.

October CU Fails to Install

After you install SharePoint cumulative updates you need to run the SharePoint 2010 Products Configuration Wizard (psconfig.exe). However, while installing the October 2011 cumulative update recently, I found it was failing on the last step where the udpate gets applied. I discovered that the SharePoint 2010 Timer service was getting shut down after the first step when running psconfig which in turn made the last step fail. The solution is simple: keep the Services console open when running psconfig and restart the Timer service before reaching the last step!

IIS Timeout When Debugging in VS

You can stop the Visual Studio debugger from timing out and stopping by changing a single setting in IIS. Just select the application pools you want and go to Advanced Settings in the IIS Manager.

image

There you can set the Ping Enabled property to False which will keep the debugger alive indefinitely.

image

You should also do this for the service application pool as well so things like the profile service don’t time out. Since it’s identified by a GUID you need right-click and View Applications to find the right pool.

image

jQuery Selectors with Server-Side ID’s

If you want to bind a client-side function to an ASP.NET control using a jQuery selector you can use the attribute selector id$= to specify what the client id ends with. The leading underscore is added to match only complete server id’s.

Take an ASP.NET control:

image

Which gets rendered with the following id:

image

We can bind an event handler in jQuery using the following selector:

image

This is a handy way to wire up client-side functionality to your server-side ASP.NET controls.

Deployment Conflict Resolution in Visual Studio

If you are provisioning a ListInstance in Visual Studio you may see the following error message:

The URL or name of this list instance conflicts with a list instance already on the server.
The list instance on the server will be deleted before deploying the new list instance.

image

If you select Resolve Automatically, your list and all of its data will be removed before a new instance of the list is provisioned. To prevent this, you can set the Deployment Conflict Resolution property of the list instance in the Solution Explorer to None. Simply right-click on the list instance project item in VS and select Properties. Then select None from the dropdown list of values:

image

Logging SPRequest Allocation Call Stacks

When writing code to extend SharePoint functionality it is important to make sure we are disposing SPWeb and SPSite objects carefully. However, no matter how careful you are a few undisposed webs and sites can creep into your code. If this happens you will see entries in the log files that say "An SPRequest object was not disposed before the end of this thread".

image

This general message doesn’t help though to find the specific problem, you need to see the actual call stack. To do this, you can enable CollectSPRequestAllocationCallStacks using a PowerShell script like the following.

image

Afterwards, you will see the full stack trace where the web or site was not disposed.

image

Cross-Site Collection Navigation

Background

SharePoint uses the provider model for supplying data to its navigation controls. The v4.master, for instance, uses an AspMenu control for the top navigation whose DataSourceId is set declaratively to an instance of a SiteMapDataSource. The data source control is wrapped in a delegate control so it can be easily swapped out using a feature.

<SharePoint:AspMenu
  ID="TopNavigationMenuV4"
  Runat="server"
  EnableViewState="false"
  DataSourceID="topSiteMap"
  AccessKey="<%$Resources:wss,navigation_accesskey%>"
  UseSimpleRendering="true"
  UseSeparateCss="false"
  Orientation="Horizontal"
  StaticDisplayLevels="2"
  MaximumDynamicDisplayLevels="1"
  SkipLinkText=""
  CssClass="s4-tn" />
<SharePoint:DelegateControl runat="server" ControlId="TopNavigationDataSource" Id="topNavigationDelegate">
    <Template_Controls>
        <asp:SiteMapDataSource
          ShowStartingNode="False"
          SiteMapProvider="SPNavigationProvider"
          id="topSiteMap"
          runat="server"
          StartingNodeUrl="sid:1002"/>
    </Template_Controls>
</SharePoint:DelegateControl>

The data source control has a SiteMapProvider property which can be set to any of the named sitemap providers declared in the web.config file.

<siteMap defaultProvider="CurrentNavigation" enabled="true">
  <providers>
    <add name="SPNavigationProvider" type="Microsoft.SharePoint.Navigation.SPNavigationProvider, Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
    <add name="SPSiteMapProvider" type="Microsoft.SharePoint.Navigation.SPSiteMapProvider, Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
    <add name="SPContentMapProvider" type="Microsoft.SharePoint.Navigation.SPContentMapProvider, Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
    <add name="SPXmlContentMapProvider" siteMapFile="_app_bin/layouts.sitemap" type="Microsoft.SharePoint.Navigation.SPXmlContentMapProvider, Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
    <add name="ExtendedSearchXmlContentMapProvider" description="Provider for navigation in Extended Search pages" siteMapFile="_app_bin/layouts.sitemap" type="Microsoft.Office.Server.Search.Extended.Administration.Common.ExtendedSearchXmlContentMapProvider, Microsoft.Office.Server.Search, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
    <add name="AdministrationQuickLaunchProvider" description="QuickLaunch navigation provider for the central administration site" type="Microsoft.Office.Server.Web.AdministrationQuickLaunchProvider, Microsoft.Office.Server.UI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
    <add name="SharedServicesQuickLaunchProvider" description="QuickLaunch navigation provider for shared services administration sites" type="Microsoft.Office.Server.Web.SharedServicesQuickLaunchProvider, Microsoft.Office.Server.UI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
    <add name="GlobalNavSiteMapProvider" description="CMS provider for Global navigation" type="Microsoft.SharePoint.Publishing.Navigation.PortalSiteMapProvider, Microsoft.SharePoint.Publishing, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" NavigationType="Global" EncodeOutput="true" />
    <add name="CombinedNavSiteMapProvider" description="CMS provider for Combined navigation" type="Microsoft.SharePoint.Publishing.Navigation.PortalSiteMapProvider, Microsoft.SharePoint.Publishing, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" NavigationType="Combined" EncodeOutput="true" />
    <add name="CurrentNavSiteMapProvider" description="CMS provider for Current navigation" type="Microsoft.SharePoint.Publishing.Navigation.PortalSiteMapProvider, Microsoft.SharePoint.Publishing, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" NavigationType="Current" EncodeOutput="true" />
    <add name="CurrentNavSiteMapProviderNoEncode" description="CMS provider for Current navigation, no encoding of output" type="Microsoft.SharePoint.Publishing.Navigation.PortalSiteMapProvider, Microsoft.SharePoint.Publishing, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" NavigationType="Current" EncodeOutput="false" />
    <add name="GlobalNavigation" description="Provider for MOSS Global Navigation" type="Microsoft.SharePoint.Publishing.Navigation.PortalSiteMapProvider, Microsoft.SharePoint.Publishing, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" NavigationType="Combined" Version="14" />
    <add name="CurrentNavigation" description="Provider for MOSS Current Navigation" type="Microsoft.SharePoint.Publishing.Navigation.PortalSiteMapProvider, Microsoft.SharePoint.Publishing, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" NavigationType="Current" Version="14" />
    <add name="SiteDirectoryCategoryProvider" description="Site Directory category provider" type="Microsoft.SharePoint.Portal.WebControls.SiteDirectoryCategoryProvider, Microsoft.SharePoint.Portal, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
    <add name="MySiteMapProvider" description="MySite provider that returns areas and based on the current user context" type="Microsoft.SharePoint.Portal.MySiteMapProvider, Microsoft.SharePoint.Portal, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
    <add name="MySiteLeftNavProvider" description="MySite Left Nav provider that returns areas and based on the current user context" type="Microsoft.SharePoint.Portal.MySiteLeftNavProvider, Microsoft.SharePoint.Portal, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
    <add name="MySiteSubNavProvider" description="MySite Sub Nav provider that returns areas and based on the current user context" type="Microsoft.SharePoint.Portal.MySiteSubNavProvider, Microsoft.SharePoint.Portal, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
  </providers>
</siteMap>

Problem

This arrangement works well when you stay within a site collection. However, if you need to show navigation nodes from another site collection you will need another approach. The problem is that the SiteMapProvider is limited to returning only nodes in the current site collection. If you have created, for example, a My Site Host site collection, and want to display the root site collection’s top navigation at the top of the page instead of the default My Site Host navigation, you need a way to read the root site collection’s navigation nodes.

Solution

The solution provided below is for a publishing site and uses the PortalSiteMapProvider. The PortalSiteMapProvider has a method called GlobalNavSiteMapProvider which can be used to get the top navigation nodes as a SiteMapNodeCollection. We can interate over these nodes in a custom control and build up the navigation for our site in whatever way we want.

But, to return a SiteMapNodeCollection from another site collection we need a reference to the PortalSiteMapProvider in the context of the other site collection. One way to do this is to use an HTTP handler that can be called from the Layouts folder (e.g., _layouts/handlers/navigation.ashx) and then add the appropriate site collection’s URL. If we look at the following three site collections, their URL’s all point to the same HTTP handler:

If we call the first URL from either the Search Center or My Site Host site collections, the PortalSiteMapProvider, if it is referenced in the handler, will return navigation nodes from the root site collection. Note that this method will only work within a single web application. If we attempt to reach across applications this would amount to cross-site scripting which is prevented for security reasons.

The custom handler can then be called asynchronously from client code and a serialized set of navigation nodes returned to the browser and formatted using client script.

    Implementation

To implement this solution, add an HTTP handler to your project. The following assumes you have a mapped Layouts folder in a Visual Studio 2010 SharePoint project. Right-click the Layouts folder and Add an ASP.NET Handler from the Web item list.

image

This will create a class that inherits from IHttpHandler which requires you to implement a single method, ProcessRequest, and a single property, IsReusable.

public void ProcessRequest(HttpContext context)
{
    SPSecurity.RunWithElevatedPrivileges(GetGlobalNav);
}

public bool IsReusable
{
    get { return true; }
}

The IsReusable property allows subsequent requests to reuse the same instance of the handler. ProcessRequest acts as our entry point and, to ensure our method that retrieves navigation nodes has sufficient privileges, we call it with RunWithElevatedPrivileges.

The GetGlobalNav method then gets a reference to the PortalSiteMapProvider which then gets a SiteMapNodeCollection based on the root node. This collection is first passed into a method that converts it into a collection that can be easily serialized and then finally we call SerializeWrite to output JSON to the client.

private void GetGlobalNav()
{
    try
    {
        //Note: PortalSiteMapProvider returns data for the current request's site collection
        var rootNode = PortalSiteMapProvider.GlobalNavSiteMapProvider.RootNode;
        if (rootNode != null)
        {
            var nodes = PortalSiteMapProvider.GlobalNavSiteMapProvider.GetChildNodes(rootNode);
            SerializeWrite(GetSerializableSiteMap(nodes));
        }
    }
    catch (Exception e)
    {
        //log exception
    }
}

Since the generic List<t> can be easily serialized if we use a custom struct, all we need to do in GetSerializableSiteMap is build up a List using the specified SiteMapNodeCollection.

private List<SerializableSiteMapNode> GetSerializableSiteMap(SiteMapNodeCollection nodes)
{
    var serializableSiteMap = new List<SerializableSiteMapNode>();

    foreach (SiteMapNode node in nodes)
    {
        var serializableSiteMapNode = new SerializableSiteMapNode()
        {
            Description = node.Description,
            HasChildNodes = node.HasChildNodes,
            Key = node.Key,
            Title = node.Title,
            Url = node.Url
        };

        if (_siteMapLevel < Depth && node.HasChildNodes)
        {
            _siteMapLevel++;
            serializableSiteMapNode.ChildNodes = GetSerializableSiteMap(node.ChildNodes);
        }

        serializableSiteMap.Add(serializableSiteMapNode);
    }

    return serializableSiteMap;
}

public struct SerializableSiteMapNode
{
    public List<SerializableSiteMapNode> ChildNodes;
    public string Description;
    public bool HasChildNodes;
    public string Key;
    public string Title;
    public string Url;
}

The result of this is passed into a method that handles writing the HTTP response. Fortunately, .NET has a JavaScript serializer which makes our job easier.

private void SerializeWrite(object objectToWrite)
{
    var outputString = String.Empty;

    var serializer = new JavaScriptSerializer();
    outputString = serializer.Serialize(objectToWrite);

    HttpContext.Current.Response.Write(outputString);
}

The custom handler can be called in a user control using the appropriate site collection URL and the response data formatted using jQuery. We have added a user control to the top of the master page in our My Site Host site collection that calls our handler using a relative reference to the root site collection ("/"). So while we display the navigation in the My Site Host site collection, the nodes will be pulled from the root (main) site collection using the URL "/_layouts/handlers/navigation.ashx". The user control loads an empty unordered list element whose list items are built up asynchronously based on the response from our handler.

<script language="javascript" type="text/javascript">
    var handlerUrl = '/_layouts/handlers/navigation.ashx?&Depth=2';
    var level = 0;

    $(document).ready(function () {
        ul = $('#navGlobal');
        ul.children().remove();

        $.get(handlerUrl, function (data) {
            if (data != '') {
                tags = eval(data);
                writeNodes(tags, ul);
            }
        });
    });

    function writeNodes(tagNodes, ulNode) {
        $(tagNodes).each(function (i, n) {
            if (n.Url == null || n.Title == null) return true;

            var a = $(document.createElement('a'));
            a.html(n.Title);
            a.attr('href', n.Url);
            if (level == 0) a.attr('class', 'top_link');

            var li = $(document.createElement('li'));
            li.append(a);

            if (n.HasChildNodes) {
                level++;
                var ul = $(document.createElement('ul'));
                writeNodes(n.ChildNodes, ul);
                li.append(ul);
            }

            ulNode.append(li);
        });
    }
</script>

<ul id="navGlobal" />

This is a simple approach to simply reading and displaying navigation nodes from one site collection in another. We have not tried to merge navigation nodes using this method but I don’t see why one could not.

In addition, we have only used our handler to return data to the client. If you need to return it to server-side code, you can simply create a WebRequest object on the server that calls the handler. The data should then be serialized as XML and not JSON so you can use the .NET XML object model to handle the response.