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)]
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, theWebImage
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, theWrite
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"
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.:)
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. DataUri
class in the Delay.Web.Helpers
assembly proves useful!