Integrate an external media library into Sitecore – Part 3

Finally the last post in this serie is done.

In the previous post we covered how we did to store the information we needed in the Image field in Sitecore.

In this post I will show you what we did to get the information stored in the Image field out on the website. I’ll start with a short warning, this will get complicated, so I will not go into details on all parts. At some point in this project we took some shortcuts which will explain some of the solutions we did that could have been better. This post does not contain much code examples since this post would be way to long, so at the end is a zipped file with all classes used in this solution.

When requesting any media from Sitecore, all file ends with .ashx, this ending is mapped to a requesthandler that steams the media to your browser.
Our plan was to use this in such a way that we request an predefined media-item inside Sitecore based on one of our extended media templates with the external media id and media extension as querystrings.
So we created an item /sitecore/media library/EmptyMedia/EmptyImage based on the template Externa Image that we created in one of the earlier posts without any data.
This media-item would get the url:
~/media/EmptyMedia/EmptyImage.ashx
We were going to request this item with both the id from the external media library and the extension of the image like this:
~/media/EmptyMedia/EmptyImage.ashx?extid=30779&extension=jpg

This was going to be our base for requests, now we needed a requesthandler that would take care of our media items and not mess up the existing Sitecore media functionality.
We started by creating a complete copy of the existing MediaRequestHandler that Sitecore uses for handling all .ashx requests. And started modifying it.

Our custom requesthandler would have to check if the request was done with extid as querystring and based on this either handle it as an external media item or as a Sitecore media item.

In Sitecores MediaRequestHandler, the ProcessRequest method wich is triggered on request, runs a method bool DoProcessRequest(HttpContext context)  which validates the request and creates an important object Sitecore.Resources.Media.Media and passes this to the method bool DoProcessRequest(HttpContext context, MediaRequest request, Sitecore.Resources.Media.Media media) which then loads the stream and passes it to the response output.

Simple right? Well we thought so, and we wanted to use these methods since they were already there. We started by checking if the request was done with extid as querystring and if that was the case a ran our custom method:

bool DoProcessExternalRequest(HttpContext context, MediaRequest request, string id, string extension)

This method was responsible for creating an object of the Sitecore.Resources.Media.Media. So we did some research on the Media class and found out that to create this we needed to create a MediaData object and pass it to the constructor of Media. And to create the MediaData object we needed a MediaItem that was based on the EmptyImage item inside Sitecore.
We now knew which objects we needed, so it was time to find out where the stream of data was loaded. After some poking around we found that the MediaRequestHandler calls the method GetStream in Sitecore.Resources.Media.Media wich in turn runs the pipeline getMediaStream with the MediaData object as parameter. All of the processors in the getMediaStream pipeline triggers a chain of methods that at one point ends up running the method GetBlobStream of MediaData which would retrieve the data from the Field by calling its GetBlobStream method. This was our entry point. We were going to check if the stream delivered from the Field objects was null and if so check if so validate if the request was for an external media file and deliver its stream instead.

So we had to be able to make the external id and extension available for this method. Since we were going to use this in some different cases to we decided to make an extension of the complete chain of classes and and mimic the behavior. We already had an extension of the MediaItem called ExtMediaItem created in the previous post which could hold the external id, extension and the width and height. So over to the MediaData class, we needed it to be able to hold and pass the external id and extension and to handle the ExtMediaItem. So the class ExtendedMediaData was created and we did the following alterations in this class:

  • Added attribute ExtID to hold and pass the external media id
  • Altered MediaId to deliver a unique id for external media. The MediaId is, what I think, used for caching.
  • GetBlobStream – this method was altered to try to load the data from the external media library if the stream that was delivered by the field is null.

With that altered we tried to get this working, so we created a ExtMediaItem based on the EmptyImage in Sitecore.
We created a ExtendedMediaData based on the ExtMediaItem and provided it with the  the values from the querystring for extid and extension.
This was then fead into a Media object and sent to
bool DoProcessRequest(HttpContext context, MediaRequest request, Sitecore.Resources.Media.Media media)
in the RequestHandler.
Unfortunately this didn’t, we got an exception telling us that the object provided wasn’t a MediaData object, even though ExtendedMediaData inherits from MediaData.
Because the lack of time we decided to create an extension of the Media class aswell where we altered all MediaData objects handled in the class to ExtendedMediaData, which worked.

So now when I make the following request:
~/media/EmptyMedia/EmptyImage.ashx?extid=30779&extension=jpg
the following steps are made:

  1. We check if the url contains extid and calls a custom method to build a Media object.
  2. An ExtMediaItem based on the item /sitecore/media library/EmptyMedia/EmptyImage is created
  3. An ExtendedMediaData is created with the ExtMediaItem and also provided with the ExtId and Extension values.
  4. A custom Media class wich intherits Sitecores Media class i created with the ExtendedMediaData.
  5. This Media object is provided to the DoProcessRequest method in the requesthandler.
  6. The DoProcessRequest calls the method GetStream on the Media object which triggers the getMediaStream pipeline.
  7. At some point one of the processors in the getMediaStream pipeline triggers a chain of methods that calls the GetBlobStream in the ExtendedMediaData.
  8. If the stream delivered by the Field object in ExtendedMediaData, it tries to get a stream from the web service of the external media library based on the extid value.
  9. Once this stream has been fetched, it’s returned and handled by the processors in the getMediaStream pipeline, like being resized. This is where the extension is needed, if the ExtendedMediaData doen’t have an extension it won’t be handled as a image.
  10. When the getMediaStream pipeline is done processing the stream it’s return to the Media object which will add the stream to Sitecores media cache and then return it to the RequestHandler.
  11. This is where it all ends, when the stream has been delivered to the RequestHandler, it is written to the output stream of the HTTP response object.

So thats the end of the series on how we integrated an external media library. Below is a link to all files used, they are not in any order what so ever and there might be some or alot of code that isn’t in use 🙂

Here’s the zip-file 😀

Integrate an external media library into Sitecore – Part 2

In the previous part we looked at how to get external information into the Sitecore media library tree using a DataProvider. Usually we use DataProviders to integrate content into the master database and then publish it to the web database for faster handling of that information.

In this case we don’t want to do that because the external media library contains alot of information and we won’t be using everything on the website. So we had to find another way of storing the information and only the used information and nothing more.

We started looking at how Sitecore stores information in the Image field. Many of Sitecores fields stores its information as xml and our idea was to use this and extend the Image fields to store our extra information about the external media. What we did was create a CustomImage class that inherits the Sitecore.Shell.Applications.ContentEditor.Image and “overrided” the BrowseImage method and added a block of code that checks if the selected image is based on any of our external media library templates. If they are we add our extra attributes.

MediaItem item = new MediaItem(innerItem);
TemplateItem template = item.InnerItem.Template;
if ((template != null) && !(this.IsImageMedia(template) || MediaHelper.IsExternalImageMedia(template.ID)))
{
	SheerResponse.Alert("The selected item does not contain an image.", new string[0]);
}
else
{
	MediaUrlOptions options = new MediaUrlOptions();
	string mediaUrl = MediaManager.GetMediaUrl(item, options);

	this.XmlValue.SetAttribute("mediapath", item.MediaPath);
	this.XmlValue.SetAttribute("src", mediaUrl);
	this.XmlValue.SetAttribute("height", item.InnerItem.Fields["height"].Value);
	this.XmlValue.SetAttribute("width", item.InnerItem.Fields["width"].Value);
	if ((template != null) && MediaHelper.IsExternalImageMedia(template.ID))
	{
		this.XmlValue.SetAttribute("extmediaid", item.InnerItem.Fields["ExtMediaId"].Value);
		this.XmlValue.SetAttribute("mediaid", MediaHelper.EmptyImage.ToString());
		this.XmlValue.SetAttribute("extension", item.InnerItem.Fields["Extension"].Value);
	}
	else
		this.XmlValue.SetAttribute("mediaid", item.ID.ToString());

	this.Value = item.MediaPath;
	this.Update();
	this.SetModified();
}

In the code you can see that when it’s an external media item we set the mediaid to MediaHelper.EmptyImage.ToString() which returns an id. The reason for this is that since we don’t publish the external media items to the web database we don’t have any media item to request. Our idea here was to create an empty media node in the media library that we can refer to and request with our information as querystrings when displaying the image.

After creating this class we reconfigured the Sitecore Image Field to use this class instead of the default Image in the Core database. Now we are able to store information about the selected external image. The next step is to use the information.

We tried to extend the Sitecore.Data.Field.ImageField class but found it easier to create a completely new class since there were so many changes to do. What we mainly did was adding the custom attributes. As we went on we found it necessary to extend the Sitecore.Data.Items.MediaItem to add our attributes and to store the Width and Height. I will link these classes at the end of this post.

Now we are able to use the information stored in the field. Next post I will cover the part where we create a new mediahandler that can deliver the image.

Below is a rar-archive with all the classes created.
classes.rar