Storing binary data on SQL Server

Mon 20 February, 2006

On my current project, an ASP .NET application, the customer expressed the desire to save in the database letters, based on Word templates and automatically generated by the application. Being official government letters, if in the future you need print a copy of that document it has to be exactly the same document, although some of the data on which the document was originally based could have changed. So they need them to be stored in the database exactly as they were generated once. Therefore, we need a way to store the Word files as a binary format, as a data stream to be able to reload them again in the future and have the exact same document.

Please note: it’s not the objective of this post to show how to create Word files from C#. You can use for it Word automatization, or third-party components such as the excellent ones made by Aspose.

Let’s create a simple SQL Server table to store the documents. For the sake of this example, we call it BinaryDocs, and it will have the following design:

Field Type Nullable?
DocId Int (identity) No
Document Image No
DocName VarChar(100) No

Pretty self-explanatory: DocId is an autonumeric primary key field. Document is the one where the bit stream is going to be stored, that is the proper Word document. DocName will store the original name the document had when it was generated.

After that will create an stored procedure, UploadDoc, which will handle the actual insertion of records on BinaryDocs:

CREATE PROCEDURE UploadDoc(@doc AS Image, @name AS VarChar(100)) 
AS
INSERT INTO 
BinaryDocs (Document, DocName) values (@doc, @name)
GO

Colorized by: CarlosAg.CodeColorizer

Quite self-explanatory too: a simple INSERT INTO instruction which receives an Image and a VarChar fields as parameters.

And this is the method that stores the data on the database:

private void saveDocToDB(string sPath, string sFile)
{
    
//New FileStream object for reading the Word file as a binary stream
    
System.IO.FileStream  fs = new FileStream(sPath + sFile,
        System.IO.FileMode.Open)
;
    
//On this byte array we’ll store the binary data read by fs.
    
Byte[] data = new byte[fs.Length];
    
//Like this
    
fs.Read(data, 0, Convert.ToInt32(fs.Length));
    
//Open up a SQL Server connection. In this particular case the database
    //connection string is recovered from the web.config file
    //using the ConfigurationSettings class
    
SqlConnection cnn =
       new 
SqlConnection(ConfigurationSettings.AppSettings["DBConnection"]);
    
cnn.Open();
    
//An StoredProcedure SqlCommand object to invoke the
    //UploadDoc stored procedure created earlier
    
SqlCommand cmd = new SqlCommand("UploadDoc", cnn);
    
cmd.CommandType CommandType.StoredProcedure;
    
//This are the expected parameters
    
cmd.Parameters.Add("@doc", data)//the Word file data
    
cmd.Parameters.Add("@name", sFichero)//and its name
    //We execute the stored procedure, which inserts a new
    //row on BinaryDocs which the our data
    
cmd.ExecuteNonQuery();
    
//Let’s close the connection and the file (althought   //we could have closed the file earlier)
    
cnn.Close();
    
fs.Close();
}

Colorized by: CarlosAg.CodeColorizer

Now let’s take a look at the method to retrieve that stream from the database and display it as a Word file:

private void getDocFromDB()
{
    
//Let’s open the connection, exactly as before
    
SqlConnection cnn =
        new 
SqlConnection(ConfigurationSettings.AppSettings["DBConnection"]);
    
cnn.Open();
    
//This is the command to open the record we want. For this
    //example we open the first record, you should have to modify this
    //code and give the method a parameter stating which record
    //to retrieve from the database.
    
SqlCommand comm = new SqlCommand("SELECT * FROM BinaryDocs " +
        " WHERE docId = 1"
, cnn);
    
comm.CommandType CommandType.Text;
    
SqlDataAdapter da = new SqlDataAdapter(comm);
    
DataSet ds = new DataSet("BinaryData");
    
da.Fill(ds);
    
//We create a byte array which will have the bytes stored on the
    //table’s Document field
    
byte[] bits ((byte[])(ds.Tables[0].Rows[0].ItemArray[1]));            
    
cnn.Close();
    
//now let’s save that data stream as a normal file on the HDD,
    //a temporal file you could later delete.
    //For avoiding possible concurrency problems,
    //we generate a unique name for the file.
    
string sFile "tmp" + makeFileName() + ".doc";
    
//A new FileStream object, which will be the one to create
    //the file with the specified name.
    
FileStream fs = new FileStream(Server.MapPath(".") +
        @"\GenDocs\" 
+ sFile, FileMode.Create);
    
//And we save on disk the byte array which contains the Word data
   
fs.Write(bits, 0, Convert.ToInt32(bits.Length));
    
fs.Close();
    
//For displaying the file, we use a Javascript function
    //called showFile, which simply loads the specified
    //file, and we show it on a pop-up
   
string script "<script languaje=’javascript’> ";
    
script +"showFile(’GenDocs/" + sFile + "’) ";
    
script +"</script>" + Environment.NewLine;
    
Page.RegisterStartupScript("showFile",script);
}

Colorized by: CarlosAg.CodeColorizer

And that’s it. With this last method the Word file is loaded from the database as a byte stream and it’s displayed on a popup browseer window.

Just for the sake of completion, this is the Javascript function that loads the generated file from the database:

<script language="javascript">
function showFile(sFile) {
window.open(sFile,null,"directories=no,height=600,
    width=800,left=0,top=0,location=no,menubar=yes,
    status=no,toolbar=yes,resizable=yes"
)
document.forms(0).submit();
}
</script>

Colorized by: CarlosAg.CodeColorizer

And this is the method makeFileName(), which uses the server’s tick count for creating randomly unique filenames.

private string makeFileName()
{
    
int lastTick 0;
    while
(lastTick==Environment.TickCount) 
    {
        System.Threading.Thread.Sleep(
1);
    
}
    lastTick
=Environment.TickCount;
    return 
DateTime.Now.ToString("yyyyMMddhhmmss") + "." +
        lastTick.ToString()
;
}

Colorized by: CarlosAg.CodeColorizer

Programming personas

Fri 10 February, 2006

Today I’ve read something funny. I already knew about Mort, but today I’ve learn that there are also Elvis, Einstein and now Hugo.

Good grief.

Unleash it

Thu 9 February, 2006

Most of the times the simplest tools are the best tools. That’s just the case of Unleash it, previously known as Web Deploy, and that’s exactly its job: to copy files and folders to a remote server to make easier for us the deployment of a web application.

Its use is amazingly simple: you create one or many deployment profiles. The main thing to define on each profile is the paths, either the source path (presumably the local path where we store our development version of the project) as the destination path, which will surely be the remote server path where we’re going to deploy.

As you can see from the screen, we can also select a Visual Studio project as source, and an FTP profile or ZIP file as destination. Here’s one of the things I liked less about Unleash it!: you can only choose Visual Studio projects, not solutions. On the list by the right que mark the files we want to include on the deployment and the folders we want to exclude from the deployment and done. That would be a basic configuration.

Of course, we can complicate matters. Clicking on the profile configuration option, we access this dialog box:

upon which we can perform some actions to fine-tune our profile. Among others:

  • Show a dialog box for each deployment to specifically state which files and folders we want to deploy to destination. Very useful when we only want to upload some particular files, but using this option on every deployment kind of defeats the whole point of the tools, which is precisely to deploy our web applications with a single mouse click.
  • Logging all the actions performed by the profile.
  • Pre and post deployment commands. On my particular case I’m going to need this: one of the files I got to deploy is, naturally, web.config. But right now I’ve got to deploy it manually, because the connection strings and some other configuration options are different on the development machines than on the server. However, web.config also stores the navigation options of UIProcess, and for the time being I have to update that by hand.
  • Backup possibility.
  • Plugins. One of the most interesting options, it allows us to call external applications from Unleash It. The included plugins are a connection to NAnt, the possibility of checking with Visual SourceSafe or Sourcegear’s Vault, and being able to exclude from the deployment certain files by name instead of mask.

Lastly, we can combine several deployment profiles into one big action thanks to the Multiple Profile Deployment tab, as you can see:

Of course there are more complex and powerful applications, as of course there are projects far more complex than the one I’m working on right now. For my current needs, Unleash it is a perfect tool, which allows me to deploy a new version of my application to the server with a single click. And that’s what tools are for, to make your life simpler and gain productivity.

IE7 Beta 2

Thu 2 February, 2006

Yesterday night I was finally (that’s the problem with not having a MSDN subscription, you have to wait) able to install at home IE7 Beta 2 Preview. If you’re gonna download and try it, two advance warnings: your operating system has to be Windows XP SP2 or Windows 2003, it doesn’t work on Windows 2000 or earlier. Second, your system has to be approved by Microsoft’s original software validation check. And no, this time you can’t simply skip the validation process and keep installing.

I really like it. Much. I haven’t been able to try it out a lot, simply my everyday home navigation: Bloglines, GMail, some interesting links I’ve received by email and little else. I’m going to try it a lot more in the next days, because it’s already my main navigator, having displaced Maxthon as my default browser.

One curiosity, though: I had stored on Favorites precisely the spanish and main internet adress of this blog, www.picacodigos.com; but I had it just like that, without the http://. IE7 refused to acknowledge it as a valid link because of that.

Update 08/02/2006: It’s really beta. I like it, it works and it has a really beautiful simple and elegant design,… but it has too many errors for now. Back to Firefox. Sigh.

In order to uninstall it, just in case you find some trouble, use Control panel -> Add or Remove Programs, but be sure to have cheked the Show Updates option. It’ll be at the bottom of the list, just below all the Windows XP’s Service Packs and patches. Uninstall it, reboot the machine and that’s done.