Showing posts with label SharePoint WebParts. Show all posts
Showing posts with label SharePoint WebParts. Show all posts

Sunday, January 25, 2009

Anyone know where I can find a good Locksmith?


I recently had the need to develop a WebPart for a client that needed to be able to "unlock" a site collection. For any of you that have tried this from within a WebPart running on the SharePoint platform, you know there are quite a few issues that can come up.

There are four different types of locks that can be set, here is a quick mapping of how those locks match up with the SPSite locking properties:

Not locked / nonesite.writeLocked = false
site.readOnly = false
site.readLocked = false
Adding content prevented / noadditionssite.writeLocked = true
site.readOnly = false
site.readLocked = false
Read-only / readonlysite.writeLocked = false
site.readOnly = true
site.readLocked = false
No access / noaccesssite.writeLocked = false
site.readOnly = false
site.readLocked = true

Additionally, when a site is locked using any one of the lock options, the site.LockIssue is used to describe the reason for the lock. If the site was locked using the Central Administraion console, the Additional lock information (LockIssue) field is required when locking the site. However, this parameter is not set when locking a site using the command line interface stsadm.

Here are just a few of the error messages I ran into while trying to set the lock options outright in my WebPart:
  1. Updates are currently disallowed on GET requests. To allow updates on a GET, set the 'AllowUnsafeUpdates' property on SPWeb.
  2. The security validation for this page is invalid. Click Back in your Web browser, refresh the page, and try your operation again.
  3. Access to this Web site has been blocked.
  4. Attempted to perform an unauthorized operation.
  5. Access denied.
With the exception of the first error, the remainder of the messages all center around permissions and access. With that in mind, I went about trying to build some solutions using the typical SharePoint methods to elevate the privileges of the code being executed. The first of these is the SPSecurity.RunWithElevatedPrivileges. With impersonation turned on in our web.config file, the code is running under the context of the SharePoint user. This method appears to elevate the code to run under the service account on the application pool.
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite site = new SPSite(siteUrl))
{
site.AllowUnsafeUpdates = true;
site.ReadLocked = false;
site.ReadOnly = false;
site.WriteLocked = false;
site.LockIssue = "";
}
});
Next, with no more luck, I tried to initialize a instance of the site class running under the context of my administration account.
using (SPSite site = new SPSite(siteUrl, SPContext.Current.Web.AllUsers["domain\\adminUsername"].UserToken))
{
site.AllowUnsafeUpdates = true;
site.ReadLocked = false;
site.ReadOnly = false;
site.WriteLocked = false;
site.LockIssue = "";
}
After a little bit more investigating, I discovered that in order to perform the unlock procedures that I needed, full blown code impersonation was needed. With a little bit of additional code, I had the access that I needed.
//get global admin access to perform the work
ImpersonateValidUser("domain", "adminUsername", "password");

using (SPSite site = new SPSite(siteUrl))
{
site.WebApplication.FormDigestSettings.Enabled = false;
site.AllowUnsafeUpdates = true;
site.ReadLocked = false;
site.ReadOnly = false;
site.WriteLocked = false;
site.LockIssue = "";
}

UndoImpersonation();
The code to implement the ImpersonateValidUser and UndoImpersonation functions can be found here: http://www.codeproject.com/KB/cs/zetaimpersonator.aspx, a wonderful bit of code from Uwe Keim.

By adding the line site.WebApplication.FormDigestSettings.Enabled = false to the code, we are able circumvent error #2. All of the remaining errors should be taken care of by our code impersonation. However, be warned that if the site has either readOnly or readLocked set to true, you will still have issues accessing values of various site properties. You will need to unlock the site to get the values that you need. If desired, you can then set the locks back to their previous state (assuming you have saved the state somewhere). And yes, you will even run into errors simply trying to save off this state for later use. The specific type of error that gets thrown will tell you everything you need to know about which of the three locks has been set.

When implementing the code, be sure that it is not executing when the page initially loads, otherwise you will run into error #1. Rather run the code during a page post back (i.e. in an event handler to manage the WebPart form submittal).

Thursday, December 11, 2008

Site Navigator Released

Ensynch has just released a new SharePoint WebPart that will display a tree view of all of the sites in your SharePoint environment. Props to Joe Zamora and Jerry Camel for all of their development efforts, designing and creating this WebPart.


There are two versions of the Site Navigator, the Lite Version and the Professional Version. The Lite Version, is available at no cost and will allow you to select a starting point for the tree and additionally will allow you to "trim the tree" based on user permissions. The Professional version, which is available for a small fee, adds some additional functionality to set the automatic expansion depth, show site collections only, and adds an option to select which web applications to show instead of setting a URL starting point.


If you are interested in either the Lite or Pro version, simply drop a line to SharePointSolutions@Ensynch.com and we will get you hooked up. And in case you just want to know how we did it, here are some of the details, with a few minor changes to simplify the illustration...

After you create a Class Library project, make sure you add a reference to the Microsoft.SharePoint namespace and have all of the following using commands:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Serialization;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.WebPartPages;
using Microsoft.SharePoint.Administration;
Also, you will need to add two classes to your .cs file. One of these classes will be used to build and render the WebPart Tree and the other will be implementing the EditorPart class to build the property modification pane. There are a couple of methods that must be implemented in both classes:
public class SPSiteCollectionNav : System.Web.UI.WebControls.WebParts.WebPart, IWebEditable
{
     
     public SPSiteCollectionNav()
     {
     
     }
     
     //
     //IWebEditable Members
     //
     EditorPartCollection IWebEditable.CreateEditorParts()
     {
     
           List editors = new List();
           return new EditorPartCollection(editors);
     
     }
     
     object IWebEditable.WebBrowsableObject
     {
     
           get { return this; }
     
     }
     
     //
     //WebPart override methods
     //
     protected override void CreateChildControls()
     {
     
           base.CreateChildControls();
           this.ChildControlsCreated = true;
     
     }
     
     protected override void Render(HtmlTextWriter writer)
     {
     
           RenderChildren(writer);
     
     }
     
}

public class TreeViewEditor : EditorPart, ICallbackEventHandler
{
     
     public TreeViewEditor()
     {
     
     }
     
     //
     //EditorPart override methods
     //
     protected override void CreateChildControls()
     {
     
     }
     
     public override bool ApplyChanges()
     {
     
           EnsureChildControls();
     
           return true;
     
     }
     
     public override void SyncChanges()
     {
     
           EnsureChildControls();
     
     }
     
     //
     //ICallbackEventHandler Members
     //
     private string _argument = string.Empty;
     
     public void RaiseCallbackEvent(string eventArgument)
     {
     
           //store the argument in a local variable so we can use it in the "GetCallbackResult" method
           //later on if necessary.
           _argument = eventArgument;
     
     }
     
     public string GetCallbackResult()
     {
     
           return string.Empty;
     
     }
     
}
We are going to discuss building the SPSiteCollectionNav class first. Now that we have implemented all of the necessary functions, we can begin customizing our code. We are going to use the CreateChildControls() method to initiate the creation our tree and add it to the WebPart Controls Collection. We are also going to add a new property that we can set with our EditorPart to dynamically change properties of our tree display. Here is enough to get you started.
private TreeView tree;

public SPSiteCollectionNav()
{
     tree = new TreeView();
}

//lets add a property to turn on and off a create date tag
//appended to the tree node text, for EditorPart demonstration
private bool showCreateDate;

[WebBrowsable(false), Personalizable(PersonalizationScope.Shared)]
public bool ShowCreateDate
{
     get { return showCreateDate; }
     set { showCreateDate = value; }
}

EditorPartCollection IWebEditable.CreateEditorParts()
{
     List editors = new List();
     
     //add our EditorPart to the editors collection
     TreeViewEditor editor = new TreeViewEditor();
     editors.Add(editor);
     
     return new EditorPartCollection(editors);
}

protected override void CreateChildControls()
{
     
     base.CreateChildControls();
     Controls.Clear();
     
     //Elevate our privs so that we can get access to the Server Farm
     SPSecurity.CodeToRunElevated myCodeToRun = new SPSecurity.CodeToRunElevated(PopulateTreeControl);
     SPSecurity.RunWithElevatedPrivileges(myCodeToRun);
     
     //Create the tree and add it to the page
     Controls.Add(tree);
     
     this.ChildControlsCreated = true;
     
}

private void PopulateTreeControl()
{
     
     //clear out the tree so we can start fresh
     tree.Nodes.Clear();
     
     //get the servers in the local farm
     SPFarm farm = SPFarm.Local;
     SPWebService service = farm.Services.GetValue("");
     
     //enumerate the servers and add them to the tree
     foreach (SPWebApplication webApp in service.WebApplications)
     {
           TreeNode webAppNode = new TreeNode();
           webAppNode.Text = webApp.Name;
     
           if (showCreateDate)
           {
                 webAppNode.Text += " Created On: " + webApp.Created.ToString();
           }
     
           webAppNode.NavigateUrl = null;
           tree.Nodes.Add(webAppNode);
     
           //TODO: add code to enumerate the sites under
           //the web app and add them to the tree
     }
     
     
}
Next, we will work on adding some customizable properties to our WebPart. This is done with the EditorPart.
private CheckBox showCreateDateCheckBox;

public TreeViewEditor()
{

     //instantiate our create date checkbox
     showCreateDateCheckBox = new CheckBox();

}

protected override void CreateChildControls()
{
     
     //add the create date checkbox to our properties pane
     showCreateDateCheckBox.Text = "  Show Create Date with Site Title";
     Controls.Add(showCreateDateCheckBox);
     Controls.Add(new LiteralControl("<div style='width:100%' class='UserDottedLine'></div>"));

}
     
public override bool ApplyChanges()
{
     
     EnsureChildControls();
     
     SPSiteCollectionNav part = WebPartToEdit as SPSiteCollectionNav;
     
     if (part != null)
     {
           //save the checkbox value back to the WebPart
           part.ShowCreateDate = showCreateDateCheckBox.Checked;
     }
     else
     {
           return false;
     }
     
     return true;
     
}
     
public override void SyncChanges()
{
     
     EnsureChildControls();
     
     SPSiteCollectionNav part = WebPartToEdit as SPSiteCollectionNav;
     
     if (part != null)
     {
           //sync any changes to the web part back to its properties
           showCreateDateCheckBox.Checked = part.ShowCreateDate;
     }
     
}
And there you have it. The Create Date checkbox will appear in the properties pane of the WebPart and can be used to turn on and off the display of the Site Create Date in the Site Node text of our Navigation WebPart.