Pravin Pravin - 10 days ago 7
Objective-C Question

Objective C to ASP.net Web API file upload giving error Unexpected end of MIME multipart stream MIME multipart message is not complete

I have following ASP.net WebAPI code which handles file uploads. Works great using a simple HTML file upload form.

public Task<IEnumerable<string>> UploadFile()
{
if (Request.Content.IsMimeMultipartContent())
{

string fullPath = HttpContext.Current.Server.MapPath("~/uploads");

MultipartFormDataStreamProvider streamProvider = new MultipartFormDataStreamProvider(fullPath);

var task = Request.Content.ReadAsMultipartAsync(streamProvider).ContinueWith(t =>
{
if (t.IsFaulted || t.IsCanceled)
{
throw new HttpResponseException(HttpStatusCode.InternalServerError);
}

var fileInfo = streamProvider.FileData.Select(i =>
{
var info = new FileInfo(i.LocalFileName);
return "File uploaded as " + info.FullName + " (" + info.Length + ")";
});
return fileInfo;

});
return task;
}
else
{
HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "Invalid Request!"));
return null;
}
}


But gives "Unexpected end of MIME multipart stream MIME multipart message is not complete" if called from Objective C code, this I figured out through tracing on the API side. Following is the Objective C method...

- (void) uploadFile
{
NSString *fileUploadSrvURL = @"http://server1/service/api/controller/uploadfile";

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:fileUploadSrvURL]];
[request setHTTPMethod:@"POST"];
NSString *boundary = @"---------------------------14737809831466499882746641449";
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundary];
[request setValue:contentType forHTTPHeaderField:@"content-type"];

NSURL *fullFileURL = [self getFilePath:CurrentVisitId];
NSData *fileData = [NSData dataWithContentsOfURL:fullFileURL];

NSMutableData *body = [NSMutableData data];

[body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"FileName\"; filename=\"%@\"\r\n",@"810474.rtf"] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithString:@"Content-Type: application/rtf\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:fileData];
[body appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[request setHTTPBody:body];

NSHTTPURLResponse* response =[[NSHTTPURLResponse alloc] init];
NSError* error = [[NSError alloc] init] ;

NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (error)
{

}

NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
NSLog(@"%@", responseString);

}


As the ASP.net Web API side code works fine if integrated with HTML Form upload, so, probabaly there is something wrong with the way I am calling it from Objective C (I am new to Objective C)

Answer

I had similar issue and I manage to sorted it by creating a helper class (I found it online but I don't remember where):

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

    public override string GetLocalFileName(System.Net.Http.Headers.HttpContentHeaders headers)
    {
        var name = !string.IsNullOrWhiteSpace(headers.ContentDisposition.FileName) ? headers.ContentDisposition.FileName : "NoName";
        return name.Replace("\"", string.Empty); //this is here because Chrome submits files in quotation marks which get treated as part of the filename and get escaped
    }
}

And you also needs to change couple of line in your code:

if (Request.Content.IsMimeMultipartContent())
        {
            string fullPath = HttpContext.Current.Server.MapPath("~/uploads");
            var streamProvider = new CustomMultipartFormDataStreamProvider(fullPath);
            var task = Request.Content.ReadAsMultipartAsync(streamProvider).ContinueWith<IEnumerable<string>>(t =>
            {
                if (t.IsFaulted || t.IsCanceled)
                {
                    throw new HttpResponseException(HttpStatusCode.InternalServerError);
                }
        ///.........
        /// rest of your code
}

The difference is that I call the web api via AFNetworking if it doesn't work for you I can share my AFNetworking code as well.

// EDITED My .h file

#import "AFHTTPClient.h"

typedef void (^DataResponseSuccessBlock)(id JSON);
typedef void (^DataResponseFailureBlock)(NSError *error);

@interface MyHTTPClient : AFHTTPClient


+ (MyHTTPClient *)sharedMyHTTPClient;

- (id)initWithBaseURL:(NSURL *)url;

- (void)saveTemplateData:(TemplateData*)data success:(DataResponseSuccessBlock)successBlock failure:(DataResponseFailureBlock)failureBlock;

@end

My .m file

@implementation GLHTTPClient
+ (MyHTTPClient *)sharedMyHTTPClient
{
    static dispatch_once_t pred;
    static MyHTTPClient *_sharedMyHTTPClient = nil;

    dispatch_once(&pred, ^{ _sharedMyHTTPClient = [[self alloc] initWithBaseURL:[NSURL URLWithString:kJSON_URL]]; });

    return _sharedMyHTTPClient;
}

- (id)initWithBaseURL:(NSURL *)url
{
    self = [super initWithBaseURL:url];
    if (!self)
    {
        return nil;
    }

    [self registerHTTPOperationClass:[AFJSONRequestOperation class]];
    [self setDefaultHeader:@"Accept" value:@"application/json"];
    [self setParameterEncoding:AFJSONParameterEncoding];

    return self;
}
- (void)saveTemplateData:(TemplateData*)data success:(DataResponseSuccessBlock)successBlock failure:(DataResponseFailureBlock)failureBlock
{
    NSData *imageData = UIImagePNGRepresentation(data.image);
    NSMutableURLRequest *request = [self multipartFormRequestWithMethod:@"POST" path:@"SaveData" //My web api class for uploading image
                                                             parameters:nil
                                              constructingBodyWithBlock:^(id <AFMultipartFormData>formData)
                                    {
                                        [formData appendPartWithFileData:imageData
                                                                    name:@"image"
                                                                fileName:@"image.png"
                                                                mimeType:@"image/png"];
                                    }];

    AFHTTPRequestOperation *op = [self HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject) {
        if(successBlock)
            successBlock(responseObject);
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        if(failureBlock)
           failureBlock(error);
    }];

    [self enqueueHTTPRequestOperation:op];
}
@end

I have change this code so if something doesn't work for you give me a shout and I can have a look on it again. You need to just call saveTemplateData:success:failure method and it should work.

Comments