Nested Json with multipart/form-data in AFNetworking 2.x - ios

I am trying to send nested json with an image from my ios app using AFNetworking library.I am able to send the json data and image successfully but in server the json structure is coming differently.
HTTP request inside my app :-
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSDictionary *params = #{#"requestData":#{#"username":#"200OK",#"password":#"password"}};
manager.requestSerializer = [AFJSONRequestSerializer serializer];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
[manager POST:Self_URL
parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileData:UIImagePNGRepresentation(image)
name:#"file"
fileName:#"file"
mimeType:#"image/jpeg"];
} success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Response: %#", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
So in my django server I am expecting the params as a dictionary same as params but request.POST is coming as
content_type---> multipart/form-data; boundary=Boundary+247685AB6DF2B3BA
<QueryDict: {'requestData[password]': [u'password'], 'requestData[username]': [u'200OK']}>
How can I send json data so that it will be easy to access from server ?
Anything I am missing or what is going wrong?

constructingBodyWithBlock is overriding your AFJSONRequestSerializer and your dictionary is encoded as form data. There is no way to have two content types (multipart/form-data and application/json) for a request at the same time, so you'll have to do this another way.
One possibility is to encode the JSON as an NSData object and append it to the multipart form along with the image data:
NSError *error = nil;
NSData *paramData = [NSJSONSerialization dataWithJSONObject:params
options:0
error:&error];
[formData appendPartWithFileData:paramData
name:#"params"
filename:#"params"
mimeType:#"application/json"]
You can access the serialized parameters through request.FILES['params']. Despite the mime-type, I doubt that django will automatically parse the JSON data into a dictionary, but you could do that manually with json.loads.

In AFNetworking3.0, you can introduce JSON in a form using the appendPartWithFormData function
[formData appendPartWithFormData:params name:#"params"];
On the server side, you can access the serialized data using json.loads() on the object. Here is a full snippet from some code I wrote:
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc]initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:#"POST"
URLString:requestUrl parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFormData:[self dictToJson:params] name:#"params"];
[formData appendPartWithFileData:fileData name:fileGeneralName fileName:fileName mimeType:mimeType];
} error:nil];
[[manager uploadTaskWithStreamedRequest:request progress:nil completionHandler:^(NSURLResponse* response, id responseObject, NSError* error) {
[self callCompletionBlock:responseObject withError:error withResponse:response completionHandler:completionBlock];
}] resume];
There's a serialization function in that snippet, which I'll also paste for completion:
+(NSData*)dictToJson:(NSDictionary*)dict {
NSError* error = nil;
return [NSJSONSerialization dataWithJSONObject:dict options:0 error:&error]; }

Related

AFNetworking 3 AFMultipartFormData getting bytes data in response

I'm doing this to send images in an array to server:
NSString *string = [NSString stringWithFormat:#"%#/API/Upload",BaseURLString];
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc]initWithBaseURL:[NSURL URLWithString:BaseURLString]];
[manager setRequestSerializer:[AFHTTPRequestSerializer serializer]];
[manager setResponseSerializer:[AFHTTPResponseSerializer serializer]];
for(NSData *eachImage in self.fetchedAtt) {
[manager POST:string
parameters:nil
constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
[formData appendPartWithFormData:eachImage
name:#"image1"];
}
progress:nil
success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
if (responseObject != nil)
{
//NSDictionary *jsonDictionary = responseObject;
NSLog(#"%#",responseObject);
}
}
failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(#"%#",error.localizedDescription);
}];
}
For now I only send one image to test. And it get responseObject which is bytes data.
<696d6167 65313a20 efbfbdef bfbdefbf bdefbfbd 00104a46 49460001
01000048 00480000 efbfbdef bfbd0058 45786966 00004d4d 002a0000
00080002 01120003 00000001 00010000 efbfbd69 00040000 00010000
00260000 00000003 efbfbd01 00030000 00010001 0000efbf bd020004
00000001 0000001e efbfbd03...
As per the web guy response should be the URL of the image. Web backend is in ASP.NET What am I doing wrong?
UPDATE
If I change the response serializer I get:
Request failed: unacceptable content-type: application/octet-stream
I've even tried this:
NSString *string = [NSString stringWithFormat:#"%#/API/Upload",BaseURLString];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager setRequestSerializer:[AFHTTPRequestSerializer serializer]];
[manager setResponseSerializer:[AFHTTPResponseSerializer serializer]];
NSError *error;
for(NSData *eachImage in self.fetchedAtt) {
NSURLRequest *request = [manager.requestSerializer multipartFormRequestWithMethod:#"POST" URLString:string parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
//NSString *value = #"qux";
//NSData *data = [value dataUsingEncoding:NSUTF8StringEncoding];
//[formData appendPartWithFormData:eachImage name:#"image1"];
[formData appendPartWithFileData:eachImage name:#"image1" fileName:#"test.jpg" mimeType:#"image/jpeg"];
} error:&error];
NSURLSessionDataTask *task = [manager dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
if (error) {
NSLog(#"%#", error.localizedDescription);
return;
}
NSLog(#"%#", responseObject);
}];
[task resume];
}
But got Request failed: internal server error (500). if I uncomment the appendPartWithFormData and comment other line I get same bytes in responseObject.
More Updates:
We tested it on postman and the image was uploaded successfully. But if I give something in parameter I get
Request failed: unsupported media type (415)
When you upload images, you'd expect some response from the server (just to acknowledge that they were received successfully). Having said that, looking at your hex data, it's curious. What it looks like is:
The "JFIF" and "Exif" references are consistent with a JPEG response. And the image1 looks like it was the name of the image you sent. But this is a very curious response. It's definitely not the URL of the image.
You should contact the web service authors and get details of what precisely they're sending back, because it doesn't conform to standard responses (XML, JSON, etc.).
By the way, your method of uploading the image looks incorrect. You'd usually do something like:
[formData appendPartWithFileData:eachImage
name:#"image1"
fileName:#"test.jpg"
mimeType:#"image/jpeg"];
Clearly, you'd specify the fileName and mimeType to match what the NSData really was. And I'd double check with the author of the web service whether they really want to use a field name of image1 ... that's a little atypical.

AFNetworking 3.0 The data couldn’t be read because it isn’t in the correct format

There are other questions with similar titles but none of them helped me. I've to send a PUT request to server in order to change the status of appointment so I've made this method -(void)appointmentStatusChangedTo:(NSString *)statusID atAppointmentID:(NSString *)appointmentID In which I'm setting the URL and Parameters as
NSString *string = [NSString stringWithFormat:#"%#/API/Appointments/3",BaseURLString];
NSDictionary *para = #{
#"AppointmentStatusId":statusID,
#"ID":appointmentID
};
Then I've made URL request as
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSMutableURLRequest *req = [[AFJSONRequestSerializer serializer] requestWithMethod:#"PUT" URLString:string parameters:para error:nil];
After that I'm setting the header for an authorization token as
NSString *token = [NSString stringWithFormat:#"Bearer %#",[[NSUserDefaults standardUserDefaults] objectForKey:#"userToken"]];
[req setValue:token forHTTPHeaderField:#"Authorization"];
So finally I'm calling it as
[[manager dataTaskWithRequest:req completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error){
if (!error) {
if (response) {
NSLog(#"Respose Object: %#",responseObject);
[self.patientsAppointmentsTableView reloadData];
}
}
else {
// NSLog(#"Error: %#, %#, %#", error, response, responseObject);
NSLog(#"Error: %#", error.localizedDescription);
}
}] resume];
Now it is successfully sending the data to the server but as a response I'm getting
Error: The data couldn’t be read because it isn’t in the correct
format.
I am not sure what the response might look like at the moment as I'm not in contact with backend guy. But as far as I remember it was just a simple 1. SO kindly tell me how to handle any type of response using AFNetworking 3.0 or any change in my code.
try to use below code:
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
AFJSONRequestSerializer *serializer = [AFJSONRequestSerializer serializer];
[serializer setStringEncoding:NSUTF8StringEncoding];
manager.requestSerializer=serializer;
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
Try following code using Afnetworking 3.0
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
[manager GET:url parameters:nil progress:nil success:^(NSURLSessionTask *task, id responseObject) {
NSLog(#"%#",responseObject);
self.responseHandlers(YES,responseObject);
} failure:^(NSURLSessionTask *operation, NSError *error) {
self.responseHandlers(NO,nil);
}];

How To Send NSData with AFNetworking 3 Without Using AFMultipartFormData

I am trying to send wav file as a NSData to rest service with AFNetworking 3. I figured out how to send with AFMultipartFromData but i got an error like that
errorMessage = "Can Not Map Content-Type String multipart/form-data; boundary=Boundary+02588C5 To Media Type ";
When i spoke with the guy who created rest service then he told me i have to send just NSData not anything like AFMultipartFormData. I need some help here because i could not find any way to send "just" NSData.
My code is below;
NSURL *URL = [NSURL URLWithString:#"http://xxxMyService"];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.HTTPAdditionalHeaders = #{#"xx": #"yy ; zz"};
AFHTTPSessionManager *manager2 = [[AFHTTPSessionManager alloc] initWithBaseURL:URL sessionConfiguration:configuration];
manager2.responseSerializer = [AFJSONResponseSerializer serializer];
//I converted wav file to NSData
NSData *data=[self setVoiceRecordToNSData];
[manager2 POST:#"http://xxxMyService" parameters:nil
constructingBodyWithBlock:^(id<AFMultipartFormData> formData)
{
[formData appendPartWithFileData:data name:#"data" fileName:#"Path.wav" mimeType:#"audio/wav"];
}
progress:nil success:^(NSURLSessionTask *task, id responseObject
{ NSLog(#"JSON: %#", responseObject);}
failure:^(NSURLSessionTask *operation, NSError *error) {
NSLog(#"Error: %#", error); }];
Try add this code before POST
manager2.responseSerializer.acceptableContentTypes = [NSSet setWithObject:#"multipart/form-data"];

Uploading video to youtube using AFNetworking 2.0

I'm trying to upload a video to YouTube using v3 API using AFNetworking 2.0. The upload itself is working fine and I am able to see a video at my channel. What I'm having a problem is the parameters (the video resource) to specify the title, description etc which I have to put as my request body (along with the video itself) Here is the code I am using.
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
[manager.requestSerializer setValue:#"Bearer #_token_goes_here#" forHTTPHeaderField:#"Authorization"];
NSDictionary *parameters = #{#"snippet" : #{#"title" : #"random_title",
#"description" : #"random_description"}};
NSURL *filePath = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"video" ofType:#"mov"]];
[manager POST:#"https://www.googleapis.com/upload/youtube/v3/videos?part=snippet,status" parameters:parameters constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileURL:filePath name:#"video" fileName:#"video.mov" mimeType:#"video/*" error:NULL];
} success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Success: %#", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
How do I properly set the JSON parameters for the request?
Thanks.
Ok, so if anyone is interested, the only solution I found for this is to send the second PUT update request to set the proper title and description.
You have to append the snippet to the multipart form data
NSData *jsonData = [NSJSONSerialization dataWithJSONObject: parameters options:NSJSONWritingPrettyPrinted error:NULL];
NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
[mutableHeaders setValue:[NSString stringWithFormat:#"form-data; name=\"%#\"", #"snippet"] forKey:#"Content-Disposition"];
[mutableHeaders setValue:#"application/json" forKey:#"Content-Type"];
[formData appendPartWithHeaders:mutableHeaders body:jsonData];
[formData appendPartWithFileURL:filePath name:#"video" fileName:#"video.mov" mimeType:#"video/*" error:NULL];
One request, and only need the permissions to upload.

Differences between sending POST with AFNetworking and curl

I
ve been using AFNetworking 2 with success for some time, but I stumbled upon case where I don't know where to turn my head to. I have to make POST request in order to log in, send credentials alongside and receive JSON back with operation status. For some weird reason sending it via AFNetworking like this:
NSDictionary *params = #{#"login": self.loginField.text, #"password" : self.passField.text};
NSDictionary *data = #{#"data": params};
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
//manager.responseSerializer = [AFJSONResponseSerializer serializer];
[manager POST:server parameters:#{#"data" : params} success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"success: %#", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error: %#", error);
}];
I have to pack dictionary with parameters as a single "data" parameter. I do get back OK 200, but if I take a look in server logs I see that no data was transferred, which ended up as user not recognized.
But if send with curl this:
curl -d 'data={"login":"xx", "password":"yy"}' http://someurl/api/login/
I got perfectly fine response and in logs I see some actual data. Does anyone know how to deal with such a case?
If it helps, I'm doing it in iPhone simulator, not tested on actual device.
If you have a look at the AFNetworking source code, namely the class AFJSONRequestSerializer, you will see that the serialise function for that serialiser puts the parameters you hand over into the body - and not as an HTTP parameter as you expect.
You should use an AFHTTPRequestSerializer in your case - that will add the parameters as (URL-encoded) HTTP parameters to your URL.
Something like that should work in your case:
// build the JSON parameter string
NSDictionary *params = #{#"login": self.loginField.text, #"password" : self.passField.text};
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:params
options:0
error:&error];
NSString *jsonString = [[NSString alloc] initWithBytes:[jsonData bytes]
length:[jsonData length]
encoding:NSUTF8StringEncoding];
// and now send the JSON parameter string as HTTP parameter
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFHTTPRequestSerializer serializer];
[manager POST:server
parameters:#{#"data" : jsonString}
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"success: %#", responseObject);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error: %#", error);
}];

Resources