Flash Based Image Upload For ASP.NET With Client Side Resize

August 11, 2011

Recently, I had to provide a way for internal users to upload images via the company website. The images are usually 12-16 mega pixels and have huge files sizes (>5MB). I needed a way of  resizing the images on the client before uploading and also make it easier for users to upload a batch of images with progessbar.

During my search, I found plupload, an open source upload control which provides many ways of uploading including Flash, Silverlight, Google Gears, BrowserPlus, HTML5 and HTML4. The control provided all the features I needed except that all the examples were in PHP and it took me some time to get it working with ASP.NET. I am writing this post to help anyone else who may want to integrate this control into their ASP.NET websites. The control can be downloaded from the following location

http://www.plupload.com/index.php

After you download and extract the zip file within your website, you would need to add two pages in your project. One of the pages would host the upload control and the other page would receive the uploaded files and save them. I have only used Flash runtime of the control so this example will focus on the Flash runtime.

Here is the markup of the page that will host the Flash runtime of the plupload control.

<%@ Page Language="vb" AutoEventWireup="false" CodeBehind="UploadToCRM.aspx.vb" Inherits="ImageUploader.UploadToCRM" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Upload files to CRM</title>
    <link rel="stylesheet" href="js/jquery.plupload.queue/css/jquery.plupload.queue.css" type="text/css" media="screen" />

<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js"></script>
<script type="text/javascript" src="http://bp.yahooapis.com/2.4.21/browserplus-min.js"></script>

<script type="text/javascript" src="js/plupload.js"></script>
<script type="text/javascript" src="js/plupload.flash.js"></script>
<script type="text/javascript" src="js/jquery.plupload.queue/jquery.plupload.queue.js"></script>

    <style type="text/css">
        #btnSubmit
        {
            width: 92px;
        }
    </style>

</head>
<body>
    <form id="form1" runat="server">
    <table>
     <tr>
      <td colspan="2">
         <h3>Upload Files To CRM</h3>
      </td>
     </tr>
     <tr>
      <td colspan="2">
       <br />
   	    <div style="float: left; margin-right: 20px">
		 <div id="flash_uploader" style="width: 450px; height: 330px;">Your browser doesn't have Flash installed.</div>
	     </div>
         <br style="clear: both" />
      </td>
     </tr>
     <tr>
      <td colspan="2">
       <asp:Button id="btnSubmit" runat="server" Text="Save" Enabled="false" />
          <asp:Label ID="lblMessage" runat="server" ForeColor="Red"></asp:Label>
      </td>
     </tr>
    </table>
    </form>
    <script type="text/javascript">
        $(function () {
            // Setup flash version
            $("#flash_uploader").pluploadQueue({
                // General settings
                runtimes: 'flash',
                url: 'upload.aspx',
                max_file_size: '10mb',
                chunk_size: '1mb',
                unique_names: true,
                filters: [
			{ title: "Image files", extensions: "jpg" }
		],

                // Resize images on clientside if we can
                resize: { width: 800, height: 600, quality: 90 },

                // Flash settings
                flash_swf_url: 'js/plupload.flash.swf',

                init: { StateChanged: function (up) {
                    // Called when the state of the queue is changed
                    if (up.state == plupload.STOPPED) {
                        $("#btnSubmit").removeAttr("disabled");
                    }
                }
                }

            });

        });

    </script>
</body>
</html>

The code for submit button uses the Request.Form collection to find out the names of files uploaded.

Protected Sub btnSubmit_Click(sender As Object, e As EventArgs) Handles btnSubmit.Click
        If Session("ID") Is Nothing Then
            lblMessage.Text = "You don't seem to have uploaded any pictures."
            Exit Sub
        Else
            Dim FileCount As Integer = Request.Form(Request.Form.Count - 2)
            Dim FileName, TargetName As String
            Try
                Dim Path As String = "\\servername\foldername\"
                Dim StartIndex As Integer
                Dim PicCount As Integer
                For i As Integer = 0 To Request.Form.Count - 1
                    If Request.Form(i).ToLower.Contains("jpg") Then
                        StartIndex = i + 1
                        Exit For
                    End If
                Next
                For i As Integer = StartIndex To Request.Form.Count - 4 Step 3
                    FileName = Request.Form(i)
                    If IO.File.Exists(Path & FileName) Then
                        TargetName = Path & FileName
                        Dim j As Integer = 1
                        While IO.File.Exists(TargetName)
                            TargetName = Path & IO.Path.GetFileNameWithoutExtension(FileName) & "(" & j & ")" & IO.Path.GetExtension(FileName)
                            j += 1
                        End While
                    Else
                        TargetName = Path & FileName
                    End If
                    IO.File.Move(Server.MapPath("Uploads/" & Session("ID") & "/" & FileName), TargetName)
                    PicCount += 1
                Next
                lblMessage.Text = PicCount & IIf(PicCount = 1, " Picture", " Pictures") & " Saved!"
                lblMessage.ForeColor = Drawing.Color.Black
            Catch ex As Exception
                lblMessage.Text = ex.ToString
			End Try
        End If
    End Sub

This code loops through the Request.Form collection and moves each file to the destination folder. Remember that, at this stage, the files have already been uploaded and saved in the “Uploads” folder with a GUID subfolder. This GUID is then saved into the session the link the two pages together.

The code behind (there is nothing special in the aspx) for the page which receives the uploaded files is below

Public Class upload
    Inherits System.Web.UI.Page

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        If IsNothing(Request.Form("chunk")) = False Then
            If Session("ID") Is Nothing Then
                Session("ID") = Guid.NewGuid.ToString
                IO.Directory.CreateDirectory(Server.MapPath("Uploads/" & Session("ID")))
            End If
            Dim chunk As Integer = Request.Form("chunk")
            Dim chunks As Integer = Request.Form("chunks")
            Dim filename As String = Request.Form("name")
            If chunk = 0 Then
                Request.Files(0).SaveAs(Server.MapPath("Uploads/") & Session("ID") & "/" & Request.Files(0).FileName)
            Else
                Dim OutputStream As New IO.FileStream(Server.MapPath("Uploads/") & Session("ID") & "/" & Request.Files(0).FileName, IO.FileMode.Append)
                Dim InputBytes(Request.Files(0).ContentLength) As Byte
                Request.Files(0).InputStream.Read(InputBytes, 0, Request.Files(0).ContentLength)
                OutputStream.Write(InputBytes, 0, InputBytes.Length - 1)
                OutputStream.Close()
            End If
        End If
    End Sub

End Class

The Flash runtime of the plupload control sends the file in 4 chunks. The “chunk” variable in Form collection holds the current chunk number. The “chunks” variable holds the total number of chunks. If the chunk is 0, this means that the upload of this file has just started so we create the file on the server. If the chunk is greater than 0, this means that another chunk of existing file has been received so we append this chunk to the existing file.

This page is very easy to use for the users and my users (and managers) are very happy with it. I hope this post makes it easier for others to use this great upload control in their ASP.NET websites than it was for me.

SortableList(of T)

January 17, 2011

If you need to bind a list of objects to a control like DataGridView, you would have to implement custom sorting. However, if you use the following custom list class, you would not have to worry about the sorting.

Public Class AdvancedList(Of T)
Inherits BindingList(Of T)
Implements IBindingListView

Protected Overrides ReadOnly Property IsSortedCore() As Boolean
Get
Return sorts IsNot Nothing
End Get
End Property

Protected Overrides Sub RemoveSortCore()
sorts = Nothing
End Sub

Protected Overrides ReadOnly Property SupportsSortingCore() As Boolean
Get
Return True
End Get
End Property

Protected Overrides ReadOnly Property SortDirectionCore() As ListSortDirection
Get
Return If(sorts Is Nothing, ListSortDirection.Ascending, sorts.PrimaryDirection)
End Get
End Property

Protected Overrides ReadOnly Property SortPropertyCore() As PropertyDescriptor
Get
Return If(sorts Is Nothing, Nothing, sorts.PrimaryProperty)
End Get
End Property

Protected Overrides Sub ApplySortCore(ByVal prop As PropertyDescriptor, _
                                      ByVal direction As ListSortDirection)
Dim arr As ListSortDescription() = {New ListSortDescription(prop, direction)}
ApplySort(New ListSortDescriptionCollection(arr))
End Sub
Private sorts As PropertyComparerCollection(Of T)

Public Sub ApplySort(ByVal sortCollection As ListSortDescriptionCollection)
       Implements IBindingListView.ApplySort
Dim oldRaise As Boolean = RaiseListChangedEvents
RaiseListChangedEvents = False
Try
Dim tmp As New PropertyComparerCollection(Of T)(sortCollection)
Dim items As New List(Of T)(Me)
items.Sort(tmp)
Dim index As Integer = 0
For Each item As T In items
SetItem(index, item)
index += 1
Next
sorts = tmp
Finally
RaiseListChangedEvents = oldRaise
ResetBindings()
End Try
End Sub

Private Property IBindingListView_Filter() As String Implements IBindingListView.Filter
Get
Throw New NotImplementedException()
End Get
Set(ByVal value As String)
Throw New NotImplementedException()
End Set
End Property

Private Sub IBindingListView_RemoveFilter() Implements IBindingListView.RemoveFilter
Throw New NotImplementedException()
End Sub

Private ReadOnly Property IBindingListView_SortDescriptions() As ListSortDescriptionCollection
   Implements IBindingListView.SortDescriptions
Get
Return sorts.Sorts
End Get
End Property

Private ReadOnly Property IBindingListView_SupportsAdvancedSorting() As Boolean
   Implements IBindingListView.SupportsAdvancedSorting
Get
Return True
End Get
End Property

Private ReadOnly Property IBindingListView_SupportsFiltering() As Boolean
   Implements IBindingListView.SupportsFiltering
Get
Return False
End Get
End Property
End Class

Public Class PropertyComparerCollection(Of T)
Implements IComparer(Of T)
Private ReadOnly m_sorts As ListSortDescriptionCollection
Private ReadOnly comparers As PropertyComparer(Of T)()

Public ReadOnly Property Sorts() As ListSortDescriptionCollection
Get
Return m_sorts
End Get
End Property

Public Sub New(ByVal sorts As ListSortDescriptionCollection)
If sorts Is Nothing Then
Throw New ArgumentNullException("sorts")
End If
Me.m_sorts = sorts
Dim list As New List(Of PropertyComparer(Of T))()
For Each item As ListSortDescription In sorts
list.Add(New PropertyComparer(Of T)(item.PropertyDescriptor, _
                                    item.SortDirection = ListSortDirection.Descending))
Next
comparers = list.ToArray()
End Sub

Public ReadOnly Property PrimaryProperty() As PropertyDescriptor
Get
Return If(comparers.Length = 0, Nothing, comparers(0).[Property])
End Get
End Property

Public ReadOnly Property PrimaryDirection() As ListSortDirection
Get
Return If(comparers.Length = 0, ListSortDirection.Ascending, _
       If(comparers(0).Descending, ListSortDirection.Descending, ListSortDirection.Ascending))
End Get
End Property

Private Function IComparer_Compare(ByVal x As T, ByVal y As T) As Integer
   Implements IComparer(Of T).Compare
Dim result As Integer = 0
For i As Integer = 0 To comparers.Length - 1
result = comparers(i).Compare(x, y)
If result <> 0 Then
Exit For
End If
Next
Return result
End Function

End Class

Public Class PropertyComparer(Of T)
Implements IComparer(Of T)

Private ReadOnly m_descending As Boolean
Public ReadOnly Property Descending() As Boolean
Get
Return m_descending
End Get
End Property

Private ReadOnly m_property As PropertyDescriptor
Public ReadOnly Property [Property]() As PropertyDescriptor
Get
Return m_property
End Get
End Property

Public Sub New(ByVal [property] As PropertyDescriptor, ByVal descending As Boolean)
If [property] Is Nothing Then
Throw New ArgumentNullException("property")
End If
Me.m_descending = descending
Me.m_property = [property]
End Sub

Public Function Compare(ByVal x As T, ByVal y As T) As Integer
   Implements IComparer(Of T).Compare
' todo; some null cases
Dim value As Integer = Comparer.[Default].Compare(m_property.GetValue(x), m_property.GetValue(y))
Return If(m_descending, -value, value)
End Function
End Class

Drag Outlook Attachment To Your WinForm

July 20, 2010

My applications usually allow users to drag and drop Outlook attachments and save these as files. I have a simple method which saves the memory stream from Outlook to a temporary file and returns the path to the file.

Here is the function

Public Function GetOutlookAttachment(ByVal e As System.Windows.Forms.DragEventArgs) As String
Dim theStream As IO.Stream = DirectCast(e.Data.GetData("FileGroupDescriptor"), IO.Stream)
Dim fileGroupDescriptor As Byte() = New Byte(511) {}
theStream.Read(fileGroupDescriptor, 0, 512)

' used to build the filename from the FileGroupDescriptor block
Dim fileName As New System.Text.StringBuilder("")

' this trick gets the filename of the passed attached file
Dim i As Integer = 76
While fileGroupDescriptor(i) <> 0
fileName.Append(Convert.ToChar(fileGroupDescriptor(i)))
i += 1
End While
theStream.Close()
Dim path As String = IO.Path.GetTempPath()

' put the zip file into the temp directory
Dim theFile As String = path + fileName.ToString()
TempFiles.Add(theFile)

Dim ms As IO.MemoryStream = DirectCast(e.Data.GetData("FileContents", True), IO.MemoryStream)

' allocate enough bytes to hold the raw data
Dim fileBytes As Byte() = New Byte(ms.Length - 1) {}

' set starting position at first byte and read in the raw data
ms.Position = 0
ms.Read(fileBytes, 0, CInt(ms.Length))

' create a file and save the raw zip file to it
Dim fs As New IO.FileStream(theFile, IO.FileMode.Create)
fs.Write(fileBytes, 0, CInt(fileBytes.Length))

fs.Close()
' close the file

Return theFile
End Function

This function can be called like this

Private Sub MyForm_DragDrop(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles Me.DragDrop

If e.Data.GetFormats(False)(1) = "RenPrivateFileAttachments" Then

Dim FilePath as String = GetOutlookAttachment(e)

End If

End Sub

Hello world!

April 16, 2010

Ah. I have finally started my blog! The reason it took me so long is that I find it very hard to get any time to do posts. Most of my personal time (any time left after the company sponsered time) is spent either asnwering questions on Experts Exchange or in gym.

Another reason for starting this blog at this particular time is that, after attending the Microsoft UK Techdays event this week, I am embarking on the journey to learn new stuff such as VS2010, .NET 4.0, WCF, Silverlight, WPF, WWF, ADO.NET Entity Framework, ADO.NET Data Services, and ASP.NET MVC etc. I thought it would be a good idea to post the steps and the information related to the samples that I develop for my learning.

I am hoping that I would be able to post some useful information on this blog.