jsan
jsan

Reputation: 743

Error with ReadAsMultipartAsync and MultiPartFormDataStreamProvider

I'm trying to upload a file asynchronously to my web server ( ASP.NET MVC). Specifically to my web api endpoint. I followed several tutorials but I keep getting an error of null parameters.

It happens when I subclass MultipartFormDataStreamProvider. The method GetLocalFileName, gets called first correctly, with the proper HttpContentHeaders, but then gets called a second time with null headers.

The file is successfully saved with the filename I set, but as I get an exception, I am not able to know what's the name generated.

However if I just use the base MultipartFormDataStreamProvider class, the file is saved perfectly, with no errors, but I can't control the filename in this case.

This is the api method:

[HttpPost]
    public async Task<FilesUploadResult> Save()
    {
        // Check if the request contains multipart/form-data.
        if (!Request.Content.IsMimeMultipartContent())
        {
            var ret = new FilesUploadResult(HttpStatusCode.UnsupportedMediaType, null);
            ret.Message = "Unsupported Media Type. Request must contain multipart/form-data form.";
            return ret;
        }

        var path = UploadsFolderPath;

        // to-do: this works but for some reason makes a call with empty headers.
        var provider = new SimpleMultipartFormDataStreamProvider(path);

        // this provider saves the file with an arbritrary name
        //var provider = new MultipartFormDataStreamProvider(path);

        try
        {
            // Read the form data.
            await Request.Content.ReadAsMultipartAsync(provider as MultipartFormDataStreamProvider);

            var files = from f in provider.FileData
                        let info = new FileInfo(f.LocalFileName)
                        select new FileUploadDescription(info.Name, path + "\\" + info.Name, info.Length, HttpStatusCode.Created);

            var ret = new FilesUploadResult(HttpStatusCode.OK, files);
            return ret;
        }
        catch (Exception ex)
        {
            var ret = new FilesUploadResult(HttpStatusCode.InternalServerError, null);
            ret.Message = ex.Message;
            return ret;
        }
    }

This is the code for my SimpleMultipartFormDataStreamProvider:

public class SimpleMultipartFormDataStreamProvider : MultipartFormDataStreamProvider
{
    public SimpleMultipartFormDataStreamProvider(string path) : base(path) { }

    public override string GetLocalFileName(HttpContentHeaders headers)
    {

        // make sure the headers are valid
        if (headers == null)
        {

            throw new ArgumentNullException("headers");
        }

        // filename
        var filename = CleanString(headers.ContentDisposition.FileName); ;

        // create the local file name
        var localFileName = string.Format("_{0}_{1}", GetRandomName(), filename);

        //this is here because Chrome submits files in quotation marks which get treated as part of the filename and get escaped
        return localFileName.Replace("\"", string.Empty);
    }

    private string GetRandomName()
    {
        return GuidHelper.ShortUniqueId(16);
    }

    private static string CleanString(string str)
    {
        if (string.IsNullOrWhiteSpace(str))
            return string.Empty;

        var sb = new StringBuilder();
        foreach (char c in str)
        {
            if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '.' || c == '_')
            {
                sb.Append(c);
            }
        }
        return sb.ToString();
    }
}

Can anybody tell me why I am getting the GetLocalFileName called twice and the 2nd time with empty headers?

Upvotes: 1

Views: 2910

Answers (3)

jsan
jsan

Reputation: 743

I figured it out by myself.

The issue was that the class I implemented to subclass the MultipartDataStreamProvider was in a different project.

That project had references to .Net .Http assemblies that were different to the ones currently in the Web Api project.

Only thing I did was to move that CustomMultipartDataStreamProvider class to the same project, and everything worked perfectly.

Upvotes: 2

Jose M.
Jose M.

Reputation: 1316

GetLocalFileName is called once for each file in the multipart request. If you you send two files in the same request GetLocalFileName will be invoked twice. I think you should check how are you invoking the service and use fiddler or something similar to see if you really are sending two files. If you send several parts in the same request try to specify the ContentType of each part.

Upvotes: 2

Nikitesh
Nikitesh

Reputation: 1305

Even i had encountered such behavior in MVC4 it submits 2 files one of which is empty, so better would be to have a check whether the file has any content and then proceed with saving the file. There can be multiple approaches for this: One of them would be to get the list of all the files from Request.Files (this is an array of strings )

      foreach (string fileName in Request.Files)
            {
                HttpPostedFileBase file = Request.Files[fileName];
                //Save file content goes here
                if (file != null && file.ContentLength > 0)
                 {
                     //logic to save the content
                 }
             }

Upvotes: 2

Related Questions