Archive

Posts Tagged ‘Share Point’

SharePoint: How to set Id to just created SPContentType

September 15th, 2011 No comments

     When adding Content Type programmatically, sometimes you may need to set a certain Id to it, to ensure that other parts of the application can refer to the Content Type using a known identifier. If you use SharePoint 2010 you can get this done instantly, as SharePoint 2010 provides a handy SPContentType constructor, which accepts SPContentTypeId as parameter:

public SPContentType(SPContentTypeId contentTypeId, SPContentTypeCollection contentTypes, string name);

But if you use SharePoint 2007, you don’t have such a constructor or any built-in means to set the required identifier. Besides, the Id property of SPContentType appears to be read-only. After studying the SPContentType class with Reflector I’ve discovered that the Content Type id is stored in the private m_id property:

private SPContentTypeId m_id;

This means that we can use Reflection to set this property. Here is a special SetContentTypeId method I’ve implemented for doing that:

public void SetContentTypeId(SPContentType spContentType, SPContentTypeId contentTypeId)
{
    try
    {
        FieldInfo fi = typeof(SPContentType).GetField("m_id", BindingFlags.NonPublic | BindingFlags.Instance);                
        fi.SetValue(spContentType, contentTypeId);                
    }
    catch (Exception ex)
    {
        Console.Write(string.Format("Couldn't set content type id! {0}", ex.Message));
    }
}

The method accepts the instance of the SPContentType class and the required identifier as the instance of the SPContentTypeId class. Here is an example of use:

public void AddSomeNewContentType(SPWeb spWeb, SPList spList)
{
    SPContentType parentContentType = spWeb.AvailableContentTypes["some parent content type name"];
    if (parentContentType != null)
    {
        SPContentType spContentType = new SPContentType(parentContentType, spList.ContentTypes, "some new content type name");
        SetContentTypeId(spContentType, new SPContentTypeId("0x0100078C8B39671A4532AB9C5AB6DCB388A6")); // content type id you need has to be here, for example 0x0100078C8B39671A4532AB9C5AB6DCB388A6

        // set other properties of content type

        spList.ContentTypes.Add(spContentType);
        spContentType.Update();
        spList.Update();
    }
}

Please note that you must NOT use the SetContentTypeId method with existing built-in or earlier created Content Types. Otherwise, it may corrupt the SharePoint data integrity, especially when there are list items created based on the changed Content Type.

Note again, use the SetContentTypeId method ONLY immediately following the creation of a Content Type and ONLY before adding it to whatever collection of Content Types or before calling SPContentType.Update(). This is very important.

My next post describes how to add Content Type programmatically.

SharePoint: How to enumerate shared services

August 19th, 2011 No comments

     Continuing the previous article – How to get Shared Service name, I’m going to show how to enumerate all available Shared Services (or Shared Resource Providers, to be more precise). Here is the code (with Reflection, of course 🙂 ) :

public static void EnumerateSharedServices(SPSite spSite, Action<object> action)
{
    ServerContext serverContext = ServerContext.GetContext(spSite);
    object serverFarm = serverContext.GetType().GetField("m_ServerFarm", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(serverContext);
    var sspCollectionProp = serverFarm.GetType().GetProperty("SharedResourceProviders");
    var sspCollection = sspCollectionProp.GetValue(serverFarm, null) as IEnumerable;
    foreach (SPPersistedObject sharedServiceProvider in sspCollection)
        action(sharedServiceProvider);    
}

You have to use Reflection to get a property value of a Shared Service Provider; the general use of it is something similar to this:

EnumerateSharedServices(SPContext.Current.Site, delegate(object sharedResourceProvider)
{
    string sspName     = sharedResourceProvider.GetType().GetProperty("Name").GetValue(sharedResourceProvider, null).ToString(); 
    string sspUserName = sharedResourceProvider.GetType().GetProperty("UserName").GetValue(sharedResourceProvider, null).ToString();      
    string sspPassword = sharedResourceProvider.GetType().GetProperty("Password").GetValue(sharedResourceProvider, null).ToString();

    Console.WriteLine(string.Format("{0} [UserName: {1}, Passw: {2}]", sspName, sspUserName, sspPassword));

    // enumerate web applications that are served by the shared service
    var appCollection = sharedResourceProvider.GetType().GetProperty("WebApplications").GetValue(sharedResourceProvider, null) as System.Collections.IEnumerable;
    foreach (Microsoft.SharePoint.Administration.SPWebApplication app in appCollection)
        Console.WriteLine(string.Format("Web app: {0}", app.Name));
});

Let’s take a deeper look at EnumerateSharedServices. Its key object is serverFarm, which is an object of the internal ServerFarm class (the Microsoft.Office.Server.Administration namespace in the Microsoft.Office.Server DLL) containing a collection of Shared Resource Providers (collection of SharedResourceProvider objects). Navigating through this collection an user action is called for each provider.

To make this code more readable, I’d recommend using an object-wrapper for Shared Resource Provider. For example:

public class SharedServiceInfo
{
    protected readonly object _rawSharedResourceProvider;

    public string Name
    {
        get { return GetPropertyValue("Name").ToString(); }
    }
    public string UserName
    {
        get { return GetPropertyValue("UserName").ToString(); }
    }
    public string Password
    {
        get { return GetPropertyValue("Password").ToString(); }
    }
    public List<SPWebApplication> WebApplications
    {
        get 
        {
            IEnumerable iEnumerable = (IEnumerable)GetPropertyValue("WebApplications");
            return new List<SPWebApplication>(iEnumerable.Cast<SPWebApplication>()); 
        }
    }

    public SharedServiceInfo(object rawSharedResourceProvider)
    {
        _rawSharedResourceProvider = rawSharedResourceProvider;
    }

    protected object GetPropertyValue(string propName)
    {
        return _rawSharedResourceProvider.GetType().GetProperty(propName).GetValue(_rawSharedResourceProvider, null);
    }
}
public static void EnumerateSharedServices(SPSite spSite, Action<SharedServiceInfo> action)
{
    ServerContext serverContext = ServerContext.GetContext(spSite);
    object serverFarm = serverContext.GetType().GetField("m_ServerFarm", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(serverContext);

    var sspCollectionProp = serverFarm.GetType().GetProperty("SharedResourceProviders");
    var sspCollection = sspCollectionProp.GetValue(serverFarm, null) as IEnumerable;
    foreach (SPPersistedObject sharedServiceProvider in sspCollection)
        action(new SharedServiceInfo(sharedServiceProvider));
}

You can extend SharedServiceInfo with the properties you need.

Sample of usage:

EnumerateSharedServices(SPContext.Current.Site, delegate(SharedServiceInfo sharedServiceInfo)
{
    Console.WriteLine(string.Format("{0} [UserName: {1}, Passw: {2}]", sharedServiceInfo.Name, sharedServiceInfo.UserName, sharedServiceInfo.Password));

    // enumerate web applications that are served by the shared service
    foreach (Microsoft.SharePoint.Administration.SPWebApplication app in sharedServiceInfo.WebApplications)
        Console.WriteLine(string.Format("Web app: {0}", app.Name));
});

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: