Friday, May 30, 2008

Getting Date Information From Photos in SharePoint

Synopsis: SharePoint doesn't automatically extract the date the photo was taken when loaded into a SharePoint picture library, although this information is already embedded in JPEG images.  This article walks through the process of tapping into the photo image data when uploaded in SharePoint using an item event receiver.

SharePoint is a great tool for many things.  I find many applications for it in both the business world and the personal world.  I'm usually using MOSS (Microsoft Office SharePoint Server 2007) in most business scenarios and I use WSS (Windows SharePoint Services 3.0) for a lot of personal uses.  In either product, I find uploading photos a useful thing to do.  However, why should I have to go back and manually put in the date the photo was taken when my JPEG photos already contain this information?

The date the photo was taken as well as a lot of other information is embedded in certain image types, JPEG and TIFF being just two of those formats.  The standard for storing data in images is known as Exif.  Exif is an abbreviation for Exchangeable Image File Format.  I've written about how to use .NET to tap into this information here.

Now that we know how to get our date from our image, let's turn our attention to SharePoint.  In SharePoint, you have the ability to respond to events by way of "receivers."  We can respond to several different event receivers, here are a few:

  • SPItemEventReceiver - for responding to list item events, such as when we insert or update list item data
  • SPListEventReceiver - for responding to list structure changes, i.e. when new columns are created/modified
  • SPFeatureReceiver - for responding to feature activation and installation events

Since we want to intercept pictures as their loaded into a picture library, we're going to be interested in the SPItemEventReceiver.  We'll also need to use the SPFeatureReceiver to "wire everything up."  First, let's look at item events.

When items are inserted or updated in a list or document library, we can respond to those events.  Looking at possible overrides available on the SPItemEventReceiver class shows us some of the possible events.  We're interested in the ItemAdded event.

Before going too much further, lets talk about our Visual Studio project.  I'm using Visual Studio 2005 and STSDEV, which is a tool released by Ted Pattison on Codeplex.  This tool simplifies the creation of software that will be deployed in SharePoint.  There's a lot of good training on the Codeplex site so I won't go into the details.  In short, this tool simply creates my Visual Studio project from which I'll build my feature for looking at images.  Here's a snapshot of my Visual Studio solution:

Most of the files in the solution are created for me automatically.  I only need to modify the solution config XML file.  Again, there's training on the STSDEV site, so I won't go into that.  However, notice that there are two code files: FeatureReceiver.cs and PictureItemEventReceiver.cs.  FeatureReceiver is used to "wire up" the event to all picture libraries.  The PictureItemEventReceiver file is used to do the work of getting the image date and setting that in the item's metadata.

Let's look at the PictureItemEventReceiver file first.

    public class PictureItemEventReceiver : SPItemEventReceiver
    {
        public override void ItemAdded(SPItemEventProperties properties)
        {
            try
            {
                SPListItem item = properties.ListItem;

                if (item.File.Name.ToLower().EndsWith("jpg") || item.File.Name.ToLower().EndsWith("jpeg")) )
                {
                    Stream stream = properties.ListItem.File.OpenBinaryStream();

                    Image image = Image.FromStream(stream);

                    Nullable<DateTime> imageDate = GetImageDate(image);

                    if (imageDate != null)
                    {
                        properties.ListItem["Date Picture Taken"] =

                               ((DateTime)imageDate).ToString("MM/dd/yyyy hh:mm tt");

                        item.Update();
                    }
                }
            }
            catch (Exception ex)
            {
                Trace.WriteLine(

                   string.Concat("Error obtaining image datestamp: ", ex.ToString()));
            }
        }

First we need to inherit from SPItemEventReceiver.  Then we override the ItemAdded event, which occurs after the image is added. 

    public class PictureItemEventReceiver : SPItemEventReceiver
    {
        public override void ItemAdded(SPItemEventProperties properties)

At this point, we get a reference of the item itself using the properties parameter. 

SPListItem item = properties.ListItem;

If the file is a JPG type, then we go about opening a binary stream from the File property of the item.  Next, we go about getting a System.Drawing.Image from the binary stream.  At this point, we are able to call another block of code (shown in a previous blog post here) that will get the date stamp out of the image.

        Stream stream = properties.ListItem.File.OpenBinaryStream();

        Image image = Image.FromStream(stream);

        Nullable<DateTime> imageDate = GetImageDate(image);

Once we have that date, then we can update the SharePoint list item metadata.

        properties.ListItem["Date Picture Taken"] =

              ((DateTime)imageDate).ToString("MM/dd/yyyy hh:mm tt");

        item.Update();

Note that we must update the date with the correct date format for SharePoint.  The call to GetImageDate retrieves the date from the Exif information embedded in the image.

Next, we turn our attention to the FeatureReceiver.  We need to tell SharePoint that we want to watch for ItemAdded events for all Picture Libraries.  The FeatureReceiver.cs file contains that code:

public class FeatureReceiver : SPFeatureReceiver
{
    private string assembly = "MetaPic, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ce54f2f07031df02";
    private string receiver = "MetaPic.PictureItemEventReceiver";

    public override void FeatureActivated(SPFeatureReceiverProperties properties)
    {
        try
        {
            SPWeb site = (SPWeb)properties.Feature.Parent;

            // look for any picture library in the site
            for (int i = 0; i < site.Lists.Count; i++)
            {
                if (site.Lists[i].BaseTemplate == SPListTemplateType.PictureLibrary)
                {
                    // add the event receiver to wath for new images
                    site.Lists[i].EventReceivers.Add(
                        SPEventReceiverType.ItemAdded,
                        assembly,
                        receiver);
                }
            }
        }
        catch (Exception ex)
        {
            Trace.WriteLine(ex.ToString());
            throw;
        }
    }

In the FeatureReceiver, we inherit from SPFeatureReceiver.  This lets us respond to Activation/Deactivation events.  Upon activation of this feature, we enumerate through all lists that are of type SPListTemplateType.PictureLibrary.  For each one of these lists, we add an event receiver.

                    site.Lists[i].EventReceivers.Add(
                        SPEventReceiverType.ItemAdded,
                        assembly,
                        receiver);

Note that we must add the event type by specifying the proper enumeration SPEventReceiverType.ItemAdded.  Then we must pass in the fully-qualified name of the signed assembly and the actual receiver class.  This requires that you at least sign and build the project to get the public key token using Reflector or sn.exe.

Finally we clean up when the feature is deactivated using this chunk of code:

public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
    try
    {
        SPWeb site = (SPWeb)properties.Feature.Parent;

        // look for any picture library in the site
        for (int i = 0; i < site.Lists.Count; i++)
        {
            if (site.Lists[i].BaseTemplate == SPListTemplateType.PictureLibrary)
            {
                // find and remove the event handler
                foreach (SPEventReceiverDefinition receiver in site.Lists[i].EventReceivers)
                {
                    if (receiver.Type == SPEventReceiverType.ItemAdded)
                    {
                        receiver.Delete();
                        break;
                    }
                }
            }
        }
    }
    catch (Exception ex)
    {
        Trace.WriteLine(ex.ToString());
        throw;
    }
}

This code runs when the feature is deactivated and removes the event receivers previously added.  You'll want to activate this feature after you create one or more picture libraries.

Room for Opportunity

While the solution here is pretty cool, there are some things I'd like to address someday.  Here are some of them:

1. Add to the event receiver to a content type instead of each picture library so you don't have to re-activate the feature each time a new picture library is created.
2. Fix the user interface when a single image is loaded.  When a single item is uploaded you're sent to the edit page, but that page shows the date info as blank when the date is really there.  It really needs to be displayed as a read-only field.  Here's what you see:

And if you simply navigate to the image without updating, you see that it is there.

Multiple file upload, which is most useful, works great (of course I changed my view to show the date info):


3. Obtain image data upon adding the image to the document library instead of after the image has already been added.  Right now, we're going through an additional step to re-open the image.  It would be better to intercept the upload stream.

I hope you enjoy this.  I know I'll get a lot more use out of the picture library now that I can get date information out of my images.  Hopefully, you can use the above code to look into other event receiver concepts.

Tuesday, May 27, 2008

Kentucky Area Code Camp 2008

I'm co-leading the KY Day of .NET code camp for this Summer.  If you're interested in speaking or volunteering, please check out the details here.

Thursday, May 22, 2008

Creating Connection Strings in 30 Seconds with UDL Files

I did this once in a training session and the class was amazed.  For years, I've been whipping out connection strings using a little-known technique involving UDL (Universal Data Link) files.  Here's the trick:

1. Create a new text file on your desktop and rename it to "Test.UDL" instead of the default  ".TXT" extension.  You'll need to show file extensions for this.

2. Next, double-click on this file to open up a connection string editor called "Data Link Properties."

3. Change to the Provider tab and choose the provider (OLE DB Provider for SQL Server in this example).

image_8_6A764D87.png (377×471)

4. Change to the Connection tab and set your basic connection settings.  You can also test your connection at this point if you want.

image_10_6A764D87.png (377×471)

5. Set any additional settings in the Advanced and/or All tabs, such as Connection Timeout.  Then click OK to save the file.

image_12_6A764D87.png (377×471)

6. Open the .UDL file with notepad and there you go...

Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=TestDB;Data Source=localhost

You can remove the property called "Persist Security Info" because it's only used by the tool itself.  Enjoy.