Uploading Files using AJAX

Normally, if you want to to upload files through a website you would use a <form> element with enctype=”multipart/form-data” that posts the file from a nested file-<input> element to the specified action (in ASP.NET MVC you would receive that file on the server side by means of a HttpPostedFileBase variable as argument of your action method).

But if you don’t have or don’t want to use a <form> for some reason you have to find other ways to upload a file.

The obvious way would be to assemble and submit a invisible form with Javascript when the user triggers the upload event. To make this work properly you would also have to create an <iframe> as “target” for the <form> that receives the response. The advantage of this method is that it works with all browsers.

Another approach would be to use AJAX, that is using an XMLHttpRequest object. This is also very simple and done in a few lines of Javascript code:

var xhr = new XMLHttpRequest();
xhr.open("POST", url, true); // method, url, async
xhr.setRequestHeader("Cache-Control", "no-cache");
xhr.setRequestHeader("X-File-Name", file.fileName);
xhr.setRequestHeader("X-File-Size", file.fileSize);
xhr.setRequestHeader("Content-Type", "multipart/form-data");
xhr.send(file);

In the above code the “url” would be the “Controller/Action” to which the Request is posted to and the “file” can be extracted from any file-<input> element.

The “X-File-Name” and “X-File-Size” are just self-chosen optional parameters that will be appended to the RequestHeader. That way you can send additional data with the Request.

Again, the “Content-Type” has the be “multipart/form-data” as we’re sending a file/binary data.

The “Cache-Control” is set to “no-cache”, to bypass server cache, so it cannot respond with a “304 Not Modified” response header and skip sending the data.

Finally, we have to subscribe to the “onreadystatechange” event in order to receive the Response from the server:

...
xhr.onreadystatechange = function() {
    if (xhr.readyState == 4) {      // completed
        if (xhr.status == 200) {    // OK
            var response = xhr.responseText;
            // do something
        }
    }
}
...

This solution should work quite well. But if you are using jQuery I would recommend you to use its .ajax() function instead. jQuery does several optimizations for different browsers and also takes over error handling.

$.ajax({
    beforeSend: function (xhr) {
        xhr.setRequestHeader("Cache-Control", "no-cache");
        xhr.setRequestHeader("X-File-Name", file.fileName);
        xhr.setRequestHeader("X-File-Size", file.fileSize);
        xhr.setRequestHeader("Content-Type", "multipart/form-data");
    },
    type: 'POST',
    url: url,
    processData: false,
    data: file,
    success: function (data, textStatus, xhr) {
        // do something
    },
    error: function (xhr, textStatus, errorThrown) {
        // do something
    }
});

We’re modifying the XMLHttpRequest to be sent in the “beforeSend” callback and set all needed options.
It’s important to set “processData” to “false”. This will prevent jQuery to transform the data to the default content type “application/x-www-form-urlencoded”.

Server Side:

A server side action method could then look like this:

[HttpPost]
public ActionResult UploadFile()
{
    bool success = false;
    string message = String.Empty;

    // Read custom attributes
    string fileName = Request["HTTP_X_FILE_NAME"];  // notice the "HTTP_" prefix
    string fileSize = Request["HTTP_X_FILE_SIZE"];

    try
    {
        // Read input stream from request
        byte[] buffer = new byte[Request.InputStream.Length];
        int offset = 0;
        int cnt = 0;
        while ((cnt = Request.InputStream.Read(buffer, offset, 10)) > 0)
        {
            offset += cnt;
        }
        // Save file
        using (FileStream fs = new FileStream(@"C:\Path\To\Save\File\" + fileName, FileMode.Create))
        {
            fs.Write(buffer, 0, buffer.Length);
            fs.Flush();
        }
        // Or in one line
        // System.IO.File.WriteAllBytes(@"Path\To\Save\File\" + fileName, buffer);

        success = true;
        message = "Success...";
    }
    catch (Exception)
    {
        success = false;
        message = "Error...";
    }

    // Create JSON Response
    var jsonData = new
    {
        success = success,
        message = message
    };

    return Json(jsonData);
}

Making global resources public

Regarding the previous post you may want to use strings from a global resources .resx file (App_GlobalResources) as error messages or descriptions in your attributes. This won’t work because global resource files are by default marked as internal and so you can’t access them from your controller or your model. Also the little “Access Modifier” dropdown menu in the resource editor is grayed out, so you can’t change the access level without further ado.

However there’s a way to change the access rights without editing the designer file by hand.

You just have to open the file properties of the resource file and change the “Custom Tool” from “GlobalResourceProxyGenerator” to “PublicResXFileCodeGenerator”, which is the default Tool for local resource files. Next you have to change the “Build Action” to “Embedded Resource”. You may also want to assign a proper Custom Tool Namespace like “Resources” in order to access the file properly, but this isn’t necessary.

Now rebuild the project and you should notice that the resource access is now public and you can access its contents from anywhere in your project.

E.g. like that:


[Required(ErrorMessageResourceType = typeof(Resources.Resource1), ErrorMessageResourceName = "Comment_Text")]
public string Myproperty { get; set; }

Localizing DisplayNameAttribute

When trying to put a localized string from a resource file (.resx) into the DisplayName Attribute you will get a the following build error:

An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type

An easy way to solve this is to create your own custom DisplayName Attribute. To do that we will create a new class DisplayNameLocalizedAttribute and derive it from the original DisplayNameAttribute and then override the base DisplayName property to return the resource object we want.


public class DisplayNameLocalizedAttribute : DisplayNameAttribute
{
    private readonly string m_ResourceName;
    private readonly string m_ClassName;
    public DisplayNameLocalizedAttribute(string className, string resourceName)
     {
         m_ResourceName = resourceName;
         m_ClassName = className;
     }

     public override string DisplayName
     {
         get
         {
             // get and return the resource object
             return HttpContext.GetGlobalResourceObject(
                    m_ClassName,
                    m_ResourceName,
                    Thread.CurrentThread.CurrentCulture).ToString();
         }
     }
}

Use:


    [DisplayNameLocalized("MyResource", "MyString")]
    public string MyProperty { get; set; }

As you can see, we are now able to pass in a resource name (className) and a resource key (resourceName) and will get the correct string returned. The same procedure should work with similar Attributes such as the DescriptionAttribute.

Update 1: Another way would be by using reflection.

public class DisplayNameLocalizedAttribute : DisplayNameAttribute
{
    private string _displayName;

    public DisplayNameLocalizedAttribute(string resourceKey, Type resourceType)
    {
        PropertyInfo propInfo = resourceType.GetProperty(resourceKey,
                System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
        _displayName = (string)propInfo.GetValue(propInfo.DeclaringType, null);
    }

    public override string DisplayName
    {
        get
        {
            return _displayName;
        }
    }
}

Use:

[DisplayNameLocalized("MyString", typeof(MyResource))]
public string MyProperty { get; set; }

Update 2: In .NET 4.0 this procedure seems to be obsolete as you can just use the new [Display]-Attribute and specify the “ResourceType” and “Name” Properties:

[Display(ResourceType = typeof(MyResource), Name = "MyString")]
public string MyProperty { get; set; }
%d bloggers like this: