CarDealer

This is the fourth installment about using and accessing RESTful webservices on the iPhone.

You can download the example project artifacts here. This includes the iPhone example project and SQL Server scripts for generating the CarDealer DB and demo data.


Getting Started with iPhone + Data Services

Getting this post out took much longer than I thought. Setting up my .NET development and hosting environment was painful. My personal environment is different from my work environment, so I had some catch up after several months of not using it. 

Setting up SQL Server Express was not bad at all. I had an older version and decided to upgrade it to '08. Trying to create the data services project was painless as well. The pain began when I tried deploying to IIS 6 locally. While I'm sure someone out there can do it, I was unable to make it work. I decided to setup a Windows Server '08 VM to ease the pain. It helped a lot, but was very time consuming.

I started looking around for a free .NET hosting service that I could use to make the services public for the example. After several hours a gave up on this. I could have paid for a cheap one, but I doubt that I'd ever use it for anything else. 

I then took some cycles and looked at using Azure. I do not have Vista (XP ftw) for my dev environment and could not install the SDK required for deployment. I was able to setup an Azure SQL database and import all of my example tables, but without being able to deploy the data services from the cloud it was useless. 

Please note that my indication of wanting to use Azure for this example is not representative of my being a cloud advocate. Cloud computing, like most other technologies, has its uses in certain circumstances but it is not and will never be a silver bullet.


Setting up the backend

I finally decided that I would just make the database scripts available and point people to this article on creating data services. If you have the prerequisites below, then it should take less than half an hour to be up and running. I suggest naming it CarDealer.svc. I apologize for not supplying the source, but I don't want to have to muck around with cleaning out my connection strings and such. It is fairly easy and you will probably need to setup new data services in the future, so why not get some practice.

In order to setup the necessary backend you will need VS '08 SP1 [or later], SQL Server Express '08 [any version may work], and IIS 7.0. The key is deploying the data service is a way that is visible to the iPhone/Simulator. If you cannot load the CarDealer.svc in a browser on the client platform then you will not be able to use the data service in the iPhone example.

To make the iPhone example work with your services, modify the kServicesURL constant found in CarDealerAppDelegate. It should point to the fully qualified URL of your deployed CarDealer.svc.


Car Dealer

For this example I chose to model something that demonstrates relationships. Relationships are one of the learning curves with the current implementation of ADO.NET Data Services.

This system is used to store information about people, their cars, and any maintenance that has been performed on those cars. It is geared toward a maintenance department, not a sales team. It is something that is simple but non-trivial. There are a couple of primitive data elements and then tables to represent many-to-many relationship between those elements. 

edm

Bigger Version

The Example

Once you have everything running, and configured to point to your shiny new services, you will be presented with the screen on the right. It is a simple UI that displays a list of people in the system. Selecting a person will reveal their cars. Picking a car will show its maintenance records.

Like all my examples in this series, this one is plain, out-of-the-box, and meant to be a technology demonstration. Don't submit these to the app store and expect to win a design award.


Person

The Code

RestConnection has been updated in this example. The BSJSONAdditions have also been brought into the project, but are unchanged in this example.

I'm going to talk about the interesting code below. I will not go into the mundane details of UINavigationController et al. You can find more info on that here.


RestConnection

There are a couple of modifications to RestConnection. 

Data Services has a couple of nuances. First, it always wraps its responses in a root object, "d". Second, a data service can expose resources that are either singular or plural. If a solitary resource is specified by a URL, then the root object will contain a single object [dictionary]. If the resource specifies a group of objects, then the root object will contain an array. 

It is up to the client to understand which will be returned. In order to better support this behavior, RestConnection has been extended to A) "unwrap" the data for us and B) provide an additional method, - (NSArray *)arrayData, for handling collections.


Person

The person view controller is meant to display all the people in the system. For a production environment this would have to be retooled so it supported paging and allowed for searching. That is out of the scope of this example but there will be a post about both concepts later.

In the viewDidLoad method, a RestConnection is setup for use by the screen. kServicesURL defines the absolute path to the .svc.

restConnection = [[RestConnection alloc] initWithBaseURL:kServicesURL];
											restConnection.delegate = self;

In viewWillAppear a request is built that points to the Person resource. That object is then used to load the data.

- (void)viewWillAppear:(BOOL)animated {
											    [super viewWillAppear:animated];
											    
											    NSMutableURLRequest *request = 
											        [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"Person"]];
											    [restConnection performRequest:request];
											    [request release];
											}

Finally, in the finishedReceivingData method, the table view is told to reload itself.

- (void)finishedReceivingData:(NSData *)data
											{
											    [self.tableView reloadData];
											}
											
The table view delegate methods are implemented to grab data out of the RestConnection and display it. Here is the code used by the cellForRowAtIndexPath to display the person name.
// Configure the cell.
											NSDictionary *person = [restConnection.arrayData objectAtIndex:indexPath.row];
											cell.textLabel.text = [NSString stringWithFormat:@"%@, %@",
											                            [person objectForKey:@"lastName"],
											                            [person objectForKey:@"firstName"]];

Selecting a table cell causes a new view controller to be created, PersonCarViewController. The view controller is told which person it should be displaying information about and then push it on the stack.

PersonCarViewController *vc = 
											    [[PersonCarViewController alloc] initWithNibName:@"PersonCarViewController" bundle:nil];
											vc.person = [[restConnection arrayData] objectAtIndex:indexPath.row];
											[self.navigationController pushViewController:vc animated:YES];
											[PersonCarViewController release];


PersonCar

PersonCar represents a relationship between a person and their cars. The screen the right displays a list of a person's cars. Selecting a car will display its maintenance history. PersonCar

This view controller is structured very similar to the Person view controller. The first difference is the request that gets built. Here cars are loaded for a specific person. The following code is used to format a URL which loads a person's resource, the associated PersonCars, and each PersonCars' Car.

NSString *resource = [NSString stringWithFormat:@"Person(%@)?$expand=PersonCar,PersonCar/Car", [person objectForKey:@"personID"]];

The syntax is a bit odd at first, most REST systems use a path component to specify unique identifiers, so it would be written like "Person/242134" instead of "Person(987234)". Other than that, we have the query string addition to expand the necessary information required by our UI.

So a request URI like this:

http://192.168.1.112/EDMTest/CarDealer.svc/Person(1)?$expand=PersonCar,PersonCar/Car

Would yield a response like this:

{"d":
											    {"__metadata":
											        {"uri":"http://192.168.1.112/EDMTest/CarDealer.svc/Person(1)",
											         "type":"edmexampleModel.Person"},
											     "personID":1,
											     "firstName":"Bob",
											     "lastName":"Samson",
											     "ssn":"543872934",
											     "createDate":"/Date(1257008908440)/",
											     "PersonCar":[
											        {"__metadata":
											            {"uri":"http://192.168.1.112/EDMTest/CarDealer.svc/PersonCar(1)",
											             "type":"edmexampleModel.PersonCar"},
											         "personCarID":1,
											         "createStamp":"/Date(1257103813990)/",
											         "Car":
											            {"__metadata":
											                {"uri":"http://192.168.1.112/EDMTest/CarDealer.svc/Car(2)",
											                 "type":"edmexampleModel.Car"},
											             "carID":2,
											             "make":"Ford",
											             "model":"Explorer",
											             "manufactureDate":"/Date(1099242508440)/",
											             "createStamp":"/Date(1257008940840)/",
											             "PersonCar":
											                {"__deferred":
											                    {"uri":"http://192.168.1.112/EDMTest/CarDealer.svc/Car(2)/PersonCar"}}},
											         "Person":
											            {"__deferred":
											                {"uri":"http://192.168.1.112/EDMTest/CarDealer.svc/PersonCar(1)/Person"}},
											         "PersonCarMaintenance":
											            {"__deferred":
											                {"uri":"http://192.168.1.112/EDMTest/CarDealer.svc/PersonCar(1)/PersonCarMaintenance"}}},
											        {"__metadata":
											            {"uri":"http://192.168.1.112/EDMTest/CarDealer.svc/PersonCar(2)",
											             "type":"edmexampleModel.PersonCar"},
											         "personCarID":2,
											         "createStamp":"/Date(1257109628720)/",
											         "Car":
											            {"__metadata":
											                {"uri":"http://192.168.1.112/EDMTest/CarDealer.svc/Car(5)",
											                 "type":"edmexampleModel.Car"},
											             "carID":5,
											             "make":"Ford",
											             "model":"Mustang",
											             "manufactureDate":"/Date(1075680000000)/",
											             "createStamp":"/Date(1257109563660)/",
											             "PersonCar":
											                {"__deferred":
											                    {"uri":"http://192.168.1.112/EDMTest/CarDealer.svc/Car(5)/PersonCar"}}},
											         "Person":
											            {"__deferred":
											                {"uri":"http://192.168.1.112/EDMTest/CarDealer.svc/PersonCar(2)/Person"}},
											         "PersonCarMaintenance":
											            {"__deferred":
											                {"uri":"http://192.168.1.112/EDMTest/CarDealer.svc/PersonCar(2)/PersonCarMaintenance"}}}]}}

As you can see there is a lot of crap in there. I'll talk about __deferred and __metadata later on, there is plenty of discussion to be had about them.

The UI wants to display each description of each car for the person in context. This is done in cellForRowAtIndex as such.

NSDictionary *car = [[personCars objectAtIndex:indexPath.row] objectForKey:@"Car"];
											cell.textLabel.text = [car objectForKey:@"model"];
											

Once a car has been selected in the table, the car's maintenance history should display. A new view controller is instantiated and the selected PersonCar information passed on.


PersonCarMaintenance

This view controller is very similar to the PersonCar VC. The purpose is to display a list of maintenance activities for the car in context.

PersonCarMaint

This is the URL template that is used to get the required resources.

NSString *resource = 
											    [NSString stringWithFormat:@"Person(%@)?$expand=PersonCar,PersonCar/Car", [person objectForKey:@"personID"]];

And here is how we display the maintenance description.

NSDictionary *personCarMaintenance = [personCarMaintenances objectAtIndex:indexPath.row];
											cell.textLabel.text = 
											    [[personCarMaintenance objectForKey:@"Maintenance"] objectForKey:@"description"];


Wrapping it up

So there you have it. I have discussed connecting an iPhone application an ADO.NET Data Service. Next time I'll talk more about this and discuss the CUD in CRUD.

Until next time, enjoy!

Even your worst day has a lesson to learn.