RIA Services is a relatively new addition to Microsoft’s repertoire of frameworks and services for Application Development; and the understanding of Domain services is the core concept behind it all.

In this article I will explain the architecture that combines a Silverlight Client Application to a RIA services solution. The solution will connect to a SQL Database back-end that uses SQL Server FileStream types for file storage.

Specifically I will explain how you can transfer files in a Silverlight + RIA Services architecture to and from a SQL Server Database using FileStream Types…

Prerequisites


The Basics

To begin with let me explain some of the basic components:

  • You have a Silverlight Client Application with XAML pages
  • You have Domain Services exposed to the Client Application with servicing methods
  • The mid-tier traverses through a communications layer to reach Repository Classes
  • Repository Classes are used to connect to the Database to set and retrieve information

With this in mind, the diagram below represents a Request from the Client to the Database Server:

RIA Services – Client to Server Request:

Downloading

Before we consider File Uploading via RIA Services lets consider its easier counter-part:  File Downloading via RIA Services! Please take 5 minutes to read my article on File Buffering and Streaming because the concepts used here are exactly the same…

Below is the initial Download Request from the Client Application. The Request passes through a File Handler. This File Handler is the key component for Streaming data between two systems. The Request is pretty straight forward, the File Handler pipes the Request to the Domain Service – which in turn calls our Manager Class and Repository Classes.

The Repository Class is responsible for querying the SQL Database to get the File Stream data from the SQL Table.

Later in this article there is a code-snippet that explains how I achieved this.

File Download – Client to Server Request:

Once the Repository has a reference to the File entity. The Repository passes the Byte[] array data through all of the channels back via the Domain Service to the File Handler. The diagram below is a representation of this process:

note: The RED arrows indicate that Streaming is happening and shows the direction that the streamed data is moving.

File Download (Streamed) – Server to Client Response:

Uploading

Now lets discuss the File Upload counter-part. When I first designed the solution I ran into performance issues. To give you a better understanding I will describe my original design below:

  • The XAML page loads a BackgroundWorker to upload packets of data to the File Handler.
  • On the ProcessRequest() method of the File Handler I piped the data through the Domain Service
  • Which in turn piped the data to the Repository
  • The Repository opened a data stream to the Database and flushed the data into this steam.
  • The Repository then closes the database connection.
  • This repeats for as many upload Packets as necessary.

The performance hit that got me was that the BackgroundWorker’s processing method needed to wait until the File Handler completed its entire process; and this happens after all the steps above have finished – including open and closing the database connection and writing the contents to the FileStream table column.

So an alternate approch was devised…

File uploading via RIA Service is split into 2 parts:

  1. Transferring the file from the Client Application to the Web Server via the File Handler
  2. Transferring the file from the Web Server to the Database Server as a SQL FileStream Type


1. File Transfer (Client Application to Web Server)

This first process is identical to the steps explained in the article: File Buffering and Streaming

File Upload (Streamed) – Client to Web Server:

2. File Transfer (Web Server to DB Server)

This diagram is the representation on how the File Handler transfers data packets to the SQL Database via Domain Services:

File Upload (Streamed) – Web Server to DB Server:

We extend what we previously had in the File Handler ProcessRequest() method by adding extra functionality that executes when the last file packet is retrieved and appended to the Web Server file. Below is the entire ProcessRequest() method – you will notice an additional call is made on detection of the “completed” flag to a private method TransferFileToDB().

        public void ProcessRequest(HttpContext context)
        {
            if (context.Request.QueryString.AllKeys.Contains("attachmentid")
                    && context.Request.QueryString.AllKeys.Contains("filename")
                    && context.Request.QueryString.AllKeys.Contains("uniquefilename"))
            {
                // Write to the Upload Folder
                System.IO.FileInfo fi = new System.IO.FileInfo(@"C:\MyUploads"
                                                + context.Request.QueryString["uniquefilename"]);
                System.IO.FileStream fs;

                try
                {
                    if (fi.Exists)
                    {
                        fs = new System.IO.FileStream(fi.FullName
                                                          , System.IO.FileMode.Append);
                    }
                    else
                    {
                        fs = new System.IO.FileStream(fi.FullName
                                                          , System.IO.FileMode.OpenOrCreate
                                                          , System.IO.FileAccess.Write);
                    }

                    // Insert the data chunk into the file
                    byte[] b = new byte[context.Request.InputStream.Length];
                    context.Request.InputStream.Read(b, 0, b.Length);
                    fs.Write(b, 0, b.Length);
                    fs.Close();

                    // Check if this is the last data packet.
                    // If so, call TransferFileToDB() to upload to the DB
                    if (context.Request.QueryString.AllKeys.Contains("completed"))
                    {
                        TransferFileToDB(
                                        System.Guid.Parse(
                                              context.Request.QueryString["attachmentid"]
                                        )
                                        , fi.FullName
                                        , context.Request.QueryString["uniquefilename"]
                                        , context.Request.QueryString["filename"]);
                    }
                }
                catch (Exception ex)
                {
                    //Handle Error - Remove the temporary file etc
                }
            }
        }

The TransferFileToDB method is responsible for streamlining the local Web Server file contents to a Domain Service method that has been coded to accept data packets. So we continuously call the UploadStreamed() method of the Domain Service until we reach the end of the file.

When we transfer the final packet to the SQL Server we can safely clean up by deleting the temporary file on the Web Server – as we are now sure that the entire contents have been uploaded to the SQL Server Database.

But if any exception is thrown you need to clean up the partially uploaded file on the SQL Database. Plus remove the temporary file on the Web Server, and alert the User accordingly.

        private void TransferFileToDB(
                                            System.Guid attachmentID
                                            , string filePath
                                            , string newFileName
                                            , string originalFileName)
        {
            AttachmentService svc = new AttachmentService();
            System.IO.FileStream fs;

            // Get the file from the Upload Folder
            System.IO.FileInfo fi = new System.IO.FileInfo(filePath);

            if (fi.Exists)
            {
                try
                {
                    byte[] buffer;
                    int position = 0;
                    int packetSize = 10 * 1000 * 1024;

                    // Open the file for reading
                    fs = new System.IO.FileStream(filePath, System.IO.FileMode.Open);
                    long length = fs.Length;

                    // Stream the data packets to the Domain Service for storage
                    while (position < length)                     {                          if (position + packetSize > length)
                        {
                            buffer = new byte[length - position];
                        }
                        else
                        {
                            buffer = new byte[packetSize];
                        }

                        fs.Read(buffer, 0, buffer.Length);
                        svc.UploadStreamed(attachmentID
                                            , newFileName
                                            , originalFileName
                                            , buffer
                                            , position);
                        position += buffer.Length;
                    }
                    fs.Close();

                    // Once all data packets are uploaded, clean up the file.
                    fi.Delete();

                }
                catch (Exception ex)
                {
                    //Handle error here
                }
            }
        }

Next up…. the Domain Service implementation!

Enjoy!