Click here to Skip to main content
Click here to Skip to main content
Go to top

Multiple Image Uploads in Kartris

, 25 Aug 2014
Rate this:
Please Sign up or sign in to vote.
Allow Administrators to Upload Mutliple Images at once for Products

Introduction

Kartris is an open source E-commerce cart system written in VB.Net within the ASP.Net WebForms environment making it easy to use and modify.

This system comes with multiple administrator pages allowing the site administrators to add, remove and edit products, categories and other data. One of these pages allows admins to upload product images but currently only allows you to upload one image at a time, up until recently the only work around to this was to use third party utilities or to cut your own.

Since the introduction of .Net Framework 4.5 ASP.Net natively supports multiple file uploads via the FileUpload control in any HTML 5 capable browser. This article explains how to modify the existing user controls within Kartris to leverage this functionality.

Breaking Changes

Kartris is written in ASP.Net WebForms 4.0 and the controls discussed here require upgrading to 4.5. This is an 'in-place update' to 4.0 and since upgrading my installations of Kartris from 4.0 to 4.5 I have notice no breaking changes. Your milage may vary and if you do experience any breaking changes as a result of this upgrade please inform me so that this article can be updated.

Using the code

Kartis is supplied as a collection of ASP.Net pages and controls which are installed on your target server and converted to an application from IIS, and is not supplied as a Visual Studio Solution. It is not advised to alter the source code without Visual Studio which performs checks on altered code. If you are using Kartris as a drop-in product and are not running Visual Studio you are recommended to:

  1. Ensure that .Net Framework 4.5 is installed on your target server. See here.
  2. Alter the web.config file to use 4.5 in a text editor. See here.
  3. Drop in the altered code modules that can be downloaded from this article.

Alternatively you could purchase Visual Studio from the Microsoft online store.

As is the case with any open source code there are many things that each developer would change given the time and opportunity and the same is true for myself within this application. With that said I will not be using this article as an opportunity to rewrite any existing code unless it is directly related to the functional change that I am trying to make or unless leaving the existing code would cause a functional problem.

Upgrading to Net Framework 4.5

Installing 4.5

You can download the .Net Framework from here. Instructions on how to install this on your server are included on the same site and there are many tutorials online to assist you with installation problems so this article will not repeat their message.

Once the Framework is installed you do not need to change the configuration within IIS as it will automatically pick up the new framework although you may need to restart IIS for the changes to occur. Any application pool set to .Net Framework 4.0.xxxxx will work with 4.5 once installed.

Changing the web.config

Change this line in the web.config file

<compilation debug="true" batch="false" targetFramework="4.0">

to this

<compilation debug="true" batch="false" targetFramework="4.5">

Changing the Visual Studio Solution

If you use Visual Studio (VS) to make modifications to the Kartris source code VS will have automatically generated a .sln file in the root of the project with your applications name. For example if you have called your project "MyWebApp" you will have a file called "MyWebApp.sln" in the project root. You need to modify this using a text editor or similar.

There will be a line that reads

TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.0"

This will need to be changed to

TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.5"

Once you have done this Visual Studio's Intellisense will correctly retrieve the methods and properties of the FileUpload class that were introduced in .Net Framework 4.5.

If you want to simply drop in the updated code modules you can do that now. The rest of this article covers the modifications required to implement this functionality.

Changing the Source Code

AllowMultiple

To allow multiple file uploads the FileUpload control in the uploader popup user control needs the flag AllowMultiple to be set to true but we do not want to do this at the control level or else this will affect how this control behaves in all instances, even outside of the product editting page. Instead, we want to be able to enable multiple uploads from the product editting page itself and have this setting cascaded down the control hierarchi to the FileUpload control. To enable this we must setup a public property inside _UploaderPopup.ascx which can be set by _FileUploader.ascx, this inturn will be set within its instance in _ModifyProduct.aspx. The default values for the FileUploader will be such that it will operate in single upload mode (current behaviour) unless specifically set to multiple.

Within _UploaderPopup.ascx.vb create a variable called AllowMultiple and set its default value to false. I have also created the variable LastFileName which is used in the SaveFile() routine explained later.

Partial Class UserControls_Back_UploaderPopup
    Inherits System.Web.UI.UserControl
    Private LastFileName As String = String.Empty       ' Prevent multiple saves of the same file.
    Public Property AllowMultiple As Boolean = False

In _FileUploader.ascx.vb create a variable by the same name and pass the value through to its instance of UploaderPopup.

Public Property AllowMultiple As Boolean
    Get
        Return _UC_UploaderPopup.AllowMultiple
    End Get
    Set(value As Boolean)
        _UC_UploaderPopup.AllowMultiple = value
    End Set
End Property

While we are in _FileUploader.ascx.vb we also need to update the property OneItemOnly so that if the OneItemOnly property is set the AllowMultiple property is also set to match. As mentioned in the introduction I am only going to modify code that directly affects the functionality we want to create, I am not using this to modify the surrounding code to fit my natural style.

Public WriteOnly Property OneItemOnly() As Boolean
    Set(ByVal value As Boolean)
        c_blnOneFileOnly = value
        chkOneItemOnly.Checked = value
        _UC_UploaderPopup.AllowMultiple = Not value
    End Set
End Property

The last step in this sequence is to add the property AllowMultiple to the FileUploader instance defined in the markup of _ModifyProduct.aspx.

<%-- Product Images Tab (Uploader) --%>
<asp:View runat="server" ID="tabImages">
    <div class="subtabsection">
        <_user:FileUploader ID="_UC_Uploader" AllowMultiple="true" runat="server" />
    </div>
</asp:View>

Uploader Popup Functions

There are a few functions within _UploaderPopup that return counts and strings related to the file that has been uploaded. Complementary functions need to be created to work with collections of uploaded files.

The function HasFile() is changed to

Public Function HasFile() As Boolean
    Return filUploader.HasFile Or filUploader.HasFiles
End Function

To complement the function FileName() we add the function FileNames()

Public Function FileNames() As List(Of String)
    ' Return a list of file names from the collection of files that have been uploaded.
    Dim Names As New List(Of String)
    If HasFile() Then
        For Each pf As HttpPostedFile In filUploader.PostedFiles
            ' Loop through each posted file and add its name to the list.
            Names.Add(pf.FileName)
        Next
    End If
    ' Return the list.
    Return Names
End Function

To completent the function GetFileName() we add the function GetFileNames(). As you can see one of these functions is redundant and may be removed in a later release of the application.

Public Function GetFileNames() As List(Of String)
    Return FileNames()
End Function

We will be returning to this module later on to make further changes but for now let's move over to the module _FileUploader.

File Uploader

SaveFile()

We are going to change two methods in the _FileUploader module, one is SaveFile() and the other is UploadFile(). We start with SaveFile() below which is responsible for error checking and file type checking. An explanation follows the code sample.

Public Sub SaveFile()

    Select Case c_enumImageType
        Case IMAGE_TYPE.enum_CategoryImage
            Session("tab") = "images"
        Case IMAGE_TYPE.enum_ProductImage
            Session("tab") = "images"
        Case IMAGE_TYPE.enum_VersionImage
            Session("tab") = "versions"
            Session("inner-tab") = "images"
        Case IMAGE_TYPE.enum_PromotionImage
            Session("tab") = "images"
    End Select
    If _UC_UploaderPopup.HasFile() Then
        CreatePath()
        If Not Directory.Exists(Server.MapPath(c_strUploadPath)) Then
            Directory.CreateDirectory(Server.MapPath(c_strUploadPath))
        End If
        ' Load a list of extensions for each file in the upload control.
        Dim strFileExts As New List(Of String)
        For Each FileName As String In _UC_UploaderPopup.GetFileNames()
            strFileExts.Add(Path.GetExtension(FileName))
        Next

        'Need to check the file being uploaded is not
        'of a type listed in the excludedUploadFiles
        'setting in the web.config. For security, we
        'block the uploader from uploading files of
        'such types. This prevents an attacker who has
        'gained back end access to Kartris from being
        'able to upload files that could be used to
        'modify or write new files, or read sensitive
        'info such as from the web.config. Basically,
        'damage limitation.
         '(Similar(ish) code in _UploaderPopup.ascx.vb)
        Dim arrExcludedFileTypes() As String = ConfigurationManager.AppSettings("ExcludedUploadFiles").ToString().Split(",")
        For Each ext As String In strFileExts
            For i As Integer = 0 To arrExcludedFileTypes.GetUpperBound(0)
                If Replace(ext.ToLower, ".", "") = arrExcludedFileTypes(i).ToLower Then
                    'Banned file type, don't upload
                    'Log error so attempts can be seen in logs
                    CkartrisFormatErrors.LogError("Attempt to upload a file of type: " & arrExcludedFileTypes(i).ToLower)
                    litStatus.Text = "It is not permitted to upload files of this type. Change 'ExcludedUploadFiles' in the web.config if you need to upload this file."
                    popExtender.Show()
                    Exit Sub
                End If
            Next
        Next

        'This is a softer check, it checks images are of an acceptable
        'type. The security check on file type above will overrule
        'this 'allow' list here.
        Dim arrAllowedImageTypes() As String = KartSettingsManager.GetKartConfig("backend.imagetypes").Split(",")
        Dim CheckPassed As Boolean = False
        For Each ext As String In strFileExts
            CheckPassed = False     ' Per loop reset.
            For i As Integer = 0 To arrAllowedImageTypes.GetUpperBound(0)
                If ext.ToLower = arrAllowedImageTypes(i).ToLower Then
                    'UploadFile()
                    CheckPassed = True
                    Exit For
                    'Exit Sub
                End If
            Next
            If Not CheckPassed Then
                ' Check failed, there is a file that is not acceptable.
                CkartrisFormatErrors.LogError("Attempt to upload a file with rejected extension: " & ext.ToLower)
                litStatus.Text = "An attempt was made to upload a file with an extension that is not permitted. Add this file to the accepted file extension list if required. Extension was " & ext.ToLower
                popExtender.Show()
                Exit Sub
            End If
        Next
        UploadFile()
    Else
        litStatus.Text = GetGlobalResourceObject("_Kartris", "ContentText_NoFile")
        popExtender.Show()
    End If
End Sub

The first change to this module is a loop that checks all files that have been uploaded and obtains their file name extensions. This list includes the period before the file name (e.g. .jpeg, .bmp, .zip etc.). This replaces a single line that loaded the one file extension into a string variable.

Dim strFileExts As New List(Of String)
For Each FileName As String In _UC_UploaderPopup.GetFileNames()
    strFileExts.Add(Path.GetExtension(FileName))
Next

Next we load a list of excluded file types, cycle through each of the uploaded file name extensions and check to see if any of these match. If a match is found then an error is logged, a message is shown to the end user and the routine ends.

Dim arrExcludedFileTypes() As String = ConfigurationManager.AppSettings("ExcludedUploadFiles").ToString().Split(",")
For Each ext As String In strFileExts
    For i As Integer = 0 To arrExcludedFileTypes.GetUpperBound(0)
        If Replace(ext.ToLower, ".", "") = arrExcludedFileTypes(i).ToLower Then
            'Banned file type, don't upload
            'Log error so attempts can be seen in logs
            CkartrisFormatErrors.LogError("Attempt to upload a file of type: " & arrExcludedFileTypes(i).ToLower)
            litStatus.Text = "It is not permitted to upload files of this type. Change 'ExcludedUploadFiles' in the web.config if you need to upload this file."
            popExtender.Show()
            Exit Sub
        End If
    Next
Next

Next we load a list of included file type, cycle through each of the uploaded file name extensions and check to see if any of these do not match. On each reset of the cycle the flag CheckPassed is set to false. If a match is found then the flag is set to true. If by the end of the cycle no match has been made and the flag CheckPassed is still false then an error is logged, a message is shown to the end user and the routine ends.

Dim arrAllowedImageTypes() As String = KartSettingsManager.GetKartConfig("backend.imagetypes").Split(",")
Dim CheckPassed As Boolean = False
For Each ext As String In strFileExts
    CheckPassed = False     ' Per loop reset.
    For i As Integer = 0 To arrAllowedImageTypes.GetUpperBound(0)
        If ext.ToLower = arrAllowedImageTypes(i).ToLower Then
            'UploadFile()
            CheckPassed = True
            Exit For
            'Exit Sub
        End If
    Next
    If Not CheckPassed Then
        ' Check failed, there is a file that is not acceptable.
        CkartrisFormatErrors.LogError("Attempt to upload a file with rejected extension: " & ext.ToLower)
        litStatus.Text = "An attempt was made to upload a file with an extension that is not permitted. Add this file to the accepted file extension list if required. Extension was " & ext.ToLower
        popExtender.Show()
        Exit Sub
    End If
Next

The final step in this routine is to call UploadFile().

UploadFile()

In this routine we cycle through each of the files that have been uploaded, define a name for this file and ensure that this name is unique in the target directory. At this point the routine hands over to the _UploaderPopup module to save the file at which point this module compresses the file to the target quality.

The routine in its entirety is shown below and is explained afterwards.

Private Sub UploadFile()
    Try
        Dim existingFiles() As String = Nothing
        Dim numTotalFiles = 0
        ' --------------------------
        Dim strTempName As String = String.Empty

        Dim FileNames As List(Of String) = _UC_UploaderPopup.GetFileNames

        If c_blnOneFileOnly And FileNames.Count > 1 Then
            ' Error. Too many files.
            CkartrisFormatErrors.LogError("Attempt to upload too many files. OneFileOnly set as true while uploaded file count is " & FileNames.Count.ToString)
            litStatus.Text = "An attempt was made to upload more than one file. This is not permitted in the current context."
            popExtender.Show()
            Exit Sub
        End If

        For I = 0 To FileNames.Count - 1
            ' Cycle through all of the file names in order and save each one individually.
            ' Get list of existing files.
            existingFiles = Directory.GetFiles(Server.MapPath(c_strUploadPath))
            numTotalFiles = existingFiles.Length()

generateNewName:
            Randomize()

            If c_blnOneFileOnly Then
                ' Used if the target folder will only ever have one file in it.
                strTempName = c_numItemID & Path.GetExtension(FileNames(I))
            Else
                strTempName = c_strFileName & CStr(Int(2 * Rnd() + (numTotalFiles * Rnd() + numTotalFiles / 2))) & Path.GetExtension(_UC_UploaderPopup.GetFileName())
            End If

            If Not File.Exists(Server.MapPath(c_strUploadPath & strTempName)) Then
                _UC_UploaderPopup.SaveFile(Server.MapPath(c_strUploadPath & strTempName), I, I < (FileNames.Count - 1))
                Dim strCompressQuality As String = KartSettingsManager.GetKartConfig("general.imagequality")
                If IsNumeric(strCompressQuality) AndAlso strCompressQuality > 0 AndAlso strCompressQuality < 100 Then CompressImage(Server.MapPath(c_strUploadPath & strTempName), CLng(strCompressQuality))
                ' Method below REM'd out as pointless. It is supersceded by the later call to LoadImages()
                '_UC_ItemSorter.AddNewItem(strTempName)
            ElseIf c_blnOneFileOnly Then
                ' Prevent infinite loop.
                CkartrisFormatErrors.LogError("Existing file found when c_blnOneFileOnly = True")
                litStatus.Text = "A file already exists where we are trying to put a new file. Internal Error."
                popExtender.Show()
                Exit Sub
            Else
                GoTo generateNewName
            End If
        Next
        ' Show images.
        LoadImages()
        updMain.Update()
    Catch ex As Exception
        litStatus.Text = "Fail " & ex.Message
    End Try
End Sub

The start of this procedure is very clear. It starts by getting a list of files that have been uploaded as shown below and then gets a list of files that already exist in the target directory.

Dim FileNames As List(Of String) = _UC_UploaderPopup.GetFileNames

If the target directory is only supposed to have one single file in it we give the file a number and append its file name extention. If, however, there are multiple files in the target directory we take the uploaded file name append a random number to the end and append its file name extension.

If c_blnOneFileOnly Then
    ' Used if the target folder will only ever have one file in it.
    strTempName = c_numItemID & Path.GetExtension(FileNames(I))
Else
    strTempName = c_strFileName & CStr(Int(2 * Rnd() + (numTotalFiles * Rnd() + numTotalFiles / 2))) & Path.GetExtension(_UC_UploaderPopup.GetFileName())
End If

We check to see if this file name already exists in the target directory and if it does we cycle back and perform the randomisation again. In the event that there should only ever be one file in the target directory the routine throws an error and exits as can be seen here.

If Not File.Exists(Server.MapPath(c_strUploadPath & strTempName)) Then
    _UC_UploaderPopup.SaveFile(Server.MapPath(c_strUploadPath & strTempName), I, I < (FileNames.Count - 1))
    Dim strCompressQuality As String = KartSettingsManager.GetKartConfig("general.imagequality")
    If IsNumeric(strCompressQuality) AndAlso strCompressQuality > 0 AndAlso strCompressQuality < 100 Then CompressImage(Server.MapPath(c_strUploadPath & strTempName), CLng(strCompressQuality))
    ' Method below REM'd out as pointless. It is supersceded by the later call to LoadImages()
    '_UC_ItemSorter.AddNewItem(strTempName)
ElseIf c_blnOneFileOnly Then
    ' Prevent infinite loop.
    CkartrisFormatErrors.LogError("Existing file found when c_blnOneFileOnly = True")
    litStatus.Text = "A file already exists where we are trying to put a new file. Internal Error."
    popExtender.Show()
    Exit Sub
Else
    GoTo generateNewName
End If

If the file does not exists then we hand over to the _UploaderPopup module to perform the actual upload. In this call there are a couple of extra parameters that are passed that do not exist in the standard code. These will be looked at after the analysis of this module.

_UC_UploaderPopup.SaveFile(Server.MapPath(c_strUploadPath & strTempName), I, I < (FileNames.Count - 1))

We then check to see if a preset image quality has been defined, if it has we ensure that it is between some hard coded limits, and provided that it passes the check we compress the recently uploaded image.

Dim strCompressQuality As String = KartSettingsManager.GetKartConfig("general.imagequality")
If IsNumeric(strCompressQuality) AndAlso strCompressQuality > 0 AndAlso strCompressQuality < 100 Then CompressImage(Server.MapPath(c_strUploadPath & strTempName), CLng(strCompressQuality))

Uploader Popup Method

Finally we return to the __UploaderPopup module to perform the actual save. In the previous paragraphs I mentioned that this module has some extra parameters in the SaveFile() method. These are:

  • FileIndex - The uploaded file's ordinal position in the collection.
  • SurpressRefersh - At the end of this routine is a command that raises an event. This event bubbles up through the controls collection. This should only be fired once all of the files have been uploaded and so needs to be surpressed on all files that are saved before the last one.

I have also added a new module level variable called LastFileName which was mentioned at the start of this article.

Only the updated code is shown below.

' To avoid saving the file twice
If strPath <> LastFileName Then
    filUploader.PostedFiles(FileIndex).SaveAs(strPath)
    LastFileName = strPath
End If

If Not SurpressRefresh Then RaiseEvent NeedCategoryRefresh()

Here we check to ensure we are not trying to save the file twice, and then provided we are not we save the file to the directory. If this is the last file we then raise the event NeedCategoryRefresh(). Which refreshes the display.

Results

Now that all of these modules have been changed you can deploy them to the server, refresh IIS (to refresh the installation of .Net Framework) and then try to form.

You will notice that when you try to add images now you are able to select multiple files in the upload window.

Now that multiple images have been selected the file uploader dialog box says "2 files selected".

And finally the 2 images that were uploaded are visible against our product.

Points of Interest

Within the module _UploaderPopup there appears to be some redundant functions (e.g. FileName() and GetFileName()) which I would like to see removed in later versions, I will raise a snag with the development team to see if they can resolve this.

History

Revision 1.0. No changes.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Craig A. Moore
CEO Deadline Automation Limited
United Kingdom United Kingdom
Craig is a developer with 20 years' experience in the industry. He has been involved in application development within manufacturing specialising in the food & drink sector. He currently works as Director of Deadline Automation and is an Automation Engineer for Coca Cola Enterprises in the UK.
 
Craig has experience with mobile (iOS), desktop and web technologies focusing on ASP.Net development and T-SQL.
 
Outside of IT Craig has a beautiful partner with whom he spends most of his free time, be that at a social events or indoors simply watching the TV.
Follow on   LinkedIn

You may also be interested in...

Comments and Discussions

 
GeneralMy vote of 5 PinprofessionalSibeesh KV23-Sep-14 18:07 
GeneralRe: My vote of 5 PinprofessionalCraig A. Moore27-Sep-14 6:32 
GeneralRe: My vote of 5 PinprofessionalSibeesh KV27-Sep-14 7:51 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140926.1 | Last Updated 25 Aug 2014
Article Copyright 2014 by Craig A. Moore
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid