Enable download of older file versions in Episerver

C# Episerver

One of the customers I work with have an Episerver with the old filesystem (with UnifiedFile etc.) and wants to upgrade it to the latest version. With this upgrade comes of course the new filesystem when all the files are IContent.

The problem

One of the problems I noticed with this upgrade was that they have workspaces where they can upload files, share notes, calendar etc. and nothing is a problem here. But one if the functions in the file archive is that they list all the versions and give the users the option to download older version of a file.

What I could understand from the code they had, this functionality was is built in with the old filesystem which enabled them to have a direct URL to a version of a file. When I tried to replicate this functionality in the new Episerver version it was no suprise I did not get a specific URL for each version as they are ContentReferences, even if I loaded all available versions from the IContentVersionRepository.

One of the possible solutions

As I knew it was possible to get the older file from the Blob property if I loaded an older version of a MediaData item, there were a few solutions for this problem. So I choose to try out the partial router functionality, as it seemed to fit my needs.

My goal was to be able to download a file at the URL /globalassets/file.txt/ so with a class implementing IPartialRouter with MediaData as the content class and the built in ContentVersion as the data class, there was not that much of code to implement.

public object RoutePartial(MediaData content, SegmentContext segmentContext)
{
    var segment = segmentContext.GetNextValue(segmentContext.RemainingPath);

    int version;
    if (!int.TryParse(segment.Next, out version) || version <= 0)
        return null;

    segmentContext.RemainingPath = segment.Remaining;

    var list = ContentVersionRepository.Service.List(content.ContentLink);
    return list.FirstOrDefault(cv => cv.ContentLink.WorkID == version);
}

With this I implemented that part of the URL with be parsed as an int and be matched against the WorkID of the ContentReference on an older version. So this would validate the version parameter and also fetch the version of the file I would want.

To download the file there is a simple and built in way in MVC to send a Stream to the user with the specified filename, which perfectly matched my needs here.

public ActionResult Index()
{
    ContentVersion contentVersion = Request.RequestContext.GetRoutedData<ContentVersion>();
    if (contentVersion == null)
        return HttpNotFound();

    MediaData mediaData = contentLoader.Get<MediaData>(contentVersion.ContentLink);
    if (mediaData == null)
        return HttpNotFound();

    FileStreamResult imageResult = new FileStreamResult(mediaData.BinaryData.OpenRead(), mediaData.MimeType);
    imageResult.FileDownloadName = mediaData.Name;

    return imageResult;
}

Here I simply fetch the parsed ContentVersion from the routed data, checks if the content is a file (MediaData) and then returns the file to the browser as a Stream.

To get this to work I simply need to register the partial router in an IInitializableModule as done below.

public void Initialize(InitializationEngine context)
{
    RouteTable.Routes.RegisterPartialRouter(new MediaDataVersionPartialRouter());
}

The code

For some this might not be the best solution, but it works for our scenario. Also, the complete code is available on GitHub here.