The blog of dlaa.me

Images in a web page: meh... Images *in* a web page: cool! [Delay.Web.Helpers assembly now includes an ASP.NET web helper for data URIs (in addition to Amazon S3 blob/bucket support)]

Delay.Web.Helpers DataUri sample page

The topic of "data URIs" came up on a discussion list I follow last week in the context of "I'm in the process of creating a page and have the bytes of an image from my database. Can I deliver them directly or must I go through a separate URL with WebImage?" And the response was that using a data URI would allow that page to deliver the image content inline. But while data URIs are fairly simple, there didn't seem to be a convenient way to use them from an ASP.NET MVC/Razor web page.

Which was kind of fortuitous for me because I've been interested in learning more about data URIs for a while and it seemed that creating a web helper for this purpose would be fun. Better yet, I'd already released the Delay.Web.Helpers assembly (with support for Amazon S3 blob/bucket access), so I had the perfect place to put the new DataUri class once I wrote it! :)

Aside: For those who aren't familiar, the WebImage class provides a variety of handy methods for dealing with images on the server - including a Write method for sending them to the user's browser. However, the Write method needs to be called from a dedicated page that serves up just the relevant image, so it isn't a solution for the original scenario.

 

In case you've not heard of them before, data URIs are a kind of URL scheme documented by RFC 2397. They're quite simple, really - here's the relevant part of the specification:

data:[<mediatype>][;base64],<data>

dataurl    := "data:" [ mediatype ] [ ";base64" ] "," data
mediatype  := [ type "/" subtype ] *( ";" parameter )
data       := *urlchar
parameter  := attribute "=" value

It takes two pieces of information to create a data URI: the data and its media type (ex: "image/png"). (Although the media type appears optional above, it defaults to "text/plain" when absent - which is unsuitable for most common data URI scenarios.) Pretty much the only interesting thing you can do with data URIs on the server is write them, so the DataUri web helper exposes a single Write method with five flavors. The media type is always passed as a string (feel free to use the MediaTypeNames class to help here), but the data can be provided as a file name string, byte[], IEnumerable<byte>, or Stream. That's four methods; the fifth one takes just the file name string and infers the media type from the file's extension (ex: ".png" -> "image/png").

Aside: Technically, it would be possible for the other methods to infer media type as well by examining the bytes of data. However, doing so would require quite a bit more work and would always be subject to error. On the other hand, inferring media type from the file's extension is computationally trivial and much more likely to be correct in practice.

 

For an example of the DataUri helper in action, here's the Razor code to implement the "image from the database" scenario that started it all:

@{
    IEnumerable<dynamic> databaseImages;
    using (var database = Database.Open("Delay.Web.Helpers.Sample.Database"))
    {
        databaseImages = database.Query("SELECT * FROM Images");
    }

    // ...

    foreach(var image in databaseImages)
    {
        <p>
            <img src="@DataUri.Write(image.Content, image.MediaType)" alt="@image.Name"
                 width="@image.Width" height="@image.Height" style="vertical-align:middle"/>
            @image.Name
        </p>
    }
}

Easy-peasy lemon-squeezy!

 

And here's an example of using the file name-only override for media type inference:

<script src="@DataUri.Write(Server.MapPath("Sample-Script.js"))" type="text/javascript"></script>

Which comes out like this in the HTML that's sent to the browser:

<script src="data:text/javascript;base64,77u/ZG9j...PicpOw==" type="text/javascript"></script>
Aside: This particular example (using a data URI for a script file) doesn't render in all browsers. Specifically, Internet Explorer 8 (and earlier) blocks script delivered like this because of security concerns. Fortunately, Internet Explorer 9 has addressed those concerns and renders as expected. :)

 

[Click here to download the Delay.Web.Helpers assembly, complete source code, automated tests, and the sample web site.]

[Click here to go to the NuGet page for Delay.Web.Helpers which includes the DLL and its associated documentation/IntelliSense XML file.]

[Click here to go to the NuGet page for the Delay.Web.Helpers.SampleWebSite which includes the sample site that demonstrates everything.]

 

Data URIs are pretty neat things - though it's important to be aware they have their drawbacks as well. Fortunately, the Wikipedia article does a good job discussing the pros and cons, so I highly recommend looking it over before converting all your content. :) Creating a data URI manually isn't rocket science, but it is the kind of thing ASP.NET web helpers are perfectly suited for. If you're a base-64 nut, maybe you'll continue doing this by hand - but for everyone else, I hope the new DataUri class in the Delay.Web.Helpers assembly proves useful!