The goal of a recent project was to provide a way for a non-technical person to be able to add images and metadata to an ASP.NET page without the intervention of a developer. I’m using SharePoint Foundation 2010 as the “user-friendly” interface where anyone with the right permissions can upload an image and add some metadata. And accessing the Picture Library using the new SharePoint 2010 Client Object Model (ClientOM).
I first started by writing a simple .aspx page to retrieve the images from SharePoint. In order for this to work I had to enable anonymous access to the document library. I used the MSDN examples as a starting point. In order for the below to work, you will need to add a reference to Microsoft.SharePoint.Client.dll and the Microsoft.SharePoint.Client.Runtime.dll. Below is what I used to retrieve the necessary list item collection. Ignore the parameters in the CAML query, I’ll explain those last:
using (ClientContext clientContext = new ClientContext(url + "/sites/siteName/"))
{
// gets the credentials of the web application
clientContext.Credentials = System.Net.CredentialCache.DefaultCredentials;
List oList = clientContext.Web.Lists.GetByTitle("listTitle");
StringBuilder query = new StringBuilder();
query.Append("<View>")
.Append("<Query>")
.Append(" <Where>")
.Append(" </Where>")
.Append(" <OrderBy>")
.Append(" <FieldRef Name='" + OrderColumn
+ "' Ascending='" + Ascending.ToString() + "' />")
.Append(" </OrderBy>")
.Append("</Query>")
.Append("<RowLimit>" + RowLimit)
.Append("</RowLimit>")
.Append("</View>");
CamlQuery camlQuery = new CamlQuery();
camlQuery.ViewXml = query.ToString();
Microsoft.SharePoint.Client.ListItemCollection items =
oList.GetItems(camlQuery);
// load server data and required columns
clientContext.Load(
items,
listItems => listItems.Include(
item => item.File,
item => item.Id,
item => item["Title"],
item => item["Description"],
item => item["ImageCreateDate"],
item => item["votes"]));
clientContext.ExecuteQuery();
}
The trick to making this code efficient is to only request the columns you need data from. I have used the .Include() statement to only retrieve the necessary columns.
Once I had my ListItemCollection, it was easy to iterate the items in the collection and write some HTML out onto the page. The foreach takes advantage of SharePoint’s auto-generated thumbnails and displays them on the page. The full sized image is accessed via JavaScript using a LightBox type presentation.
In my first attempt I was “guessing” as to how big the thumbnails should be. But a co-worker pointed out to me SharePoint auto-generated its own thumbnails! So, after doing some simple research I determined that every time an image was uploaded to a picture library it used a consistent algorithm to generate the thumbnail. By adding a ‘/_t/’ to the URL then replacing the last . (period) with an _ (underscore) and appending “.jpg” to the end of the image URL, I could grab the auto-generated thumbnails, wicked cool!
// iterate each list item and generate HTML
foreach (Microsoft.SharePoint.Client.ListItem listItem in items)
{
// images accessed via SP Library, library must allow anonymous read on items
string imageUrl = url + listItem.File.ServerRelativeUrl;
string thumbUrl = imageUrl.Substring(0, imageUrl.LastIndexOf('/'));
thumbUrl = thumbUrl + "/_t/" + listItem.File.Name.Replace('.', '_') + ".jpg";
DateTime taken = (listItem["ImageCreateDate"]) != null ?
DateTime.Parse(listItem["ImageCreateDate"].ToString()) : DateTime.Now;
// sanatize data from SharePoint
string title = (listItem["Title"] != null) ? listItem["Title"].ToString() : "No Title";
string description = (listItem["Description"] != null) ? listItem["Description"].ToString() : "";
int itemId = listItem.Id;
StringBuilder sb = new StringBuilder();
sb.Append("<div class='picItem'>");
sb.Append(" <div class='picItemImage'>");
sb.Append(" <a title='" + description + "' href='" + imageUrl + "' rel='lightbox[" + ThumbnailGroup + "]' />");
sb.Append(" <img src='" + thumbUrl + "' alt='" + title + "' />");
sb.Append(" </a>");
sb.Append(" </div>");
sb.Append(" <p>" + taken.ToShortDateString() + " - " + title + " </p>");
sb.Append("</div>");
imagePane.Controls.Add(new LiteralControl(sb.ToString()));
}
The last piece of functionality I added to the project was wrapping everything up into a user control and adding Properties to the user control. I used the properties in two places: 1. Parameters to be passed into the CAML query to change the sort order, and sort column values of the returned ListItemCollection and 2. as configurable thumbnail group names so if the control is re-used on the page the images can be grouped together or separated.
public int RowLimit { get; set; }
public string OrderColumn { get; set; }
public bool Ascending { get; set; }
public string ThumbnailGroup { get; set; }
To keep things simple, all my code executes in the Page_Load() method. And, because I have the ListItem’s ID, I can manipulate the SharePoint list item. But that is for another post.
Let me know what you think? Any improvements or suggestions? For instance, how can I eliminate the need for anonymous access on the Picture Library and just display the images from memory?