AIR multiple file uploads: drag n drop to client zip to server upload and unzip

25 03 2009

The aim of this post is to get an AIR application to take a collection of files dragged n dropped onto it, zip them up on the client, and send them off to the server (ASP.NET). 

There are a few things to note here:

  • Flash officially supports files of up to 100MB, trying to ZIP anything larger and you WILL run into server memory issues; Flash will crash. You can use File.upload() for files > 100MB, but trying to ZIP them will cause the crash.
  • ZIPping on the client takes time, 50MB could take from 15s to a minute. The code supplied performs it sync (not async). 
  • client software FZip, server zip : ISharpZipLib 
  • uploading manually (using URLLoader instead of File.upload means in you cannot track the Upload’s progress – as of AIR 1.5).

 

Step 1. The Drag & Drop in AIR

This is quite trivial. Allowing the user to drag and drop as many files/folders onto your application.

Client (AIR) code:

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication 

	nativeDragEnter="onDragEnter(event)"
	nativeDragDrop="onDragDrop(event)"

	layout="absolute" 

	xmlns:mx="http://www.adobe.com/2006/mxml" >

	<mx:Script>
		<![CDATA[

			import mx.managers.DragManager;
			import mx.events.DragEvent;

			private function onDragEnter(evt:NativeDragEvent):void
			{
				//ensure clipboard contains files
				if (evt.clipboard.hasFormat(ClipboardFormats.FILE_LIST_FORMAT))
				{
					NativeDragManager.acceptDragDrop(this);
				}
			}

			private var _filesToUpload:Array;

			private function onDragDrop(evt:NativeDragEvent):void
			{
				_filesToUpload = evt.clipboard.getData(ClipboardFormats.FILE_LIST_FORMAT) as Array;
			}

		]]>
	</mx:Script>

</mx:WindowedApplication>

 

Step 2. Zipping on the client

The nicest solution I could find for compressing files in AIR to ZIP is FZip.

I tried another library, but ISharpLib didn’t like it, it was giving me EOF file header issues in the ZIP decompression.

So the solution is to take the ZIP byteArray and to send it via the lower level URLLoader class.

NOTE: this uses the FileStream.open is sync mode. You could use FileStream.openAsAsync though it would obviously require listening and handling the corresponding events.

Client (AIR) code [URLLoader.load() solution]:

var request:URLRequest = new URLRequest("http://example.com/Handlers/UploadHandler.ashx");

request.method = URLRequestMethod.POST;
request.contentType = "application/octet-stream";

var loader:URLLoader = new URLLoader();

loader.dataFormat =  URLLoaderDataFormat.BINARY;

loader.addEventListener(Event.OPEN, onFileUploadStart,false,0,true);

//no point listening for progress, because it doesn't work for URLLoader uploads, only downloads
//loader.addEventListener(ProgressEvent.PROGRESS, onFileUploadProgress,false,0,true);

loader.addEventListener(Event.COMPLETE,onFileUploadComplete,false,0,true);

var fzip:FZip = new FZip();

for each (var file:File in model.filesToUpload)
{
	var fs:FileStream = new FileStream();
	fs.open(file, FileMode.READ);
	var data:ByteArray = new ByteArray();
	fs.readBytes(data);
	fs.close();

	fzip.addFile(file.name, data);
}

var bytes:ByteArray = new ByteArray();
fzip.serialize(bytes);

request.data = bytes;

loader.load(request);

 

What about sending extra parameters?

Client (AIR) code:
var request:URLRequest = new URLRequest("http://example.com/Handlers/UploadHandler.ashx?someVariable=" + someVariable);

What about the upload progress bar? 

If you need the progress bar, then you’re best option is writing this zip file to the client and uploading that. 

This also means that if you have optional parameters, you don’t have to append them to the URL but can send them in the data.

This changes the client code to use File.upload().

Client (AIR) code – [File.upload() solution]:

var params:URLVariables = new URLVariables();

params.someVariable = someVariable;
//add other variables here

var request:URLRequest = new URLRequest(model.UPLOAD_URL);

request.method = URLRequestMethod.POST;
request.data = params;

var fzip:FZip = new FZip();

for each (var file:File in model.filesToUpload)
{
	var fs:FileStream = new FileStream();
	fs.open(file, FileMode.READ);
	var data:ByteArray = new ByteArray();
	fs.readBytes(data);
	fs.close();

	fzip.addFile(file.name, data);
}

//get the zip as a byte array
var bytes:ByteArray = new ByteArray();
fzip.serialize(bytes);

//write the zip to a local file
var fsW:FileStream = new FileStream();
//change "tmp.zip" to a better name :)
var wFile:File = File.applicationStorageDirectory.resolvePath("tmp.zip");

fsW.open(wFile, FileMode.WRITE);
fsW.writeBytes(bytes);
fsW.close();

//listen to upload events
wFile.addEventListener(Event.OPEN, onFileUploadStart,false,0,true);
wFile.addEventListener(ProgressEvent.PROGRESS, onFileUploadProgress,false,0,true);
wFile.addEventListener(Event.COMPLETE,onFileUploadComplete,false,0,true);

//start the upload
wFile.upload(request);

 

Step 3. Unzipping on the server

Now, I use SharpZipLib in .NET because it’s Open Source, and because it’s also bundled with WebORB .NET, the remoting gateway that I often use.

If you use the URLLoader.load() option above (from Step 2) – then you must use Request.InputStream to access the parameters

Server (ASP.NET C# Handler – ashx) code: (SharpZipLib extraction)

using ICSharpCodeInternal.SharpZipLib.Zip; 
   public class UploadHandler : IHttpHandler {

        public void ProcessRequest (HttpContext context)
        {
            String someVariable = context.Request["someVariable"];

            ZipInputStream zipStream = new ZipInputStream(context.Request.InputStream);
            ZipEntry zipEntry;

            while (true)
            {
                zipEntry = zipStream.GetNextEntry();

                if (zipEntry == null)
                {
                    break;
                }

                //choose location to extract files to
                string serverFolder = context.Server.MapPath("~/Uploads/");

                FileStream streamWriter = File.Create(( serverFolder + zipEntry.Name));

                int size = 2048;

                byte[] data = new byte[2048];

                while (true)
                {
                    size = zipStream.Read(data, 0, data.Length);

                    if (size > 0)
                    {
                        streamWriter.Write(data, 0, size);
                    }
                    else
                    {
                        break;
                    }
                }

                streamWriter.Close();

            }

            zipStream.Close();
        }
    }

 

 

If you are using File.upload() on the client 

You’re Handler in ASP.NET now will be looking in the context.Request.Files array for the attached ZIP.

Modified Server (ASP.NET C# Handler – ashx) code:

ZipInputStream zipStream = new ZipInputStream(context.Request.Files[0].InputStream);

 

If you need folder structures

To do ZIP folders, you simply add the folder name before the file when you ZIP:

Modified Client (AIR) code:

fzip.addFile(folderName + "/" + file.name, data);

 

And then in the server code, you need to create directories if required:

Modified Server (ASP.NET C# Handler – ashx) code:

//existing code
...
string serverFolder = context.Server.MapPath("~/Uploads/");
string dir = Path.GetDirectoryName(serverFolder + zipEntry.Name);

if (!Directory.Exists(dir))
{
    Directory.CreateDirectory(dir);
}                

//existing code
FileStream streamWriter = File.Create(( serverFolder + ze.Name));

...





Flex, .NET 3.5 with LINQ to SQL

23 09 2008

March 2009: For more information see the latest article on FLEX & LINQ:

https://justinjmoses.wordpress.com/2009/03/19/rich-clients-flex-air-silverlight-linq-data-across-the-pipe/

——————————————————————————-

I had certainly delayed looking at LINQ for too long.

After seeing a screencast on some of the new features in ASP.NET I noticed the very handy LINQ to SQL item.

Well.

Let’s just say I’m not using strongly typed datasets anymore.

LINQ provides strongly typed access to your database (tables, views, sprocs and function) while also allowing you to query those results.

Whilst many still advocate the use of stored procs for larger and more advanced or intensive queries, at least some of the simple overhead can be reduced. Furthermore, updating your C# classes to reflect your DB is as simple as updating the LINQ to SQL item.

But the major advantage from a Flex remoting perspective is how easy it is to have all this returned data in your client.

Say I have three tables: (already linked with relationships via foreign keys)

Employee, Company & Position

I add the LINQ to SQL item to my solution. Inside, I connect the LINQ object up to the db and drag in my database objects. Voila, I have the strongly typed classes: “Employee”, “Company” and “Position”. None of this DataRow, DataTable, DataAdapter business.

Using LINQ syntax, I can do simple queries on the spot, like:

DataClassesDataContext db = new DataClassesDataContext();

var data = from e in db.Employees where e.CompanyID == someCompanyID select p;

List<Employee> employees = data.ToList<Employee>();

Now, I can return this list to either FluorineFx or WebORB and the ArrayCollection that the List is serialised into will contain an array of Employee Value Objects (my Employee.as file). Nothing special there.

BUT, the “Company” property is also provided. This is the Company property that is automatically generated in LINQ to SQL that provides the foreign key reference to the company.

This holds for all your foreign keys.

So if you want to reduce your overhead from a C# (writing classes for your db objects) and DB perspective (writing sprocs for every data call), then take a gander at .NET 3.5.

NOTE:

  • If you weren’t aware, .NET 3.5 framework is actually an addition to .NET 2.0. This means that once you install the framework on a machine, no manual IIS settings are required (as opposed to the 1.1 to 2.0 switch) to activate 3.5 on a website or virtual directory.
  • Both FluorineFx and WebORB support .NET 3.5




Threading in ASP.NET : quick start

28 03 2008

Threading. God loves it, and so should you.

It’s pretty easy to get started threading in .NET. So why wait?

But why would you want to multithread? There are plenty of different occasions, but essentially when you have some processes that should run asynchronously. This could include an automatic conversion process for some uploaded file – there’s nothing better than throwing it to a thread (or even a Windows Service) to process. That way, the user doesn’t have to wait for conversion to complete before they get back to your site.

Getting going is pretty easy, the complications come when you start looking at threads that use the same data.

Essentially, you start the Thread either as an empty method call, or by passing it some object. You should at least know what a delegates is first (essentially it is a function that can be passed around like a variable).

Now, I prefer the Parameterised (i’m Aussie, so I don’t like using Z where it’s an S 😛 ) option below just because I often want to pass at least something.

C#

using System.Threading;

ParameterizedThreadStart starter = delegate(object prop){SomeObject.SomeMethod(prop)); };
new
Thread(starter).Start(someObject);

One thing you need to remember here though is, if you are calling this in ASP.NET, then it’s the aspnet_wp.exe process that starts the thread, and if this is killed or restarted somehow, then it will kill your thread also.

It could be killed or restarted for the following reasons:

  • change in global.asax file,
  • change in machine.config,
  • change in web.config
  • change of content of /bin folder
  • IIS service is interrupted (incl restart)
  • Server is restarted

One thing you will learn to love: the Thread.Sleep() method and the System.Threading.Timer class…

Enjoy!

Useful Reference: This article gives a great intro into threads.





Flex.NET remoting methodology – coding standards and best practises

27 03 2008

I’ve been dabbling with different methodologies into Flex.NET remoting, and trying to find a coding standard that works and is appropriate for this technology.

I’ve provided my solution in the hope that it will spur comments and generate other opinions in this rather new field.

One of the annoying things with Flex remoting is having to define the same object twice – once in C# and once in ActionScript 3 (AS3). Obviously as good coders, we know this to be a no-no.

Of course, there are codegen solutions out there, but I’ve been dissatisfied with them, and prefer the manual touch.

This is what I’ve come up with:

  1. In Visual Studio, create a DataSet that interfaces with the database, exposing specific TableAdapters to each of your objects.
  2. In both Flex and .NET, create an object that spans across the two platforms. As it’s defined twice, use some codegen tool to generate the class in both C# and AS3.
  3. In your .NET library, create a “Service” for the action that provides the ability to interface between the client (Flex) and the server (.NET). This service talks to the DB via the above mentioned TableAdapters.
  4. In your Flex application, create a “Service” that mimics the methods of the service in .NET that you want to access

Don’t worry, I never read those summaries either. Here’s the example that explains it all:

Problem: I want to modify a User object across the platform

Solution:

  1. Create my DAL by adding a DataSet to my Service Layer (the assembly that will handle all the .NET interfacing). In the DAL, create a UserTableAdapter and expose a Get method called : GetUserByID(int userID).
  2. Create the object in C# and is AS3 . Note the use of the compiler directive “RemoteClass” in the AS3 is the syntax for WebORB’s remoting solution.
    C# (User.cs)

    namespace JustinJMoses.Examples.RemotingExample.Objects
    {
    public class User
    {
    public int UserID = -1;

           public String GivenNames;

           public String Surname;

           public String Email;
    }
    }

    AS3 (User.as)

    package com.justinjmoses.examples.remotingexample

    {

    [RemoteClass(alias=”JustinJMoses.Examples.RemotingExample.Objects.User”)]

    public class User

    {

    public var UserID:int = -1;

    public var Email:String;

    public var GivenNames:String;

    public var Surname:String;

    }

    }

  3. Make a service for the User in C# – UserService.cs

    using JustinJMoses.Examples.RemotingExample.Objects;

    using JustinJMoses.Examples.RemotingExample.DAL;

    using JustinJMoses.Examples.RemotingExample.DAL.MyDALTableAdapters;

    namespace JustinJMoses.Examples.RemotingExample.Services
    {

    public class UserService
    {

    public User Load(int UserID)
    {

    UserTableAdapter adapter = new UserTableAdapter();

    MyDAL.UserDataTable table = adapter.GetUserByID(UserID);

    MyDAL.UserRow row = (MyDAL.UserRow)table.Rows[0];

    User u = new User();

    u.UserID = row.UserID;

    u.GivenNames = row.GivenNames;

    u.Surname = row.Surname;

    u.Email = row.Email;

    return u;

    }

    }

    }

  4. Make a service for the Flex app in AS3: UserService.as

    package com.justinjmoses.examples.remotingexample
    {

    import mx.rpc.events.ResultEvent;

    public class UserService extends BaseService
    {

    public static function Load(onResultFunction:Function,userID:int):void
    {

    init(“UserService”);

    remoteObject.Load.addEventListener(ResultEvent.RESULT,onResultFunction);

    remoteObject.Load(userID);

    }

    }

    }

    that extends from the base service class BaseService.as

    package com.justinjmoses.examples.remotingexample
    {

    import mx.controls.Alert;

    import mx.core.Application;

    import mx.rpc.events.FaultEvent;

    import mx.rpc.events.InvokeEvent;

    import mx.rpc.events.ResultEvent;

    import mx.rpc.remoting.mxml.RemoteObject;

    public class BaseService

    {

    protected static var remoteObject:RemoteObject = null;

    protected static function init(serviceName:String):void

    {

    remoteObject = null;

    remoteObject = new RemoteObject(“GenericDestination”);

    remoteObject.showBusyCursor = true;

    remoteObject.addEventListener(FaultEvent.FAULT, BaseService.onFault);

    remoteObject.source = “JustinJMoses.Examples.RemotingExample.Services.” + serviceName;

    }

    protected static function onFault (event:FaultEvent):void

    {

    Alert.show(event.fault.faultString, “Error”);

    }

    }

    }

  5. The function can now be called statically in AS3

    private function onCrtComplete():void
    {

    UserService.Load(onUserLoaded, 12);

    }

    private function onUserLoaded(evt:ResultEvent):void
    {

    var user:User = User(evt.result);
    }

NOTE: This soln is done via WebORB remoting. Syntax includes the remote object destination of “Generic Destination”, and the compiler directive “RemoteObject()” in AS3.





link to code generation tool

19 02 2008

OK. So I’ve found a great tool that does exactly what I need in Flex.

Cheers to the writer.

C#/AS3/VB.NET Class Generator.