iosobjective-cafnetworkingmultipartform-data

Jsonobject + Image Multipart AFNetworking


We are trying to send multi-part request to server using AFNetworking. We need to send one JSON object and two image files. The following is the curl request for same.

curl -X POST http://localhost:8080/Circle/restapi/offer/add -H "Content-Type: multipart/form-data" -F "offerDTO={"code": null,"name": "Merry X'Mas - 1","remark": "25% discount on every purchase","validityDate": "22-12-2014","domainCode": "DO - 1","merchantCode": "M-4","isApproved": false,"discountValue": 25,"discountType": "PERCENTAGE"};type=application/json" -F "image=@Team Page.png;type=image/png" -F "letterhead=@Team Page.png;type=image/png"

I know this should be fairly easy as I've implemented the server as well as Android code for same. And my friend is working on iOS part of this. Also I searched a lot on google, but did not get anything useful.

Edit

Here is the code that we tried:

UIImage *imageToPost = [UIImage imageNamed:@"1.png"];
NSData *imageData = UIImageJPEGRepresentation(imageToPost, 1.0);

offerDTO = [[NSMutableDictionary alloc]init];
[offerDTO setObject(angry)"" forKey:@"code"];
[offerDTO setObject:[NSString stringWithFormat:@"Testing"] forKey:@"discountDiscription"];
[offerDTO setObject:[NSString stringWithFormat:@"Test"] forKey:@"remark"];
[offerDTO setObject:@"07-05-2015" forKey:@"validityDate"];
[offerDTO setObject:@"C-7" forKey:@"creatorCode"];
[offerDTO setObject:@"M-1" forKey:@"merchantCode"];
[offerDTO setObject:[NSNumber numberWithBool:true] forKey:@"isApproved"];
[offerDTO setObject:@"2.4" forKey:@"discountValue"];
[offerDTO setObject:[NSString stringWithFormat:@"FREE"] forKey:@"discountType"];

NSURL *urlsss = [NSURL URLWithString:@"http://serverurl:8180"];
AFHTTPClient *client= [AFHTTPClient clientWithBaseURL:urlsss];
NSMutableURLRequest *request = [client multipartFormRequestWithMethod:@"POST" 
                                    path:@"/restapi/offer/add" parameters:nil constructingBodyWithBlock: ^(id <AFMultipartFormData>formData)
                                    {
                                        NSData *myData = [NSJSONSerialization dataWithJSONObject:offerDTO
                                                            options:NSJSONWritingPrettyPrinted
                                                            error:NULL];

                                        [formData appendPartWithFileData:imageData name:@"image"
                                                            fileName:@"image.jpg" 
                                                            mimeType:@"image/jpeg"];
                                                            
                                        [formData appendPartWithFileData:imageData name:@"letterhead"
                                                            fileName:@"image.jpg" 
                                                            mimeType:@"image/jpeg"];
                                        
                                        [formData appendPartWithFormData:myData name:@"offerDTO"];
                                    }
                                ];

AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];

[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
    {
        NSDictionary *jsons = [NSJSONSerialization JSONObjectWithData:responseObject options:kNilOptions error:nil];     
    }
     failure:^(AFHTTPRequestOperation operation, NSError error)
    {    
        NSLog(@"error: %@", [operation error]);
         
    }
];

Solution

  • A couple of observations:

    1. Your example is AFNetworking 1.x. AFNetworking 3.x rendition might look like:

      NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"1" withExtension:@"png"];
      
      // If you need to build dictionary dynamically as in your question, that's fine,
      // but sometimes array literals are more concise way if the structure of
      // the dictionary is always the same.
      
      // Also note that these keys do _not_ match what are present in the `curl`
      // so please double check these keys (e.g. `discountDiscription` vs
      // `discountDescription` vs `name`)!
      
      NSDictionary *offerDTO = @{@"code"                : @"",
                                 @"discountDescription" : @"Testing",
                                 @"remark"              : @"Test",
                                 @"validityDate"        : @"07-05-2015",
                                 @"creatorCode"         : @"C-7",
                                 @"merchantCode"        : @"M-1",
                                 @"isApproved"          : @YES,
                                 @"discountValue"       : @2.4,
                                 @"discountType"        : @"FREE"};
      
      // `AFHTTPSessionManager` is AFNetworking 3.x equivalent to `AFHTTPClient` in AFNetworking 1.x
      
      NSURL *baseURL = [NSURL URLWithString:@"http://serverurl:8180"];
      AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:baseURL];
      
      // The `POST` method both creates and issues the request
      
      [manager POST:@"/restapi/offer/add" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData>  _Nonnull formData) {
          NSError *error;
          BOOL success;
      
          success = [formData appendPartWithFileURL:fileURL
                                               name:@"image"
                                           fileName:@"image.jpg"
                                           mimeType:@"image/png"
                                              error:&error];
          NSAssert(success, @"Failure adding file: %@", error);
      
          success = [formData appendPartWithFileURL:fileURL
                                               name:@"letterhead"
                                           fileName:@"image.jpg"
                                           mimeType:@"image/png"
                                              error:&error];
          NSAssert(success, @"Failure adding file: %@", error);
      
          NSData *jsonData = [NSJSONSerialization dataWithJSONObject:offerDTO options:0 error:&error];
          NSAssert(jsonData, @"Failure building JSON: %@", error);
      
          // You could just do:
          //
          // [formData appendPartWithFormData:jsonData name:@"offerDTO"];
          //
          // but I now notice that in your `curl`, you set the `Content-Type` for the
          // part, so if you want to do that, you could do it like so:
      
          NSDictionary *jsonHeaders = @{@"Content-Disposition" : @"form-data; name=\"offerDTO\"",
                                        @"Content-Type"        : @"application/json"};
          [formData appendPartWithHeaders:jsonHeaders body:jsonData];
      } progress:^(NSProgress * _Nonnull uploadProgress) {
          // do whatever you want here
      } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
          NSLog(@"responseObject = %@", responseObject);
      } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
          NSLog(@"error = %@", error);
      }];
      
    2. You are creating an operation here, but never add it to a queue to start it. I assume you do that elsewhere. It's worth noting that AFHTTPSessionManager doesn't support operations like the deprecated AFHTTPRequestOperationManager or AFHTTPClient used to. The above code just starts the operation automatically.

    3. Note, AFNetworking now assumes the response will be JSON. Given that your code suggests the response is JSON, then note that no JSONObjectWithData is needed, as that's done for you already.

    4. Right now your code is (a) creating UIImage; (b) converting it back to a NSData; and (c) adding that to the formData. That is inefficient for a number of reasons:

      • Specifically, by taking the image asset, loading it into a UIImage, and then using UIImageJPEGRepresentation, you may be making the resulting NSData considerably larger than the original asset. You might consider just grabbing the original asset, bypassing UIImage altogether, and sending that (obviously, if you're sending PNG, then change the mime-type, too).

      • The process of adding NSData to the request can result in larger memory footprint. Often if you supply a file name, it can keep the peak memory usage a bit lower.