Skip to main content
 
Go Search
Home
Categories
Bloggers
By: Bart Tubalinal | Posted: February 19, 2012 at 4:42 PM

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:

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 Controller to the canvas.
  2. With the new Table View Controller selected, from the Editor Menu, select Embed In > Navigation Controller. This will add a Navigation Controller to 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!

    By: Bart Tubalinal | Posted: February 18, 2012 at 8:11 PM

    This post has moved. New location

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

    Intro

    A few weeks ago, I posted a video and a follow-up blog on why I think using Windows Azure as a middle-tier or backend platform for mobile is compelling. Remaining on the topic of Azure and mobile, I wanted to write a series of blog posts that show you how to use various Azure services to build a native mobile app. Through this series, I’m going to build an iPhone app called SpeakEasy. The app will keep track of speakers/presenters and various events that they speak at. It’s a simple application but hopefully one that will show you how you can leverage Windows Azure when it’s time for you to build your next great mobile app.

    In the first part of the series, I’ll go through using the Windows Azure Table Storage and Blob Storage. The full series (as of this writing) will look like this:

    Azure Tables and Blobs Setup

    In order to get started, we’ll need some data. Since setting up the tables and blob containers isn’t the focus of this blog post, I am just going to post the code I used to populate the tables and blob container with minimal explanation.

    SpeakerEntity.cs

    The SpeakerEntity class is a simple class used to hold our speaker data. A speaker entity will be held in the “speakers” partition and will use a guid as its row identifier.

      1: using System;
    
      2: using System.Collections.Generic;
    
      3: using System.Linq;
    
      4: using System.Text;
    
      5: using Microsoft.WindowsAzure.StorageClient;
    
      6: 
    
      7: namespace SpeakEasyStorageSetup
    
      8: {
    
      9:     public class SpeakerEntity : TableServiceEntity
    
     10:     {
    
     11:         public SpeakerEntity(string name, string title, string bio, string imageUrl)
    
     12:         {
    
     13:             this.PartitionKey = "speakers";
    
     14:             this.RowKey = Guid.NewGuid().ToString();
    
     15: 
    
     16:             this.Name = name;
    
     17:             this.Title = title;
    
     18:             this.Bio = bio;
    
     19:             this.ImageUrl = imageUrl;
    
     20:         }
    
     21: 
    
     22:         #region Properties
    
     23:         public string Name { get; set; }
    
     24:         public string Title { get; set; }
    
     25:         public string Bio { get; set; }
    
     26:         public string ImageUrl { get; set; }
    
     27:         #endregion
    
     28:     }
    
     29: }

     

    EventEntity.cs

    The EventEntity class will be used to hold event data can will be in the “events” partition. Its row key will also be a guid.

      1: using System;
    
      2: using System.Collections.Generic;
    
      3: using System.Linq;
    
      4: using System.Text;
    
      5: using Microsoft.WindowsAzure.StorageClient;
    
      6: 
    
      7: namespace SpeakEasyStorageSetup
    
      8: {
    
      9:     public class EventEntity : TableServiceEntity
    
     10:     {
    
     11:         public EventEntity(DateTime eventDate, string eventName, 
    
     12:             string eventLocation, string eventDescription, SpeakerEntity speaker)
    
     13:         {
    
     14:             this.PartitionKey = "Events";
    
     15:             this.RowKey = Guid.NewGuid().ToString();
    
     16: 
    
     17:             this.EventDate = eventDate;
    
     18:             this.EventName = eventName;
    
     19:             this.EventLocation = eventLocation;
    
     20:             this.EventDescription = eventDescription;
    
     21:             this.SpeakerKey = speaker.RowKey;
    
     22:             this.SpeakerName = speaker.Name;
    
     23:         }
    
     24: 
    
     25:         #region Properties
    
     26: 
    
     27:         public DateTime EventDate { get; set; }
    
     28:         public string EventName { get; set; }
    
     29:         public string EventLocation { get; set; }
    
     30:         public string EventDescription { get; set; }
    
     31:         public string SpeakerKey { get; private set; }
    
     32:         public string SpeakerName { get; private set; }
    
     33: 
    
     34:         #endregion
    
     35:     }
    
     36: }
    
     37: 

     

    Program.cs

    The code below takes care of creating the table and blob storage in my Azure account. First, the blob storage container is created and permissions are set to public access. The container “speakers” holds each speaker’s images (note that the images are embedded resources in my project). After the blob container is created and populated, I then create a table with two partitions, “speakers” and “events”. The speakers and events are then populated with some randomized data.

      1: using System;
    
      2: using System.Collections.Generic;
    
      3: using System.Linq;
    
      4: using System.Text;
    
      5: using System.Configuration;
    
      6: using Microsoft.WindowsAzure;
    
      7: using Microsoft.WindowsAzure.StorageClient;
    
      8: using Microsoft.WindowsAzure.StorageClient.Tasks;
    
      9: using System.IO;
    
     10: using System.Reflection;
    
     11: 
    
     12: namespace SpeakEasyStorageSetup
    
     13: {
    
     14:     class Program
    
     15:     {
    
     16:         static string[] imgs = new string[] { "BTubalinal.jpg", "BJohnson.jpg", "TNielsen.jpg", "MMorse.jpg", 
    
     17:             "MOmar.jpg", "DOrlova.jpg", "CJones.jpg", "EGardner.jpg" };
    
     18: 
    
     19:         static void Main(string[] args)
    
     20:         {
    
     21:             //get the storage account from a connection string
    
     22:             CloudStorageAccount storageAccount = CloudStorageAccount.Parse(
    
     23:                 ConfigurationManager.ConnectionStrings["StorageConnectionString"].ConnectionString);
    
     24: 
    
     25:             //create the blob storage
    
     26:             var blobClient = storageAccount.CreateCloudBlobClient();
    
     27:             string containerName = "speakers";
    
     28:             var blobContainer = blobClient.GetContainerReference(containerName);
    
     29: 
    
     30:             if (blobContainer.CreateIfNotExist())
    
     31:             {
    
     32:                 blobContainer.SetPermissions(new BlobContainerPermissions()
    
     33:                 {
    
     34:                     PublicAccess = BlobContainerPublicAccessType.Container
    
     35:                 });
    
     36: 
    
     37:                 Console.WriteLine("Blob container created. Now populating ...");
    
     38:                 PopulateBlob(blobContainer);
    
     39:             }
    
     40: 
    
     41: 
    
     42:             //create the table 'events'
    
     43:             var tableClient = storageAccount.CreateCloudTableClient();
    
     44:             string eventsTable = "events";
    
     45:             if (tableClient.CreateTableIfNotExist(eventsTable))
    
     46:             {
    
     47:                 Console.WriteLine("Table created. Now populating...");
    
     48:                 PopulateTables(tableClient, blobContainer.Uri.ToString());
    
     49:             }
    
     50: 
    
     51:             Console.WriteLine("Completed.");
    
     52:             Console.ReadLine();
    
     53: 
    
     54:         }
    
     55: 
    
     56:         static void PopulateBlob(CloudBlobContainer blobContainer)
    
     57:         {
    
     58:             Assembly assm = Assembly.GetExecutingAssembly();
    
     59: 
    
     60:             for (int i = 0; i < imgs.Length; i++)
    
     61:             {
    
     62:                 string imgName = imgs[i];
    
     63:                 using (Stream imgStream = assm.GetManifestResourceStream(
    
     64:                     string.Format("SpeakEasyStorageSetup.images.{0}", imgName)))
    
     65:                 {
    
     66:                     var blob = blobContainer.GetBlobReference(imgName);
    
     67:                     blob.UploadFromStream(imgStream);
    
     68:                 }
    
     69:             }
    
     70:         }
    
     71: 
    
     72:         static void PopulateTables(CloudTableClient tableClient, string imgBaseUrl)
    
     73:         {
    
     74:             string[] names = new string[] { "Bart Tubalinal", "Bert Johnson", "Travis Nielsen", 
    
     75:                 "Matt Morse", "Mo Omar", "Darya Orlova", "Callie Jones", "Ela Gardner" };
    
     76:             string[] titles = new string[] { "Solutions Architect", "Solutions Architect", "Principal Consultant", 
    
     77:                 "Practice Manager", "Principal Consultant", "Consultant", "Consultant", "Consultant" };
    
     78: 
    
     79:             TableServiceContext serviceCtx = tableClient.GetDataServiceContext();
    
     80: 
    
     81:             string defaultDescription = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit. 
    
     82: In commodo fermentum adipiscing. Maecenas id dolor velit, sit amet commodo ipsum. Cras lacus dui, bibendum 
    
     83: et commodo at, scelerisque ac enim. Mauris nec purus ante. Vestibulum ante ipsum primis in faucibus orci luctus 
    
     84: et ultrices posuere cubilia Curae; Praesent ut lobortis lorem. Nullam tempor, erat dignissim sodales blandit, 
    
     85: turpis velit fringilla augue, posuere ultricies augue augue quis quam. Quisque sed felis augue. Nam sit amet 
    
     86: enim tortor. Aenean ut fringilla leo. Phasellus tincidunt turpis ut diam dictum id ullamcorper nunc faucibus.
    
     87: Nunc semper dapibus orci sit amet suscipit. Maecenas viverra est volutpat lectus lacinia suscipit. Nullam 
    
     88: sapien elit, fermentum et pretium vitae, sodales quis eros. Ut et nulla urna. Nunc congue viverra nisi, eget 
    
     89: porta est lobortis non. Cras in turpis urna, sed porta ipsum. In suscipit purus a nibh facilisis eget 
    
     90: pharetra odio rhoncus. Morbi faucibus blandit mattis.";
    
     91: 
    
     92:             List<SpeakerEntity> speakers = new List<SpeakerEntity>();
    
     93:             for (int i = 0; i < names.Length; i++)
    
     94:             {
    
     95:                 SpeakerEntity speaker = new SpeakerEntity(names[i], titles[i], 
    
     96:                     string.Format("{0} is a {1} in the {2}", names[i], titles[i], defaultDescription),
    
     97:                     string.Format("{0}/{1}", imgBaseUrl, imgs[i]));
    
     98:                 speakers.Add(speaker);
    
     99: 
    
    100:                 serviceCtx.AddObject("events", speaker);
    
    101:             }
    
    102: 
    
    103:             serviceCtx.SaveChangesWithRetries(System.Data.Services.Client.SaveChangesOptions.Batch);
    
    104: 
    
    105:             string[] eventNames = new string[] { "Mobile Mondays", "iOS Meetup", "Windows Phone Meetup", 
    
    106:                 "Android Meetup", "HTML5 Meetup", "SharePoint Saturdays" };
    
    107:             string[] eventLocations = new string[] { "Chicago, IL", "Milwaukee, WI", "Madison, WI", 
    
    108:                 "Boston, MA", "Washington, DC", "Los Angeles, CA", "Las Vegas, NV", "New York City, NY" };
    
    109: 
    
    110:             List<EventEntity> events = new List<EventEntity>();
    
    111:             Random random = new Random();
    
    112: 
    
    113:             for (int i = 1; i <= 100; i++)
    
    114:             {
    
    115:                 string eventName = eventNames[random.Next(0, eventNames.Length - 1)];
    
    116:                 string eventLocation = eventLocations[random.Next(0, eventLocations.Length - 1)];
    
    117:                 SpeakerEntity eventSpeaker = speakers[random.Next(0, speakers.Count - 1)];
    
    118:                 DateTime date = CalcDate(random, DateTime.Now, new DateTime(2012, 12, 31));
    
    119: 
    
    120:                 if (!events.Exists(delegate(EventEntity e)
    
    121:                 {
    
    122:                     return (e.EventName == eventName && 
    
    123:                         e.EventLocation == e.EventLocation && 
    
    124:                         e.SpeakerKey == eventSpeaker.RowKey && 
    
    125:                         e.EventDate == date);
    
    126:                 }))
    
    127:                 {
    
    128:                     EventEntity e = new EventEntity(date, eventName, eventLocation, 
    
    129:                         defaultDescription, eventSpeaker);
    
    130: 
    
    131:                     events.Add(e);
    
    132:                     serviceCtx.AddObject("events", e);
    
    133:                 }
    
    134:             }
    
    135: 
    
    136:             serviceCtx.SaveChangesWithRetries(System.Data.Services.Client.SaveChangesOptions.Batch);
    
    137: 
    
    138:         }
    
    139: 
    
    140:         static DateTime CalcDate(Random random, DateTime minDate, DateTime maxDate)
    
    141:         {
    
    142:             TimeSpan timeSpan = maxDate - minDate;
    
    143:             TimeSpan randomSpan = new TimeSpan((long)(timeSpan.Ticks * random.NextDouble()));
    
    144:             return minDate + randomSpan;
    
    145: 
    
    146:         }
    
    147:     }
    
    148: }
    
    149: 

    After this code runs, I can now see that I have the following blobs and table data:

     

    Blob Data

    image

     

    Table Data – Speakers

    image

     

    Table Data – Events

    image

     

    Windows Azure iOS Toolkit Prep

    The next thing to do is to download and build the Windows Azure iOS Toolkit from GitHub. Despite what the Readme says, it doesn’t seem to have the binaries so you have to build yourself.

    When you’re building the toolkit, make sure you set to build to the correct device you’ll be using the toolkit from, otherwise when you go to use the toolkit in the project, you’ll end up with linking errors. For example, if you’re testing on the simulator, make sure to build the toolkit with that selected as the target. After you’ve successfully built the toolkit, find the libwatoolkitios.a file and save it off somewhere where you can reference it later.

    SpeakEasy iPhone App

    Project Settings

    Now with all of the prep work out of the way, it’s time to start building the app. Open XCode and create a new project. SpeakEasy will have two tabs, one for events and one for speakers so select Tabbed Application for the project type. The following options should be set for your project:

    • Product Name: SpeakEasy
    • Company Identifier: com.yourcompanyname (mine is set to com.pointbridge – I wrote this code prior to our acquisition)
    • Class Prefix: leave as default
    • Device Family: iPhone
    • Use Storyboard: checked
    • Use Automatic Reference Counting: checked
    • Include Unit Tests: unchecked

    Click through the rest of the New Project wizard. The next step is to set up the structure of your project. In the Project Navigator view, you should see the SpeakEasy project with three Groups: SpeakEasy, Frameworks and Products. Here are some basic steps to set up the project.

    1. Add a new group called lib. Add the libwatoolkitios.a file and the toolkit’s headers folder to this group.
    2. Select the SpeakEasy project from the Project Navigator. This should bring up the project settings in the standard editor.
    3. In the standard editor, make sure you’ve got the SpeakEasy project selected and go to the Build Settings tab.
    4. Search for the Other Linker Flags build setting and add the following settings:
      • -ObjC
      • -all_load
    5. Now select the SpeakEasy target and select the Build Phases tab.
    6. Open up the section Link Binary with Libraries and make sure libwatoolkitios.a is there. Then add a link to libxml2.2.dylib (or higher).

    Your project and settings should now look a lot like this:

    image

     

    Setting up the Project Groups and Files

    Open the SpeakEasy group in the Project Navigator and add the following three groups:

    • Model – the classes we’ll add to this group are going to be used to retrieve the data from Azure and model our events and speakers
    • ViewControllers – various view controllers we’ll create to present the model to our views
    • Views – for our custom view classes

    Open up the file MainStoryboard.storyboard and delete both the First and Second View Controllers. Also delete the following files from the Project Navigator:

    • FirstViewController.h
    • FirstViewController.m
    • SecondViewController.h
    • SecondViewController.m

    Finally, under the Supporting Files group, add a new plist file called SpeakEasy-AzureSettings.plist. This will hold our Azure settings. To this file, add two new keys, AccessKey and StorageAccount (both type String), and set these values to your appropriate Azure account settings.

    Your project structure should now look like this:

    image

    The next step is to build our model. We basically need three classes: a class to model our speaker entities, a class to model our event entities, and a data access class that takes care of pulling the data from Azure. All of these classes should be in the Model group of the project.

    Model Files

    SESpeaker

    This class is what models our speaker entity. The interface and class looks like this:

      1: //SESpeaker.h
    
      2: 
    
      3: #import <Foundation/Foundation.h>
    
      4: #import "WATableEntity.h"
    
      5: 
    
      6: @class SESpeaker;
    
      7: 
    
      8: @protocol SESpeakerDelegate <NSObject>
    
      9: 
    
     10: @optional
    
     11: 
    
     12: -(void) speaker:(SESpeaker *)speaker didLoadImage:(UIImage *)image;
    
     13: 
    
     14: @end
    
     15: 
    
     16: @interface SESpeaker : NSObject {
    
     17: @private
    
     18:     NSString *_partitionKey;
    
     19:     NSString *_rowKey;
    
     20:     NSDate   *_timestamp;
    
     21: }
    
     22: @property (retain) id delegate;
    
     23: @property(readonly) NSString *partitionKey;
    
     24: @property(readonly) NSString *rowKey;
    
     25: @property(readonly) NSDate *timestamp;
    
     26: @property(nonatomic, retain) NSString *name;
    
     27: @property(nonatomic, retain) NSString *title;
    
     28: @property(nonatomic, retain) NSString *bio;
    
     29: @property(nonatomic, retain) NSURL *imageUrl;
    
     30: @property(nonatomic, retain) UIImage *image;
    
     31: 
    
     32: -(id) initWithEntity:(WATableEntity *) entity;
    
     33: 
    
     34: @end
    
     35: 
    
     36: //SESpeaker.m
    
     37: 
    
     38: #import "SESpeaker.h"
    
     39: #import "SEData.h"
    
     40: 
    
     41: @implementation SESpeaker
    
     42: @synthesize delegate;
    
     43: @synthesize partitionKey = _partitionKey;
    
     44: @synthesize rowKey = _rowKey;
    
     45: @synthesize timestamp = _timestamp;
    
     46: @synthesize name;
    
     47: @synthesize title;
    
     48: @synthesize bio;
    
     49: @synthesize imageUrl;
    
     50: @synthesize image;
    
     51: 
    
     52: -(id) initWithEntity:(WATableEntity *)entity
    
     53: {
    
     54:     if(self = [super init])
    
     55:     {
    
     56:         _partitionKey = entity.partitionKey;
    
     57:         _rowKey = entity.rowKey;
    
     58:         _timestamp = entity.timeStamp;
    
     59:         name = [entity objectForKey:@"Name"];
    
     60:         title = [entity objectForKey:@"Title"];
    
     61:         bio = [entity objectForKey:@"Bio"];
    
     62:         imageUrl = [NSURL URLWithString:[entity objectForKey:@"ImageUrl"]];
    
     63:         
    
     64:         SEData *data = [SEData sharedManager];
    
     65:         [data fetchBlobDataFromURL:imageUrl withCompletionHandler:^(NSData *imageData, NSError *error) {
    
     66:             if (!error) 
    
     67:             {
    
     68:                 image = [UIImage imageWithData:imageData]; 
    
     69:                 [[self delegate] speaker:self didLoadImage:image];
    
     70:             }
    
     71:         }];
    
     72:     }
    
     73:     
    
     74:     return self;
    
     75: }
    
     76: 
    
     77: -(void) dealloc
    
     78: {
    
     79:     delegate = nil;
    
     80:     _partitionKey = nil;
    
     81:     _rowKey = nil;
    
     82:     _timestamp = nil;
    
     83:     name = nil;
    
     84:     title  = nil;
    
     85:     bio = nil;
    
     86:     imageUrl = nil;
    
     87:     image = nil;
    
     88: }
    
     89: 
    
     90: @end
    
     91: 

    The interface basically defines a set of properties that I’ll populate with the values returned for a speaker entity from our Azure table storage. This population occurs in initWithEntity:. The WATableEntity class from the iOS toolkit is for working with entities retrieved from Azure table storage. It has a few basic properties that all entities in a table have (partition and row keys and timestamp). To access your own entity properties (the ones we created with our SpeakerEntity class in the setup and population console app), we send an objectForKey: message to the entity with the name of the property we’re retrieving and then assign its value to a property of the SESpeaker class.

    For the speaker’s image, notice that I have properties for both the image url and the actual image. The image url is what I stored with the speaker entity in the table. When I initialize an SESpeaker, I use this image url to grab the image via my SEData data access class (lines 64-71). That class has a helper method for retrieving blob data from my Azure blob container. After the image is retrieved, I then send a message that the image is ready to any delegate implementing the SESpeakerDelegate protocol  assigned to this entity instance.

    SEEvent

    The SEEvent interface and class is used to model the EventEntity. The h/m files look like this:

      1: //SEEvent.h
    
      2: #import <Foundation/Foundation.h>
    
      3: #import "WATableEntity.h"
    
      4: 
    
      5: @interface SEEvent : NSObject {
    
      6: @private
    
      7:     NSString *_partitionKey;
    
      8:     NSString *_rowKey;
    
      9:     NSDate *_timeStamp;
    
     10:     
    
     11: }
    
     12: 
    
     13: @property(readonly) NSString *partitionKey;
    
     14: @property(readonly) NSString *rowKey;
    
     15: @property(readonly) NSDate *timeStamp;
    
     16: @property(nonatomic, retain) NSString *eventName;
    
     17: @property(nonatomic, retain) NSString *eventLocation;
    
     18: @property(nonatomic, retain) NSString *eventDescription;
    
     19: @property(nonatomic, retain) NSDate *eventDate;
    
     20: @property(nonatomic, retain) NSString *speakerKey;
    
     21: @property(nonatomic, retain) NSString *speakerName;
    
     22: 
    
     23: -(id) initWithEntity:(WATableEntity *) entity;
    
     24: -(NSString*) eventDateAsString;
    
     25: 
    
     26: @end
    
     27: 
    
     28: 
    
     29: //SEEvent.m
    
     30: #import "SEEvent.h"
    
     31: 
    
     32: @implementation SEEvent
    
     33: @synthesize partitionKey = _partitionKey;
    
     34: @synthesize rowKey = _rowKey;
    
     35: @synthesize timeStamp = _timeStamp;
    
     36: @synthesize eventName;
    
     37: @synthesize eventLocation;
    
     38: @synthesize eventDescription;
    
     39: @synthesize eventDate;
    
     40: @synthesize speakerKey;
    
     41: @synthesize speakerName;
    
     42: 
    
     43: -(id) initWithEntity:(WATableEntity *) entity
    
     44: {
    
     45:     if(self = [super init])
    
     46:     {
    
     47:         _partitionKey = entity.partitionKey;
    
     48:         _rowKey = entity.rowKey;
    
     49:         _timeStamp = entity.timeStamp;
    
     50:         eventName = [entity objectForKey:@"EventName"];
    
     51:         eventLocation = [entity objectForKey:@"EventLocation"];
    
     52:         eventDescription = [entity objectForKey:@"EventDescription"];
    
     53:         speakerKey = [entity objectForKey:@"SpeakerKey"];
    
     54:         speakerName = [entity objectForKey:@"SpeakerName"];
    
     55:         
    
     56:         //date comes back as a string; convert to an NSDate
    
     57:         NSString* eventDateString = [entity objectForKey:@"EventDate"];        
    
     58:         if(eventDateString)
    
     59:         {
    
     60:             NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
    
     61:             [dateFormat setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSSSSS'Z'"];
    
     62:             eventDate = [dateFormat dateFromString:eventDateString];
    
     63:         }
    
     64:     }
    
     65:     
    
     66:     return self;
    
     67: }
    
     68: 
    
     69: - (void)dealloc
    
     70: {
    
     71:     _partitionKey = nil;
    
     72:     _rowKey = nil;
    
     73:     _timeStamp =nil;
    
     74:     eventName = nil;
    
     75:     eventLocation = nil;
    
     76:     eventDescription = nil;
    
     77:     eventDate = nil;
    
     78:     speakerKey = nil;
    
     79:     speakerName = nil;
    
     80: }
    
     81: 
    
     82: -(NSString*) eventDateAsString
    
     83: {
    
     84:     NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    
     85:     [dateFormatter setDateStyle:NSDateFormatterShortStyle];
    
     86:     return [dateFormatter stringFromDate:eventDate];
    
     87: }
    
     88: 
    
     89: @end
    
     90: 

    This class functions the same way as SESpeaker, except that it doesn’t do any loading of any blobs. It still uses a WATableEntity to initialize itself with an entity from the Azure table storage (in this case, an EventEntity object).

    SEData

    Finally, we have SEData. SEData is basically my data access class used to retrieve items from the Azure storage services. Below is the interface definition (I will go through the implementation separately):

      1: #import <Foundation/Foundation.h>
    
      2: #import "WAAuthenticationCredential.h"
    
      3: #import "WACloudStorageClient.h"
    
      4: #import "WATableFetchRequest.h"
    
      5: #import "WABlobContainer.h"
    
      6: #import "WABlob.h"
    
      7: #import "SEEvent.h"
    
      8: #import "SESpeaker.h"
    
      9: 
    
     10: #define kEventsStorageTable @"events"
    
     11: 
    
     12: @interface SEData : NSObject{
    
     13: @private
    
     14:     WAAuthenticationCredential *_credential;
    
     15:     WACloudStorageClient *_storageClient;
    
     16:     
    
     17:     NSMutableArray *_events;
    
     18:     NSMutableArray *_speakers;
    
     19: }
    
     20: 
    
     21: -(void)fetchEventsWithCompletionHandler:(void (^)(NSMutableArray *events, NSError *error))block;
    
     22: -(void)fetchSpeakersWithCompletionHandler:(void (^)(NSMutableArray *speakers, NSError *error))block;
    
     23: -(void)fetchSpeakerWithRowKey:(NSString *)rowKey withCompletionHandler:(void (^)(SESpeaker *speaker, NSError *error))block;
    
     24: -(void)fetchBlobDataFromURL:(NSURL *)imageUrl withCompletionHandler:(void (^)(NSData *blobData, NSError *error))block;
    
     25: 
    
     26: +(SEData*)sharedManager;
    
     27: 
    
     28: @end

    A WAAuthenticationCredential are the credentials that are used to access Azure and the WACloudStorageClient is primarily a façade that allows you to invoke operations on and return data from Azure storage. The interface also keeps an array of events and speakers. There are four methods for the interface to retrieve all events, all speakers, a single speaker, and a blob. The class also has a static method sharedManager that returns an instance of the SEData class. This is part of implementing this class as a Singleton.

    Below is the full implementation:

      1: #import "SEData.h"
    
      2: 
    
      3: @interface SEData (hidden)
    
      4: -(void) privateInit;
    
      5: -(void) fetchEntitiesFromTable:(NSString *)table WithFilter:(NSString *)filter withCompletionHandler:(void (^)(NSArray *, NSError *))block;
    
      6: @end
    
      7: 
    
      8: @implementation SEData
    
      9: 
    
     10: static SEData *sharedDataManager = nil;
    
     11: 
    
     12: #pragma mark - Singleton Implementation
    
     13: 
    
     14: +(SEData*)sharedManager
    
     15: {
    
     16:     if(sharedDataManager == nil){
    
     17:         sharedDataManager = [[super allocWithZone:NULL] init];
    
     18:         [sharedDataManager privateInit];
    
     19:     }
    
     20:     
    
     21:     return sharedDataManager;
    
     22: }
    
     23: 
    
     24: + (id)allocWithZone:(NSZone *)zone
    
     25: {
    
     26:     return [self sharedManager];
    
     27: }
    
     28: 
    
     29: - (id)copyWithZone:(NSZone *)zone
    
     30: {
    
     31:     return self;
    
     32: }
    
     33: 
    
     34: #pragma mark - Public Methods
    
     35: 
    
     36: 
    
     37: -(void)fetchEventsWithCompletionHandler:(void (^)(NSMutableArray *events, NSError *error))block;
    
     38: {    
    
     39:     if(!_events)
    
     40:     {        
    
     41:         [self fetchEntitiesFromTable:kEventsStorageTable WithFilter:@"PartitionKey eq 'Events'" withCompletionHandler:^(NSArray *entities, NSError *error) {
    
     42:             if(error) 
    
     43:             {
    
     44:                 block(nil, error);
    
     45:             }
    
     46:             else
    
     47:             {
    
     48:                 _events = [[NSMutableArray alloc] initWithCapacity:entities.count];
    
     49:                 
    
     50:                 for (WATableEntity *entity in entities) 
    
     51:                 {
    
     52:                     SEEvent *event = [[SEEvent alloc] initWithEntity:entity];
    
     53:                     
    
     54:                     [_events addObject:event];
    
     55:                 }
    
     56:                 
    
     57:                 NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"eventDate" ascending:YES];
    
     58:                 NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
    
     59:                 _events = [NSMutableArray arrayWithArray:[_events sortedArrayUsingDescriptors:sortDescriptors]];
    
     60:                 
    
     61:                 block(_events, nil);
    
     62:                 
    
     63:             }
    
     64:         }];
    
     65:     }
    
     66:     else
    
     67:     {
    
     68:         block(_events, nil);
    
     69:     }
    
     70: }
    
     71: 
    
     72: -(void)fetchSpeakersWithCompletionHandler:(void (^)(NSMutableArray *speakers, NSError *error))block
    
     73: {
    
     74:     if(!_speakers)
    
     75:     {
    
     76:         [self fetchEntitiesFromTable:kEventsStorageTable WithFilter:@"PartitionKey eq 'speakers'" withCompletionHandler:^(NSArray *entities, NSError *error) {
    
     77:             if(error) 
    
     78:             {
    
     79:                 block(nil, error);
    
     80:             }
    
     81:             else
    
     82:             {
    
     83:                 _speakers = [[NSMutableArray alloc] initWithCapacity:entities.count];
    
     84:                 
    
     85:                 for (WATableEntity *entity in entities) 
    
     86:                 {
    
     87:                     SESpeaker *speaker = [[SESpeaker alloc] initWithEntity:entity];
    
     88:                     
    
     89:                     [_speakers addObject:speaker];
    
     90:                 }
    
     91:                 block(_speakers, nil);
    
     92:                 
    
     93:             }
    
     94:         }];
    
     95:         
    
     96:     }
    
     97:     else
    
     98:     {
    
     99:         block(_speakers, nil);
    
    100:     }
    
    101:         
    
    102: }
    
    103: 
    
    104: -(void) fetchSpeakerWithRowKey:(NSString *)rowKey withCompletionHandler:(void (^)(SESpeaker *speaker, NSError *error))block;
    
    105: {
    
    106:     if(!_speakers)
    
    107:     {        
    
    108:         [self fetchEntitiesFromTable:kEventsStorageTable 
    
    109:                             WithFilter:[NSString stringWithFormat:@"PartitionKey eq 'speakers' and RowKey eq '%@'", rowKey] 
    
    110:                             withCompletionHandler:^(NSArray *entities, NSError *error) {
    
    111:             if(error)
    
    112:             {
    
    113:                 block(nil, error);
    
    114:             }
    
    115:             else
    
    116:             {
    
    117:                 SESpeaker *speaker = [[SESpeaker alloc] initWithEntity:[entities objectAtIndex:0]];
    
    118:                 block(speaker, nil);
    
    119:             }
    
    120:         }];
    
    121:         
    
    122:     }
    
    123:     else
    
    124:     {
    
    125:         NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
    
    126:             SESpeaker *speaker = (SESpeaker*)evaluatedObject;
    
    127:             return ([speaker.rowKey isEqualToString:rowKey]);
    
    128:         }];
    
    129:         
    
    130:         NSArray *results = [_speakers filteredArrayUsingPredicate:predicate];
    
    131:         
    
    132:         if (results) {
    
    133:             block([results objectAtIndex:0], nil);
    
    134:         }
    
    135:         else {
    
    136:             //todo: load from speakers 
    
    137:             block(nil, nil);
    
    138:         }
    
    139:     }
    
    140: }
    
    141: 
    
    142: -(void) fetchBlobDataFromURL:(NSURL *)imageUrl withCompletionHandler:(void (^)(NSData *blobData, NSError *error))block
    
    143: {   
    
    144:     WABlobContainer *container = [[WABlobContainer alloc] initContainerWithName:@"speakers"];
    
    145:     
    
    146:     NSString *imageUrlString = [imageUrl absoluteString];
    
    147:     NSString *filename = [imageUrlString substringFromIndex:[imageUrlString rangeOfString:@"/" options:NSBackwardsSearch].location + 1];
    
    148:     
    
    149:     WABlob *blob = [[WABlob alloc] initBlobWithName:filename URL:imageUrlString container:container];
    
    150:     
    
    151:     [_storageClient fetchBlobData:blob withCompletionHandler:^(NSData *data, NSError *error) {
    
    152:         block(data, error);
    
    153:     }];
    
    154: }
    
    155: 
    
    156: #pragma mark - Private Methods
    
    157: -(void) privateInit
    
    158: {
    
    159:     NSString *path = [[NSBundle mainBundle] pathForResource:@"SpeakEasy-AzureSettings" ofType:@"plist"];
    
    160:     
    
    161:     NSDictionary *settings = [[NSDictionary alloc] initWithContentsOfFile:path];
    
    162:     
    
    163:     _credential = [WAAuthenticationCredential 
    
    164:                    credentialWithAzureServiceAccount:[settings objectForKey:@"StorageAccount"]
    
    165:                    accessKey:[settings objectForKey:@"AccessKey"]];         
    
    166:      
    
    167:     _storageClient = [WACloudStorageClient storageClientWithCredential:_credential];
    
    168: }
    
    169: 
    
    170: 
    
    171: -(void) fetchEntitiesFromTable:(NSString *)table WithFilter:(NSString *)filter withCompletionHandler:(void (^)(NSArray *, NSError *))block
    
    172: {
    
    173:     WATableFetchRequest* fetchRequest = [WATableFetchRequest fetchRequestForTable:table];
    
    174:     fetchRequest.filter = filter;
    
    175:     [_storageClient fetchEntities:fetchRequest withCompletionHandler:^(NSArray *entities, NSError *error) 
    
    176:      {
    
    177:          block(entities, error);
    
    178:      }];
    
    179: 
    
    180: }
    
    181: 
    
    182: @end
    
    183: 

    Lines 3-5 just adds a few hidden (not private) methods using a category (hidden) to my SEData class that should only be called internally. privateInit, implemented in lines 157-168, sets up my _credential and _storageClient variables which will be used in other methods to grab the data from Azure. It uses the information I stored in the SpeakEasy-AzureSettings.plist file to create the credentials.

    fetchEntitiesFromTable:withFilter:withCompletionHandler: uses a WATableFetchRequest to retrieve entities from table storage. The filter can be used to limit the entities retrieved and has the same filter syntax you’d use when using the $filter parameter in OData. Finally, when the entities have been retrieved (or if an error has occurred), the completion handler block that is passed is called.

    SEData is implemented as a singleton in lines 14-32 as per the Apple guideline.

    Lines 142-154 is the implementation of fetchBlobDataFromURL:withCompletionHandler:. This method creates a WABlobContainer instance for the speakers container, then creates an instance of a WABlob using the filename and blob url in that container. It then uses the _storageClient to make a request to retrieve the binary data for this blob.

    fetchEventsWithCompletionHandler:, on lines 37-70, takes care of retrieving the EventEntity objects from Azure. It uses the filter “PartionKey eq ‘Events’” to make sure we’re only grabbing the EventEntity objects from our table and not any of the SpeakerEntity objects. If the fetch request I successful, I iterate through the WATableEntity array that’s returned and create my own array of strongly-typed SEEvent objects. Once I’m done with that, I sort the events by date and then send the array back to the completion handler block.

    fetchSpeakersWithCompletionHandler:, on lines 72-102, functions identically as the method for events except we filter only for the SpeakerEntity objects to create an array of SESpeaker objects and that there’s no sorting.

    Finally, fetchSpeakerWithRowKey:WithCompletionHandler: retrieves an individual SESpeaker object. If we’ve already got a list of all the speakers, then it uses that a predicate to find the correct object within that array. If the speaker array hasn’t yet been loaded, then the code goes back to Azure and finds the correct speaker using a filter.

    Conclusion

    The model code above is technically all the code that directly interacts with Windows Azure. In Part 2, I cover creating the user interface for the iPhone app. As a sneak peak, that will look like below, so if you want to know how I built it, please continue with the series.

    image image image  image

    By: Bart Tubalinal | Posted: February 2, 2012 at 8:56 PM

    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!

    By: Bart Tubalinal | Posted: October 14, 2011 at 12:43 AM

    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?3 Do 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?


    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).

    By: Bart Tubalinal | Posted: September 26, 2011 at 2:08 PM

    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 Files\Common Files\Microsoft Shared\Web Server Extensions\14\Resources\wss.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 Files\Common Files\Microsoft Shared\Web Server Extensions\14\Resources\wss.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 Files\Common Files\Microsoft Shared\Web Server Extensions\14\Resources\wss.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 Files\Common Files\Microsoft Shared\Web Server Extensions\14\Resources\wss.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 Files\Common Files\Microsoft Shared\Web Server Extensions\14\Resources\. 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?

     

     Twitter

     About Bart Tubalinal

    Solutions ArchitectBart Tubalinal is a solutions architect with more than nine years of experience in designing and developing software and applications. Within the last few years, Bart has concentrated on application d... [more]
    Microsoft Certified Professional Developer - Web Developer 4

     Tag Cloud

     External Links

     ‭(Hidden)‬ Admin Links