Archive

Posts Tagged ‘ASP.NET’

SharePoint: How to make error messages more detailed

December 21st, 2011 No comments

    If an unexpected error occurs on a SharePoint environment, by default, you get a meaningless error message as the following: An unexpected error has occurred. Despite the given error message is considered as friendly one for users, we as developers want to have more information to detect the reason. SharePoint is built on ASP.Net technology, it means we can enable the detailed error message and, in addition, displaying of call-stack by changing some settings in a web.config file. We need to find the node customErrors and change its mode to “Off”, which specifies that custom errors are disabled and the detailed errors are shown to the local and remote clients. Then we need to find the node SafeMode and change its CallStack to “true”. CallStack attribute defines whether a call-stack and an exception message are displayed when a system-level exception takes place while ASP.NET processes a request from the local and remote clients. The last step is to save the made changes. The nodes we are interested in are shown below:

Before:

<customErrors mode="On" />
<SafeMode MaxControls="200" CallStack="false" ... >

After:

<customErrors mode="Off" />
<SafeMode MaxControls="200" CallStack="true" ... >

The required web.config file containing main settings locates in \inetpub\wwwroot\wss\VirtualDirectories\<SHAREPOINT APP PORT NUMBER>. For SharePoint 2007 it’s usually enough to make changes in the given file only. As for SharePoint 2010, the changes also should be applied to a number of additional web.config files in 14 hive (\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14) of SharePoint 2010. If you make a search for web.config inside the 14 folder you likely will find more than 30 different files. I recommend firstly to make changes in web.config files locating in \14\TEMPLATE\ADMIN and \14\TEMPLATE\LAYOUTS and then check whether you still get not detailed error message. If so, continue making the changes file by file until the message will get more detailed.

Related posts:

SharePoint: How to find all controls of a certain type

October 12th, 2011 No comments

    A small, but useful method to find recursively all controls of a certain type:

public static List<T> FindControlRecursiveByType<T>(Control root) where T : Control
{
    List<T> res = new List<T>();

    if (root != null)
    {
        Stack<Control> tmpStack = new Stack<Control>();
        tmpStack.Push(root);

        while (tmpStack.Count > 0)
        {
            Control ctrl = tmpStack.Pop();
            if (ctrl is T)
                res.Add(ctrl as T);

            foreach (Control childCtrl in ctrl.Controls)
                tmpStack.Push(childCtrl);
        }
    }

    return res;
}

The sample usage is below:

// return all save buttons on the page
List<SaveButton> saveButtons = FindControlRecursiveByType<SaveButton>(Page);
Related posts:
Categories: ASP.NET, Share Point Tags: ,

SharePoint: How to use SPLongOperation

August 11th, 2011 No comments

     If you need to run a lengthy server operation, it’s reasonable to use SPLongOperation. SPLongOperation shows a spinning wheel indicator with a specified text on the web page during a lengthy operation. You have probably seen SPLongOperation at work while creating new web applications or site collection using SharePoint Central Administration.

SPLongOperation - spin wheel with associated text

To use SPLongOperation, you need to create a new object of the SPLongOperation type, set the LeadingHTML and TrailingHTML properties to tell user what’s going on at the moment and call the SPLongOperation.Begin method to start the process. Then run the code of the lengthy server operation. Once the operation is done, call the SPLongOperation.End method passing the URL where user will be redirected to. Here is a typical code that utilizes SPLongOperation on a web page is:

void startLongOperationButton_Click(object sender, EventArgs e)
{
    using (SPLongOperation operation = new SPLongOperation(this.Page))
    {

        operation.LeadingHTML  = "Long operation";
        operation.TrailingHTML = "Please wait while the long operation is in progress";

        operation.Begin();

        // the code of the lengthy operation

        // redirecting to a page that, for example, informs user that the long operation has successfully completed
        operation.End("SomePage.aspx");
    }
}

I use the SPLongOperation, for example, when I need to create a list item with a huge number of unique permissions. It can take dozens of seconds, and it’s a good practice to use the spinning wheel indicator with a descriptive text to tell an impatient user that the application is in progress and not hung on him.

I’ve wrapped interaction with SPLongOperation into the following class and method:

/// <summary>
/// Represents all settings that can be specified to use SPLongOperation
/// </summary> 
public class SPLongOperationExecutionParams
{
    public string LeadingHtml  { get; set; }
    public string TrailingHtml { get; set; }
    public string RedirectUrl  { get; set; }
}

/// <summary>
/// Invokes an action inside SPLongOperation
/// </summary>
/// <param name="executionParams">Settings to use SPLongOperation</param>
/// <param name="action">User specified action</param>
public static void DoInSPLongOperationContext(SPLongOperationExecutionParams executionParams, Action action)
{
    if (HttpContext.Current.CurrentHandler is Page)
    {
        using (SPLongOperation operation = new SPLongOperation(HttpContext.Current.CurrentHandler as Page))
        {
            operation.LeadingHTML  = !string.IsNullOrEmpty(executionParams.LeadingHtml)  ? executionParams.LeadingHtml  : "Long operation";
            operation.TrailingHTML = !string.IsNullOrEmpty(executionParams.TrailingHtml) ? executionParams.TrailingHtml : "Please wait while the long operation is in progress";

            operation.Begin();

            if (action != null)
                action();

            try
            {
                operation.End(executionParams.RedirectUrl, SPRedirectFlags.Trusted, HttpContext.Current, "");
            }
            catch (System.Threading.ThreadAbortException)
            {
                // This exception is thrown because the SPLongOperation.End 
                // calls a Response.End internally
            }
        }
    }
    else
        throw new ApplicationException("Couldn't find a host page!");
}

Here is an example of how to use the wrapper:

void startLongOperationButton_Click(object sender, EventArgs e)
{
    SPLongOperationExecutionParams spLongOperationExecutionParams = new SPLongOperationExecutionParams() { LeadingHtml = "Creation of a new list item", TrailingHtml = "Please wait while the item is being created", RedirectUrl = "SomePage.aspx" };
    DoInSPLongOperationContext(spLongOperationExecutionParams, delegate()
    {
        // the code of the lengthy operation
        Thread.Sleep(5000);
    });
}

As you have probably noticed, I call the SPLongOperation.End inside the try…catch. I do so because this method may cause ThreadAbortException. The SPLongOperation.End internally calls Response.End, which stops execution of the page by means of the Thread.CurrentThread.Abort method. Here is the code of the Response.End method extracted by Reflector:

public void End()
{
    if (this._context.IsInCancellablePeriod)
    {
        InternalSecurityPermissions.ControlThread.Assert();
        Thread.CurrentThread.Abort(new HttpApplication.CancelModuleException(false)); (*)
    }
    else if (!this._flushing)
    {
        this.Flush();
        this._ended = true;
        if (this._context.ApplicationInstance != null)
        {
            this._context.ApplicationInstance.CompleteRequest();
        }
    }
}

The Thread.Abort method raises ThreadAbortException in the thread where it is invoked to begin the process of terminating the thread. I catch those exceptions and just do nothing about them.

In some blogs, people tell that SPLongOperation.End calls Response.Redirect internally, but Reflector shows that it’s not so in the reality. SPLongOperation.End calls Response.End directly. The redirection is accomplished through a special JavaScript, which is added to the web page right after SPLongOperation.End is invoked. I emphasize this fact, because you may attempt to pass the SPRedirectFlags.DoNotEndResponse flag to the SPLongOperation.End method. This flag makes sense only when we use the Response.Redirect, because only in that case Response.End wouldn’t be invoked, and, consequently, ThreadAbortException wouldn’t be thrown. But unfortunately, as I said above, SPLongOperation.End doesn’t use Response.Redirect and, therefore, SPRedirectFlags.DoNotEndResponse will be ignored, and the exception will be thrown.

Please also note that SPLongOperation keeps connection between client and server alive and the Response stream open. Therefore, if your long operation takes longer than the value of httpRuntime.executionTimeout defined in Web.config (or Machine.config) you will receive the ‘Request Timed Out’ exception. Therefore, to perform a really long operation (for example, longer than the default time-out value, which is 90 seconds), you would have to refuse SPLongOperation and look for some other approach.

SharePoint: How to customize Content breadcrumb navigation

August 3rd, 2011 No comments

     Recently, I’ve stumbled over a problem with a Content breadcrumb navigation. Content breadcrumb navigation is a set of hyperlinks that enables site users to quickly navigate up the hierarchy of sites within a site collection.

Content breadcrumb navigation

The problem was that in the edit mode the link with the item title (for example, like the Test post on image) sometimes had a wrong url. Content breadcrumb navigation is a SiteMapPath control, which is rendered based upon site map nodes, provided by a SiteMapProvider. An obvious way to solve the issue is to find the problematic site map node in the tree and try to modify the node in the memory, i.e. to replace its url or the entire node.

I have got a fully customized edit page for my list (for each content type, actually); in other words, I got access to the executable code (so-called, code-behind) and the markup of the aspx-page. That is the main requirement for the successful resolution of the issue.

First of all, I found a place where SiteMapPath had been declared in default.master:

<asp:ContentPlaceHolder id="PlaceHolderTitleBreadcrumb" runat="server">
    <asp:SiteMapPath SiteMapProvider="SPContentMapProvider" id="ContentMap" SkipLinkText="" NodeStyle-CssClass="ms-sitemapdirectional" runat="server"/> 

&nbsp;
</asp:ContentPlaceHolder>

Then I copied it to my edit aspx-page with the appropriate changes:

<asp:Content ID="ContentBreadcrumb" ContentPlaceHolderID="PlaceHolderTitleBreadcrumb" runat="server">    
    <asp:SiteMapPath SiteMapProvider="SPContentMapProvider" id="SiteMapPathContentMap" SkipLinkText="" NodeStyle-CssClass="ms-sitemapdirectional" 

runat="server" /> &nbsp;
</asp:Content>

From that point, I could easily manipulate the SiteMapPath control in the code-behind of aspx-page.

The standard way to programmatically modify site map nodes in the memory is to hande the SiteMap.SiteMapResolveevent on an ASP.Net web page. This way is described in msdn http://msdn.microsoft.com/en-us/library/ms178425%28v=VS.85%29.aspx. The following listing demonstrates how to connect to the required SiteMapResolve event. Note that our target SiteMapProvider is SPContentMapProvider.

protected void Page_Load(object sender, EventArgs e)
{            
    Microsoft.SharePoint.Navigation.SPContentMapProvider prov = 

(Microsoft.SharePoint.Navigation.SPContentMapProvider)SiteMap.Providers["SPContentMapProvider"];
    if (prov != null)
        prov.SiteMapResolve += new SiteMapResolveEventHandler(prov_SiteMapResolve);
}

In the prov_SiteMapResolve handler, I tried using two variants of code. The first variant is to set the required url to a certain SiteMapNode, which turns into a link with the item title:

SiteMapNode prov_SiteMapResolve(object sender, SiteMapResolveEventArgs e)
{
    SiteMapNode currentNode = e.Provider.CurrentNode; // in our case current node is 'Edit Item'
    SiteMapNode parentNode  = currentNode.ParentNode; // parent node is 'Test post'

    if (parentNode != null && parentNode.Title.Equals(SPContext.Current.ListItem.Title, StringComparison.OrdinalIgnoreCase))
    {
        bool originalReadOnly = parentNode.ReadOnly;
        parentNode.ReadOnly   = false;
        parentNode.Url        = "microsoft.com";
        parentNode.ReadOnly   = originalReadOnly;
    }
    return currentNode;
}

This variant passes through without errors, but at the same time produces no result. The problematic hyperlink remains with a wrong url.

Another variant tries to replace current node with a new SiteMapNode object:

SiteMapNode prov_SiteMapResolve(object sender, SiteMapResolveEventArgs e)
{
    SiteMapNode currentNode = e.Provider.CurrentNode; // in our case current node is 'Edit Item'
    SiteMapNode parentNode  = currentNode.ParentNode; // parent node is 'Test post'

    if (parentNode != null && parentNode.Title.Equals(SPContext.Current.ListItem.Title, StringComparison.OrdinalIgnoreCase))
    {
	// Clone the current node and all of its relevant parents. This
        // returns a site map node that can then be walked and modified.
        // Since the cloned nodes are separate from the underlying
        // site navigation structure, the changes that are made do not
        // effect the overall site navigation structure.
        currentNode    = currentNode.Clone(true);
        parentNode     = currentNode.ParentNode;
        parentNode.Url = "microsoft.com";
    }
    return currentNode;
}

This code throws an exception – “Unable to cast object of type ‘System.Web.SiteMapNode’ to type ‘Microsoft.SharePoint.Navigation.SPSiteMapNode’.” Which is quite expected, as SPContentMapProvider indeed provides a tree of SPSiteMapNodes and not SiteMapNodes. Moreover, there is no way to go around this obstacle, as SPSiteMapNode is an internal class, so we cannot create instances of it. So, I must conclude that the use of the SiteMapResolve event, which successfully works for ASP.Net, doesn’t work for SharePoint.

But let’s try a different way. The SiteMapPath control has these two events: ItemCreated and ItemDataBound. Theoretically, they could be used for changing the url of nodes. I’ve tried using them both as follows:

protected override void OnInit(EventArgs e)
{
    base.OnInit(e);

    SiteMapPathContentMap.ItemCreated += new SiteMapNodeItemEventHandler(SiteMapPathContentMap_ItemCreated);
    //SiteMapPathContentMap.ItemDataBound += new SiteMapNodeItemEventHandler(SiteMapPathContentMap_ItemDataBound);
}
void SiteMapPathContentMap_ItemCreated(object sender, SiteMapNodeItemEventArgs e)
{
    if (e.Item.ItemType == SiteMapNodeItemType.PathSeparator)
        return;

    if (e.Item.SiteMapNode.Title.Equals(SPContext.Current.ListItem.Title, StringComparison.OrdinalIgnoreCase))
    {
        bool originalReadOnly       = e.Item.SiteMapNode.ReadOnly;
        e.Item.SiteMapNode.ReadOnly = false;
        e.Item.SiteMapNode.Url      = "microsoft.com";
        e.Item.SiteMapNode.ReadOnly = originalReadOnly;
    }
}

Unfortunately, that produces no result too. e.Item.SiteMapNode.Url does change indeed, but the rendered hyperlink still remains with a wrong url. What could cause that? Let’s take a look inside SiteMapPath by means of Reflector. SiteMapPath contains the CreateItem method, which is called for each site map node, and which creates SiteMapNodeItem.

private SiteMapNodeItem CreateItem(int itemIndex, SiteMapNodeItemType itemType, SiteMapNode node)
{
    SiteMapNodeItem item = new SiteMapNodeItem(itemIndex, itemType);
    int index = (this.PathDirection == PathDirection.CurrentToRoot) ? 0 : -1;
    SiteMapNodeItemEventArgs e = new SiteMapNodeItemEventArgs(item);
    item.SiteMapNode = node;
    this.InitializeItem(item);
    this.OnItemCreated(e);
    this.Controls.AddAt(index, item);
    item.DataBind();
    this.OnItemDataBound(e);
    item.SiteMapNode = null;
    item.EnableViewState = false;
    return item;
}

The ItemCreated and ItemDataBound events are fired inside this method. Note that ItemCreated activates immediately after calling InitializeItem. Let’s take a look at InitializeItem.

protected virtual void InitializeItem(SiteMapNodeItem item)
{
    // some code is skipped

    SiteMapNode siteMapNode = item.SiteMapNode;

    // some code is skipped


    if (itemType == SiteMapNodeItemType.PathSeparator)
    {
        Literal child = new Literal
        {
            Mode = LiteralMode.Encode,
            Text = this.PathSeparator
        };
        item.Controls.Add(child);
        item.ApplyStyle(s);
    }
    else if ((itemType == SiteMapNodeItemType.Current) && !this.RenderCurrentNodeAsLink)
    {
        Literal literal2 = new Literal
        {
            Mode = LiteralMode.Encode,
            Text = siteMapNode.Title
        };
        item.Controls.Add(literal2);
        item.ApplyStyle(s);
    }
    else
    {
        HyperLink link = new HyperLink(); // (*)
        if ((s != null) && s.IsSet(0x2000))
        {
            link.Font.Underline = s.Font.Underline;
        }
        link.EnableTheming = false;
        link.Enabled = this.Enabled;
        if (siteMapNode.Url.StartsWith(@"\\", StringComparison.Ordinal))
        {
            link.NavigateUrl = base.ResolveClientUrl(HttpUtility.UrlPathEncode(siteMapNode.Url));
        }
        else
        {
            link.NavigateUrl = (this.Context != null) ? 
this.Context.Response.ApplyAppPathModifier(base.ResolveClientUrl(HttpUtility.UrlPathEncode(siteMapNode.Url))) : siteMapNode.Url;
        }
        link.Text = HttpUtility.HtmlEncode(siteMapNode.Title);
        if (this.ShowToolTips)
        {
            link.ToolTip = siteMapNode.Description;
        }
        item.Controls.Add(link);
        link.ApplyStyle(s);
    }
}

As we can see, InitializeItem creates and fills out a HyperLink object (see the line marked as (*)) that corresponds to the passed SiteMapNodeItem and, consequently, to the linked SiteMapNode. After the creation and initialization, the HyperLink object is added to the control tree of SiteMapNodeItem. Thereby, the ItemCreated event is fired immediately after all the required controls, including hyperlinks, are already created and added to the control tree. That means that all our changes of SiteMapNode inside the ItemCreated handler are doomed to have no effect, just because by that time all the hyperlinks are already created and are not going to be modified.

How to get over this problem? I’ve created an enhanced version of the SiteMapPath control. This derived class has an ItemCreating event, which is fired immediately after SiteMapNodeItem is created but before InitializeItem is called. Here is the control:

public class MySiteMapPath : SiteMapPath
{
    private static readonly object _eventItemCreating = new object();

    public event SiteMapNodeItemEventHandler ItemCreating
    {
        add    { Events.AddHandler(_eventItemCreating, value); }
        remove { Events.RemoveHandler(_eventItemCreating, value); }
    }

    protected virtual void OnItemCreating(SiteMapNodeItemEventArgs e)
    {
        SiteMapNodeItemEventHandler handler = (SiteMapNodeItemEventHandler)base.Events[_eventItemCreating];
        if (handler != null)
            handler(this, e);
    }

    protected override void InitializeItem(SiteMapNodeItem item)
    {
        OnItemCreating(new SiteMapNodeItemEventArgs(item));
        base.InitializeItem(item);
    }
}

In the handler of ItemCreating event, we can freely change site map nodes, and the hyperlinks will reflect those changes. Here is an example of the use of the MySiteMapPath in code-behind:

public MySiteMapPath SiteMapPathContentMap;

protected override void OnInit(EventArgs e)
{
    base.OnInit(e);

    if(SiteMapPathContentMap != null)
        SiteMapPathContentMap.ItemCreating += new SiteMapNodeItemEventHandler(SiteMapPathContentMap_ItemCreating);
}

void SiteMapPathContentMap_ItemCreating(object sender, SiteMapNodeItemEventArgs e)
{
    if (e.Item.ItemType == SiteMapNodeItemType.PathSeparator)
        return;

    if (e.Item.SiteMapNode.Title.Equals(SPContext.Current.ListItem.Title, StringComparison.OrdinalIgnoreCase))
    {
        bool originalReadOnly       = e.Item.SiteMapNode.ReadOnly;
        e.Item.SiteMapNode.ReadOnly = false;
        e.Item.SiteMapNode.Url      = "microsoft.com";
        e.Item.SiteMapNode.ReadOnly = originalReadOnly;
    }	    
}

This is the murkup that has to be in aspx-page:

<asp:Content ID="ContentBreadcrumb" ContentPlaceHolderID="PlaceHolderTitleBreadcrumb" runat="server">    
    <myNamespace:MySiteMapPath SiteMapProvider="SPContentMapProvider" id="SiteMapPathContentMap" SkipLinkText="" 

NodeStyle-CssClass="ms-sitemapdirectional" runat="server" /> &nbsp;
</asp:Content>

So, if you want to change certain properties of a content breadcrumb navigation hyperlink, just declare MySiteMapPath and handle its ItemCreating event in the appropriate way.

SharePoint: Updates are currently disallowed on GET requests

March 4th, 2011 No comments

     When I need to set unique permissions to a SPListItem I usually use the code like the following:

using (SPSite spSite = new SPSite("some url"))
{
    using (SPWeb spWeb = spSite.OpenWeb())
    {
        bool oldAllowUnsafeUpdates = spWeb.AllowUnsafeUpdates;
        spWeb.AllowUnsafeUpdates = true;
        spWeb.Update();

        try
        {
            SPList spList = spWeb.Lists["some list"];

            SPListItem spLisItem = spList.GetItemById(someId);
            spLisItem.BreakRoleInheritance(false);

            SPRoleDefinition reader = spWeb.RoleDefinitions.GetByType(SPRoleType.Reader);
            SPGroup someGrp = spWeb.Groups["some group"];

            SPRoleAssignment roleAssignment = new SPRoleAssignment(someGrp);
            roleAssignment.RoleDefinitionBindings.Add(reader);
            spListItem.RoleAssignments.Add(roleAssignment); // (***) exception

        }
        catch (Exception ex)
        {
            // logging
        }

        spWeb.AllowUnsafeUpdates = oldAllowUnsafeUpdates;
    }
}

     It works fine almost everywhere: in feature receivers, in jobs, in console applications and so on. But today I’ve found out that it doesn’t work correctly if it runs from aspx-page’s code-behind when we have GET request (Page.IsPostBack = false). In the line marked (***) I receives a traditional exception – “Updates are currently disallowed on GET requests. To allow updates on a GET, set the ‘AllowUnsafeUpdates’ property on SPWeb”. As you can see I set spWeb.AllowUnsafeUpdates to true and even do spWeb.Update() (though it’s unnecessary in most cases), but nothing helps. Wrapping this code in SPSecurity.RunWithElevatedPrivileges doesn’t help either.

     After debugging for a while I’ve noticed that spWeb.AllowUnsafeUpdates gets false after spLisItem.BreakRoleInheritance:

// here spWeb.AllowUnsafeUpdates = true
spLisItem.BreakRoleInheritance(false);
// here spWeb.AllowUnsafeUpdates = false

     It should be noted that the same happens when we have a POST request (Page.IsPostback = true), but in this case it doesn’t cause exception. Interestingly that we have some kind of special treatment for GET request here 🙂

     The reason of such behavior has been found, as usual, by means of .NET Reflector. Not going into details I say that the calling of BreakRoleInheritance leads to the calling of Invalidate() method of SPWeb. Let’s take a look at this method:

internal void Invalidate()
{
    this.ReleasePinnedResource();
    if (this.m_Request != null)
    {
        if (this.m_RequestOwnedByThisWeb)
        {
            SPRequestManager.Release(this.m_Request);
        }
        this.m_Request = null;
    }
    this.m_bInited = false;
    this.m_bPublicPropertiesInited = false;
    this.m_Url = null;
}

     It looks like Invalidate releases some resources and cleans itself (m_bInited = false), in the same time, doesn’t dispose itself, but just provokes reinitializing during the next address to it. After reinitialization spWeb.AllowUnsafeUpdates turns into false. An evident workaround is to set true to spWeb.AllowUnsafeUpdates again after BreakRoleInheritance.

// some code is skipped
spLisItem.BreakRoleInheritance(false);
spWeb.AllowUnsafeUpdates = true;
spWeb.Update();
// some code is skipped

     I think there are many methods, which can cause SPWeb.Invalidate, for example, ResetRoleInheritance does the same, and, in a few special cases, SPWeb.Update either. That is why be ready to add restoring of AllowUnsafeUpdates to true. I hope this post will save time for somebody.

Related posts: