Working with Media programmatically
Prior to version 7.5, files in EPiServer CMS (as exposed in the old file manager) were handled by a VPP (Virtual Path Providers) system. In the latest version of EPiServer a new media file system with a new user interface is introduced. Content stored in this system is referred to as “media”. This system is based on the same content (IContent implementations) system used for blocks and pages, as well as products from the catalog in Commerce. This means that files shares same support for routing, custom properties, typed models, custom renderings, security etc. as other content types.
To work programmatically with media is much like working with other content types (e.g. pages). For example are the CRUD operations done through IContentRepository. In this post I will go through some of the interfaces/base classes used for media and give some examples on how to programmatically work with media.
IContentMedia
The requirement for a media type is that it implements IContentMedia which is defined as:
public interface IContentMedia : IContent, IBinaryStorable
{
Blob Thumbnail { get; set; }
string MimeType { get; }
}
We can see that IContentMedia extends IContent and also IBinaryStorable which is defined as:
public interface IBinaryStorable
{
Blob BinaryData { get; set;}
Uri BinaryDataContainer { get; }
}
The Property BinaryData of type Blob (see class below) is used to handle the actual binary content of the media. The property BinaryDataContainer is the Uri for the container containing the Blob. All binaries for a content instance (including versions and languages) are stored in a common container. The definition of Blob looks like (I have omitted the static methods):
public class Blob
{
public Uri ID { get; }
public virtual Stream OpenRead();
public virtual Stream OpenWrite();
public virtual void Write(Stream data);
}
So a blob instance basically contains stream based Read and Write operations. The actual Blob instance is delivered by the configured BlobProvider. The default blob provider stores the binary data on disk (there is a subfolder blobs in the folder specified by attribute basePath on configuration element appData). The NuGet packages EPiServer.Azure and EPiServer.Amazon contains provider to store binary data in Azure Blob storage respectively Amazon S3.
Defining a media type
A media type is defined by having a class implementing IContentMedia and decorated with ContentTypeAttibute. The attribute MediaDescriptorAttribute can be used to map which extensions a certain type handles. There are some base classes MediaData, ImageData and VideoData that can be used to specify media types. So for example a project can have types defined as:
[ContentType(GUID = "EE3BD195-7CB0-4756-AB5F-E5E223CD9820")]
public class GenericMedia : MediaData
{
public virtual String Description { get; set; }
}
[ContentType(GUID = "0A89E464-56D4-449F-AEA8-2BF774AB8730")]
[MediaDescriptor(ExtensionString = "jpg,jpeg,jpe,ico,gif,bmp,png")]
public class ImageFile : ImageData
{
public virtual string Copyright { get; set; }
}
[ContentType(GUID = "85468104-E06F-47E5-A317-FC9B83D3CBA6")]
[MediaDescriptor(ExtensionString = "flv,mp4,webm")]
public class VideoFile : VideoData
{
public virtual string Copyright { get; set; }
[UIHint(UIHint.Image)]
public virtual ContentReference PreviewImage { get; set; }
}
Creating a new media instance
Below is an example on how to create a new media instance. Here the media item is created under SiteDefinition.Current.GlobalAssetRoot which is the folder for assets that is shared between sites (this is the root for the media gadget). To structure media instances of EPiServer.Core.ContentFolder can be created.
var contentRepository = ServiceLocator.Current.GetInstance<IContentRepository>();
var blobFactory = ServiceLocator.Current.GetInstance<BlobFactory>();
//Get a new empty file data
var file1 = contentRepository.GetDefault<GenericFile>(SiteDefinition.Current.GlobalAssetsRoot);
file1.Name = "Readme.txt";
//Create a blob in the binary container
var blob = blobFactory.CreateBlob(file1.BinaryDataContainer, ".txt");
using (var s = blob.OpenWrite())
{
StreamWriter w = new StreamWriter(s);
w.WriteLine("Hello world");
w.Flush();
}
//Assign to file and publish changes
file1.BinaryData = blob;
var file1ID = contentRepository.Save(file1, SaveAction.Publish);
Get media type from extension
There is a ContentMediaResolver that can be used to resolve which content type to use for a certain extension. See below:
var contentRepository = ServiceLocator.Current.GetInstance<IContentRepository>();
var contentTypeRepository = ServiceLocator.Current.GetInstance<IContentTypeRepository>();
var mediaDataResolver = ServiceLocator.Current.GetInstance<ContentMediaResolver>();
//Get a suitable MediaData type from extension
var mediaType = mediaDataResolver.GetFirstMatching(".txt");
var contentType = contentTypeRepository.Load(mediaType);
//Get a new empty file data
var media = contentRepository.GetDefault<IContentMedia>(SiteDefinition.Current.GlobalAssetsRoot, contentType.ID);
Replace the binary data for a media item
When a binary data is to be replaced for a media item a new Blob instance should be created. That is you should not call OpenWrite on the existing blob instance. The reason for this is to support versioning so that if an editor reverts back to an old version the binary data should follow. Normally you should not need to bother about deleting Blobs, there is a scheduled jobs that takes care of deleting unused Blobs. Below is an example on how to replace the binary data for a media in a new version:
var contentRepository = ServiceLocator.Current.GetInstance<IContentRepository>();
var blobFactory = ServiceLocator.Current.GetInstance<BlobFactory>();
//the file to update, not hardcoded of course
var fileID = new ContentReference(444);
//Get the file
var file1 = contentRepository.Get<GenericFile>(fileID).CreateWritableClone() as GenericFile;
//Create new blob
file1.BinaryData = blobFactory.CreateBlob(file1.BinaryDataContainer, ".txt");
using (var s = file1.BinaryData.OpenWrite())
{
StreamWriter w = new StreamWriter(s);
w.WriteLine("Hello world");
w.Flush();
}
//publish new version
var file1ID = contentRepository.Save(file1, SaveAction.Publish);
Working with content assets
In previous versions it was possible to have a local VPP folder for a content where files related to that content could be stored. Now with media based assets a content instance that implements EPiServer.Core.IResourcable can have related assets (media, blocks etc.) stored in an instance of EPiServer.Core.ContentAssetFolder. Resources stored as content assets are to be seen as exclusive assets for that content instance and hence the resources are not selectable from other content instances.
The method GetOrCreateAssetFolder on class EPiServer.Core.ContentAssetHelper can be used to get the asset folder for a content item.
Nice Post Johan, Much needed that is for sure. Keep them coming!
Can't wait to work with this!
Great summary.