Try our conversational search powered by Generative AI!

Johan Björnfot
Dec 3, 2013
  25606
(8 votes)

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.

Dec 03, 2013

Comments

Joshua Folkerts
Joshua Folkerts Dec 3, 2013 07:51 PM

Nice Post Johan, Much needed that is for sure. Keep them coming!

Johan Kronberg
Johan Kronberg Dec 4, 2013 11:46 AM

Can't wait to work with this!

Dec 5, 2013 09:35 AM

Great summary.

Please login to comment.
Latest blogs
Roll Your Own Security Headers

Proper security headers are a must for your Optimizely driven website. There are a variety of tools out there that will help with this, but when...

Ethan Schofer | Feb 21, 2024

Migrate Catalog content properties

A colleague asked me yesterday – how do we migrate properties of catalog content. There is, unfortunately, no official way to do it. There are...

Quan Mai | Feb 20, 2024 | Syndicated blog

Adjust log levels in Optimizely DXP

You may adjust the log levels for your site in Optimizely DXP yourself, but only for the Integration environment. Follow this step-by-step guide.

Tomas Hensrud Gulla | Feb 20, 2024 | Syndicated blog

Introducing Search & Navigation Dashboard for Resource Usage

We're excited to unveil the latest addition to the Search & Navigation suite: a dashboard designed to proactively monitor your resource usage. It's...

Edvin Dackelid Johansson | Feb 20, 2024