Archive

Archive for the ‘Share Point’ Category

SharePoint: How to get Shared Service name

August 18th, 2011 No comments

     As you probably know, you can deploy several Shared Services in a SharePoint farm, having each of them serve one or more web applications. If you need to get the name of Shared Serviceserving your web application programmatically, you can use the following method:

public static string GetSharedServiceName(SPSite spSite)
{
    try
    {
        ServerContext sc = ServerContext.GetContext(spSite);
        PropertyInfo srpProp = sc.GetType().GetProperty("SharedResourceProvider", BindingFlags.NonPublic | BindingFlags.Instance);
        object srp = srpProp.GetValue(sc, null);
        PropertyInfo srpNameProp = srp.GetType().GetProperty("Name", BindingFlags.Public | BindingFlags.Instance);
        string sspName = (string)srpNameProp.GetValue(srp, null);
        return sspName;
    }
    catch
    {
        return null;
    }
}

The method looks a bit obscure because of Reflection we have to use, as there is no supported way to get what we need without it. The ServerContext object contains the internal SharedResourceProvider property, which is an object of the internal SharedResourceProvider class. So, there is no way we can do that without using Reflection.

Please also note that according to MSDN, the ServerContext object provides run-time methods for Shared Services in Microsoft Office SharePoint Server 2007. In other words, ServerContext is available only in MOSS (I’m talking about SharePoint 2007. Yes, I still use it 🙂 ). The ServerContext is defined in Microsoft.Office.Server.Administration namespace in the Microsoft.Office.Server DLL.

Example of use:

string sharedServiceName = GetSharedServiceName(SPContext.Current.Site);

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 shrink transaction log file

August 3rd, 2011 No comments

     While restoring a production database in my local environment, first of all I shrink the database transaction log file, because it usually takes up a lot of space. I use SQL Server 2008, and in order to get the transaction log shrunk, I can suggest swithing the database recovery model from FULL to SIMPLE and back. To accomplish that:

  1. Switch the database recovery model to SIMPLE using the following command:
    • ALTER DATABASE <Your Database Name> SET RECOVERY SIMPLE;
  2. Shrink the database or transaction log file using Microsoft SQL Server Management Studio:
    Shrink Transaction Log
    Or use the following command to shrink the transaction log file, for example, to 5 MB:

    • DBCC SHRINKFILE (<Your Database Name>_Log, 5);
  3. Switch the database recovery model back to FULL using the following command:
    • ALTER DATABASE <Your Database Name> SET RECOVERY FULL;

If it’s not a production database, you can actually skip step 3, leaving the database recovery model in SIMPLE state; the Transaction log in this case is not going to grow. But keep in mind that some people might complain that it causes problems when attempting to delete SiteCollection from SharePoint Central Administration. I’ve never faced such issues, but you may want to keep FULL recovery model just in case.

Related posts:

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: The usage of SPUtility.GetLocalizedString

May 12th, 2011 No comments

     Sometimes when I parse the Schema.xml of some list I need to resolve such attribute values as:

  • “$Resources:core,lists_Folder;/ONEOFYOURLISTNAME” – often can be found in Lookup-field definitions;
  • “$Resources:groupExpColl;” – can be found in View definition;
  • “$Resources:core,moreItemsParen;” – can be found in View definition;
  • “$ResourcesNoEncode:core,noXinviewofY_LIST;” – can be found in View definition;
  • “$Resources:spscore,FeaturePushdownTaskTitle;”;
  • “$Resources:cmscore,PagesListDisplayName”;
  • and so on.

The method SPUtility.GetLocalizedString(string source, string defaultResourceFile, uint language) is dedicated for retrieving the value for a named resource string from the resource file for a specified language. In example above, a number of strings already contains indications what resource file should be used for searching the localized value, i.e. core, spscore and cmscore. That is why, in most cases, null for defaultResourceFile-parameter can be passed into SPUtility.GetLocalizedString.

If resource file name isn’t included into string (e.g. “$Resources:groupExpColl;”) and defaultResourceFile-parameter is null, I believe, SPUtility.GetLocalizedString searchs in core.resx or in its language-specific variant, e.g. core.en-US.resx (according to language-parameter).

I’ve wrapped the usage of SPUtility.GetLocalizedString into small method, which takes it upon itself to detect the current language.

public static string GetLocalizedString(SPWeb spWeb, string source)
{
    if (string.IsNullOrEmpty(source) || !source.StartsWith("$Resources", StringComparison.OrdinalIgnoreCase))
        return source;
    uint language = spWeb != null ? spWeb.Language : (HttpContext.Current != null ? (uint)Thread.CurrentThread.CurrentUICulture.LCID : 1033);
    return SPUtility.GetLocalizedString(source, null, language);
}

public static string GetLocalizedString(string source)
{
    return GetLocalizedString(null, source);
}

Below the results of GetLocalizedString in respect to each string from the above-mentioned example

String

Localized string

“$Resources:core,lists_Folder;/ONEOFYOURLISTNAME” “Lists/ONEOFYOURLISTNAME”
“$Resources:groupExpColl;” “Expand/Collapse”
“$Resources:core,moreItemsParen;” “(More Items…)”
“$ResourcesNoEncode:core,noXinviewofY_LIST;” “<HTML>There are no items to show in this view of the \”</HTML><ListProperty Select=\”Title\” HTMLEncode=\”TRUE\”><HTML>\” list.</HTML>”
“$Resources:spscore,FeaturePushdownTaskTitle;” “Enable new features on existing sites”
“$Resources:cmscore,PagesListDisplayName” “Pages”
Related posts: