Mike
Tips, tricks and solutions for software developers.
 

WCF Web Services & iOS - Part 7 - Writing the iPhone app using XCode (continued)

By Mike Gledhill

Pages:

Well, I've had lots of great feedback from you guys, and one of the requests was to show an example of how to write Customer records back to the server.

In this section, I'll whizz through the building blocks that you'll need to save data via a JSON service, but I will leave you to add your own XCode buttons to kick off saving. As I keep saying, this is a JSON walkthough, not an iOS tutorial !

(Learning to develop iOS apps is already brilliantly documented in the The iOS Apprentice series, so if you need help learning how to do iOS coding, have a look there.)

Check your Services !

Okay, so we're going to need a web service to create Customer records with.

http://www.iNorthwind.com/Service1.svc/createCustomer

Don't try to run this URL from your browser - this is a POST web service, which means you need to call this URL and attach a chunk of data to it (i.e our Customer record).

I've said it before, when you're writing an XCode app which uses web services, make sure the web services are written and tested before you start playing around with XCode. So I'd recommend that you fire up Fiddler, or the TestPostWebService tester app which I gave you on Page 3 and check that writing a Customer record works okay.

In the TestPostWebService web page, enter the URL for our createCustomer service..

http://www.iNorthwind.com/Service1.svc/createCustomer

Then cut'n'paste some JSON data to send to the service:

{
    "CustomerID": "AB123",
    "CompanyName": "Mikes Knowledge Base Corp",
    "City": "Zurich"
}

Testing the services

Did you remember that we asked our createCustomer web service to return some JSON, to say whether the insert was successful or not, and to return an error message if something went wrong ?

{
    "Exception" : "",
    "WasSuccessful" : 1
}

Now, the interesting thing is that your CustomerID value becomes the Primary Key value for this [Customer] record. Have a look at what happens if you try to insert this record twice.

{
    "Exception" : "Violation of PRIMARY KEY constraint 'PK_Customers'. Cannot insert duplicate key in object 'dbo.Customers'. The duplicate key value is (ABC13).\u000d\u000aThe statement has been terminated.",
    "WasSuccessful" : 0
}

So, when we're calling this service from our XCode app, we need to remember to send the new Customer record in JSON format, and to parse the JSON return results, to find out whether it was successful or not.

One last thing:
Please help to keep my SQL Server Northwind database tidy by removing your Customer records when you've finished with them. Simply click on the following link to do this:

http://www.iNorthwind.com/Service1.svc/resetCustomers

This service will simply remove your - and anyone one else's - inserted Customer records, and put the SQL Server [Customer] table back to the way it looked initially.

Back to XCode

Once you're happy that your create web service is working okay, it's time to reluctantly head back into XCode.

First, let's create a new NSObject class, to receive the return results from our JSON web service.

Click on File, New, File, select "Objective-C class" as the file type, click on Next, then create a class called "SQLResult" and select that it's a Subclass of "NSObject". Accept the default directory, and click on Create.

In the SQLResult.h file, let's add two properties.

#import <Foundation/Foundation.h>

@interface SQLResult : NSObject

@property(nonatomic) NSInteger WasSuccessful;
@property(strong, nonatomic) NSString * Exception;


@end

..and in the SQLResult.m file, let's add this..

#import "SQLResult.h"

@implementation SQLResult

@synthesize WasSuccessful;
@synthesize Exception;


@end

In the Customer.m file, we'll add a new function which returns a Customer record in JSON format.

-(NSString*)getJSON
{
    // Get the contents of this Customer record in JSON format.
    //
    NSString* JSON = [NSString stringWithFormat:@"{\"CustomerID\":%@,\"CompanyName\":\"%@\",\"City\":\"%@\"}", Customer_ID, Company_Name, City];
    return JSON;
}

And we need to add this function's declaration in the Customer.h file

@interface Customer : NSObject

@property(strong, nonatomic) NSString * Customer_ID;
@property(strong, nonatomic) NSString * Company_Name;
@property(strong, nonatomic) NSString * City;

-(NSString*)getJSON;


@end

In the JSONHelper.m file, we'll add a new helper function which takes a JSON string, sends it to a specified web service URL, then parses the results into a SQLResult record.

I'm also going to slip in a showError function, which simply display a popup dialog with an error message in it.
Gosh, I miss the days of MessageBox.Show("Error");...

+(SQLResult*)postJSONDataToURL:(NSString *)urlString JSONdata:(NSString*)JSONdata
{
    // Attempt to send some data to a "POST" JSON Web Service, then parse the JSON results
    // which get returned, into a SQLResult record.
    //
    SQLResult* result = [[SQLResult alloc] init];
    
    NSURL *url = [NSURL URLWithString:urlString];
    
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    NSData *postData = [JSONdata dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
    NSString *postLength = [NSString stringWithFormat:@"%d", [postData length]];
    [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
    [request setHTTPMethod:@"POST"];
    [request setValue:postLength forHTTPHeaderField:@"Content-Length"];
    [request setHTTPBody:postData];
    
    NSError *error;
    NSData *data = [ NSURLConnection sendSynchronousRequest:request returningResponse: nil error:&error ];
    if (!data)
    {
        // An error occurred while calling the JSON web service.
        // Let's return a [SQLResult] record, to tell the user the web service couldn't be called.
        result.WasSuccessful = 0;
        result.Exception = [NSString stringWithFormat:@"Could not call the web service: %@", urlString];
        return result;
    }
    

    // The JSON web call did complete, but perhaps it hit an error (such as a foreign key
    // constraint, if we've accidentally sent an unknown User_ID to the [Survey] INSERT command).
    //
    // The ResultString will return a "WasSuccessful" value, and an Exception value:
    //     { "Exception":"", "WasSuccesful":1 }
    // or this
    //     { "Exception:":"The INSERT statement conflicted with ...", "WasSuccesful":0 }"
    //
    
    NSString *resultString = [[NSString alloc] initWithBytes: [data bytes] length:[data length] encoding: NSUTF8StringEncoding];
    
    if (resultString == nil)
    {
        result.WasSuccessful = 0;
        result.Exception = [NSString stringWithFormat:@"An exception occurred: %@", error.localizedDescription];
        return result;
    }

    // We need to convert our JSON string into a NSDictionary object
    NSData *data2 = [resultString dataUsingEncoding:NSUTF8StringEncoding];
    NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data2 options:kNilOptions error:&error];
    if (dictionary == nil)
    {
        result.WasSuccessful = 0;
        result.Exception = @"Unable to parse the JSON return string.";
        return result;
    }
    
    result.WasSuccessful = [[dictionary valueForKey:@"WasSuccessful"] integerValue];
    result.Exception = [dictionary valueForKey:@"Exception"];

    return result;
}

+(void)showError:(NSString *)errorMessage
{
    dispatch_async(dispatch_get_main_queue(), ^{
        UIAlertView *alertView = [[UIAlertView alloc]
            initWithTitle:@"Error"
            message:errorMessage
            delegate:nil
            cancelButtonTitle:@"OK"
            otherButtonTitles:nil];

        [alertView show];
    });
}

And don't forget to add the declarations for these functionsto the JSONHelper.h file.

#import "SQLResult.h"

+(SQLResult*)postJSONDataToURL:(NSString *)urlString JSONdata:(NSString*)JSONdata;
+(void)showError:(NSString *)errorMessage;

Over to you !

You now have all of the building blocks that you need to call our web service, and parse the results.

You could now add a button to your storyboard, to call a function in CustomersTableViewController.m which creates a new Customer record (with some values in it), and calls our createCustomer web service.

- (IBAction)btnAddCustomer:(id)sender
{
    Customer* cust = [[Customer alloc] init];
    cust.Customer_ID = @"MJG12";
    cust.Company_Name = @"Mikes Company";
    cust.City = @"Switzerland";

    NSString* JSON = [cust getJSON];
    NSString* URL = @"http://www.iNorthwind.com/Service1.svc/createCustomer";
    SQLResult* result = [JSONHelper postJSONDataToURL:URL JSONdata:JSON];

    if (result.WasSuccessful == 1)
    {
        [listOfCustomers addObject:cust];
    
        // This code will run once the JSON-loading section above has completed.
        [self.tableView reloadData];
    }
    else
    {
        // Our call to the web service failed. Display an error dialog, with details of the problem.
        [JSONHelper showError:result.Exception];
    }
}

Perhaps you would like to design your own iPhone screen containing three TextFields, where the user can enter details about a new customer, then click on a Save button to save the record using the web service.

Add an extra screen

For clarity, I've also chosen to call the web service synchronously, rather than asynchronously. In other words, the entire app will grind to a halt while it waits for the web service to respond. This is bad iOS design, but, as I keep saying, this is a short JSON tutorial, not an iOS tutorial !

Over to you...

< Previous Page
Next Page >

Comments