The simplest way to Download binary data in LightSwitch

Introduction

Most line of business apps needs the ability to store and retrieve binary data (e.g. a pdf, an excel file, a picture) in and from the application. Typically, these files associated with a specific entity; for example a contract is associated with Customer abc.

Obviously, these files should adhere to the security rules of the application in such a way a user can only download files associated with records that the current user can manage.

The ability to use web-api in LightSwitch, in combination with the ServerApplicationContext really simplifies this complete process.

The Data Model

The data model needed for storing binary data is pretty simple. The most crucial part is to separate the binary data itself (which can be large) from the meta data of the binary data. By doing so, it will become more comfortable to visualize file meta data without being bothered by a heavy load of data.

So, we have a FileMetaData table:

metadat

 

This table has a 1 to 0..1 relation with the FileStore table:

store

 

The FileMetaData table can be linked via a 0..1 to many relation with any entity type that has the need for attaching file data. In my example I provide the customer table to attach 0..n files to each customer:

 

Customers

 

 

How to transport the data from the server to the client?

Intuitively, one would think that the most simple way to get the data to the client is simply to request the record containing the file data. That’s true when the data would simply stay inside the client application, but in most cases (as it is here), we want that the user can save the data to his local disk.

Obviously, it’s possible to foresee a File dialog where the user can select a file location and then save the binary stream to disk, but this is quite a lot of code and furthermore the user will need to type the file name in the File dialog. Silverlight has no way to do this differently.

Instead, we would like the following:

save

Luckily, this is possible with  web-api.

setting up a web-api File Controller

First, add a global.asax file to the server project:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Routing;
using System.Web.Security;
using System.Web.SessionState;
using System.Web.Http;
namespace LightSwitchApplication
{
     public class Global : System.Web.HttpApplication
     {

        protected void Application_Start(object sender, EventArgs e)
         {
             // needs references: System.Web.Http, System.web.Http.WebHost en System.Net.Http
             RouteTable.Routes.MapHttpRoute(
                name: “DefaultApi”,
                routeTemplate: “api/{controller}/{action}/{id}”,
                defaults: new { action = “get”, id = RouteParameter.Optional }
            );
         }
     }
}

Make sure the server project has a reference to System.Web.Http, System.web.Http.WebHost and System.Net.Http

Seconly, add a web api class to your server:

using LightSwitchApplication.Poco;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Web.Http;
using Microsoft.LightSwitch;
using Microsoft.LightSwitch.Details;
namespace LightSwitchApplication
{
     public class FileController : LightSwitchApiControllerbase
     {
        [HttpGet]
         public HttpResponseMessage Download(int id)
         {
             MediaTypeHeaderValue _mediaType = MediaTypeHeaderValue.Parse(“application/octet-stream”);
             try
             {
                 using (var ctx = this.Context)
                 {
                     var myFileStoreEntry = ctx.DataWorkspace.ApplicationData.FileStores_SingleOrDefault(id);

                    if (myFileStoreEntry == null)
                     {
                         throw new HttpResponseException(HttpStatusCode.NotFound);
                     }

                    else
                     {
                         byte[] myBytes = myFileStoreEntry.FileBinaryContent;
                         string fileName = myFileStoreEntry.FileMetaData.FileName;
                         MemoryStream memStream = new MemoryStream(myBytes);
                         HttpResponseMessage fullResponse = Request.CreateResponse(HttpStatusCode.OK);
                         fullResponse.Content = new StreamContent(memStream);
                         fullResponse.Content.Headers.ContentType = _mediaType;
                         fullResponse.Content.Headers.ContentDisposition
                             = new ContentDispositionHeaderValue(“fileName”) { FileName = fileName };
                         return fullResponse;
                     }

                }
             }
             catch
             {
                 throw new HttpResponseException(HttpStatusCode.NotFound);
             }

        }

        }
}

I’m using a small base class to expose the ServerApplicationContext to the web api class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;

namespace LightSwitchApplication
{
     public abstract class LightSwitchApiControllerbase : ApiController
     {
         private ServerApplicationContext _context;

        public ServerApplicationContext Context
         {
             get
             {
                 return _context;
             }
         }

        public ServerApplicationContext GetContext()
         {

            return _context;
         }

        public LightSwitchApiControllerbase()
         {
             _context = ServerApplicationContext.Current;
             if (_context == null)
             {
                 _context = ServerApplicationContext.CreateContext();
             }
         }

    }
}

 

Oh my goodness, that’s all server side.

Consuming the File Controller

You can already consume the web-api controller, simply from your browser:

http://localhost:45171/api/File/Download?Id=11

We presume here that we have already a binary data record in the FileStore table with Id 11.

Of course, we want to consume it from the silverlight client as well:

So, imagine we have something like this in mind:

screen

 

So, a simple out of the box list Detail screen, which has no code at all, except :

 

partial void Download_Execute()
       {
           int fileStoreId = this.FileMetaDatas.SelectedItem.Id;
             Dispatchers.Main.Invoke(() =>
           {
               Uri baseAddress = new Uri(new Uri(System.Windows.Application.Current.Host.Source.AbsoluteUri), “../../”); //works both in debug and deployed !
               string url = string.Format(@”{0}api/File/Download?Id={1}”, baseAddress.AbsoluteUri, fileStoreId);
               HtmlPage.Window.Navigate(new Uri(url), “_blank”);
           });
       }

Conclusion

What can I say? Can this simpler? In a next post I’ll cover uploading files via web-api or via a table.