Colour coding event types on SharePoint calendar

On a SharePoint calendar you might have different event types, e.g. Birthday, Meeting or Holiday. Wouldn’t it be nice if you can display these events on the calendar and colour code them by event type?

Well you probably can achieve this by applying the right XSL to the calendar list – but this could get nasty. Alternatively you can use a no-code solution involving calendar overlays in SharePoint 2010.

You would end up with something like this:

The steps to achieve this include:

  1. Filter the default calendar view to show only items of event type Meeting (for example)
  2. Create another calendar view, and filter to show only items of event type Holiday
  3. Create another calendar view, and filter to show only items of event type Birthday
  4. Add the Holiday and Birthday views to the default view as overlays

This approach has some drawbacks, but it is simple enough that it could be a satisfactory workaround.

As a side note, check this post for adding calendar overlays programmatically in SharePoint 2010: http://blog.falchionconsulting.com/index.php/2011/06/programmatically-setting-sharepoint-2010-calendar-overlays

Posted in SharePoint 2010 | 1 Comment

Making WebRequest to SharePoint using Windows Authentication in Mixed Mode Authentication

Mixed mode authentication is when both Form Based Authentication (FBA) and Windows Authentication are enabled on the SharePoint site. I was writing some code to download a file from SharePoint in this scenario. The code needs to authenticate using Windows Authentication.

I found my solution at this post: http://buyevich.blogspot.com/2011/03/accessing-mixed-authentication-web-app.html. Essentially we need to add the header “X-FORMS_BASED_AUTH_ACCEPTED” to the request and set its value to “f”. I tested this on the WebClient class and it works!

As a side note, I found this post to make the request using FBA, i.e. with user name and password: http://dhondiyals.wordpress.com/2010/07/05/file-download-from-sharepoint-document-library-in-forms-based-authentication-scenario/. Haven’t tested so don’t know if it works – but it looks promising.

Update: And if your site is using SSL, you may run into the error below in DEV/TEST:

The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.

In which case, try setting the ServerCertificateValidationCallback as described in this post: http://stackoverflow.com/questions/703272/could-not-establish-trust-relationship-for-ssl-tls-secure-channel-soap (it worked for me!).

Posted in Claim Authentication, Form Based Authentication, Mixed Mode Authentication | Leave a comment

URL for Windows Authentication in SharePoint 2010

When using Claims Authentication in SharePoint 2010 you can configure multiple authentication providers for your site. Specifically, you can use both Windows Authentication and Form Based Authentication – and your site users can choose which method to use to authenticate to your site. This is also referred to as Mixed Mode Authentication.

When Mixed Mode Authentication is enabled, the URL that would perform the Windows Authentication is /_windows/default.aspx. This means navigating to http://server/siteCollection/_windows/default.aspx will automatically log you in to that site collection using Windows Authentication (even when Mixed Mode Authentication is enabled).

The URL above requires a returning URL to be passed in as part of the query string. The full URL you should use is http://server/siteCollection/_windows/default.aspx?ReturnUrl=%2f_layouts%2fAuthenticate.aspx%3fSource%3d%252F&Source=%2FThis URL will authenticate the user using Windows Authentication and takes the user to the landing page of the site.

This is useful, for example, when you customise the login page. Your page could have the standard Username/password textboxes (as most of your users are external users that would rely on Form Based Authentication). You could then add a link to the above URL for the few internal users (who would be using Windows Authentication).

As a side note, you could use the code below to query the SharePoint object model to identify the URL used by the 2 authentication providers:

using (var site = new SPSite("http://yourServer"))
			{
				SPWebApplication webApplication = site.WebApplication;
				SPUrlZone urlZone = webApplication.AlternateUrls[site.Url].UrlZone;
				SPIisSettings settings = webApplication.IisSettings[urlZone];

				foreach (SPAuthenticationProvider provider in settings.ClaimsAuthenticationProviders)
				{
					Console.WriteLine(provider.DisplayName + ": " + provider.AuthenticationRedirectionUrl);
				}
			}

Another side note, the URL to sign-out is http://server/siteCollection/_layouts/signout.aspx.

Posted in Claim Authentication, SharePoint 2010 | 4 Comments

How to add new SPNavigationNode to Quick Link as type Link and not Heading

In SharePoint 2010, if you add a new SPNavigationNode as a child to a Heading link, then the type of the new link will automatically be Link:

If you however add it as a root level node, then the type will be Heading:

Use the following code to add a root level node as type Link:

using (var site = new SPSite("http://myServer"))
{
	using (var web = site.OpenWeb())
	{
		var newNode = new SPNavigationNode("Root Level Link", "http://www.ms.com", true);
		newNode = web.Navigation.QuickLaunch.AddAsFirst(newNode);
		newNode.Properties.Add("NodeType", "AuthoredLinkPlain");
		newNode.Update();
	}
}

newNode.Properties.Add(“NodeType”, “AuthoredLinkPlain”) is the magic line that makes it works.

Posted in Branding, SharePoint 2010 | Leave a comment

Error message “Unable to access web scoped feature because it references a non-existent or broken web on site” when performing feature upgrade

I got the error message below when performing a feature upgrade on a web-scoped feature:

Unable to access web scoped feature (Id: 0257689c-9e69-4700-a169-004f648d0924) because it references a non-existent or broken web (Id: 90d6c515-a4a7-4f25-a46d-d0505e54d973) on site ‘http://myServer’.  Exception: System.ArgumentException: Value does not fall within the expected range.

   at Microsoft.SharePoint.SPWebCollection.get_Item(Guid id)

   at Microsoft.SharePoint.SPFeatureEnumeratorBase.GetCachedWeb(SPSite site, Guid webId, Guid featureId)

The error occurred when I attempted to enumerate the results of SPSite.QueryFeature(). The second Guid appears to be the ID of a web, but there were no webs in my site collection with that ID (hence the error). I noticed there were some deleted sites in the Site Collection Recycle Bin. I emptied this Recycle Bin and was then able to upgrade the feature successfully.

The feature I’m trying to upgrade was activated at these deleted sites (before they were deleted).

Posted in Feature Upgrade, SharePoint 2010 | 4 Comments

Walkthrough: Custom field type for uploading and displaying images in SharePoint 2010 lists

—————–

UPDATE (2013-06-06): This solution is now also available for SharePoint 2013. Download it here. The 2010 version is still available for download.

—————–

In SharePoint 2010 the user experience of uploading & inserting images into publishing pages has been greatly improved. Uploading and displaying images in list items however is still the same as 2007, which is pretty bad. You can hack this by adding an existing site column of type Publishing Image to the list. The user experience however is still pretty clunky.

In this post I will provide a walkthrough of developing a custom field type that will provide an intuitive user experience for uploading and display images in list items.

The user gets a link to upload an image:

Clicking on the link launches a dialog that prompts the user to browse and upload an image from their computer:

Clicking Upload will upload the selected image to a specified location and update the list item:

The location that the images will be uploaded to is specified in the setting of the column:

The rest of this post will be a walkthrough for creating this custom field type.

1. Creating the field type class

First we will create the class that defines our field type. Our field type will inherit from SPFieldUrl (Hyperlink or Picture column type). Create an empty SharePoint project in Visual Studio (mine is called BNH.SharePoint) and add a public class call ImageUploadFieldType. Implement the 2 constructors of the base class. As our field is specialised in images, in the constructors set the base DisplayFormat property to be Image:

namespace BNH.SharePoint
{
	public class ImageUploadFieldType : SPFieldUrl
	{
		public ImageUploadFieldType(SPFieldCollection fields, string fieldName)
			: base(fields, fieldName)
		{
			this.DisplayFormat = SPUrlFieldFormatType.Image;
		}

		public ImageUploadFieldType(SPFieldCollection fields, string typeName, string displayName)
			: base(fields, typeName, displayName)
		{
			this.DisplayFormat = SPUrlFieldFormatType.Image;
		}
	}
}

We need to specify the FieldRenderingControl for our field type. Think of the field rendering control as the code-behind of the field’s UI. We specify the FieldRenderingControl by overriding the base property. Add the following to the ImageUploadFieldType class:

public override BaseFieldControl FieldRenderingControl
{
	get
	{
		return new ImageUploadFieldControl() { FieldName = base.InternalName };
	}
}

Here we are returning an instance of ImageUploadFieldControl to be used as the field rendering control. We will create the ImageUploadFieldControl class in the next step.

2. Creating the field rendering control class

Add a public class call ImageUploadFieldControl to the project and make it inherits from BaseFieldControl. This will require you to add a reference to System.Web.dll to your project – go ahead and do that.

In this class, we will specify the RenderingTemplate to be used as the UI for the field. These will be in the form of ASCXs, which we will create later. Add the following to the ImageUploadFieldControl class:

protected override string DefaultTemplateName
{
	get
	{
		return base.ControlMode == SPControlMode.Display ? this.DisplayTemplateName : "ImageUploadField";
	}
}

public override string DisplayTemplateName
{
	get { return "ImageUploadFieldDisplay"; }
}

Here we are specifying that a rendering template name ImageUploadFieldDisplay should be used when the field is in display mode. Otherwise, a template name ImageUploadField should be used. We will create these 2 templates next.

3. Creating the rendering templates

Rendering templates are defined in ASCX (User Control) files. In Visual Studio create a SharePoint mapped folder to {SharePointRoot}\TEMPLATE\CONTROLTEMPLATES. Right click on the CONTROLTEMPLATES folder (not on the project) in the solution and add a new User Control project item (under the SharePoint 2010 category) name ImageUploadFieldControl.ascx. Delete the ascx.cs and ascx.designer.cs files associated with the control. Your project should now look like below:

Edit the ImageUploadFieldControl.ascx file and change the <%@ Control %> directive to be as below:

<%@ Control Language=”C#” %>

Add the following beneath the <%@ Control %> directive:

<SharePoint:RenderingTemplate ID="ImageUploadField" runat="server">
	<Template>
	</Template>
</SharePoint:RenderingTemplate>
<SharePoint:RenderingTemplate ID="ImageUploadFieldDisplay" runat="server">
	<Template>
	</Template>
</SharePoint:RenderingTemplate>

As you can see we have declared 2 rendering templates. As specified in our field rendering control class, the 1st template will be used when the field is in new/edit mode. The 2nd will be used for display mode. These templates are empty at the moment. We will now add controls to them.

Update the 1st template (ImageUploadField) to be as follow:

<SharePoint:RenderingTemplate ID="ImageUploadField" runat="server">
	<Template>
		<script src="/_layouts/BNH.SharePoint/ImageUploadField.js" type="text/javascript"></script>
		<div><asp:Image ID="imgImage" runat="server" /></div>
		<a id="lnkLaunchUploadImagePage" runat="server" href="javascript:launchUploadImagePage('{0}', '{1}', '{2}', '{3}')">Upload an image</a>&nbsp;&nbsp;
		<asp:LinkButton ID="btnRemoveImage" runat="server" Text="Remove image" />
		<input type="hidden" id="hiddenImageUrl" runat="server" />
	</Template>
</SharePoint:RenderingTemplate>

The elements we have just added are as follow:

  • Reference to ImageUploadField.js: file containing all JavaScript functions for our field. We will create this file later.
  • imgImage: the control that will display the image. JavaScript will be used to update this control with the URL of the uploaded image.
  • lnkLaunchUploadImagePage: a link control that will execute a JavaScript function to launch our dialog page for the user to browse and upload an image. This function requires 4 params, which will be determined by our field rendering control on the server side. More on this later.
  • btnRemoveImage: a button to allow the user to remove the image.
  • hiddenImageUrl: a hidden control that will hold the URL of the uploaded image on postBack. The URL of the imgImage control will be updated by JavaScript on the client side. As this control does not participate in postBack however, we will need to also store the URL in a holder control that does. At all times, the value of the hiddenImageUrl control and the ImageUrl property of imgImage should be in sync.

Update the 2nd template (ImageUploadFieldDisplay) to be as follow:

<SharePoint:RenderingTemplate ID="ImageUploadFieldDisplay" runat="server">
	<Template>
		<asp:Image ID="imgImage" runat="server" /></Template>
</SharePoint:RenderingTemplate>

The lnkLaunchUploadImagePage link executes a JavaScript function that takes in 4 params. These will be determined by the field rendering control class on the server side. These params are:

  • imageControlID: client side ID of the imgImage control
  • urlHolderControlID: client side ID of the hiddenImageUrl control
  • listID: SharePoint GUID of the current list
  • fieldID: SharePoint GUID of our field

We will now add code to the rendering control class to pass in these params and manage the controls on the templates. First we’ll need to declare and instantiate the controls in our rendering control class so we can work with them on the server side. Add the following instance variables to ImageUploadFieldControl.cs:

//These controls are common to both edit/display templates
private Image imgImage;

//Edit template controls
private HtmlAnchor lnkLaunchUploadImagePage;
private LinkButton btnRemoveImage;
private HtmlInputHidden hiddenImageUrl;

Still in the ImageUploadFieldControl.cs class, add the following to override the CreateChildControls() method:

protected override void CreateChildControls()
{
	//If the field we are working on is null then exit and do nothing
	if (base.Field == null)
	{
		return;
	}

	base.CreateChildControls();

	//Now instantiate the control instance variables with the controls defined in the rendering templates.
	InstantiateMemberControls();

	if (base.ControlMode == SPControlMode.Display)
	{
		SetupDisplayTemplateControls();
	}
	else
	{
		SetupEditTemplateControls();
	}
}

Add the following code for the InstantiateMemberControls() method. This will instantiate the instance variable controls we declared earlier:

private void InstantiateMemberControls()
{
	//Common
	imgImage = (Image)base.TemplateContainer.FindControl("imgImage");

	//Edit controls
	lnkLaunchUploadImagePage = (HtmlAnchor)base.TemplateContainer.FindControl("lnkLaunchUploadImagePage");
	btnRemoveImage = (LinkButton)base.TemplateContainer.FindControl("btnRemoveImage");
	hiddenImageUrl = (HtmlInputHidden)base.TemplateContainer.FindControl("hiddenImageUrl");
}

Add the following code for the SetupDisplayTemplateControls() and SetupEditTemplateControls() methods:

private void SetupDisplayTemplateControls()
{
	bool isFieldValueSpecified = base.ItemFieldValue != null;

	//Hide the image if we do not have a URL to display
	SetControlCssVisibility(imgImage, isFieldValueSpecified);

	if (isFieldValueSpecified)
	{
		imgImage.ImageUrl = ((SPFieldUrlValue)base.ItemFieldValue).Url;
	}
}

private void SetupEditTemplateControls()
{
	lnkLaunchUploadImagePage.HRef = String.Format(lnkLaunchUploadImagePage.HRef,
		imgImage.ClientID, hiddenImageUrl.ClientID, base.List.ID, base.Field.Id);

	bool isFieldValueSpecified = base.ItemFieldValue != null;
	SetupEditControlsForFieldValueState(isFieldValueSpecified);

	btnRemoveImage.Click += new EventHandler(btnRemoveImage_Click);
}

private void SetControlCssVisibility(WebControl control, bool visible)
{
	//While we want to hide the control, we still want it to be rendered out to the client. This is
	//so that we can use JS to make the control visible again (e.g. after the user has uploaded an image). This
	//is why we are hiding by CSS.
	if (!visible)
	{
		control.Style["display"] = "none";
	}
	else
	{
		control.Style.Remove("display");
	}
}

/// <summary>
/// Tweaks controls depending on whether a value has been specified for the field.
/// </summary>
/// <param name="isValueSpecified"></param>
private void SetupEditControlsForFieldValueState(bool isValueSpecified)
{
	SetControlCssVisibility(imgImage, isValueSpecified);
	SetControlCssVisibility(btnRemoveImage, isValueSpecified);

	lnkLaunchUploadImagePage.InnerText = isValueSpecified ? "Upload another image" : "Upload an image";
}

void btnRemoveImage_Click(object sender, EventArgs e)
{
	imgImage.ImageUrl = hiddenImageUrl.Value = String.Empty;
	SetupEditControlsForFieldValueState(false);
}

Main points regarding the above code:

  • Since we are inheriting from SPFieldUrl, the type of our field value will always be SPFieldUrlValue.
  • In the SetupEditTemplateControls() method, we are changing the href of the lnkLaunchUploadImagePage link to pass in the 4 params to our JavaScript. Also in this method, we are hooking up the server-side handler for the btnRemoveImage link.

The last thing we need to do in this class (ImageUploadFieldControl.cs) is override the Value property. The get/set of this property is called when the field enters/exits edit mode, and we are passed the value of our field. Add the following code:

public override object Value
{
	//This is called at begining and editing of a list item. This means we only have to deal with
	//the editing template. Our field is based on the URL field so we will need to deal with SPFieldUrlValue.
	get
	{
		EnsureChildControls();

		if (String.IsNullOrEmpty(hiddenImageUrl.Value))
		{
			//Returning null here will cause SharePoint to NOT call the GetValidatedString() method
			//in our field type class. Return an empty UrlValue instead.
			return new SPFieldUrlValue();
		}

		var urlValue = new SPFieldUrlValue();
		urlValue.Url = urlValue.Description = hiddenImageUrl.Value;
		return urlValue;
	}
	set
	{
		EnsureChildControls();

		var urlValue = (SPFieldUrlValue)value;
		if (urlValue != null)
		{
			imgImage.ImageUrl = hiddenImageUrl.Value = urlValue.Url;
		}
		else
		{
			imgImage.ImageUrl = hiddenImageUrl.Value = String.Empty;
		}
	}
}

4. Creating JavaScript to launch the dialog page

We will deploy a .JS file to the LAYOUTS folder to hold all of our JavaScript. In Visual Studio add a SharePoint “Layouts” Mapped Folder to the project. Visual Studio will create a sub-folder under the Layouts folder with the same name as your project. Right click on this sub-folder and add a text file call ImageUploadField.js. Your project structure should now look like the below:

Add the following code for the launchUploadImagePage() function to the newly added JS file. This function is called by our edit rendering template.

function launchUploadImagePage(imageControlID, urlHolderControlID, listID, fieldID) {
	/*This function launches a dialog page that allows user to upload the image. The dialogCallBack function
	will update the specified image control with the URL of the newly uploaded image. The callBack function
	will also copy this URL to a specified hidden control. This hidden control will allow our custom field to
	extract the new URL and save it to the SharePoint field on postBack.

	imageControlID:	the HTML ID of the IMG element that is used to display the uploaded image.
	urlHolderControlID:	the HTML ID of the INPUT (hidden) element that will hold the URL of the uploaded image.
	listID: SharePoint ID of the list containing the field that represents the image. This will be passed to the
	dialog page as part of the queryString
	fieldID: SharePoint ID of the field that represents this image. This will be passed to the dialog page as part of the
	queryString.*/

	var dialogOptions = {
		url: SP.Utilities.Utility.getLayoutsPageUrl("BNH.SharePoint/UploadImage.aspx?lID=" + listID + "&fID=" + fieldID),
		title: "Upload an Image",
		args: { imageElementID: imageControlID, urlHolderElementID: urlHolderControlID },
		dialogReturnValueCallback: Function.createDelegate(null, uploadImagePage_OnClose)
	};

	SP.UI.ModalDialog.showModalDialog(dialogOptions);
}

This function is pretty straight forward. We are launching the UploadImage.aspx application page (to be created) in a dialog and specify uploadImagePage_OnClose as the callback JavaScript function (to be created) to be called when the dialog closes. Note that we are passing an argument object (via JavaScript) to the application page using the args property of the dialogOptions object. This argument object has 2 properties, imageElementID and urlHolderElementID. The application page will eventually pass this argument object back to the OnClose callback function.

5. Creating the ASPX dialog page

This is an application page that will prompt the user to select an image from their computer. In Visual Studio’s, right click on the project and add a new item. Choose the Application Page template and name it UploadImage.aspx. This should create a new ASPX page under the Layouts\[projectName] folder, as shown below:

Open the markup for UploadImage.aspx and enter the code below:

<asp:Content ID="PageHead" ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server">

</asp:Content>

<asp:Content ID="Main" ContentPlaceHolderID="PlaceHolderMain" runat="server">
	Browse for an image to upload and display below.
	<br /><br />
	<asp:FileUpload ID="fileUpload" runat="server" />
	<br /><br />
	<asp:Button ID="btnUpload" runat="server" Text="Upload" OnClick="btnUpload_Click" />
	<asp:Button ID="btnCancel" runat="server" Text="Cancel" OnClick="btnCancel_Click" />
</asp:Content>

<asp:Content ID="PageTitle" ContentPlaceHolderID="PlaceHolderPageTitle" runat="server">
Upload an Image
</asp:Content>

<asp:Content ID="PageTitleInTitleArea" ContentPlaceHolderID="PlaceHolderPageTitleInTitleArea" runat="server" >
Upload an Image
</asp:Content>

The above code is pretty straightforward. We added an ASP.NET FileUpload control (fileUpload), a button (btnUpload) to allow the user to initiate the upload process, and a button (btnCancel) to cancel.

We will display this application page in a dialog. For this to work properly we will need to do some additional plumbing as described in Waldek Mastykarz’s post: http://blog.mastykarz.nl/sharepoint-2010-application-pages-modal-dialogs/. First we will create a base class for our application page. The code in this base class is mostly from Waldek’s post. Add a public abstract class to your project and name it DialogAwareLayoutsPageBase.cs and enter the code below:

public enum ModalDialogResult
	{
		//Source: http://blog.mastykarz.nl/sharepoint-2010-application-pages-modal-dialogs/

		Invalid = -1,
		Cancel = 0,
		OK = 1
	}

/// <summary>
	/// Modal Dialog-aware Application Page base class.
	///
	/// Source: http://blog.mastykarz.nl/sharepoint-2010-application-pages-modal-dialogs/ (used with minor
	/// refactoring and renaming).
	/// </summary>
	public abstract class DialogAwareLayoutsPageBage : LayoutsPageBase
	{
		/// <summary>
		/// URL of the page to redirect to when not in Dialog mode.
		/// </summary>
		protected string PageToRedirectOnOK { get; set; }

		/// <summary>
		/// Returns true if the Application Page is displayed in Modal Dialog.
		/// </summary>
		protected bool IsInDialogMode
		{
			get
			{
				return !String.IsNullOrEmpty(base.Request.QueryString["IsDlg"]);
			}
		}

		/// <summary>
		/// Closes the dialog and returns the OK result to the client script caller.
		/// </summary>
		protected void EndOperation()
		{
			EndOperation(ModalDialogResult.OK);
		}

		/// <summary>
		/// Closes the dialog and returns the specified result to the client script caller.
		/// </summary>
		/// <param name="result"></param>
		protected void EndOperation(ModalDialogResult result)
		{
			EndOperation(result, this.PageToRedirectOnOK);
		}

		/// <summary>
		/// Closes the dialog and returns the specified result and returnValue to the client script caller.
		/// </summary>
		/// <param name="result"></param>
		/// <param name="returnValue">Value to pass to the client script callback method defined when opening the Modal Dialog.</param>
		protected void EndOperation(ModalDialogResult result, string returnValue)
		{
			if (this.IsInDialogMode)
			{
				Page.Response.Clear();
				Page.Response.Write(String.Format(CultureInfo.InvariantCulture,
					"<script type=\"text/javascript\">window.frameElement.commonModalDialogClose({0}, {1});</script>",
					new object[]
					{
						(int)result,
						String.IsNullOrEmpty(returnValue) ? "null" : returnValue
					}));
				Page.Response.End();
			}
			else
			{
				RedirectOnOK();
			}
		}

		/// <summary>
		/// Redirects to the URL specified in the PageToRedirectOnOK property.
		/// </summary>
		private void RedirectOnOK()
		{
			SPUtility.Redirect(this.PageToRedirectOnOK ?? SPContext.Current.Web.Url, SPRedirectFlags.UseSource, this.Context);
		}
	}

Back in the code-behind of our application page, make it inherits from the abstract class above, and enter the code below:

public partial class UploadImage : DialogAwareLayoutsPageBage
	{
		protected void Page_Load(object sender, EventArgs e)
		{
		}

		protected void btnUpload_Click(object sender, EventArgs e)
		{
			//TODO: Handle case where file to upload has not been selected

			var uploadLocation = GetUploadLocation();

			//TODO: Handle case where uploadLocation is not specified
			var imageUrl = UploadImageToLibrary(uploadLocation);

			/*Close the dialog and set the eventArgs param for the JS callBack function. The fields are as follow:
			 *
			 * imageUrl:	URL	of the uploaded image
			 * controlIDs:	The args object that was originally passed to this dialog
			 * */

			var eventArgsJavaScript = String.Format("{{imageUrl:'{0}',controlIDs:window.frameElement.dialogArgs}}", imageUrl);
			base.EndOperation(ModalDialogResult.OK, eventArgsJavaScript);
		}

		protected void btnCancel_Click(object sender, EventArgs e)
		{
			base.EndOperation(ModalDialogResult.Cancel);
		}

		private string GetUploadLocation()
		{
			//TODO: Handle case where the IDs are not specified.
			var listID = this.Request.QueryString["lID"];
			var fieldID = this.Request.QueryString["fID"];
			var list = SPContext.Current.Web.Lists.GetList(new Guid(listID), false);
			var field = list.Fields[new Guid(fieldID)];

			return (string)field.GetCustomProperty("UploadImagesTo");
		}

		/// <summary>
		/// Returns the URL of the newly uploaded image
		/// </summary>
		/// <param name="uploadLocation">The name of a library in the current web where the image should be uploaded to</param>
		/// <returns></returns>
		private string UploadImageToLibrary(string uploadLocation)
		{
			if (fileUpload.HasFile)
			{
				//TODO: Handle case where location is not valid
				var web = SPContext.Current.Web;
				var library = web.Lists[uploadLocation];

				//TODO: Handle existing file - for now we are overwriting
				SPFile uploadedFile = library.RootFolder.Files.Add(fileUpload.FileName, fileUpload.PostedFile.InputStream, true);
				return web.Url.TrimEnd('/') + '/' + uploadedFile.Url.TrimStart('/');
			}
			return String.Empty;
		}
	}

There are 2 important parts in the code above. First is the GetUploadLocation() method. In this method we use the list and field GUIDs (that were passed by our JavaScript) and retrieve the parent list of our image field, then the image field itself, then a custom property of the field, namely ‘UploadImagesTo‘. We will define this custom property for our field in a later step. The other important bit is at the end of the btnUpload_Click() method, where we pass back the URL of the uploaded image and control IDs (of the image control and the hidden holder control) to the JavaScript callBack function.

6. Creating JavaScript callBack function to update HTML

We will now create the JavaScript callBack function that will pick up the URL of the uploaded image from our dialog page and update the HTML to display the image on the page. In Visual Studio open the ImageUploadField.js field under the Layouts folder and add the following code:

function uploadImagePage_OnClose(result, eventArgs) {
	/*eventArgs is an object containing the following fields:

	eventArgs.imageUrl:							URL	of the uploaded image
	eventArgs.controlIDs.imageElementID:		HTML ID of the IMG that is used to display the image
	eventArgs.controlIDs.urlHolderElementID:	HTML ID of the hidden control that is used to store the URL of the image
	*/

	if (result == SP.UI.DialogResult.OK) {
		//Clean any " from the URL
		while (eventArgs.imageUrl.indexOf('"') != -1) {
			eventArgs.imageUrl = returnValue.imageUrl.replace('"', '');
		}

		//The Image and the Remove Image elements would have been hidden if the field did not have
		//a value specified. Unhide them here.
		var imgElement = document.getElementById(eventArgs.controlIDs.imageElementID);
		imgElement.src = eventArgs.imageUrl;
		imgElement.style.display = "block";

		var removeImgElement = getRelatedElementFromImageElementID(imgElement.id, "btnRemoveImage");
		removeImgElement.style.display = "inline";

		//Further UI tweaks
		var uploadImgElement = getRelatedElementFromImageElementID(imgElement.id, "lnkLaunchUploadImagePage");
		uploadImgElement.innerText = "Upload another image";

		document.getElementById(eventArgs.controlIDs.urlHolderElementID).value = eventArgs.imageUrl;
	}
}

function getRelatedElementFromImageElementID(imgElementID, relatedElementServerSideID) {
	/*Returns the specified related element (e.g. Upload Image or Remove Image link)
	from the specified Image element ID. As these elements are nested within the same set of server
	side controls, we can expect their client ID to be the same, except for their server side IDs of course.*/
	return document.getElementById(imgElementID.replace("imgImage", relatedElementServerSideID));
}

The above code is fairly straightforward, but review the in-code comments for further explanation.

7. Create the fldtypes XML to register the field type

OK we have done a lot of work so far, but we haven’t actually tell SharePoint about our custom field yet. To do this we need to create an XML file. In Visual Studio create a SharePoint mapped folder to {SharePointRoot}\TEMPLATE\XML. Right click on this folder (not on the project) and add a new XML file named fldtypes_ImageUploadField.xml. The file name needs to start with ‘fldtypes’ for it to work. Your project should now look like this:

Enter the following XML:

<FieldTypes>
	<FieldType>
		<Field Name="TypeName">ImageUpload</Field>
		<Field Name="ParentType">URL</Field>
		<Field Name="TypeDisplayName">Image Upload</Field>
		<Field Name="TypeShortDescription">Image Upload and Display</Field>
		<Field Name="UserCreatable">TRUE</Field>
		<Field Name="ShowOnListCreate">TRUE</Field>
		<Field Name="ShowOnSurveyCreate">TRUE</Field>
		<Field Name="ShowOnDocumentLibraryCreate">TRUE</Field>
		<Field Name="ShowOnColumnTemplateCreate">TRUE</Field>
		<Field Name="FieldTypeClass">BNH.SharePoint.ImageUploadFieldType, $SharePoint.Project.AssemblyFullName$</Field>
		<Field Name="FieldEditorUserControl">/_controltemplates/ImageUploadFieldEditor.ascx</Field>
		<PropertySchema>
			<Fields>
				<Field Name="UploadImagesTo"
						DisplayName="UploadImagesTo"
						MaxLength="255"
						DisplaySize="100"
						Type="Text"
						Hidden="TRUE">
					<Default>Images</Default>
				</Field>
			</Fields>
		</PropertySchema>
	</FieldType>
</FieldTypes>

Some important points about the XML above:

  • FieldEditorUserControl: Our field type will have custom properties. This user control will be displayed and used to edit the custom properties when we create/edit columns of our field type. We will create the referenced user control in a later step.
  • PropertySchema: This defines the custom properties associated with our field type. While MSDN states that PropertySchema has been deprecated, I have found that it is required for the value of custom properties to be saved. Another thing to note is that we have set the Hidden property of our custom UploadImagesTo property to be TRUE. This is because we want to use our user control as the UI for editing the custom properties. The Hidden property is actually not documented but it works.

8. Create the field editor user control

In Visual Studio right click on the CONTROLTEMPLATES folder (and not the project) and add a new user control from the SharePoint\2010 category. Name this user control ImageUploadFieldEditor.ascx. Your project should now look like below:

Edit the markup for the ASCX of the control and add the code below:

<table class="ms-authoringcontrols" border="0" width="100%" cellspacing="0" cellpadding="0">
	<tr>
		<td class="ms-authoringcontrols" colspan="2">
			Upload images to:
		</td>
	</tr>
	<tr><td><img src="/_layouts/images/blank.gif" width="1" height="3" style="display: block" alt=""></td></tr>
	<tr>
		<td width="11px"><img src="/_layouts/images/blank.gif" width="11" height="1" style="display: block" alt=""/></td>
		<td class="ms-authoringcontrols" width="99%">
			<asp:TextBox ID="txtUploadImagesTo" runat="server" Text="Images" /></td>
	</tr>
</table>

Most of the above code is styling to get the SharePoint look and feel. The control of interest is txtUploadImagesTo, which is a text box that will allow the user to configure where the field should upload the images to.

Next, edit the code-behind for the above user control and add the code below:

public partial class ImageUploadFieldEditor : UserControl, IFieldEditor
	{
		private const string UPLOAD_IMAGES_TO_PROPERTY_NAME = "UploadImagesTo";

		protected void Page_Load(object sender, EventArgs e)
		{
		}

		public bool DisplayAsNewSection
		{
			get { return false; }
		}

		public void InitializeWithField(SPField field)
		{
			//In this method we will sync the custom settings of the field with
			//our custom setting controls on the editor screen. Field however will be null when we are in create mode, in
			//which case the default value (as specified in the fldtypes XML will apply).
			if (!Page.IsPostBack && field != null)
			{
				txtUploadImagesTo.Text = (string)field.GetCustomProperty(UPLOAD_IMAGES_TO_PROPERTY_NAME);
			}
		}

		public void OnSaveChange(SPField field, bool isNewField)
		{
			/*This is perhaps the most tricky part in implementing custom field type properties.
			 * The field param passed in to this method is a different object instance to the actual field being edited.
			 * This is why we'll need to set the value to be saved into the LocalThreadStorage, and retrieve it back out
			 * in the FieldType class and update the field with the custom setting properties. For more info see
			 * http://msdn.microsoft.com/en-us/library/cc889345(office.12).aspx#CreatingWSS3CustomFields_StoringFieldSetting */

			//TODO: Handle case where location is not specified or not valid
			Thread.SetData(Thread.GetNamedDataSlot(UPLOAD_IMAGES_TO_PROPERTY_NAME), txtUploadImagesTo.Text);
		}
	}

Review the in-code comments above, but the main points are:

  • The code-behind class needs to implement the IFieldEditor interface.
  • We need to use the LocalThreadStorage to save the values of custom properties (review the link in the comment). We also need to do additional work in the main field type class in the next step to make this works. Remember that we still need PropertySchema defined in the fldtypes XML for the field type, even though MSDN states that PropertySchema has been deprecated.

9. Updating the main field type class to save custom properties

In the last step our field editor user control puts our custom property value into the LocalThreadStorage. We now need to edit our main field type class (ImageUploadFieldType.cs) to intercept the Update event to pull the custom property value out of the LocalThreadStorage and save it with the field. We also need to intercept the OnAdded event and force an update to save our custom property value when the field is being created.

Edit the ImageUploadFieldType.cs and add the following code:

public override void OnAdded(SPAddFieldOptions op)
		{
			/*We will need to update the field again after it is added to save the custom setting properties. For more
			 * info see http://msdn.microsoft.com/en-us/library/cc889345(office.12).aspx#CreatingWSS3CustomFields_StoringFieldSetting */

			base.OnAdded(op);
			this.Update();
		}

		public override void Update()
		{
			base.SetCustomProperty("UploadImagesTo",
				Thread.GetData(Thread.GetNamedDataSlot("UploadImagesTo")));

			base.Update();
		}

10. Add validation for the field type

We will add 1 simple validation for our field type, and that is the required field validation. Add the following code to the ImageUploadFieldType.cs class:

public override string GetValidatedString(object value)
		{
			if (!base.Required)
			{
				return base.GetValidatedString(value);
			}

			var urlValue = value as SPFieldUrlValue;
			if (urlValue == null || String.IsNullOrEmpty(urlValue.Url))
			{
				throw new SPFieldValidationException(base.Title + " is a required field.");
			}
			return base.GetValidatedString(value);
		}

One thing to be mindful of in the above code is that the type of value will be SPFieldUrlValue as our field is inherited from SPFieldUrl.

11. Add XSL to render the field in list view

OK, the last thing we have to do (phew!) is to add an XSL to render our field in a list view. Without this XSL the field won’t be rendered. In Visual Studio create a SharePoint mapped folder to {SharePointRoot}\Template\LAYOUTS\XSL. Right click on the XSL folder (and not on the project) and add a new XSL file. Name this file fldtypes_ImageUploadField.xsl. Ensure that the file extension is xsl and not xslt. The extension has to be xsl for it to work. Add the code below for the XSL:

<xsl:stylesheet xmlns:x="http://www.w3.org/2001/XMLSchema"
        xmlns:d="http://schemas.microsoft.com/sharepoint/dsp"
        version="1.0"
        exclude-result-prefixes="xsl msxsl ddwrt"
        xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime"
        xmlns:asp="http://schemas.microsoft.com/ASPNET/20"
        xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:msxsl="urn:schemas-microsoft-com:xslt"
        xmlns:SharePoint="Microsoft.SharePoint.WebControls"
        xmlns:ddwrt2="urn:frontpage:internal">

	<xsl:template match ="FieldRef[@FieldType='ImageUpload']" mode="URL_body">
		<xsl:param name="thisNode" select="."/>
		<xsl:variable name="desc" select="$thisNode/@URL.desc" />
		<xsl:variable name="url" select="substring-before($thisNode/@*[name()=current()/@Name],',')"/>
		<xsl:choose>
			<xsl:when test="$url=''">
			</xsl:when>
			<xsl:otherwise>
				<img onfocus="OnLink(this)" src="{$url}" alt="{$desc}" />
			</xsl:otherwise>
		</xsl:choose>
	</xsl:template>
</xsl:stylesheet>

The key bit in the above is “FieldRef[@FieldType=’ImageUpload’]”. This ensures that this XSL is only applied to instances of our field type.

That’s it! Go ahead and deploy your solution. You will need to do an IISRESET after deploying for SharePoint to pick up the new field though.

12. Download complete code

You can download the complete Visual Studio solution here: download. (See Updates section below.)

13. Other posts you may be interested in

Want to improve the user experience in SharePoint? Check out my other custom solutions here.

14. Updates

The code as originally posted here does not allow you to programatically set the UploadImagesTo property of the field. I have refactored the code a bit and expose UploadImagesTo as a public property. You can download the new code here, which also contains a test harness demonstrating how to programatically create the field and set the UploadImagesTo property.

15. Appendix 1 – Provisioning a site column of this type through XML

Below is an example of the XML you can use to provision a site column of this field type from a feature. Ensure that the field type is already deployed before installing/activating this feature.

<Field Type="ImageUpload" Name="UserPhoto" DisplayName="User Photo" ID="{B4FB10B5-CBE7-4395-B2B1-27CDBD8EC006}" Format="Image" Required="FALSE">
		<Customization>
			<ArrayOfProperty>
				<Property>
					<Name>UploadImagesTo</Name>
					<Value xmlns:q1="http://www.w3.org/2001/XMLSchema" p4:type="q1:string" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">ImagesLibrary</Value>
				</Property>
			</ArrayOfProperty>
		</Customization>
	</Field>
Posted in Custom Field Types, SharePoint 2010 | 136 Comments

How to remove “No results are available. Either no query is specified, or the query came from advanced search (Federated Webparts do not support Advanced Search queries)” when no search keywords are specified

The OOTB Search Core Results web part will show the message below when no search keywords are specified (i.e. no k=blah in the queryString):

No results are available. Either no query is specified, or the query came from advanced search (Federated Webparts do not support Advanced Search queries).

The result display of this web part is driven by XSL, and it would seem that one can turn off this message by tweaking the XSL. But try it and you’ll find that it won’t work. (If you can get it to work let me know!)

You can hide this message with a somewhat no-code solution by adding a Content Editor web part to the page and some JQuery. See this link: http://social.technet.microsoft.com/Forums/en-US/sharepoint2010general/thread/08bec7c2-c7da-41cd-a718-3fd754ceeb4a. As the JQuery needs the ID of the web part however, this is not a very reusable solution.

For a coded (and reusable solution) you will need to extend the OOTB CoreResultsWebPart. The code is very simple. Create a Web Part (not a Visual Web Part), then inherit from CoreResultsWebPart. You will need to add a reference to Microsoft.Office.Server.Search.dll to the project. Enter the code below for your web part class:

/// <summary>
    /// The OOTB web part displays an error message when no search keywords are specified. This inherited web part hides itself (hence the error message)
    /// when it is in Display mode and no keywords are specified.
    /// </summary>
    [ToolboxItemAttribute(false)]
    public class MyCoreResultsWebPart : CoreResultsWebPart
    {
		protected override void OnLoad(EventArgs e)
		{
			base.OnLoad(e);

			base.Hidden = (base.QueryInfo == null) && !this.IsInEditMode;
		}

        private bool IsInEditMode
        {
            get { return (base.WebPartManager != null && !base.IsStandalone) && base.WebPartManager.DisplayMode.AllowPageDesign; }
        }
    }

Also, enter these properties in the .webpart file for your web part:

<properties>
				<property name="Title" type="string">My Search Core Results</property>
				<property name="Description" type="string">Use to display search results</property>
				<property name="ChromeType" type="chrometype">None</property>
				<property name="ShowActionLinks" type="bool">false</property>
				<property name="ExportMode" typ="exportmode">All</property>
			</properties>

These properties are not mandatory, but they will replicate some of the key behaviours of the OOTB web part for our custom one.

That’s it. Go ahead and deploy your solution.

Posted in Search, SharePoint 2010, WebPart | Leave a comment

Removing duplicate web parts when changing page layout via the ribbon

In SharePoint 2010, if a page layout contains web parts, then when you change the page layout via the Ribbon while editing the page, then the page will have web parts of both the previous and the new page layouts.

Update: An alternate, and potentially more comprehensive, approach to handle this is added at the end of the post.

This is observed with SP1 and August 2011 CU, and occurred to both custom and OOTB page layouts.

A similar issue is when you reactivate the feature that provisions the custom page layout. In this case, the web parts are duplicated on the custom page layout itself. Waldek Mastykarz has blogged about a solution for this case: http://blog.mastykarz.nl/preventing-provisioning-duplicate-web-parts-instances-on-feature-reactivation/.

Back to the problem at hand. I tried everything in my custom page layout and could not stop the web parts from being duplicated when the user changes the page layout. I was inspired by Waldek’s solution above and eventually solved the problem by adding a code-behind for the page layout. In the code-behind I detect when the ChangePageLayout event is just about to occur. I then removes all the web parts defined in the current page layout from the page. Below is a walkthrough of the code.

First create a public class and inherit from PublishingLayoutPage (you will need a reference to Microsoft.SharePoint.Publishing.dll). Override the OnLoad method and add the code as shown below.

public class MyLayoutPage : PublishingLayoutPage
{
	protected override void OnLoad(EventArgs e)
	{
		base.OnLoad(e);

		if (SPContext.Current.FormContext.FormMode != SPControlMode.Display && this.IsCurrentPostBackChangePageLayoutEvent)
		{
			var currentPageLayoutWebParts = GetCurrentPageLayoutWebParts();
			RemoveMatchingWebPartsFromCurrentPage(currentPageLayoutWebParts);

			//We do not need to save the page as SharePoint will save the page as part of the ChangePageLayoutEvent.
		}
	}
}

Add the code below for the this.IsCurrentPostBackChangePageLayoutEvent property. This was the tricky part to figure out. The SharePoint object model does not have any mean to hook into this event. However, as part of the ASP.NET framework, a form parameter is included in the HttpRequest to identify the postBack event.

private bool IsCurrentPostBackChangePageLayoutEvent
{
	get { return this.Request["__EVENTARGUMENT"] == "ChangePageLayoutEvent"; }
}

Add the code below for the GetCurrentPageLayoutWebParts() method. Essentially we get the current page layout of the page, get the list item and ASPX file that represent the page layout, and return the web parts defined on that page layout. The only messy thing here is we are returning a Dictionary of web parts, where the value is the ZoneID of the web part. This is further explained in the in-code comment.

/// <summary>
/// Returns a dictionary of web parts defined in the page layout of the current page. The key is the web part object.
/// The value is the Zone ID of the web part.
/// </summary>
/// <remarks>We need to return a dictionary here because we also want to return the Zone ID of the web part for comparison later.
/// The WebPart class we are using is System.Web.UI.WebControls.WebPart, and this does not expose the ZoneID property. The
/// Zone property of the WebPart for some reason is always null.</remarks>
/// <returns></returns>
private Dictionary<WebPart, string> GetCurrentPageLayoutWebParts()
{
	var webPartDictionary = new Dictionary<WebPart, string>();

	var currentPageLayoutValue = this.CurrentPageLayout;
	if (String.IsNullOrEmpty(currentPageLayoutValue))
	{
		return webPartDictionary;
	}

	//The page layout value will be of the format [absoluteUrl], [content type]. We will need to get the
	//URL, and use that to retrieve the page layout list item in the catalog.
	var currentPageLayoutUrl = currentPageLayoutValue.Split(new[] { ", " }, StringSplitOptions.RemoveEmptyEntries)[0];
	try
	{
		//Official MS guidance is not to call Dispose() on SPSite.RootWeb
		SPListItem currentPageLayout = SPContext.Current.Site.RootWeb.GetListItem(currentPageLayoutUrl);
		var webPartManager = currentPageLayout.File.GetLimitedWebPartManager(PersonalizationScope.Shared);
		var webParts = webPartManager.WebParts.OfType<WebPart>().ToList();

		webParts.ForEach(w => webPartDictionary.Add(w, webPartManager.GetZoneID(w)));

		if (webPartManager.Web != null)
		{
			webPartManager.Web.Dispose();
		}
		webPartManager.Dispose();

		return webPartDictionary;
	}
	catch (FileNotFoundException e)
	{
		//TODO: Log this
		return webPartDictionary;
	}
}

private string CurrentPageLayout
{
	get
	{
		try
		{
			return SPContext.Current.ListItem["PublishingPageLayout"] as string;
		}
		catch (ArgumentException)
		{
			//This will occur if the page does not have a page layout associated, e.g. when we open
			//the page layout itself (as oppose to the content page).
			return String.Empty;
		}
	}
}

Add the code below for the RemoveMatchingWebPartsFromCurrentPage() method. The comparison is by web part’s Title, Type and Zone ID.

/// <summary>
/// Web parts are matched on Title, Type and Zone ID.
/// </summary>
/// <param name="webPartsToMatch">A dictionary of the web parts to match (i.e. to find and delete on the current page).
/// The key is the web part object. The value is the Zone ID of the web part.</param>
private void RemoveMatchingWebPartsFromCurrentPage(Dictionary<WebPart, string> webPartsToMatch)
{
	var webPartManager = SPContext.Current.File.GetLimitedWebPartManager(PersonalizationScope.Shared);
	foreach (WebPart toMatchWebPart in webPartsToMatch.Keys)
	{
		var matches = webPartManager.WebParts.OfType<WebPart>().Where(
			w => w.Title == toMatchWebPart.Title && w.GetType() == toMatchWebPart.GetType() && webPartManager.GetZoneID(w) == webPartsToMatch[toMatchWebPart]);

		foreach (var match in matches.ToList())
		{
			webPartManager.DeleteWebPart(match);
		}
	}
	if (webPartManager.Web != null)
	{
		webPartManager.Web.Dispose();
	}
	webPartManager.Dispose();
}

Lastly we will need to hook up our code-behind class to our page layout. For all the custom page layouts, locate the <%@ Page %> directive and change the Inherits attribute from the OOTB class to our class:

<%@ Page language="C#" Inherits="[fullNamespace].[className], [strongAssemblyName]" meta:progid="SharePoint.WebPartPage.Document" %>

That’s it. Go ahead and deploy your solution.

——————————–

Update: There is one drawback with the above method, and that is it won’t work if the user changes from an OOTB page layout to a new page layout. Obviously this is because our code-behind is not hooked up to the OOTB page layouts.

Another way to handle this, which will also work for the OOTB page layouts, is to attach an event receiver to the Pages library and detect the ChangePageLayout event there. With this approach you do not need code-behind for the page layouts. You however will need to attach this event receiver to all existing and new sites – which may (or not) be easier for you.

The code above can be adapted to work inside an event receiver so I won’t go over it again. I will highlight a few key points:

  • The event to perform the work is the ItemUpdating event. When the user changes the page layout, SharePoint automatically saves the page.
  • While you can compare properties.BeforeProperties[“PublishingPageLayout”] and properties.AfterProperties[“PublishingPageLayout”] to see if the page layout has changed, you should still rely on the PostBack event argument (like we did for the code-behind) as your best check. This is because the event still occurs (and the web parts still get duplicated) if the user changes to the current page layout – and when this happens the BeforeProperties and AfterProperties will be the same!
  • To check the PostBack event argument you will need to access HttpContext.Current. This is accessible only within the constructor of the event receiver. Create an instance variable and set it to HttpContext.Current so you can reuse it afterward. See code below.
  • HttpContext.Current will sometime not be available at all, e.g. when the item is being updated via a console app, or when the web is being created. Therefore, you should also check the Before/AfterProperties as a fall back.

The code below illustrates the above points:

public class PagesLibraryItemEventReceiver : SPItemEventReceiver
{
	private readonly HttpContext _httpContext;

	public PagesLibraryItemEventReceiver() : base()
	{
		_httpContext = HttpContext.Current;
	}

//Other code omitted...

	private bool IsPageLayoutChanging(SPItemEventProperties properties)
	{
		if (_httpContext != null)
		{
			return _httpContext.Request["__EVENTARGUMENT"] == "ChangePageLayoutEvent";
		}

		/*The page layout value is 1 of 2 formats:
		 * http://server/_catalogs/masterpage/mypagelayout1.aspx, Content Page 1
		 * /_catalogs/masterpage/mycontentpagelayout2.aspx, Content Page 2
		 *
		 * We therefore will take a substring starting from the last '/' and compare the 2 values.
		 * */

		var currentPageLayout = properties.BeforeProperties["PublishingPageLayout"] as string ?? String.Empty;
		var newPageLayout = properties.AfterProperties["PublishingPageLayout"] as string ?? String.Empty;

		if (!String.IsNullOrEmpty(currentPageLayout))
		{
			currentPageLayout = currentPageLayout.Substring(currentPageLayout.LastIndexOf('/') + 1);
		}
		if (!String.IsNullOrEmpty(newPageLayout))
		{
			newPageLayout = newPageLayout.Substring(newPageLayout.LastIndexOf('/') + 1);
		}

		return !currentPageLayout.Equals(newPageLayout, StringComparison.InvariantCultureIgnoreCase);
	}
}

Hope this helps!

Posted in Page Layout, SharePoint 2010, WebPart | 5 Comments

GetValidatedString() is not called for custom field type?

I was developing a custom field type that inherits from SPFieldUrl. I overrided the GetValidatedString(object value) method to perform the validation when the field was set to be mandatory. My overriden method however was not called by SharePoint, and hence the required field validation failed.

It turns out that it was because I was returning null in my field control class when a value was not specified for the field:

public override object Value
{
	get
	{
		EnsureChildControls();

		if (String.IsNullOrEmpty(txtUrl.Value))
		{
			return null;
		}

	        //...
	}
	//...
}

It appears that SharePoint does not call GetValidatedString() when the value is null. I changed the above code to below and everything works as expected:

if (String.IsNullOrEmpty(txtUrl.Value))
{
	return new SPFieldUrlValue();
}

In my next post I will provide a walkthrough of developing a custom field type that allows users to intuitively browse, upload and display an image to a SharePoint list. Watch this space :).

Posted in Custom Field Types, SharePoint 2010 | Leave a comment

Setting up trusted domain in SharePoint 2010: An exception occurred in AD claim provider when calling SPClaimProvider.FillSearch(): Requested registry access is not allowed.

I was setting up trusted domain for the people picker in SharePoint 2010. I was able to get it to work on one environment but not the other. On the environment that wasn’t working, the steps to set it up were all successful. The people picker however could not resolve any username, including those in the current domain.

Every time the people picker failed to resolve the username, there was this error message in the ULS:

An exception occurred in AD claim provider when calling SPClaimProvider.FillSearch(): Requested registry access is not allowed..

This is then followed by another message:

Claims Search call failed. Error Message: Requested registry access is not allowed. Callstack:
at Microsoft.Win32.RegistryKey.OpenSubKey(String name, Boolean writable)
at Microsoft.SharePoint.Administration.SPCredentialManager.<>c__DisplayClass1.<get_ApplicationCredentialKey>b__0()…….

The first message also appears in the Event Viewer.

The messages clearly indicate that something did not have access to read something from the Registry. I downloaded Process Monitor (http://technet.microsoft.com/en-us/sysinternals/bb896645) from MS Sysinternals, which is an incredible tool that allows us to monitor all access to the Registry (among other things).

Run this tool and set it to Show Registry Activity only, and filter it down to Process = “w3wp.exe” and Result = “access denied” as below:

This will quickly show that the problem occurs when the w3wmp.exe process tries to read the Registry key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Shared Tools\Web Server Extensions\14.0\Secure. This is where the apppassword (set by the stsadm command) is stored.

Double click the failing entry in Process Monitor and you will see that the user who’s trying to read the Registry above is the app pool account of the SharePoint web app.

Check the permission of this Registry key and you will see that the following groups have read access:

  • System
  • Network Service
  • WSS_Restricted_WPG_V4
  • Local Administrators group

I added the app pool account to the local Administrators group, reset IIS and everything started to work. I checked the other environment where it was working and indeed the app pool account was added to the local Administrators group. (Probably better to add the app pool account to the WSS_Restricted_WPG_V4 group though).

Not sure why the app pool account was not setup properly by default or if I missed a required config step – but anyway it’s working now and I’m happy :).

Posted in Claim Authentication, SharePoint 2010 | 9 Comments