Microsoft

Blog Categories

Subscribe to RSS feed

Archives

Follow Microsoft Technologies on Pinterest

Posts by this author: RSS

Using Azure Storage Services from an iPhone App – Part 2: Building the User Interface

This post has moved. New location

Future updates to the series will only be posted to the new location.

Intro

This is part two of the first series of posts I’m writing on developing native mobile apps using the Windows Azure platform. In the first part, I covered the basic setup of my azure storage, I walked through the steps of building the iOS Windows Azure Toolkit and using it in a project, and I wrote the code to use the toolkit in my model classes for the SpeakEasy app, a fictitious app that keeps track of speakers and events.

This post covers building the UI for the app and, though not specifically about Azure, it does go through using the model classes I wrote from various view controllers. Since the rest of the series will be building off of this app, I thought it made sense to go over how the UI was built.

As a reminder, this series includes:

  • Using Azure Storage Services from an iPhone App
  • Adding Authentication and Authorization with Access Control Services
  • Push Notifications with Queues and Workers
  • Offline Synching using the Sync Framework (tentative)

Events and Speakers View Controllers

As I said in the first part of this series, SpeakEasy is a Tabbed Application with two tabs – one for Events and the other for Speakers. Below is how each will look like:

image image

The first thing we want to do is to build the view for the Events Table View Controller. This view is used to present a list of upcoming events and is the view that’s presented in the first tab of the application. To begin building this view, follow these steps:

  1. Open up the MainStoryboard.storyboard file and drag a Table View Controllerto the canvas.
  2. With the new Table View Controller selected, from the Editor Menu, select Embed In > Navigation Controller. This will add a Navigation Controllerto the app so that any view that is added after the navigation controller will be handled as part of the navigation stack.
  3. Now, with the Tab Bar Controller selected (the initial view controller for the app), Control + Click from the Tab Bar Controller and drag to the new Navigation Controller that was just added to the storyboard. From the popup menu, select ‘Relationship – viewControllers’.
  4. In the Events Table View Controller, set the Title of the Navigation Item to ‘Events’.
  5. In the Tab Bar Item on the Navigation Controller, set the title to ‘Events’ and the Image to ‘first.png’.

Building the view for Speakers Table View Controller is nearly identical as above except for making the title to ‘Speakers’ instead and using ‘second.png’ as the Image for the Tab Bar Item. After you’ve followed these steps, the storyboard should look like this:

image

 

Events Table View

The next step is to design the Events Table View. Since all the content in this view will be presented the same way, the Events Table View will use a single prototype cell. Configure the Table View to have Dynamic Prototypes for the Content and 1 Prototype Cell. Select the Prototype Cell and configure the following:

  • Style:Custom
  • Identifier:EventTableViewCell
  • Accessory:Disclosure Indicator
  • Row Height: 92

The custom style indicates that we’ll be using a style that is not one of the out of the box styles provided and the Identifier will be used to indicate in code what cell we are using to map values to (later). Finally, we have a Disclosure Indicator so that we can indicate to the user that there’s more information if they click on the cell.

Now from the Object Library, drag four Label objects to the Prototype Cells container in the Events View Controller. The four labels will be used to display the Event’s name, date, speaker and summary. The following are how I configured each label’s properties (only the changes I made from the default values are indicated):

Name label

  • Font:System Bold 19.0
  • X, Y, Width, Height: 12, 7, 218, 21

imageDate label

  • Font:System 11.0
  • Text Color:Blue-ish
  • X, Y, Width, Height: 238, 7, 57, 21

Speaker label

  • Font:System Bold 19.0
  • Text Color:Blue-ish
  • X, Y, Width, Height: 13, 28, 189, 21

Summary label

  • Lines: 3
  • Font:System 14.0
  • X, Y, Width, Height: 12, 49, 283, 39

When you’re done, the Events Table View should look like the image on the right.

EventTableViewCell Class

Because we’ve created a prototype cell that isn’t using a standard style and added four labels that need to be reference-able in some way by the code, we need to create add a new class to the project that subclasses UITableViewCell. Right click the Views group from the Project Navigator and add a new UIViewController subclass file. Name the class EventTableViewCell and make it a subclass of UITableViewCell.

EventTableViewCell.h should look like this:

  1: #import <UIKit/UIKit.h>
  2:
  3: @interface EventTableViewCell : UITableViewCell
  4:
  5: @property(nonatomic, strong) IBOutlet UILabel *eventNameLabel;
  6: @property(nonatomic, strong) IBOutlet UILabel *eventDescriptionLabel;
  7: @property(nonatomic, strong) IBOutlet UILabel *eventDateLabel;
  8: @property(nonatomic, strong) IBOutlet UILabel *eventSpeakerLabel;
  9: @end

I create four properties, each as an IBOutlet that we can connect to the labels on the prototype cell we designed for the Events Table View.

The EventTableViewCell.m looks like this:

  1: #import "EventTableViewCell.h"
  2:
  3: @implementation EventTableViewCell
  4: @synthesize eventNameLabel;
  5: @synthesize eventDescriptionLabel;
  6: @synthesize eventDateLabel;
  7: @synthesize eventSpeakerLabel;
  8:
  9: - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
 10: {
 11:     self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
 12:     if (self) {
 13:         // Initialization code
 14:     }
 15:     return self;
 16: }
 17:
 18: - (void)setSelected:(BOOL)selected animated:(BOOL)animated
 19: {
 20:     [super setSelected:selected animated:animated];
 21: }
 22:
 23: @end

The above doesn’t do anything except for synthesizing the properties we defined in the header and to override some of the default methods of the UITableViewCell.

To use this new class, go back to the storyboard, select the prototype cell and set its Class to EventTableViewCell. Then open the Assistant Editor to EventTableViewCell.h and connect each IBOutlet to its appropriate label:

image

EventsTableViewController Class

In order to populate the table using the model classes I created in the first part of this blog series, I need to create a subclass of UITableViewController that will take of the logic of retrieving SEEvent model instances and mapping them to the view. Right-click the ViewControllers group in the Project Navigator and add a new file. Using the UIViewController subclass template, create a new file called EventsTableViewController as a subclass of UITableViewController.

In the interface definition, add the following line: @property (nonatomic, retain) NSMutableArray *events; and synthesize this property in the class definition. This property will be used to store the SEEvent instances that is retrieved from Azure.

In the class implementation, change viewDidLoad to this:

  1: - (void)viewDidLoad
  2: {
  3:     [super viewDidLoad];
  4:
  5:     SEData *data = [SEData sharedManager];
  6:     [data fetchEventsWithCompletionHandler:^(NSMutableArray *theEvents, NSError *error) {
  7:
  8:         if(error) {
  9:             UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" message:[error localizedDescription]
 10:                                                            delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil];
 11:             [alert show];
 12:
 13:         }
 14:         else {
 15:             self.events = theEvents;
 16:
 17:             [self.tableView reloadData];
 18:         }
 19:     }];
 20: }

Basically, what this does is it uses our SEData singleton and uses the method fetchEventsWithCompletionHandler: to attempt to retrieve all of the EventEntity objects from Azure. If it fails, an alert message will be shown to the user. If the fetch succeeds, the events will be saved to this class’s events array and then a message will be sent to the table view to reload its data.

To handle the display of the table view’s data, the UITableViewDataSource protocol methods should be defined as below:

  1: - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
  2: {
  3:     return 1;
  4: }
  5:
  6: - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  7: {
  8:     return [events count];
  9: }
 10:
 11: - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
 12: {
 13:     EventTableViewCell *cell = (EventTableViewCell *)[tableView
 14:                                       dequeueReusableCellWithIdentifier:@"EventTableViewCell"];
 15:
 16:     SEEvent *event = [self.events objectAtIndex:indexPath.row];
 17:     cell.eventNameLabel.text = event.eventName;
 18:     cell.eventDescriptionLabel.text = event.eventDescription;
 19:     cell.eventSpeakerLabel.text = event.speakerName;
 20:
 21:     cell.eventDateLabel.text = [event eventDateAsString];
 22:
 23:     return cell;
 24: }

There is only one section in the table view so I hard-code the return value of numberOfSectionsInTableView: to 1. The number of rows in our only section is based on the number of objects in the events array (lines 6-9).

In tableView:cellForRowAtIndexPath:, I get a reference to the my cell prototype that I created in the storyboard by using the ‘EventTableViewCell’ identifier (which is what I named it in the storyboard). That is then casted to a pointer to an instance of my custom EventTableViewCell with the four label IBOutlets. I then grab the correct event from the array and assign that event’s property values to the correct label in the cell.

Finally, in order to use this new table view controller, go back to the storyboard, select the Events Table View Controller and change its class to EventsTableViewController. I f you run this now, you should be able to see the list of your events in the Events tab.

Event Details View Controller

Before finishing up the Speakers View Controller, add a new Table View Controller to the storyboard. This new controller will be used to show the event details when a user clicks on one of the cells from the Events Table View. To make it easier to design this new controller’s view, make sure to use the Attributes Inspector to show the Navigation Bar as the Top Bar and the Tab Bar as the Bottom Bar and set the Navigation Item’s title to Event Details.

imageTo define the transition from the Events Table View controller to the Event Details View Controller, we need to create a segue. Control + Drag from the Events Table View’s prototype cell to the new Table View Controller and select Push as the segue type.

imageWith the segue selected, set the segue’s identifier to ‘EventDetailsSegue’ in the Attributes Inspector.

Now back in the Event Details View Controller, select the Table View and set the Contents to Static Cells and the number of Sections to 2. The Style should also be Grouped (see image on the right). We won’t be using prototype cells for this view.

Select the first Table View Section and change the number of rows to 4 and set the header to General Info. Select the second Table View Section and set the number of rows to 1 and the header to Presenter.

imageFor the single row in the second section, set its Accessory type to Disclosure Indicator. The Event Details Table View should now look like the image on the right.

EventDetailsViewController Class

Similar to how we needed to create a custom UITableViewController for the Events Table View, we need to do the same for the Event Details View. Right-click on the ViewControllers group in the Project Navigator and add a new file called EventDetailsViewController using the UIViewController subclass template and subclassing from the UITableViewController.

In the new view controller’s header file, add the following:

  1: #import <UIKit/UIKit.h>
  2: #import "SEEvent.h"
  3: #import "SESpeaker.h"
  4: #import "SEData.h"
  5:
  6: typedef enum {
  7:     EventDetailsViewControllerSectionGeneral = 0,
  8:     EventDetailsViewControllerSectionPresenter = 1
  9: } EventDetailsViewControllerSection;
 10:
 11: typedef enum {
 12:     EventDetailsViewControllerGeneralRowEventName = 0,
 13:     EventDetailsViewControllerGeneralRowEventDate = 1,
 14:     EventDetailsViewControllerGeneralRowEventLocation = 2,
 15:     EventDetailsViewControllerGeneralRowEventDescription = 3
 16: } EventDetailsViewControllerGeneralRow;
 17:
 18: @interface EventDetailsViewController : UITableViewController <SESpeakerDelegate>
 19:
 20: @property(nonatomic, retain) SEEvent *event;
 21: @property(nonatomic, retain) SESpeaker *presenter;
 22:
 23: @end
 24:

Lines 2-4 imports the classes that we’ll be using from our model in this view controller. We also define two enums, one to represent the sections we have in this view (lines 6-9) and one to represent the different rows we have in the first section of the table view (lines 11-16).

On line 18, I also indicate that this interface will conform to the SESpeakerDelegate protocol (which I defined in the model class SESpeaker), so that when the speaker’s image has been loaded, this new view controller class will be notified and can act appropriately.

Lines 20-21 just adds two properties where I’ll store the SEEvent instance and its associated SESpeaker instance that we’ll use to display data from. Make sure to synthesize both of these properties in the .m file.

The next step is to create our own setter method for this view controller for the event:

  1: -(void)setEvent:(SEEvent *)newEvent
  2: {
  3:     event = newEvent;
  4:
  5:     [self.tableView reloadData];
  6: }

The setter method just takes the new event, sets it to this class’s event property and then forces a reload of the Table View’s data.

Because we say that this class conforms to the SESpeakerDelegate protocol, we should add the implementation for speaker:didLoadImage:. The implementation is below:

  1: -(void)speaker:(SESpeaker *)speaker didLoadImage:(UIImage *)image
  2: {
  3:     NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:1];
  4:
  5:     UITableViewCell *cell = [[self tableView] cellForRowAtIndexPath:indexPath];
  6:     cell.imageView.image = speaker.image;
  7:     [cell setNeedsLayout];
  8: }

When this callback method runs, I get the image that’s passed and set it to the UIImageView’s image which is part of the UITableViewCell. Then I send a message to the cell to reset its layout.

The following code handles the required methods for the UITableViewSource protocol:

  1: - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
  2: {
  3:     return 2;
  4: }
  5:
  6: - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  7: {
  8:     EventDetailsViewControllerSection sec = section;
  9:
 10:     if(sec == EventDetailsViewControllerSectionGeneral)
 11:         return 4;
 12:     else
 13:         return 1;
 14: }
 15:
 16: - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
 17: {
 18:     EventDetailsViewControllerSection section = indexPath.section;
 19:     EventDetailsViewControllerGeneralRow row = indexPath.row;
 20:
 21:     NSString *cellIdentifier;
 22:
 23:     if (section == EventDetailsViewControllerSectionGeneral)
 24:     {
 25:         if (row != EventDetailsViewControllerGeneralRowEventDescription)
 26:         {
 27:             cellIdentifier = @"DefaultCell";
 28:         }
 29:         else
 30:         {
 31:             cellIdentifier = @"DescriptionCell";
 32:         }
 33:     }
 34:     else
 35:     {
 36:         cellIdentifier = @"PresenterCell";
 37:     }
 38:
 39:     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
 40:     if (cell == nil) {
 41:         cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
 42:
 43:         if(section == EventDetailsViewControllerSectionPresenter)
 44:         {
 45:             cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
 46:
 47:         }
 48:         else
 49:         {
 50:             if (row == EventDetailsViewControllerGeneralRowEventDescription)
 51:             {
 52:                 UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(15, 5, 290, 75)];
 53:                 textView.textColor = [UIColor blackColor];
 54:                 textView.editable = NO;
 55:                 [cell addSubview:textView];
 56:             }
 57:         }
 58:     }
 59:
 60:
 61:     if (section == EventDetailsViewControllerSectionGeneral)
 62:     {
 63:         switch(indexPath.row)
 64:         {
 65:             case EventDetailsViewControllerGeneralRowEventName:
 66:                 cell.textLabel.text = event.eventName;
 67:                 break;
 68:             case EventDetailsViewControllerGeneralRowEventDate:
 69:                 cell.textLabel.text = [event eventDateAsString];
 70:                 break;
 71:             case EventDetailsViewControllerGeneralRowEventLocation:
 72:                 cell.textLabel.text = [event eventLocation];
 73:                 break;
 74:             case EventDetailsViewControllerGeneralRowEventDescription:
 75:                 ((UITextView *)[cell.subviews objectAtIndex:cell.subviews.count-1]).text = event.eventDescription;
 76:                 break;
 77:             default:
 78:                 cell.textLabel.text = @"";
 79:                 break;
 80:         }
 81:     }
 82:     else
 83:     {
 84:         SEData *data = [SEData sharedManager];
 85:         [data fetchSpeakerWithRowKey:event.speakerKey withCompletionHandler:^(SESpeaker *speaker, NSError *error)
 86:         {
 87:             self.presenter = speaker;
 88:             self.presenter.delegate = self;
 89:
 90:             if(error)
 91:             {
 92:                 cell.textLabel.text = event.speakerName;
 93:             }
 94:             else
 95:             {
 96:                 cell.textLabel.text = speaker.name;
 97:                 cell.imageView.image = speaker.image;
 98:                 [cell setNeedsLayout];
 99:             }
100:         }];
101:
102:
103:     }
104:
105:     return cell;
106: }

numberOfSectionsInTableView: and tableView:numberOfRowsInSection: should be obvious. The first returns 2 sections like the view we designed in the storyboard and the second returns 4 rows if it’s the first section and 1 if it’s the second section (the presenter section).

The tableView:cellForRowAtIndexPath: requires some explanation. First, we determine what UITableViewCell to use based on the section and row. We have three types of cells: a default cell which we use for the Event’s name, date, and location. There is a description cell where I need to present a rather long summary of the event. And of course there’s the presenter cell, which will display a presenter’s picture, name, and title, as well as a disclosure indicator to indicate to the user that they can select the cell to see more information about the presenter (set in lines 43-47).

Because the event description text is so long, I wanted to present it inside of a UITextView because the UITextView supports scrolling, since it inherits from UIScrollView. After I’ve created and configured the UITextView, I just add it as a subview of the cell. The creation, configuration, and adding as a subview of the UITextView is all in lines 52-55.

Lines 61-81 just basically figures out the appropriate data from the Event to put in the cell, depending on what the current section and row are. If the current section/row is for the presenter, then I use the SEData singleton to send a fetch request for the speaker associated with the event. When the request has completed, I then just use the properties of the SESpeaker to set the cell’s controls properly. All of this is done in lines 84-100.

Going back to the event’s description, in order to accommodate for the length, we should increase the size of that row as well. In order to do that, we need to implement tableView:heightForRowAtIndexPath: from the UITableViewDelegate protocol. This is the implementation of that:

  1: - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
  2: {
  3:     EventDetailsViewControllerSection section = indexPath.section;
  4:     EventDetailsViewControllerGeneralRow row = indexPath.row;
  5:
  6:     if (section == EventDetailsViewControllerSectionGeneral && row == EventDetailsViewControllerGeneralRowEventDescription)
  7:     {
  8:         return 85;
  9:     }
 10:     else
 11:     {
 12:         return 45;
 13:     }
 14: }

I basically almost double the size of the description row as compared to the other rows. If there is an overflow of text, the UITextView can be scrolled.

So now that the code for this controller is done, I need to tell the EventsTableViewController that when a cell is selected in that controller, this new controller needs to be presented. To do that, I need to implement prepareForSegue:sender:. In EventsTableViewController.m, add the following:

  1: - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
  2: {
  3: 	if ([segue.identifier isEqualToString:@"EventDetailsSegue"])
  4: 	{
  5:             EventDetailsViewController *eventDetailsViewController = segue.destinationViewController;
  6:             NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
  7:             SEEvent *event = [self.events objectAtIndex:indexPath.row];
  8:
  9:             eventDetailsViewController.event = event;
 10: 	}
 11: }

Basically, all this code does is, when the segue is EventDetailsSegue, get the event that was selected from the events array and set the event property of the EventDetailsViewController to the selected event. Then the rest of the segue handling that we configured will continue. If you run the code now, then you’ll see that when you select a row from the list of events, you will be taken to the details of the event.

image

 

Presenter Details View Controller

imageWhen the user clicks on the presenter row in the event details view, I want to take them to a view that contains more information about the speaker. In order to do that, add a View Controller to the storyboard and set its title to Presenter. There are four pieces of speaker information to present in this view: the speaker’s name, his/her title, bio, and image. For the name and title, two Label controls are needed. Add a Text View control for the bio and and Image View for the speaker’s photo. The name should be bold and the image should have a mode of Aspect Fit. Position and size the controls so it looks like the view on the right.

PresenterDetailsViewController Class

Create a new UIViewController class that the Presenter Details View Controller created above will use as its class. This time, just use UIViewController as the parent class. Add the following to the .h file and connect the IBOutlets to the corresponding control on the view on the storyboard:

  1: #import <UIKit/UIKit.h>
  2: #import "SESpeaker.h"
  3: @interface PresenterDetailsViewController : UIViewController
  4:
  5: @property(nonatomic, retain) SESpeaker *speaker;
  6: @property(nonatomic, retain) IBOutlet UIImageView *image;
  7: @property(nonatomic, retain) IBOutlet UILabel *speakerName;
  8: @property(nonatomic, retain) IBOutlet UILabel *speakerTitle;
  9: @property(nonatomic, retain) IBOutlet UITextView *speakerBio;
 10:
 11: @end

To handle the view lifecycle, in the class implementation, change viewDidLoad and viewDidUnload to the following:

  1: - (void)viewDidLoad
  2: {
  3:     [super viewDidLoad];
  4:     image.image = speaker.image;
  5:     speakerName.text = speaker.name;
  6:     speakerTitle.text = speaker.title;
  7:     speakerBio.text = speaker.bio;
  8: }
  9:
 10:
 11: - (void)viewDidUnload
 12: {
 13:     [super viewDidUnload];
 14:
 15:     image.image = nil;
 16:     speakerName.text = nil;
 17:     speakerTitle.text = nil;
 18:     speakerBio = nil;
 19:     speaker = nil;
 20: }

Amazingly, that’s all you need for this view controller. Everything else is handled through control configurations and connections between the view controller’s IBOutlets and the controls in the view. Everything is handled … except for the transition from the event details view to the presenter view.

Event Details to Presenter Details Segue

In order for the user to be able to navigate from the event details to presenter details, we need to create a segue between the two. In the storyboard, make sure to have the entire UITableViewController of the Event Details selected and Control + Drag from that to the Presenter Details View. Do not create the segue from one of the cells like we did with the Events Table View. The segue identifier should be set to EventPresenterSegue and the segue style should be Push.

Now in EventDetailsViewController.m, add an #import “PresenterDetailsViewController.h” to the top of the file. We also need to implement tableView:didSelectRowAtIndexPath: from the UITableViewDelegate protocol:

  1: - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  2: {
  3:     if (indexPath.row == 0 && indexPath.section == EventDetailsViewControllerSectionPresenter) {
  4:         [self performSegueWithIdentifier:@"EventPresenterSegue" sender:self];
  5:     }
  6: }

The code above checks to see if the cell that was tapped is the cell for the presenter and if it is, it sends a message to go ahead and start the EventPresenterSegue.

In order to send the correct SESpeaker instance from the Event Details view to the Presenter Details view, also add the following to EventDetailsViewController.m:

  1: - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
  2: {
  3: 	if ([segue.identifier isEqualToString:@"EventPresenterSegue"])
  4: 	{
  5: 		PresenterDetailsViewController *presenterDetailsViewController = segue.destinationViewController;
  6: 		presenterDetailsViewController.speaker = [self presenter];
  7: 	}
  8: }

This code checks to see if the segue being performed is the EventPresenterSegue and, if it is, grabs a reference to the destination view controller which, in this case, should be a PresenterDetailsViewController. It then assigns its presenter property to the PresenterDetailsViewController’s speaker property. That’s all that’s needed to make the segue from the Event Details to the Presenter Details work.

image

 

Speakers Table View Controller

The Speakers Table View Controller, which we added in the very beginning of this post, can created similarly as the Events Table View Controller. It can also have a segue to the Presenter Details View Controller. You will still need a subclass of UITableViewController to handle the logic of presenting the speaker information to various controls on the view. You’ll only need one cell prototype as all the cells for each speaker should look the same. At the end, your full storyboard should look like this:

image

In the interest of brevity, I’m not going to go through all the steps of how to create the Speakers Table View. If you do want all of that code (and all of the code for parts 1 and 2 of this post) please tweet out a link to both posts and then ping me on Twitter to let me know that you did and I’ll go ahead and send you the projects.

Conclusion

For the first parts of this series, I took you through setting up Azure tables and blobs that were used to store information and images for speakers and events. I went through the build process for the Windows Azure iOS Toolkit, as well as using the toolkit to get data from Windows Azure. I then went through the steps on how to build a UI that presented this data.

For the next part in this series, I plan on adding some authentication and authorization to the SpeakEasy app that will force users to log in using one of the identity providers that Azure Access Control Services supports, as well as allowing users who log in through ADFS certain capabilities not available to the general public. Please keep an eye out for that post in the future.

Oh, and please don’t forget to tweet this so I can send you the code!

Using Azure Storage Services from an iPhone App – Part 1: Table and Blob setup, Azure iOS toolkit, and Model Classes

Windows Azure as a Mobile Backend Platform

Today, I posted a video blog discussing why using Windows Azure as a mobile backend platform is an attractive option. I wanted to quickly summarize the contents of that video and also supplement it with some helpful links.

Summary

Backend platform selection is important because, unless you’re talking about the most trivial of apps, most mobile applications will have some need to connect to backend services and data. It’s important to select a platform that’s flexible, scalable to your app and users’ needs, and provides tools that make building and managing your applications easier.

Windows Azure and SQL Azure is a compelling PaaS for mobile for the following reasons:

Cost

Windows Azure has an attractive “pay for what you use” pricing model. You only pay for the bandwidth, storage, and compute processing that you consume.

For more details on pricing, see the following:

Scalability

Windows Azure is a highly-scalable platform. You can start small (for example, a single instance of a VM with a single 1GHz CPU and less than 1GB of memory or a 5GB SQL Azure database), and scale up and out as the need arises. This is an especially good strategy for mobile consumer apps (where app usage will probably not be as demanding initially) until you’ve determined that your running at or near maximum capacity with your resources.

Windows Azure also provides usage monitoring tools and management APIs so you can detect and react appropriately to predictable or unpredictable bursts in usage patterns by dynamically starting more instances. There are also third party tools available, like AzureWatch, that help you monitor and dynamically adjust instances based on demand.

High Availability

Windows Azure is also a platform that can provide your mobile application with highly-available back-end services. If your service or virtual machine is down, Azure will automatically try to restart it. If the service or virtual machine can’t be restarted due to hardware failure, then the platform will automatically create a new virtual machine on another physical server and deploy and run your service on it. And if you’ve got at least two instances of your service running, then Microsoft’s Service Level Agreement (SLA) guarantees a 99.95% uptime rate.

Development Platform Support

Azure supports using the .NET framework but if you’re not accustomed to the .NET framework, Azure also supports Java, PHP, and Node.js if those are the development environments you’re more familiar with.

Java SDK

PHP SDK

Node.js SDK

Platform Services

The Azure platform also provides the following services that are typically essential for mobile applications:

Data Storage

Several different data storage options are available:

  • Relational Databases with SQL Azure
  • Non-relational, semi-structured databases with Table Storage (NoSQL)
  • Blob Storage for storing large, unstructured binary files like images, audio, and video
  • Queues for reliable and persistent messaging between applications and services

Data Syncing

For applications with the need for offline capabilities that synchronize to a back-end data store when the application is back online, Microsoft Sync Framework, through OData, supports syncing to and from mobile devices.

Sync Framework Toolkit

Content Delivery Network (CDN)

Mobile applications targeted for global use can take advantage of Azure’s CDN to help build responsive applications. Your mobile application’s assets (images, videos, etc) can be hosted on edge servers around the world and devices can retrieve these assets from the server that is closest to their location.

Authentication and Authorization

If your application has a need for authentication and authorization, Windows Azure App Fabric also has Access Control Services (ACS) which can be used to set up and manage that for you. ACS has support for different identity providers, including Facebook, Active Directory Federation Services (ADFS), and others.

Note that in the video, I mentioned Twitter; however, this is incorrect. Right now ACS supports OAuth WRAP and OAuth 2.0 and Twitter only officially supports OAuth 1.0A. So Twitter is out, for now.

Native Toolkits

Finally, the Windows Azure Platform Team has done a nice job of providing toolkits for most of the major mobile platforms (none for Blackberry). Using these toolkits makes it easy to connect to and leverage the Azure platform services.

Conclusion

I hope you found the video helpful and the subsequent summary useful. I have hopes to turn it into a series with tutorials on how to use these various services and toolkits in the future. If that is something that is of interest to you, please let me know in the comments below!

Considerations for Delivering Successful Mobile Projects

There is no denying that mobility is currently a hot topic. Staggering forecasts by various analysts have made capitalizing on the mobile applications market a top priority for many organizations’ CIOs and CTOs. However, before diving headfirst into mobility projects, it is important to consider various factors in order to define a clear strategy that addresses the many opportunities and challenges that mobility presents for your organization.

During Q4 of 2010, smartphone sales surpassed global PC sales for the first time in history. This turning point arrived quicker than most analysts predicted. By 2013, it is expected that the combined global sales of smartphones and tablets will double the sales of PCs. This estimation might be conservative. With lower-cost smartphones expected to be available on the market due to Microsoft’s partnership with Nokia and with less expensive tablets like the Kindle Fire certain to arrive in the near future as alternatives to the Apple iPad, it is likely that this turning point will arrive sooner. And these devices aren’t simply meant to supplement traditional PCs and laptops; for many users, mobile devices will be the only way they will access the internet.

Organizations should view these forecasts and trends as opportunities. The revenue potential is huge and only continues to grow. With a well-devised strategy, the mobile channel can present a unique opportunity to further engage with customers, differentiate from competitors or establish a position of leadership in your market and ultimately increase revenue. As a disruptive technology, mobile devices can even be a catalyst for organizations to create new, untapped markets (for example, location-based social network applications like Foursquare1).

In order to capitalize on these opportunities and to shape your strategy, consider the following factors:

  • Business Objectives– What are your organization’s top-level goals? It is important to align your mobility strategy to these goals. When selecting the mobile projects your organization will undertake, does your selection criteria include how the projects help your organization achieve its goals? For example, if your goals this year are to increase revenue and to increase brand awareness, are you utilizing mobile to its full potential as a revenue channel and do you have plans to integrate your mobile applications with social networks?
  • Customer Expectations – What do your high-value customers2 expect from you? Do they expect that when they browse your website from a mobile device, they’ll get a mobile-optimized view?3Do they expect applications targeted for their specific device or mobile OS? More and more smartphone and tablet users demand mobile applications that provide a rich and compelling view of content and provide the real-time information they need while on-the-go.
  • Competitive Advantage – What features do your competitions’ mobile applications provide? You can almost certainly bet that if your competition provides these features, your customers will expect similar features in your applications as well. Beyond these core features, what other features can you provide to set your applications and services apart from those of your competitors?
  • Go-to-Market Tactics – How will you launch your application? What tools will you use, which platforms and devices will you target? Can you take an agile approach and launch an application quickly (i.e., get something out on the app store or your mobile-optimized website) and do quick iterations? Or do the needs of your target customer/audience require you to have a fuller-fledged, feature-rich application from the outset? And once your application is on the market, how do you plan on driving sales? What marketing campaigns are vital and necessary to the success of the application?
  • Organizational Readiness – Is your company in a position to deliver mobile solutions? Does your company have the capacity in-house to develop and manage/support mobile-optimized web sites and/or mobile applications or will you have to establish a partnership with companies that are ready to do so? Do you have the appropriate infrastructure to support these applications or do you need to leverage cloud solution providers?

These factors are not meant to be an exhaustive list, but a starting point for developing a strategy for ensuring successful mobility projects4. However, addressing these factors and answering the related questions can help you establish a roadmap in delivering the appropriate mobile applications and services by your organization.

What other factors do you believe are important in ensuring successful mobile projects?


.ExternalClass18C4EBB83FF1476A889CF001958F0E81 .notes
{margin-bottom:5px;margin-top:5px;}

1 Can ‘location-based social network applications’ be even considered as a market?

2 By ‘high-value’, I don’t only mean to refer to customers who have spent the most money. You should also consider the social value of your customers as part of the valuation process.

3 Rhetorical question – of course they expect this. Browsing to a company’s website on my mobile device and not having it rendered optimized for mobile is one of my biggest pet-peeves. Every company should have a mobile-optimized version of their site.

4 These factors also don’t cover your enterprise mobility strategy (to be covered in a future blog post).

Cannot modify SharePoint list views with tracing enabled

I was debugging an issue with a heavily customized SharePoint site where users were unable to save any changes they made to SharePoint list views. Since this was a heavily customized site with several custom list definitions, my initial thought was that we may have botched a list definition or two and made these lists and list views non-modifiable. However, I also tested creating an out of the box SharePoint List and found out that its views were not modifiable as well. In order to isolate the issue, I created a second web application with a simple Team Site site collection. In this web app/site collection, the list views were all modifiable so I knew that the issue was definitely related to something in our particular web application.

The next thing I did was check the ULS logs. In the ULS logs, I noticed the following entries immediately posted after attempting to modify a list view:

09/26/2011 10:39:40.52     w3wp.exe (0x1790)                           0x0EB8    SharePoint Foundation             Monitoring                        nasq    Medium      Entering monitored scope (Request (POST:http://test-sp:80/_vti_bin/owssvr.dll?CS=65001))
09/26/2011 10:39:40.52     w3wp.exe (0x1790)                           0x0EB8    SharePoint Foundation             Logging Correlation Data          xmnv    Medium      Name=Request (POST:http://test-sp:80/_vti_bin/owssvr.dll?CS=65001)    9446a0f0-1577-472e-a8af-a552b1b4cc39
09/26/2011 10:39:40.60     w3wp.exe (0x1790)                           0x173C    SharePoint Foundation             General                           af71    Medium      HTTP Request method: POST    9446a0f0-1577-472e-a8af-a552b1b4cc39
09/26/2011 10:39:40.60     w3wp.exe (0x1790)                           0x173C    SharePoint Foundation             General                           af75    Medium      Overridden HTTP request method: POST    9446a0f0-1577-472e-a8af-a552b1b4cc39
09/26/2011 10:39:40.60     w3wp.exe (0x1790)                           0x173C    SharePoint Foundation             General                           af74    Medium      HTTP request URL: /_vti_bin/owssvr.dll?CS=65001    9446a0f0-1577-472e-a8af-a552b1b4cc39
09/26/2011 10:39:40.69     w3wp.exe (0x1790)                           0x173C    SharePoint Foundation             General                           b9y3    High        Failed to open the file 'C:Program FilesCommon FilesMicrosoft SharedWeb Server Extensions14Resourceswss.en-US.resx'.    9446a0f0-1577-472e-a8af-a552b1b4cc39
09/26/2011 10:39:40.69     w3wp.exe (0x1790)                           0x173C    SharePoint Foundation             General                           b9y4    High        #20015: Cannot open "": no such file or folder.    9446a0f0-1577-472e-a8af-a552b1b4cc39
09/26/2011 10:39:40.69     w3wp.exe (0x1790)                           0x173C    SharePoint Foundation             General                           b9y4    High        (#2: Cannot open "": no such file or folder.)    9446a0f0-1577-472e-a8af-a552b1b4cc39
09/26/2011 10:39:40.69     w3wp.exe (0x1790)                           0x173C    SharePoint Foundation             General                           b9y9    High        Failed to read resource file "C:Program FilesCommon FilesMicrosoft SharedWeb Server Extensions14Resourceswss.en-US.resx" from feature id "(null)".    9446a0f0-1577-472e-a8af-a552b1b4cc39
09/26/2011 10:39:40.69     w3wp.exe (0x1790)                           0x173C    SharePoint Foundation             General                           8e26    Medium      Failed to open the language resource keyfile wss.    9446a0f0-1577-472e-a8af-a552b1b4cc39
09/26/2011 10:39:40.69     w3wp.exe (0x1790)                           0x173C    SharePoint Foundation             General                           b9y3    High        Failed to open the file 'C:Program FilesCommon FilesMicrosoft SharedWeb Server Extensions14Resourceswss.resx'.    9446a0f0-1577-472e-a8af-a552b1b4cc39
09/26/2011 10:39:40.69     w3wp.exe (0x1790)                           0x173C    SharePoint Foundation             General                           b9y4    High        #20015: Cannot open "": no such file or folder.    9446a0f0-1577-472e-a8af-a552b1b4cc39
09/26/2011 10:39:40.69     w3wp.exe (0x1790)                           0x173C    SharePoint Foundation             General                           b9y4    High        (#2: Cannot open "": no such file or folder.)    9446a0f0-1577-472e-a8af-a552b1b4cc39
09/26/2011 10:39:40.69     w3wp.exe (0x1790)                           0x173C    SharePoint Foundation             General                           b9y9    High        Failed to read resource file "C:Program FilesCommon FilesMicrosoft SharedWeb Server Extensions14Resourceswss.resx" from feature id "(null)".    9446a0f0-1577-472e-a8af-a552b1b4cc39
09/26/2011 10:39:40.69     w3wp.exe (0x1790)                           0x173C    SharePoint Foundation             General                           8e26    Medium      Failed to open the language resource keyfile wss.    9446a0f0-1577-472e-a8af-a552b1b4cc39
09/26/2011 10:39:40.69     w3wp.exe (0x1790)                           0x173C    SharePoint Foundation             General                           8l3c    Medium      Localized resource for token 'multipages_direction_dir_value%>' could not be found for file with path: "(unavailable)".    9446a0f0-1577-472e-a8af-a552b1b4cc39
09/26/2011 10:39:40.73     w3wp.exe (0x1790)                           0x0EB8    SharePoint Foundation             Monitoring                        b4ly    Medium      Leaving Monitored Scope (Request (POST:http://test-sp:80/_vti_bin/owssvr.dll?CS=65001)). Execution Time=206.33069286739    9446a0f0-1577-472e-a8af-a552b1b4cc39

The key errors I noticed were the fact that, for some reason, certain resource (.resx) files couldn’t be loaded. These errors were quite strange. First, I wasn’t sure why these resource files were being loaded in the first place. Second, I could not understand why SharePoint was trying to load the files from C:Program FilesCommon FilesMicrosoft SharedWeb Server Extensions14Resources. These resource files don’t reside there; they are located under the application’s App_GlobalResources directory. But even manually placing the correct resource files in the Resources directory where the application was attempting to load from didn’t resolve the issue.

A little bit of research led me to a post by Ivan Neganov entitled ‘Writing Trace Output to ULS Log in SharePoint 2010’. In the post, Ivan describes a little caveat that enabling ASP.NET tracing caused some SharePoint instability, namely the inability to create a new web part page and the inability to open up a SharePoint site with tracing enabled with SharePoint Designer. Taking that clue, I removed the system.web/tracing element from our application’s web.config and, sure enough, that resolved the issue.

So there seems to be a little laundry of things broken by ASP.NET tracing in SharePoint 2010. Have any of you had any other issues you’ve encountered?

Powershell Cmdlet for FAST Search Document Removal

On a project I’m currently on, we had a scenario where we needed to support being able to quickly remove potentially many documents from the FAST Search index. Unfortunately, the FAST web administration only allows you to delete one document at a time, which would definitely not be suitable for our scenario. We had a couple of ideas on how we were going to tackle the problem. One of the ideas we tossed around was using the FAST Content API. Although we didn’t end up using this technique for the project, I still believed that using the Content API along with Powershell to be a very useful and powerful combination. So today, I spent a little bit of time working on a Powershell cmdlet that can remove many items from the FAST index.

Visual Studio 2010 Project Setup

The first thing to do is to create a Class Library project in Visual Studio and add a reference to the Esp-Contentapi.dll from the ESP SDK. You’ll also want to add a reference to both System.Management.Automation.dll (found in C:WindowsassemblyGAC_MSILSystem.Management.Automation1.0.0.0__31bf3856ad364e35) and System.Configuration.Install.dll (in C:WindowsMicrosoft.NETFrameworkv2.0.50727).

After adding the three dlls, you want to add two class files to the project, a Powershell snap-in class and a class for the cmdlet. In my project, my snap-in class is PointBridge.FAST.Cmdlets.PointBridgeFASTSnapIn and the cmdlet class is PointBridge.FAST.Cmdlets.Content.RemoveContentItem. The code and explanation of these classes follows.

PointBridge.FAST.Cmdlets.PointBridgeFASTSnapIn

This class derives from PSSnapIn and is used to register all the cmdlets in the assembly. When deriving from PSSnapIn, you need to override the following three properties: Name, Description, Vendor.

The class also is decorated with the RunInstaller attribute, in order to be able to install the assembly using installutil.exe.

 1: using System;
 2: using System.Collections.Generic;
 3: using System.Linq;
 4: using System.Text;
 5: using System.ComponentModel;
 6: using System.Configuration.Install;
 7: using System.Management.Automation;
 8:
 9: namespace PointBridge.FAST.Cmdlets
 10: {
 11:     [RunInstaller(true)]
 12:     public class PointBridgeFASTSnapIn : PSSnapIn
 13:     {
 14:         public override string Name
 15:         {
 16:             get { return "PointBridgeFASTSnapIn"; }
 17:         }
 18:
 19:         public override string Description
 20:         {
 21:             get { return "Various cmdlets to help with FAST management."; }
 22:         }
 23:
 24:         public override string Vendor
 25:         {
 26:             get { return "PointBridge";  }
 27:         }
 28:     }
 29: }

PointBridge.FAST.Cmdlets.Content.RemoveContentItem

This class, which derives from Cmdlet, is the main class than handles the processing. When building cmdlets, you decorate the class with a Cmdlet attribute. This attribute is used to indicate the verb-noun pair used to invoke your cmdlet. In this instance, because of this attribute, my cmdlet is invoked as ‘Remove-ContentItem’ from the shell.

The RemoveContentItem class has three Powershell parameters:

  • ContentID – the ID of the content to delete from the FAST index.
  • Collection – the name of the collection in FAST where the item is in.
  • ContentDistributor – the server/port of the FAST ContentDistributor.

In the BeginProcessing() method (overridden from the Cmdlet base class), I set up an instance of an IDocumentFeeder object to be used later, when processing each record. The IDocumentFeeder is an interface that allows you to work with a FAST ESP collection for adding/removing/updating documents within that collection. You can get an instance of an IDocumentFeeder by calling the static CreateDocumentFeeder method of the Com.FastSearch.Esp.Content.Factory class.

In the ProcessRecord() method, I call the RemoveDocument() method of the IDocumentFeeder object to queue up the removal of the content item. The ProcessRecord() method is called for each ContentID passed into the cmdlet from the pipeline.

Finally, in the EndProcessing() method, I take care of reporting and clean up. The call to IDocumentFeeder.WaitForCompletion() is used to make sure all deletes that were submitted are complete (successfully or not) before we continue. After the deletes have been processed, I used the IDocumentFeederStatus object returned from IDocumentFeeder.GetStatusReport() to build up a report of the deletes that failed or executed with warnings.

 1: using System;
 2: using System.Collections;
 3: using System.Collections.Specialized;
 4: using System.Management.Automation;
 5: using Com.FastSearch.Esp.Content;
 6: using Com.FastSearch.Esp.Content.Config;
 7: using Com.FastSearch.Esp.Content.Errors;
 8: using Com.FastSearch.Esp.Content.Util;
 9:
 10: namespace PointBridge.FAST.Cmdlets.Content
 11: {
 12:     [Cmdlet("Remove", "ContentItem")]
 13:     public class RemoveContentItem : Cmdlet
 14:     {
 15:         [Parameter(Mandatory=true, ValueFromPipeline=true, Position=0)]
 16:         public string ContentID { get; set; }
 17:
 18:         [Parameter(Mandatory=true, Position=1)]
 19:         public string Collection { get; set; }
 20:
 21:         [Parameter(Mandatory=true, Position=2)]
 22:         public string ContentDistributor { get; set; }
 23:
 24:         private IDocumentFeeder _feeder = null;
 25:
 26:         protected override void BeginProcessing()
 27:         {
 28:             base.BeginProcessing();
 29:             try
 30:             {
 31:                 _feeder = Factory.CreateDocumentFeeder(ContentDistributor, Collection);
 32:             }
 33:             catch (Exception ex)
 34:             {
 35:                 WriteError(new ErrorRecord(ex, "ContentFactoryOperationError", ErrorCategory.InvalidOperation, _feeder));
 36:             }
 37:
 38:         }
 39:
 40:         protected override void ProcessRecord()
 41:         {
 42:             base.ProcessRecord();
 43:
 44:             if (_feeder == null) return;
 45:
 46:             long opID = _feeder.RemoveDocument(ContentID);
 47:             WriteObject(string.Format("Removing item '{0}'. Operation ID: {1}", ContentID, opID));
 48:
 49:         }
 50:
 51:         protected override void EndProcessing()
 52:         {
 53:             base.EndProcessing();
 54:
 55:             if (_feeder == null) return;
 56:
 57:             _feeder.WaitForCompletion();
 58:             BuildStatusReport(_feeder.GetStatusReport());
 59:             _feeder.Dispose();
 60:         }
 61:
 62:         private void BuildStatusReport(IDocumentFeederStatus status)
 63:         {
 64:             if (status.HasDocumentErrors())
 65:             {
 66:                 WriteObject(string.Format("Total Errors: {0}", status.NumDocumentErrors));
 67:
 68:                 foreach (Pair p in status.AllDocumentErrors)
 69:                 {
 70:                     DocumentError error = (DocumentError)p.Second;
 71:
 72:                     WriteObject(string.Format("Operation ID: {0} Document ID: {1} Error Code: {2} Description: {3}",
 73:                         (long)p.First, error.DocumentId, error.ErrorCode, error.Description));
 74:
 75:                 }
 76:             }
 77:
 78:             if (status.HasDocumentWarnings())
 79:             {
 80:                 WriteObject(string.Format("Total Warnings: {0}", status.NumDocumentWarnings));
 81:
 82:                 foreach (Pair p in status.DocumentWarnings)
 83:                 {
 84:                     DocumentWarning warning = (DocumentWarning)p.Second;
 85:
 86:                     WriteObject(string.Format("Operation ID: {0} Document ID: {1} Warning Code: {2} Description: {3}",
 87:                         (long)p.First, warning.DocumentId, warning.WarningCode, warning.Description));
 88:
 89:                 }
 90:
 91:             }
 92:         }
 93:     }
 94: }

Using the cmdlet

In order to use the cmdlet, open up a new Powershell window and use installutil.exe to install the snap-in:

PS> CD [location of assemblies]
PS>  set-alias installutil $env:windirMicrosoft.NETFramework64v2.0.50727installutil
PS> installutil PointBridge.FAST.Cmdlets.dll

 

You only need to run installutil one time and the snap-in can be added on any subsequent Powershell sessions.

The following is an example of how to use the cmdlet:

 1: PS> add-pssnapin pointbridgefastsnapin
 2: PS> $contentids = "http://www.deviantpoint.com/category/ASPNET.aspx", "http://www.deviantpoint.com/?tag=/moss", "FAKEID", "http://www.deviantpoint.com/category/Workflow.aspx"
 3: PS> $contentids | remove-contentitem -collection "WebCollection" -contentdistributor "fsis:16100" | out-file "c:tempresults.txt"

Line 1 just adds the snap-in for use in my current session. Line 2 sets up an array of the content ids I want to delete from my collection. This array (or set of records to process) can be read from a file, database, wherever. Here, I just set it up directly as an example. The third id in the example above is a fake id that doesn’t actually exist in my collection. Lastly, I take my content id array, pipe it to my remove-contentitem cmdlet and the results are sent to an output file (not necessary to push to an output file but I always like to, instead of everything dumping on the screen).

The results of running this cmdlet looks like this:

Removing item 'http://www.deviantpoint.com/category/ASPNET.aspx'. Operation ID: 1
Removing item 'http://www.deviantpoint.com/?tag=/moss'. Operation ID: 2
Removing item 'FAKEID'. Operation ID: 3
Removing item 'http://www.deviantpoint.com/category/Workflow.aspx'. Operation ID: 4
Total Errors: 1
Operation ID: 3 Document ID: FAKEID Error Code: 3 Description: Document e14c677abdbd9678dbfe1e5580de9aef_WebCollection does not exist

 

The nice thing about wrapping this up in a cmdlet is that I can reuse this cmdlet in my Powershell scripts so that I can easily remove any unwanted content from my collections.

So here is the shameless plug – If you want a copy of the Visual Studio solution, use the Tweet link below to tweet this post. Then send me an email (btubalinal@pointbridge.com) and I’ll send you a copy of the solution.