Saturday, April 28, 2007

Rendering Binary Data with MonoRail

Sometimes you want to make your MonoRail controller render binary data for a download. MonoRail by default is setup to render HTML via the view engine, but you can change that behavior. I usually put a method like this in my base controller:

        protected void SetupDownload(string filename, string contentType)

        {

            CancelLayout();

            CancelView();

            Response.Clear();

            Response.ContentType = contentType;

            Response.AppendHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");

        }



By calling this method inside one of your controllers, you will change the default behavior of the controller to not perform the layout (CancelLayout()) and to not try to render a view via the view engine (CancelView()). The contentType should be something like "application/zip". It's the MIME type that is reported to the browser. This method also tells the browser that the data is not inline, and that the user should be prompted to download the file with a default filename provided by the filename argument.

So you have changed the default behavior of MonoRail to render something other than a plain vanilla view. All that's left is to write the binary data into the response's output stream. If you already have a stream, then you would do something like this:

        private void CopyStream(Stream from, Stream to)

        {

            byte[] buffer = new byte[_bufferSize];

            int bytes = 0;

            while ((bytes = from.Read(buffer, 0, buffer.Length)) > 0)

                to.Write(buffer, 0, bytes);

        }



            CopyStream(System.IO.File.OpenRead(path), Response.OutputStream);



Or, if you have a byte array (buffer in this example) or something similar, you can do something like this.

            Response.OutputStream.Write(buffer, 0, buffer.Length);



Notice that if you want to have the brower render something inline (e.g. an image), than you can just remove line that adds the Content-Disposition header that is in my example SetupDownload function.

1 comment:

Gareth said...

If you are sending file off the disk the response has a special method to do that efficiently; TransmitFile(path):

HttpResponse response = this.Context.UnderlyingContext.Response;
response.TransmitFile(path);