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>

About Bernado

Based in Australia, I am a freelance SharePoint and Dynamics CRM developer. I love developing innovative solutions that address business and everyday problems. Feel free to contact me if you think I can help you with your SharePoint or CRM implementation.
This entry was posted in Custom Field Types, SharePoint 2010. Bookmark the permalink.

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

  1. Proximus says:

    many thanks. with your solution, now i’m solved my problem with upload image.
    excellent!

  2. Aris says:

    Great post, demystifying many of Sharepoint’s capabilities!

    What if I wanted to extend the code to provide a custom field type with the ability to hold many images instead of only one, i.e. the user can repeatedly add as many images as he wants? Would I inherit from SPFieldCollection instead of SPFieldUrl? I would probably use a asp:repeater in the
    web control.

    Do you think what I am trying to do has a great degree of difficulty?

    Thank you .

    • Bernado says:

      Hey Aris, You would be better of inheriting from SPFieldText and store a delimited string as the value for the field (e.g. url1;url2;url3). SPFieldCollection is a collection of fields (and is not a field type). With what you are trying to do, you will still have just 1 field, but that field will hold multiple values. All the hooks required to make it works will still be the same – so it should be possible. You may run into some challenges in getting the view XSL to work. Then again, I’m probably just saying that because I’m not very good at XSL :D. Good luck.

    • Eric Halsey says:

      Hi Aris,
      I have the same requirement. Were you successful in modifying the code to support multiple files? If so, would you be willing to share?
      Thanks in advance,
      Eric

  3. John says:

    Great Post. How long did it take you to figure this out and how much hair did you rip out of your head in the process?

    Allot of work just to create a reusable UI Widget. I guess it is a bit more then just a UI component since you have multiple views and are providing data storage.

    • Bernado says:

      Hey John, the coding took a couple of weeks (on and off). Writing up the post took another couple of weeks :D. It was quite satisfying at the end however to have produced something that is reusable, stable, and potentially useful to others.

  4. TsAG says:

    Very useful information.
    Thanks a lot!

  5. Andrey says:

    Thanks a lot for your efforts, it’s a great article.

  6. Matt says:

    Fantastic article. This is exactly what I’m looking for.

  7. Pingback: How do I set the property for a custom field type that I am using in my custom list definition? | web technical support

  8. Pingback: How do I set the property for a custom field type that I am using in my custom list definition? | Q&A System

  9. Komail says:

    Excellent work dear!!!
    Many thanks 🙂

  10. Komail says:

    Hello Bernado,
    Its not working with document libraries, what should i do to make it working with document library.

    • Bernado says:

      Hey Komail,

      What error/issue are you getting when using the column in a document library?

      • Komail says:

        Hi,
        When i create new column & select this custom column share point shows me following error
        “Earlier versions of client programs might not support this type of column. Adding this column might block those programs from saving documents to this library.”

      • Bernado says:

        That message is expected. You also get that message if you select the OOTB Managed Metadata column type. “Client programs” here means other programs that interface with SharePoint, e.g. Office applications. When you use MS Word to edit a document in SharePoint for example, the columns of the document library will be displayed in Word, and you can edit those columns straight in Word. If the type of your column is unknown to Word, then you will not be able to edit that column in Word. And if you made this column mandatory, then you might not be able to save the document from Word into SharePoint.

        Test it out, but I don’t think it will be a major problem for you. In the worst case, the user will have to save the document in Word, then go back to SharePoint to specify a value for this column.

  11. Komail says:

    Hmmm..i have got it thank you very much for such detailed reply. 🙂

  12. Chad says:

    Hello,

    Thank you so much for this great article! I do have one question. I have a custom list style where I’m referencing the new Photo custom column. The image does not appear, the source is: http:///PublishingImages/photo_peripierone.jpg,%20http:///PublishingImages/photo_peripierone.jpg

    Why is the path being duplicated?

    Thank you!

    • Bernado says:

      Hey Chad, by custom list style, are you referring to the vwstyles.xml? Or a custom XSL you have applied to the list? I have not worked much with either of these, but if you can post the source of your custom list style perhaps I can spot something?

      • Chad says:

        Bernado,

        Thank you! Below is my custom ItemStyle.xsl. The @PhotoURL is pointing to the custom image upload column.

        <![CDATA[
        ]]>

        _blank

        READ MORE>>>

  13. Merijn de Bekker says:

    Fantasic article! Thank you!

  14. Ahmed saber says:

    Very Very Very Useful Article .. thank you alot

  15. Gustavo says:

    Excelent article. I have one question…
    Do you know how can i create a field of this FieldType and add it to a SPList programmatically?

    • Bernado says:

      Hi Gustavo, download the new code in section 14. Updates above. This now has a test harness demonstrating how to create the field programatically.

      • Gustavo says:

        Its great! Its work perfectly.
        Now i have another cuestion. Do you think that i can made like a picturegallery in a field, i mean having a field that i can upload a lot of pictures and when its shown in a list show all of them like a picture gallery (the webpart)?
        Its possible to do that?
        Thanks!

      • Bernado says:

        Hi Gustavo, the code I have here doesn’t do that, but yes – you can certainly update the code to do what you describe. It should be fun :).

      • Gustavo says:

        Bernardo, i’m new in Sharepoint. Can you tell me briefly which files should i modify to made like a picturegallery in a field, i mean having a field that i can upload a lot of pictures and when its shown in a list show all of them like a picture gallery (the webpart)?
        Do you think its very dificult to do this?
        Anything you can tell me its great to me!
        Thanks for your help.

      • Bernado says:

        You first need to update the ImageUploadFieldType class to be able to hold multiple URLs. You will then need to update the 2 rendering templates in the ImageUploadFieldControl.ascx file – this will depend on the user experience you want to give when viewing/editing the field in the UI. Next you’d probably want to update the UploadImage.aspx file to enable users to upload multiple images without leaving the page. Lastly you will need to update the fldtypes_ImageUploadField.xsl to display the images in list view.

        Overall I think the difficulty level would be medium, but it’d be a lot of work.

        I’m not sure of the business requirements you are trying to cater for, but perhaps you could take a different approach all together. Rather than using a custom field type, you could use a normal Hyperlink field. You could then create a different picture library for each item, or a different folder within the same picture library. The hyperlink field then would contain the URL to the specific picture library or folder for that item? The user then can click on the link to go to the library/folder and upload images to there?

      • Gustavo says:

        Bernardo, exists a way to change the size (height and width) of the image field?. If i select a image too big it shows in the real size, and i want a custom and fixed size for the field.
        Thanks for your help.

      • Bernado says:

        Hi Gustavo,

        You can add width/height attributes to the img tag in the XSL to control the size of the image in list views. Add width/height attributes to the asp:image tag in the 2 rendering templates to control the size in edit/display forms. If you want to make the size configurable, then follow the same pattern as UploadImagesTo to add 2 custom properties for the field type. Good luck.

  16. Dan says:

    Hi! Great!
    How can i set UploadImagesTo programmatically?

    • Bernado says:

      Hi Dan, the code originally posted does not allow you to do this. I now have refactored the code to make this possible. Download the new code in section 14. Updates above and check the test harness that demonstrates this.

  17. Matto says:

    Really, it is nice article , and you did a great effort to explain the technique. but i have an important question here, if i want to customize the display of column created with field type in list view, i mean if i want to display a column created based on this field type either with Green or Brown Based on it’s value,
    example ,i created column called degree , i want to display the value of degree column in List view with green color if column value >15 and with brown color if column value < 15 . can you please help me in this point ??

  18. John S says:

    Thanks for the article. I am having trouble implementing this though. When I try to add a new item I get an exception in the SetupEditTemplateControls() method of the ImageUploadFieldControl class.

    Its a null reference exception, specifically on this code:

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

    lnkLaunchUploadImagePage.HRef, imgImage.ClientID, and hiddenImageUrl.ClientID are all null. Where/how are they set?

    Thanks!

    • Bernado says:

      Hi John,

      lnkLaunchUploadImagePage.HRef is set in the RenderingTemplate for edit mode (in ImageUploadFieldControl.ascx). The value of the ClientID property is auto-generated by ASP.NET.

      Check that the following objects are not null: lnkLaunchUploadImagePage, imgImage, hiddenImageUrl, base.List and base.Field.

    • Erik D says:

      John,

      Did you find a solution to this? I’m running into the same error. Having trouble finding where those objects are set.
      Thanks!

      • Bernado says:

        In the C# code, the lnkLaunchUploadImagePage, imgImage, hiddenImageUrl instance variables are defined in the ImageUploadFieldControl class. Within this class, these variables are set by the InstantiateMemberControls() method.

        Within this method, these variables are set by calling TemplateContainer.FindControl(“[controlName]”). [controlName] is the name of the corresponding control as found in the ASCX that defines these control – and this is ImageUploadFieldControl.ascx. The FindControl(“[controlName]”) call will return null if the control is not found. Therefore, double check that the control name matches with the ASCX.

        Hope this helps.

      • Erik D says:

        Bernado,

        Thanks for the great article. This add on is great. I found my issue. I had created the ImageUploadFieldControl.ascx file in a sub folder by mistake. Because of this the controls were never being created. Moving the file out of the sub folder to the Control Templates folder did the trick.

      • Bernado says:

        Glad you found the problem and thanks for posting back with the fix :).

  19. Manjula says:

    HI
    can u help in uploading document instead of images to library.

    Thanks in advance.

  20. Fei says:

    Hi Bernado,
    Using the custom type, how could I able to retrieve the url value of the image is Visual Studio? Like:
    SPList CL = curWeb.Lists[“Country List”];
    SPListItemCollection items = CL.GetItems();
    foreach (SPListItem item in items)
    {
    item[“Country Flag”].ToString() ;
    }
    –Thanks in advance.

    • Bernado says:

      Hi Fei,

      As the custom field inherits from the SPFieldUrl type, you can work with its value exactly the same way you’d with the OOTB SPFieldUrl.

      To retrieve the url value:

      var fieldValue = (SPFieldUrlValue)item[“Country Flag”];
      Console.WriteLine(“Url: ” + fieldValue.Url);

  21. Duc Phan says:

    This is great! Many thanks

  22. Marcus Vinícius says:

    Congratiulations ! This is one of the best tutorials I ever seen . Thanks a lot !

  23. Heba Mosaad says:

    i have a problem after i upload the image it’s displayed on the add form but when i click save on list add form the image is not saved into the list however it’s saved on picture library

    • Bernado says:

      Hi Heba,

      It sounds like the list does not know how to render the new field type. Did you apply the XSLT in the last step correctly?

      Also, if you just download and deploy the solution, remember to do an IISRESET after deploying the solution.

  24. Mehul Patel says:

    This blog post was EXTREMELY helpful and considering its complexity, very easy to follow. Thank you!

  25. imtiaz says:

    thanks dude very great post

  26. Heba Mostafa says:

    i have atrouble after i upload the image on clicking save it display error that field is required can u help ??

  27. Rémy says:

    Hi, many thanks for this. It helps me a lot! But got one problem… when I deploy the solution in my dev environment it works very well but when I deploy it on the test environment, everythings is fine except that the xsl doesn’t seems to apply. XSL is deployed to 14/template/xsl but the image doesn’t want to show up. Have I miss a settings on the server ? Why is the xsl not applying on test server but on dev server ?

    • Bernado says:

      Hi Remy,

      Did you do an IISRESET after deploying the solution to TEST? Is it a multi-servers farm in TEST? (and DEV?) Did you deploy the solution as is – or did you make some code changes?

      • Rémy says:

        Hi Bernardo,

        Yes, iisreset is done. It is not a server farm, it’s two totaly separate servers. And no change made to the solution. I’m trying to modify directly fldtypes.xsl to see if I can find something. If I modify this template : it doens’t do anything (even after iisreset).

      • Bernado says:

        OK, so your TEST environment is a single-server farm. The solution deploys a separate XSL file, namely fldtypes_ImageUploadField.xsl. Do you see this file on the TEST server?

        Also, have you applied any special permissions to the library where the images are uploaded to?

      • Rémy says:

        Well, I just find the solution. Don’t know why but this template “” was modified in the fldtypes.xsl. This template wasn’t calling the URL_BODY mode. I fix this and now it’s OK. Thx for your help !

      • Bernado says:

        Great! Glad to hear :).

  28. David Lozzi says:

    Great job on this!
    I had to make one change on here to support external users. In protected void EndOperation(ModalDialogResult result, string returnValue) I had to remove Page.Response.End(); due to a generic “System.Threading.ThreadAbortException Thread was being aborted” error.
    Removed it and all is well! Thanks again!

  29. orshee says:

    Hi Bernardo and thanks for sharing this.
    I actually need help figuring out why it doesnt work for me,

    I’m able to select the Image upload field when creating new list column, but when i want to ad item, after selecting the image and pressing upload, i get generic error : Server Error in ‘/’ Application.

    I’ve checked and there is no folder Images in TEMPLATE\LAYOUTS so i created it and created blank.gif, but it wasnt the reason for thing to not work.

    Do you by any chance have any clue or a tip where to look for a problem?

    Many thanks!

    • Bernado says:

      Hi Orshee, the generic error you are getting is not the real error. customError is enabled by default in SharePoint, and looks like it is still enabled – and therefore you are not seeing the real error message.

      The original code uploads images to an Images library in SharePoint, not to an Images folder under the TEMPLATE\LAYOUTS folder. If you haven’t created the Images library in SharePoint, that may be the reason for the error.

      If you have already created this library, then you’ll need to disable customError so we can see the real error message and troubleshoot.

      • orshee says:

        I’ve realized i need to create library after looking at code and so i did. I alsto tried to find why i’m getting generic error even tho web config clearly says its in debug mode with no custom error pages.

        Additionaly after creating library for images, i’m no longer able to succesfully add column to the list, i do get the option tho choose the Image upload column, but after setting the name and pressing ok, i again get same generic error. I tried removing library again, retracting and redeploying solution, restaring iis, but i cant get past this steo any more.

        Once again thanks for the time and effort as this indeed is great example, and i hope we can find out whats wrong in my case.

        Cheers

      • Bernado says:

        It is strange that you are still getting the generic error message – this is not going to be helpful.

        There are 2 web.config that you need to change, 1 is under inetpub, and the other is under the LAYOUTS folder. Within each web.config, there are 3 settings you need to change. Please see this for more info: https://bernado-nguyen-hoan.com/2011/04/23/cant-disable-customerrors-in-sharepoint/

        If you are getting an error when trying to create the column using the SharePoint UI, then it looks like the problem is in the field type definition – as no custom code is executed at this point. Check that the field type definition XML is there under SharePointRoot, and check that the assembly is in the GAC. In any case, getting the real error message to show would really help.

  30. Stefan says:

    Great tutorial, thx! I’m working on also an a custom spfieldurl type and so your walkthrough was very helpful. But i think we both have the same problem: the description is not rendering in list view. I tried it with your solution and when you look at the source of the site after loading the listview, the description / alt-tag is empty….. any idea? Thx for help!

  31. G123 says:

    Gr8. The only problem I have noticed is the list form cannot be updated by InfoPath. I have a requirement to use InfoPath. Any solution? Thanks

  32. Sriniwas says:

    Hi Bernado,
    For some reasons, I’m in need of exactly the same feature in MOSS 2007. So, i did try to ape your code. After successful deployment, when i select the newly created “Custom Field Type” i hit the error, “File not found”. Could you please show some light.
    Thanks.

    • Bernado says:

      Hi Sriniwas – I don’t have access to a 2007 environment so can’t reproduce and investigate this. When you get the File not found error try right clicking on the page and select View Source. Sometime you may find the missing file identified as comment in the page source.

  33. Chris says:

    Great solution! Thanks for sharing. One question, I would like to save images as attachments rather than into a document library. How can i approach this?

    • Bernado says:

      Hi Chris,

      That is going to be a bit tricky. You probably still need to upload the image first to a temporary document library so you can show it in the list item form when a new list item is being created. You will then need to see if the SPField class (the top parent class of our custom field type class) raises any event when the item has been updated/created. If so, you will need to handle this event and create the attachment for the list item and delete the image in the temporary document library. If there’s no such event, you may need to implement an event receiver on the list, and create the attachment on the ItemAdded/Updated event. This however would make the field type clunky and not very reusable.

      You also need to make sure that you can reference the image after it has been attached to the list item by a URL. This is so that you can set the field value to this URL, and display the image in list item form and list view.

      Hope this helps.

  34. Kodle says:

    Got the code deployed and all but still got an error about somewhere is receiving a null exception, maybe I had rush into it too fast. Will be looking into it and drop a reply here as to why.

    Just curious though, is it still possible for me to create a list, programmatically (from scratch) and be able to do like the following snippet below?…

    SPList.Fields.Add(“…”,SPFieldType.ImageUpload, true);

    Possible?

    Thanks for the tutorial and regards,
    Kond

    • Bernado says:

      Hi Kond,

      Regarding the null error, check that you did NOT create the user control under a sub-folder. Please see Erik D’s comments against this post on February 5 and February 6. Hopefully that will help.

      Regarding programmatically setting up the list – yes you should be able to. Make sure you download the updated version of the code. There is a Harness project in the solution with sample code that does just that.

      Good luck.

      • Kond says:

        Hi Bernado,

        Thanks for the reply, I did up everything again and this time, it finally works! But I’m unsure as to why.

        With regards to setting up the list programmatically, is the only thing that is required to add on to this code that you have blogged above (not the available for download one) is to expose the “UploadImagesTo” to the public?

        Besides that, I would also like to try intervening the uploading process if possible to either:
        1) Check the upload size of image that is to be uploaded (either dimension or file size) and then stop the uploading process, pop-up a warning page and then that’s it (I think?)

        2) Resize the images to fit the required file size or dimension — this way, I wouldn’t have to pause the user’s upload.

        What would you advise for this part? Would it be idea to fit in any Event Handler into the code (that you had written above)?

      • Bernado says:

        Hi Kond,

        Glad that it works for you now.

        Regarding setting up the list programmatically, making the UploadImagesTo property public was one of the changes, there were other minor refactoring – which may or may not be required for it to work. It has been quite a while so I don’t remember exactly sorry. Both versions of the code are available for download.

        Regarding intervening the uploading process: for what you are trying to achieve, I’d put the code to automatically resize the image in the btnUpload_Click() handler of the ASPX dialog page (in step 5 of the post). This is where you are taking the image from the user’s machine and adding it to the library. It makes sense to add any image pre-processing logic here. It would be simpler to automatically resize the image, rather than prompting/warning the user. This would help to eliminate one interaction step.

        Good luck.

      • Miche says:

        Hi there!

        I’m writing a custom webpart and checking out the custom column that you have shown here. I’ve taken a look at your Harness test and I understand that you need to set the ‘UploadImageTo’ as a public property butI have 2 questions that came into my mind:

        The first one is — what if I’m writing the columns’ list, programatically, on another Visual Studio solution while the custom Upload Column is deployed separately in the other Visual Studio solution? If I were to write in this manner, I won’t be able to write the following snippet already:
        ————

        field = list.Fields[fieldName] as ImageUploadFieldType;
        field.UploadImageTo = “…”;

        ————

        Should I write a SchemaXML and create or update the field? (Where in the SchemaXML, I’ll write the “UploadImageTo” within.) If so, how do I write it?

        If not, is there any files I should write up within my separate solution?

        The second one is — How do I detect that this custom column type is already deployed?

        Thank you very much for you reply!

        Regards,
        Miche

      • Bernado says:

        Hi Miche,

        Is the problem being you can’t reference the class ImageUploadFieldType from your 2nd VS solution? If so, you should be able to just add a reference to the DLL of the 1st VS solution to the 2nd solution. When you do this, the 2nd VS solution will not automatically include this DLL in the WSP by default.

        I have also updated the post with section 15 to show the XML that you can use to provision a field of this type from a feature. This XML contains the setting for the UploadImagesTo property of the new field. If you want to use SchemaXML to create/modify the field, the XML should be the same as this.

        Regarding checking if the field type is already deployed, I’d suggest checking if the solution containing the field type is deployed. You can access the collection of solutions for the farm using SPFarm.Solutions.

        Good luck.

      • Miche says:

        Hi Bernado,

        I tried the following manner of writing in another Visual Studio solution file:

        But the ‘UploadImagesTo’ doesn’t gets updated with the value ‘My Image Library’ when the list gets created programmatically.

        Any idea how to fix this update?

        Thanks and regards,
        Miche

      • Miche says:

        Hi Bernado,
        With regards to the 2 issues I found in both of my last reply, I haveI found the solution to updating the ‘UploadImagesTo’ already. I totally forgot about SetCustomProperty() method is available under SPField.

        For the sake of anyone who encounter the same scenario as me, this was how I wrote it in my code when creating my list and columns programmatically::
        —————-
        //Get your list first before adding columns…

        string imgUploadFldSchema = “”;

        string imgUpldFld_InternalName = newList.Fields.AddFieldAsXml(imgUploadFldSchema);

        SPField spImgUpldFld = newList.Fields[imgUpldFld_InternalName];

        spImgUpldFld.SetCustomProperty(“UploadImagesTo”, “”);

        spImgUpldFld.Update();

        newList.Update();
        —————–

        NOTE: This piece of code was written in another Visual Studio solution where I was writing another webpart which uses custom image upload as I programmatically create my list.

        Thanks and regards,
        Miche

      • Bernado says:

        Thanks for posting back with the solution :). As per my last comment, consider adding a reference to the DLL of the field type to your project so you can avoid referring to the property by string.

  35. elmira says:

    Hi,
    Thanks for your great article,It really helped me a lot,
    I used your code and it works ,Now I want to localize it,but I can’t find where did you specify the dialog title and Upload an Image link
    any help appreciate
    regards

    • Bernado says:

      Hi Elmira,

      The dialog title is specified in step 5 where you create the ASPX dialog page. It is in the markup of the ASPX page as below.

      <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 text of the link is in the rendering template for edit:

      <a id="lnkLaunchUploadImagePage" runat="server" href="javascript:launchUploadImagePage('{0}', '{1}', '{2}', '{3}')">Upload an image</a>&nbsp;&nbsp;
      

      Hope this helps.

  36. Vannick says:

    Hi Bernardo,
    Very helpful job, thanks to share that !!!. I need the same requirment for SharePoint 2013. I will try the code as soon as possible. Did u already try it into 2013 ?
    Thanks for your response

  37. Vannick says:

    Hi Bernardo,

    When I try to add this field to a list, I have a weird error. May be you can give me some advises:

    “Application error when access /_layouts/15/FldNewEx.aspx, Error=The file ‘/_controltemplates/ImageUploadFieldEditor.ascx’ does not exist”

    What is strange is that the file ImageUploadFieldEditor.ascx is well present into the folder C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\15\TEMPLATE\CONTROLTEMPLATES

    Am I missing something ?
    Thanks for your help

    Bye

    • Bernado says:

      Hi Vannick, apology for the late reply.

      SharePoint 2013 adds ’15’ to the _layouts and _controltemplates virtual paths. Try adding ’15’ to the path where the control ImageUploadFieldEditor.ascx is being referenced in the code.

    • Bernado says:

      Vannick, I have gone ahead and convert this to SharePoint 2013. You can download the new code at the top of this post. Let me know if you run into any issues.

  38. Maddy says:

    Want to know . how to upload multiple docs for same product under different columns.

    For Ex:
    Product Release Date Tech specs Test Data
    XYZ Jan -13 Upload Upload

    Upload here means need to upload document (MS Word/PDF/Excel).

    • Bernado says:

      You will need to adapt the code to work with documents. Most of the plumbing will be the same. The upload dialog and the list view XSLT will need to change.

      Then you can create multiple columns of this type in the list.

      Good luck.

  39. Tarun Vats says:

    Thanks a lot bernado.. 🙂

  40. dbaranyi says:

    Very useful information. Thanks a lot!

  41. Nick Hurst says:

    Wow this solution looks exactly like what we were looking for. As a non-developer, could you explain how I could deploy this solution to SharePoint 2013? I downloaded the VS project, but was expecting to see the .wsp file in the bin Release folder?

    I’m also unclear whether this would be a site solution or a farm solution?

    Any help you could provide would be greatly appreciated. Thanks!

    • Bernado says:

      Hi Nick,

      The uploaded solution is uncompiled code so you’d need to open and compile it in Visual Studio. I’m guessing you don’t have access to VS as you are not a developer. I have compiled the WSP for you. You can download it here: https://app.box.com/s/3lg1i5hj1umry2ymbvjg.

      This is a farm solution. You can use the following PowerShell command to add the solution to the farm:

      Add-SPSolution “c:\your folder\bnh.sharepoint.wsp”

      You can then go to Central Admin and deploy it from System Settings \ Manage farm solutions

      Hope this helps.

  42. Pingback: Sharepoint | custom field types

  43. Lukas says:

    You are an absolute star! How can I donate some money for you!?? Such a massive piece of work completely for free? Unbelievable!

  44. Lukas says:

    Spoke too soon?
    I tested your solution in development and it worked well. (After deployment it is not listed under Site Collection features which is quite confusing. You just create a column and your solution is one of the options in the column list).
    When I wanted to do it in production (exact same replica of dev), the solution deployed successfully, but the column option is not available in ‘Create New Site Column’. What might cause this?

    • Bernado says:

      Hi Lukas,

      There is no features to activate or deactivate so it doesn’t show up in the Site Collection features page.

      Regarding the issue in PROD, did you perform an IISRESET on the server after deploying the solution? Is your PROD a multi-server farm?

      • Lukas says:

        No, I haven’t done the IISRESET. My server topology is: WFE and APP on one box + SQL on second box. Will IISRESET solve the problem? Thanks

      • Bernado says:

        The XML that defines the custom field type is cached by IIS, so it is worthwhile trying an IISRESET.

  45. Lukas says:

    IISRESET resolved the problem. Thank you!

  46. Hi! Thanks for this awesome Solution!
    This reply helped me a lot “The original code uploads images to an Images library in SharePoint, not to an Images folder under the TEMPLATE\LAYOUTS folder. If you haven’t created the Images library in SharePoint, that may be the reason for the error.”

    But then I have a question, how could I input details/metadata of the uploaded picture while uploading it?

    Thanks!

    • Bernado says:

      Hi there,

      That would require some work but should be achievable. One approach would be to extend the UploadImage.aspx layout page to include this functionality.

  47. PL says:

    Hello, thank you for your code, that work fine, but I have one question, can I use the new created image upload fiels with search ? I cannot find any crawled property in my shearch schema, the goal is to get the value to display the image in search result. is that possible ?

    • Bernado says:

      Hi there,

      I have not tested this solution against search. However, since the underlying type is based on the OOTB HyperLink field type, it should work with search as OOTB fields do. Regarding the missing crawled property, make sure that you have created at least one list item with this field populated, and then kick off a full crawl. The crawled property is only created if there is at least one list item with a value in this field.

      Hope that helps.

      • PL says:

        Hello, It’s now working, I have found the property and been able to use this solution to create a contact list.

        Search is working too, I can retrieve the hyper link to the picture and display the image using search and display template.

        Thank you for your reply and your job.

        regards

      • Bernado says:

        That’s good to hear – and thanks for reporting back that it works with search :).

  48. Triumph says:

    Bernado, thank you for the solution. I deploy it in sharepoint 2013. It works well, But when i use content query webpart for that document library which has Image upload field, the image could not display. Like Chad (Chad says: February 24, 2012 at 5:01 am ). The image source is :
    “http://portal/CoverImage/20100629-AB-13_Acid_Rain_Damages_Crops_in_Western_India.jpg, http://portal/CoverImage/20100629-AB-13_Acid_Rain_Damages_Crops_in_Western_India.jpg
    Any solution?

    • Bernado says:

      Hi there,

      I have to admit I did not test this with the content query web part. Seeing that the field type is essentially an OOTB HyperLink field underneath however, it should work as OOTB fields do. Are you using one of the OOTB item styles in the web part? Perhaps you could test adding an OOTB HyperLink field to the library and configure your item style to look up that field instead? This would help determine whether the problem is with the field type, or with the item style you are using in the web part.

  49. Triumph says:

    Thank you, Bernado for prompt reply.

    Yes, I am using OOTB Item style. As advice, OOTB HyperLink field is added. Manual added the URL for each entry. Look up to that field is working.

  50. denisbednov says:

    Bernado, thank you for the solution! Could you to recommend some books to learn SharePoint?

  51. Kay says:

    Hi Bernado, thanks for the solution. I deployed the Sharepoint 2013 version…it works fine, except for one issue. I have a Lookup (information already on this site) field type on the list…which looses its values when I click on the “Remove Image” link (does a postback). Is there a way I can fix this? Thanks.

    • Bernado says:

      Hi Kay,

      I did not test this scenario back then when I was developing the solution. Does this occur when you have picked a value for the Lookup field, but have not saved the list item? You might need to update the Remove Image link to manipulate the field using JavaScript rather than a postback.

      Hope that helps.

      • Kay says:

        Hi Bernado,
        There was no code issue. Apparently, all I had to do was change the “CSR Render Mode
        ” property of the ListFormWebPart to “Server Render (ServerRender)”…this help retain the Lookup control’s data after postback. Thanks for your help.

      • Bernado says:

        Hi Kay,

        That makes sense! Thanks for posting back the solution :)!

      • JOHN says:

        How to update the Remove Image link to manipulate the field using JavaScript rather than a postback?
        Can you give me sample code?

    • JOHN says:

      I have the same problem.
      Do you have any soluation?
      How to update the Remove Image link to manipulate the field using JavaScript rather than a postback?

  52. saeid says:

    Hi Bernado,
    I am planning to use it with sharepoint 2010. I have not implemented it yet.
    Can i use this field with infopah 2010?
    Can i add file extenstion restrictions?

    thanks

  53. Jack says:

    I was headed towards writing a custom field type and i came across this – you saved me a ton of time – it was also great that is just worked – rarely does this happen.

    MANY THANKS !!

  54. Really good work. Helped me a lot. But how can i access the field using REST API. Its failing with target invocation. Let me know… Perfect solution only one Client side issue

    -1, System.Reflection.TargetInvocationException
    Exception has been thrown by the target of an invocation.

    • Bernado says:

      Hmm that’s a good question! I have never tested this against the REST API. I will need to look into this. I’m afraid it won’t be soon though as I don’t have much capacity at the moment :(..

  55. Fabrizio Cazzola says:

    Dear Bernardo, great work!
    Sorry to bother you
    Do you have any possibility to upload a compiled version (wsp) also for SharePoint 2013?
    Thanks a lot

  56. JOHN says:

    I use SharePoint 2016 and VS2017,
    This soluation code (layouts & template) are deploy to 16 hiv location,
    But SharePoint load this field (layouts & template) from 14 hiv location when execute the field code

    How to change the exexcute location from 14 to 16 ?

    Thanks

  57. Robin Simon says:

    Hi, i would like to get some information regarding Custom Fieldtype in Sharepoint.
    Could you please share you email id , so that i can personally email you

Leave a reply to Bernado Cancel reply