Working with Media Items from code

The Media Library in Sitecore is primarily used for images and other editor-driven files, such as pdf documents etc. Media items are basically just regular items, but with a blob field that can store binary data. So this can be used for more things.

One use case can for example be storing sitemap.xml files. Those files can sometimes be quite compute intensive to render. Search engines rarely loads those, so making a scheduled background job, that runs a few times per day is more than enough. The rendered sitemap file(s) can then be stored as a media item and streamed when requested. Since they’re just regular items, you can simply make it read-only and put file permissions on it to prevent regular editors from playing with them.

“AttachStreamToMediaItem” may not do what you think it does.

If you haven’t looked into Sitecore blob storage module, it’s about time. It vastly reduces the volume of data stored in SQL Server and it makes publishing faster too, as the binaries doesn’t have to be transferred between databases.

Creating Media Items from code

The Sitecore.Resources.Media.MediaCreator can be used to create and modify media items. The CreateFromStream methods sounds like a good method, right? Well, in some scenarios it would work just fine, but at least for me, this was not a good choice at all. It did not do what I expected it to do. Also, the MediaCreator class comes with a few other methods that sounds really useful, like replacing one binary with another. But the AttacheStreamToMediaItem may not do what you think it does.

Drilling down in the reflected kernel code, it turns out that some functions in this class ends up in the protected CreateItem method. This method does a few strange things. For example, if you create an unversioned file (as most of us would probably do), there is a section there that will loop over a list of languages and store file Extension, File Path, Alt-text and write a new revision to it. Extension and File Path are shared fields, so that’s just relevant for the first pass. So what defines this languages list? The methods take a MediaCreatorOptions object with a bunch of settings about how the media item should be handled. It contains a Language property, so it would use that one, right? Well, for versioned files, yes. But for unversioned files, that most of us use, it uses the item.Database.Languages propertie, i.e. all languages defined in the database.

So, this can be a bit of a rabbit hole and cause some issues. If you’re, like me, working with almost 100 language layers, creating item versions on all language layers just wont work. It’s way too time consuming and waste of space and resources.

A better approach

So how can we do this in a better way? Well, media items are just items in Sitecore. So I found it better to just create the item, just as any other item. It can the simply be casted to a media item, like this:

var mediaItem = (MediaItem) parentItem.Add (itemName, new TemplateID(Sitecore.TemplateIDs.UnversionedFile));

We can then just attach a stream of binary data to that item:

var media = MediaManager.GetMedia(mediaItem);
media.SetStream (stream, "fileExtension");

Reading the binary data

The built-in media handler works great for delivering most binary assets, including scaled images etc, via tha -/media... handler. But we can read the binaries from code as well, and either use it in a controller or deliver it from a different file path. The sitemap.xml example mentioned above could be such case.

We can simply build or own handler or controller that maps to the URL we want and we can then grab the item from code and send it where ever we want. Depending on what scope you’re, this code will obviously be different. The key here is the GetMediaStream() and that we can also get the other meta data around this.

var mediaItem = (MediaItem) anItem;
response.ContentType = mediaItem.MimeType;
var mediaStream = mediaItem.GetMediaStream();
mediaStream.CopyTo (response.OutputStream);

When to use media items

This example with having a sitemap.xml as a media item may seem a bit overkill at first. We could just write it to disk, right? Well, not in a multi-server environment. Having it in the database allows it to be accessible from all servers without file sync issues etc. (Btw, don’t ever use file based media library). But we could just put the content in a multi-line text field, right? Well, that doesn’t work very well either for larger documents. This is because Sitecore always loads the entire content and sends it to the browser when just clicking the item. This can become really slow. Try adding 1MB of text into a text field and you’ll see how slow it becomes. Leveraging from media items, avoids the content from being loaded into the browser when working in the content editor.

I should also mention that the blobs would basically just need a field to put its data into. In practice, the blob on a media item is just a regular item but with field of blob type. It could (or should) work just adding a blob field to any other item template. I haven’t tried this myself. Searching around the database and the code where the field is used, it looks to me like it may have been hard coded into versioned and unversioned media items in some places, so I don’t know if it would actually work adding blob fields to other templates.