Showing posts with label webforms. Show all posts
Showing posts with label webforms. Show all posts

Monday, 2 July 2012

Upload very large files - Part 2

Earlier in this blog I talked about the general idea behind one solution for uploading very large files (I consider very large when they are above 2GB), how the file API is becoming more popular and the possible client side approaches. This time I’ll describe the server side part of the solution. As a quick reminder, the server is responsible for:

  • To determine if the request is to be handled by the mechanism, if not, just let it go.
  • To determine if the request is an initial packet or a data packet.
  • To handle properly each type of packet giving the appropriate response so the client can continue.

This is part of a bit more complex solution, named FreshUplaod, which has the Server component and client components using either JavaScript or Silverlight. The core element is the UploadHelper class, as the name indicates is a helper, it encapsulates the dirty logic regarding the request data reading and processing all stuff.

The methods ProcessInitialPacket and ProcessDataPacket do the actual work, but it’s necessary to know if the request data corresponds to one or another, that’s the job for these other methods IsInitialPacket and IsDataPacket. The actual helper utility is another method, ProcessRequest that acts as a façade to the whole mechanism. An example on how to use it is as follows:

[HttpPost]
public ActionResult Upload(FormCollection collection)
{
    var packetSize = 4 * 1024 * 1024; // default to 4 MB
    var filePath = Server.MapPath("~/_temp_upload/");

    var result = UploadHelper.ProcessRequest(
        Request, filePath, packetSize);

    if (result != null)
        return Json(result);
    return Content("");
}
 

This is using a controller action on ASP.NET MVC 3, where the access to the request is quite easy and the conversion to Json is stress-free too. The key points here are the packet size and the folder where the file is to be stored, the packet size must be exactly the same configured on client side in order to expect a good behavior, I’ve used the 4 MB value because this is the default constraint imposed by ASP.NET and of course this can be tweaked as you want in web.config file.

Now let’s see how to use this element on ASP.NET WebForms, the idea I suggest here is to use a handler whether it was a Generic Handler or ASP.NET Handler, it can be also a WebForm but is not elegant enough solution because the WebForms are chained in a different pipeline and there can be a bit of noise, although it work as well. Here’s the code sample:

public void ProcessRequest(HttpContext context)
{
    var packetSize = 4 * 1024 * 1024; // default to 4 MB
    var filePath = context.Server.MapPath("~/_temp_upload/");

    var result = UploadHelper.ProcessRequest(
        context.Request.RequestContext.HttpContext.Request, 
        filePath, packetSize);

    if (result != null)
    {
        JsonResult(context.Response, result);
    }
    else
    {
        context.Response.Write("");
    }
}

private void JsonResult(HttpResponse response, object result)
{
    response.ContentType = "application/json";
    var serializer = new JavaScriptSerializer();
    var json = serializer.Serialize(result);
    response.Write(json);
}
 
 

Here we have a little more hand work but not so much, the json conversion and output is done completely manual (if some WebForms people knows how to achieve this with fewer code, please let me know cause I’m a little out of this part of ASP.NET, although I support Scott Hanselman with his slogan that “Only One ASP.NET”). The other key point is how to get the Request but with an instance of HttpRequestBase class, the default context.Response is an HttpRequest instance, so after some minutes of try-error-retry I found this way to get right value, again if some knows a better way to do it, you know.

In the next posts I’ll cover the other parts of this solution. The whole source code can be found at https://bitbucket.org/abelperezok/freshupload, I am still preparing the code samples so in a few weeks they will be ready.

Tuesday, 21 February 2012

jQuery.NeatUpload – Examples refactored

jQuery.NeatUpload – Release 0.4

I’ve been using the jquery plugin for neatupload and the bugs keep coming up, well, that’s the developer’s life. Catching bugs is an ability that every developer must have and more than that, do not shame on, but be proud of.
This time I’ve improved the error handling when the server send as response some no comprehensive, for instance, there is a limit set in web.config for the maximum file size to upload and when the client attempts to send a file bigger than the limit, the server replies with some bizarre response.
As part of the mechanism implemented, it expects the HTML response for parse it and determine what to do, I mean, send information to the client indicating whether the request failed or not. Sometimes the HTML sent by the server was impossible to read because of some mysterious JavaScript error “Access denied”. I had no time for digging deeper in this matter, so I try-catch it and just a little extra setup in order to be consequent and ready. The release 0.4 can be downloaded here.

jQuery.NeatUpload – Examples refactored

In order to prepare for launch a new release of NeatUpload including these JavaScript features I’ve been doing some refactoring in the samples I published a few months ago. The first thing I’ll do is to apologize with the WebForms people, because I’ve let them in the road with the examples, just the part 1. Now the updated versions have both implementations WebForms and MVC3. Of course these new versions also have applied the bugfix mentioned above.
There’s something new in the web.config files, this time they are prepared for either IIS6 or IIS7+ and highly commented (well, this is questionable and relative to anyone’s need) every setting needed for set up NeatUpload even clarifying the confusing and inconsistent size units that Microsoft has implemented in both versions of IIS, they are in bytes and Kilobytes respectively.
Before describe what’s in these examples, I want to alert you about something that I’ve been dealing with many times and that has been cause of mysterious error in production scenarios. There is a setting in web.config that indicates the maximum time that IIS can wait before it shuts down the thread and return time out error. This is under and it’s expressed in seconds, this is especially useful when the files to be uploaded are really big.

What’s in the examples?

  • Default values: Shows how to use the minimum setup code and use the default values.
  • Changed Add and Upload texts: Shows how to change the texts for the elements that add and/or upload items rather than the default internal texts.
  • Changed Add and Upload elements: Shows how to change the default link for add and/or upload element by custom elements like buttons.
  • Custom cancel elements. Part 1: Shows how to change the cancel element by creating a simple new element inline and binding it to the internal click event.
  • Custom cancel elements. Part 2: Shows how to change the cancel element by cloning a complex element and binding it to the internal click event.
  • Start Upload Automatically: Show how to start automatically the upload without using a queue and adding one by one. This example uses just add element.
  • Show Progress Bar: Shows how to use a more cute visual feedback by styling up a progress bar that indicates the progress and changes the color if an error occurs.
  • Validating Data: Shows how to intercept the action of send the file to the server by performing validations to the associated data.
  • Sending extra Data: Shows how to send associated data along with the file by serializing it as JSON and how to receive it properly on the server.
à la prochaine mes amis!

Tuesday, 4 October 2011

Walkthrough example with NeatUpload and ASP.NET WebForms application

In my previous post I covered the minimum sample for running the plugin with an ASP.NET MVC3 application and now through this post I’ll use an ASP.NET application with WebForms. 
The source code for this code is available from here.
Setting up
Create an Empty ASP.NET Web Application by File | New Project | ASP.NET Empty Web Application. I have chosen “WebApplicationNeatUpload” as application name. Add the plugin .js file that you can download from here, in the folder “~/Scripts/” that I recommend to create for keep the things organized, but you are free for put it wherever you consider convenient. It’s also necessary to include the jquery core library you can go to (http://jquery.com for download it).
Add Reference to the Brettle.Web.NeatUpload.dll file and the appropriated modifications to the root web.config file as mentioned in previous post. For example, the section configuration/system.webServer for my sample application contains this (In the sample I use IISExpress and it reads the configuration from that section, if you use the traditional WebDev you should change this as indicated in the previous post):
<modules runAllManagedModulesForAllRequests="true">
    <add name="UploadHttpModule" type="Brettle.Web.NeatUpload.UploadHttpModule, Brettle.Web.NeatUpload" preCondition="managedHandler"/>
</modules>
<security>
    <requestFiltering>
        <requestLimits maxAllowedContentLength="1000000000"/>
    </requestFiltering>
</security>

One last thing we have to use from original project (NeatUpload) is the file (ProgressJsonHandler.ashx) which can be found in the example I attach with this post. The propose of this file is provide an entry point for call the library methods that store in session the current file progress information and of course the default path in the plugin is that, nevertheless you can call them manually for catch that information but that’s out the scope of this post. I’ll cover more detailed information of the module in further posts.
Starting with the code
Now let’s create a new WebForm, I named Default.aspx, in code behind we are going to add a Page_Load with the following line:

protected void Page_Load(object sender, EventArgs e)
{
    uploadaction.Value = ResolveClientUrl("~/StaticUploadFile.ashx");
}

This is just for set the value to the hidden field which stores the url for the handler that will receive the post data. And in the markup view let’s do something like this:
In the head section
<script src="Scripts/jquery-1.5.1.min.js" type="text/javascript"></script>
<script src="Scripts/jquery.neatupload.js" type="text/javascript"></script>
<script type="text/javascript" defer="defer">
    $(function () {
        $("#file1").neatupload({
            postDataUrl: $("input[type=hidden][id=uploadaction]").val(),
            fnShowStatus: function (plugin, index, data) {
                $('span#progress-file').text(data.PercentComplete + "%");
            }
        });
    });
</script>

And in the body section
<form id="form1" runat="server">
<div>
    <asp:HiddenField runat="server" ID="uploadaction" />
    <div>
        <label for="newfield">
            Select File:
        </label>
        <div id="file1">
            place holder for the input -> file for upload
        </div>
    </div>
    <div>
        <span id="progress-file"></span>
    </div>
    <div>
        <input type="submit" value="save" />
    </div>
</div>
</form>

There are some elements to clarify. The scripts to include, the first is the version 1.5.1 minified of the jquery library and the second is the jquery.neatupload.js plugin, it’s important the order because any jquery plugin must load after the jquery core. The $("#file1") refers to any element with an id equals to file1 (for jquery syntax go to jquery.com). The text inside the div with id = file1 is just for be sure the plugin is running properly, so you can remove it.
In this case the only options set are postDataUrl and fnShowStatus, the remaining options use the defaults defined by the plugin.
The postDataUrl option define the url that will handle the post on the server which is stored in a hidden field, the result of ResolveClientUrl("~/StaticUploadFile.ashx"); give us the resolved route for the Action StaticUploadFile.ashx handler in the project, the important note here is the strategy of not setting this value hardcoded in JavaScript code block but store it somewhere and by JavaScript code retrieve it. If we ignore this we can get a maintenance issue caused by refactoring or name changes in the files or moving from one folder to another.

The function fnShowStatus allows to the user to execute custom code each time the progress changes, this function takes three parameters: plugin, index, and data. The plugin parameter is a reference to the object container and through plugin.options object we can access to the options either defaults or custom values set by the plugin’s user. The index is the unique identifier auto generated by the plugin at time of being inserted in the hidden queue; this value may be used to establish a correspondence between the hidden form and the visual elements. The data parameter holds a JSON returned by the handler that is polled by intervals, this JSON has multiple properties but the most important is PercentComplete which give us the percent value (0 - 100) and may be used for display to the user.
The label refers to newfield because this is the name that the input takes when it’s generated. Due to security issues the input must be moved away when the user clicks the add button, and a new one is created for replace it, the best option would be change the value by code but it’s impossible, so the name is always the same.
If we run the example at this point, nothing important are going to happen but the view is rendered and we can see the form (styles and CSS are out of this post, just basic HTML) with the two links mentioned before.
What happen on the server?
Someone must receive the data posted by hidden forms when the user clicks add and then upload. Since we are using WebForms approach, in my opinion, the easier way of perform this is using a Generic Handler (.ashx); however, you can choose a classic handler or even a Page. I made it with a Generic Handler so the code is the following:

<%@ WebHandler Language="C#" Class="WebApplicationNeatUpload.StaticUploadFile" %>
using System;
using System.Collections.Generic;
using System.Web;
using Brettle.Web.NeatUpload;

namespace WebApplicationNeatUpload
{
    public class StaticUploadFile : IHttpHandler
    {
        public void ProcessRequest(HttpContext context)
        {
            try
            {
                var tempPath = context.Server.MapPath("~/_temp_upload/");
                var value = string.Empty;
                for (var i = 0; i < UploadModule.Files.Count; i++)
                {
                    value = UploadModule.Files[i].FileName;
                    UploadModule.Files[i].SaveAs(tempPath + value);
                }
                context.Response.Write(value);
            }
            catch (Exception)
            {
                context.Response.Write("<h1>Error</h1>");
            }
        }

        public bool IsReusable
        {
            get
            {
                return true;
            }
        }
    }
}

This handler is responsible for receive the post data through the context parameter. In the example we don’t do anything complex (it’s just sample) but to copy the uploaded content to a file with the same name in the hardcoded path “~/_temp_upload/” and return the name of the last file uploaded. Here, I am going to make a key point, due to the concrete implementation of the plugin in the client, the submits are made one by one, so there won’t be more than one file per upload, but it’s important to know that NeatUpload module is capable of handling more than one file per request.
The class UploadModule is our face for retrieve the uploaded content through the Files collection and the SaveAs method allows us to save in a particular path. At this point you are able to make whatever you want with the uploaded content, save to disk, create records in database or even start complex mechanisms that process these files like video encoding.
In the next post I’ll show more different plugin configurations and examples.