Generate a Blob Storage Web Site using RazorEngine

Last episode I introduced the concept of utilising RazorEngine and RazorMachine to generate html files from cshtml Razor view files and json data files, without needing a hosted ASP.Net MVC website.

We ended up with a teeny ikkle console app which could reference a few directories and spew out some resulting html.

This post will build on that concept and use Azure Blob Storage with a worker role.

File System

We already have a basic RazorEngine implementation via the RenderHtmlPage class, and that class uses some basic dependency injection/poor man’s IoC to abstract the functionality to pass in the Razor view, the json data, and the output location.

IContentRepository

The preview implementation of the IContentRepository interface merely read from the filesystem:

namespace CreateFlatFileWebsiteFromRazor
{
    internal class FileSystemContentRepository : IContentRepository
    {
        private readonly string _rootDirectory;
        private const string Extension = ".cshtml";

        public FileSystemContentRepository(string rootDirectory)
        {
            _rootDirectory = rootDirectory;
        }

        public string GetContent(string id)
        {
            var result = 
                File.ReadAllText(string.Format("{0}/{1}{2}", _rootDirectory, id, Extension));
            return result;
        }
    }
}

IDataRepository

A similar story for the IDataRepository file system implementation:

namespace CreateFlatFileWebsiteFromRazor
{
    internal class FileSystemDataRepository : IDataRepository
    {
        private readonly string _rootDirectory;
        private const string Extension = ".json";

        public FileSystemDataRepository(string rootDirectory)
        {
            _rootDirectory = rootDirectory;
        }

        public string GetData(string id)
        {
            var results = 
                File.ReadAllText(string.Format("{0}/{1}{2}", _rootDirectory, id, Extension));
            return results;
        }
    }
}

IUploader

Likewise for the file system implemention of IUploader:

namespace CreateFlatFileWebsiteFromRazor
{
    internal class FileSystemUploader : IUploader
    {
        private readonly string _rootDirectory;
        private const string Extension = ".html";

        public FileSystemUploader(string rootDirectory)
        {
            _rootDirectory = rootDirectory;
        }

        public void SaveContentToLocation(string content, string location)
        {
            File.WriteAllText(
                string.Format("{0}/{1}{2}", _rootDirectory, location, Extension), content);
        }
    }
}

All pretty simple stuff.

Blob Storage

All I’m doing here is changing those implementations to use blob storage instead. In order to do this it’s worth having a class to wrap up the common functions such as getting references to your storage account. I’ve given mine the ingenious title of BlobUtil:

class BlobUtil
{
    public BlobUtil(string cloudConnectionString)
    {
        _cloudConnectionString = cloudConnectionString;
    }

    private readonly string _cloudConnectionString;

    public void SaveToLocation(string content, string path, string filename)
    {
        var cloudBlobContainer = GetCloudBlobContainer(path);
        var blob = cloudBlobContainer.GetBlockBlobReference(filename);
        blob.Properties.ContentType = "text/html";

        using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(content)))
        {
            blob.UploadFromStream(ms);
        }
    }

    public string ReadFromLocation(string path, string filename)
    {
        var blob = GetBlobReference(path, filename);

        string text;
        using (var memoryStream = new MemoryStream())
        {
            blob.DownloadToStream(memoryStream);
            text = Encoding.UTF8.GetString(memoryStream.ToArray());
        }
        return text;
    }

    private CloudBlockBlob GetBlobReference(string path, string filename)
    {
        var cloudBlobContainer = GetCloudBlobContainer(path);
        var blob = cloudBlobContainer.GetBlockBlobReference(filename);
        return blob;
    }

    private CloudBlobContainer GetCloudBlobContainer(string path){
        var account = CloudStorageAccount.Parse(_cloudConnectionString);
        var cloudBlobClient = account.CreateCloudBlobClient();
        var cloudBlobContainer = cloudBlobClient.GetContainerReference(path);
        return cloudBlobContainer;
    }
}

This means that the blob implementations can be just as simple.

IContentRepository – Blob Style

Just connect to the configured storage account, and read form the specified location to get the Razor view:

class BlobStorageContentRepository : IContentRepository
{
    private readonly BlobUtil _blobUtil;
    private readonly string _contentRoot;

    public BlobStorageContentRepository(string connectionString, string contentRoot)
    {
        _blobUtil = new BlobUtil(connectionString);
        _contentRoot = contentRoot;
    }

    public string GetContent(string id)
    {
        return _blobUtil.ReadFromLocation(_contentRoot, id + ".cshtml");
    }
}

IDataRepository – Blob style

Pretty much the same as above, except with a different “file” extension. Blobs don’t need file extensions, but I’m just reusing the same files from before.

public class BlobStorageDataRespository : IDataRepository
{
    private readonly BlobUtil _blobUtil;
    private readonly string _dataRoot;

    public BlobStorageDataRespository(string connectionString, string dataRoot)
    {
        _blobUtil = new BlobUtil(connectionString);
        _dataRoot = dataRoot;
    }

    public string GetData(string id)
    {
        return _blobUtil.ReadFromLocation(_dataRoot, id + ".json");
    }
}

IUploader – Blob style

The equivalent for saving it is similar:

class BlobStorageUploader : IUploader
{
    private readonly BlobUtil _blobUtil;
    private readonly string _outputRoot;

    public BlobStorageUploader(string cloudConnectionString , string outputRoot)
    {
        _blobUtil = new BlobUtil(cloudConnectionString);
        _outputRoot = outputRoot;
    }
    public void SaveContentToLocation(string content, string location)
    {
        _blobUtil.SaveToLocation(content, _outputRoot, location + ".html");
    }
}

Worker Role

And tying this together is a basic worker role which looks all but identical to the console app:

public override void Run()
{
    var cloudConnectionString = 
        CloudConfigurationManager.GetSetting("Microsoft.Storage.ConnectionString");

    IContentRepository content = 
        new BlobStorageContentRepository(cloudConnectionString, "content");

    IDataRepository data = 
        new BlobStorageDataRespository(cloudConnectionString, "data");

    IUploader uploader = 
        new BlobStorageUploader(cloudConnectionString, "output");

    var productIds = new[] { "1", "2", "3", "4", "5" };
    var renderer = new RenderHtmlPage(content, data);

    foreach (var productId in productIds)
    {
        var result = renderer.BuildContentResult("product", productId);
        uploader.SaveContentToLocation(result, productId);
    }
}

The Point?

By setting the output container to be public, the html files can be browsed to directly; we’ve just created an auto-generated flat file website. You could have the repository implementations access the local file system and the console app access blob storage; generate the html locally but store it remotely where it can be served from directly!

Given that we’ve already created the RazorEngine logic, the implementations of content location are bound to be simple. Swapping file system for blob storage is a snap. Check out the example code over on github

Next up

There’s a few more stages in this master plan, and following those I’ll swap some stuff out to extend this some more.

4 thoughts on “Generate a Blob Storage Web Site using RazorEngine

  1. Nice couple of articles. I am looking forward to trying this out. It is unfortunate that Azure Blob Storage does not allow you to set custom error pages – the defaults are pretty damn ugly and if you are hosting a whole site, I think you would probably want at least a nice 404. Amazon S3 supports this so hope MS add it soon. In the mean time, it is trivial to write an IUploader implementation for S3.

    • That’s a good point; I’ve previously had Blob Storage as the origin and a CDN as the front end, where the CDN was smart enough to handle Http 404 responses and send the user to another location/display their own 404 page. I didn’t know S3 already had this built in, so thanks for that!

Leave a Reply

Your email address will not be published. Required fields are marked *