Introduction
I welcome all. In this article I will discuss how to develop an iPhone client for Microsoft SharePoint 2013.
Our application will authenticate to SharePoint and read the contents of a list provided by the SharePoint REST API. Let’s proceed.
Preparing the environment
Regarding the deployment of SharePoint environment, you can use any way that is comfortable. You can deploy SharePoint 2013 on your own server, or SharePoint as a virtual machine deployed in Windows Azure, or in another cloud (e.g. http://cloudshare.com/). I have a test environment using SharePoint Online from Office 365.
Our mobile app will display a list of photos, so add the test data for this list. Open one of the sites of site collection in your browser, which will include a list of photos. In my case it is https://dmitrykomin-public.sharepoint.com/. In administration panel click Site Settings link.
Then, in the right pane, click Site Contents link.
Site Contents page opens showing site lists among other things. Click Photos list.
Add a few photos to the list .
Authentication
SharePoint 2013 supports Claims-based authentication, which means that you can configure SharePoint 2013, so that the user can authenticate through diversity of Identity Providers: Facebook, Google, Windows Live ID, Yahoo, etc. The mechanism of Claims-based authentication is based on the fact that actually all authentication-related work is performed by Identity Provider. After successful authentication, the user in exchange for his credentials receives specific security token from Security Token Service. This token is then used to access the application that checks the token and obtains claims based on it, that is, information about a particular user is specific for each Identity Provider.
More on Claims-based authentication is here: http://social.technet.microsoft.com/wiki/contents/articles/14214.sharepoint-2013-claims-based-authentication.aspx. The feature of this authentication mechanism in SharePoint is that all these actions to get security token are made automatically based on HTTP redirects, which are performed by web browser. Security token is stored in browser cookies. But what can we do if we want to use the iPhone as a mobile client application for SharePoint? It’s simple – we use the UIWebView class to create a browser view in the iPhone application, and to authenticate user in browser to get the security token, and then it allows to communicate directly with SharePoint REST API passing the security token obtained from cookies of HTTP-request. (It is technically possible to write an authentication code without using UIWebView fully emulating the behavior of the browser, handling HTTP-redirect by yourself, create your own views for entering user credentials, etc. Compared with the solution based on the browser, this approach is very difficult. If you for some reasons need this authentication method, please let me know and I will discuss this approach in a future article.) Now let’s move on to the development of mobile application. Start the development environment Xcode. Select Create a new Xcode project. As a template of a new project, select Single View Application.
Specify the name of the project, for example SPConnectTest. Create a controller for authentication. Run the command File->New->File… As a new template file, select Objective-C class.
Specify the name of the new class LoginViewController, in the Subclass of select UIViewController. Open the file LoginViewController.h and enter the following code:
@interface LoginViewController : UIViewController
{
IBOutlet UIWebView * webView;
}
@property (nonatomic, retain) IBOutlet UIWebView *webView;
- (void)loadSharePointList : (NSHTTPCookie *) rtFaCookie : (NSHTTPCookie *) fedAuthCookie;
- (void)verifyCookies;
@end
Now open MainStoryboard_iPhone.storyboard
. Storyboard editor opens. In the right pane, locate Web View component in Objects section and drag it to an existing (it is displayed as iPhone screen).
In View Controller Scene select View Controller. In the right pane in the Class box, select LoginViewController.
Now right-click on the View Controller, find our variable webView there and drag link from it to the Web view in the workspace of Storyboard editor.
Open LoginViewController.m file. Add the following code:
@synthesize webView;
- (void)viewDidLoad
{
[super viewDidLoad];
self.webView.delegate = self;
NSString* authUrl = @"https://dmitrykomin-public.sharepoint.com/_layouts/15/authenticate.aspx?Source=/";
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString: authUrl]]];
}
-(void)webViewDidStartLoad:(UIWebView *)webView {
[self verifyCookies];
}
-(void)webViewDidFinishLoad:(UIWebView *)webView {
[self verifyCookies];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (void)verifyCookies
{
__weak NSHTTPCookie * rtFaCookie = nil;
__weak NSHTTPCookie * fedAuthCookie = nil;
NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
NSArray *cookiesArray = [storage cookies];
for (NSHTTPCookie *cookie in cookiesArray) {
if ([[cookie name] isEqualToString:@"FedAuth"]) {
fedAuthCookie = cookie;
continue;
}
if ([[cookie name] isEqualToString:@"rtFa"]) {
rtFaCookie = cookie;
continue;
}
}
if(rtFaCookie != nil && fedAuthCookie != nil){
[self loadSharePointList : rtFaCookie : fedAuthCookie];
}
}
- (void)loadSharePointList : (NSHTTPCookie *) rtFaCookie : (NSHTTPCookie *) fedAuthCookie
{
}
@end
Now look at what the code does. In the handler, viewDidLoad, which is called when loading the view, we perform a web request to the authentication URL of our website. After the user enters his credentials, a series of HTTP-redirects are performed, after each of HTTP-redirect webViewDidStartLoad
webViewDidFinishLoad
methods are invoked. In these methods, we call verifyCookies, that checks the security token in the cookies ("rtFa" and "FedAuth" cookies). If securityToken has obtained, we call loadSharePointList, that in the future will load the contents of our SharePoint list.
It's time to run our application. Click Run. If all is done correctly, in the iOS Simulator you should see the following:
Set breakpoint in loadSharePointList method, then switch to iOS Simulator window, enter the user's credentials and then click on the Sign in link. After a while you will get into the method loadSharePointList
. Thus, we have successfully implemented authentication in SharePoint.
Receiving data from REST API
Let's emphasize main points of communication with the SharePoint 2013 REST API, since I can not bring all the code. You can see it by downloading a demo example for this article. To communicate with the SharePoint 2013 REST API, I wrote a class SPRestClientRequest
. Here's the code:
SPRestClientRequest.h:
@interface SPRestClientRequest : NSObject
{
__weak NSHTTPCookie * rtFaCookie;
__weak NSHTTPCookie * fedAuthCookie;
}
- (id) initWithCookies : (NSHTTPCookie *) rtFa : (NSHTTPCookie *) fedAuth;
- (NSDictionary *) sendRequest : (NSString*) requestUrl;
@end
SPRestClientRequest.m:
@implementation SPRestClientRequest
- (id) initWithCookies : (NSHTTPCookie *) rtFa : (NSHTTPCookie *) fedAuth
{
if(self = [super init])
{
rtFaCookie = rtFa;
fedAuthCookie = fedAuth;
}
return self;
}
- (NSDictionary *) sendRequest : (NSString*) requestUrl
{
NSURL *url = [NSURL URLWithString:requestUrl];
NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:url];
NSArray* cookieArray = [NSArray arrayWithObjects: rtFaCookie, fedAuthCookie, nil];
NSDictionary * cookieHeaders = [NSHTTPCookie requestHeaderFieldsWithCookies:cookieArray];
NSMutableDictionary * requestHeaders = [[NSMutableDictionary alloc] initWithDictionary: cookieHeaders];
[requestHeaders setObject: @"application/json;odata=verbose" forKey: @"Accept"];
[theRequest setHTTPMethod:@"GET"];
[theRequest setAllHTTPHeaderFields:requestHeaders];
NSURLResponse *response;
NSError *error;
NSData *data = [NSURLConnection sendSynchronousRequest:theRequest returningResponse:&response error:&error];
if (data) {
NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@", jsonString);
return [jsonString JSONValue];
}
return nil;
}
@end
As you can see, sendRequest method generates HTTP-request, executes it and returns the result as JSON.
In addition to this class, I wrote a number of classes that create a simple object model to work with the API: SPListCollection
, SPList
, SPListItem
. For an example, here is the code of SPListCollection
:
SPListCollection.h:
@interface SPListCollection : SPRestClientRequest
- (NSDictionary *) getAllItems;
- (NSDictionary *) getItemByEntityType : (NSString *) entityType;
@end
SPListCollection.m:
@implementation SPListCollection
- (NSDictionary *) getAllItems
{
NSDictionary * jsonValue = [super sendRequest:@"https://dmitrykomin-public.sharepoint.com/_api/web/lists"];
return [[jsonValue objectForKey:@"d"] objectForKey:@"results"];
}
- (NSDictionary *) getItemByEntityType : (NSString *) entityType
{
NSDictionary *jsonResults = [self getAllItems];
for (NSDictionary *jsonResult in jsonResults) {
NSString *entityTypeName = [jsonResult objectForKey:@"EntityTypeName"];
if([entityTypeName isEqualToString: entityType]){
return jsonResult;
}
}
return nil;
}
@end
This class, like others, simply makes a request to a specific SharePoint REST endpoint and parses the result of the call. Full documentation on SharePoint 2013 REST API, we can find it here: http://msdn.microsoft.com/en-us/library/jj860569.aspx#Reference
Loading the photos from SharePoint 2013 list
Now, having object model for the REST API, let's load the data from the list of SharePoint 2013. Add a new Objective-C class named PhotosListManager
. Open PhotosListManager.h file and add the following code:
@interface PhotosListManager : NSObject
{
__weak NSHTTPCookie * rtFaCookie;
__weak NSHTTPCookie * fedAuthCookie;
}
- (id) initWithCookies : (NSHTTPCookie *) rtFa : (NSHTTPCookie *) fedAuth;
- (NSArray *) loadItems;
@end
Open PhotosListManager.m file and add the following code:
@implementation PhotosListManager
- (id) initWithCookies : (NSHTTPCookie *) rtFa : (NSHTTPCookie *) fedAuth
{
if(self = [super init])
{
rtFaCookie = rtFa;
fedAuthCookie = fedAuth;
}
return self;
}
- (NSArray *) loadItems
{
NSMutableArray * loadedItems = [[NSMutableArray alloc] init];
SPListCollection * listCollection = [[SPListCollection alloc] initWithCookies : rtFaCookie : fedAuthCookie];
NSDictionary * listMeta = [listCollection getItemByEntityType : @"PhotosList"];
if(listMeta != nil){
NSDictionary * photosMetas = [[[SPList alloc] initWithMetadataAndCookies : listMeta : rtFaCookie : fedAuthCookie] getAllItems];
for (NSDictionary *photoMeta in photosMetas) {
NSDictionary * metadata = [photoMeta objectForKey : @"__metadata"];
NSDictionary * fieldValues = [[[SPListItem alloc] initWithMetadataAndCookies : photoMeta : rtFaCookie : fedAuthCookie] fieldValuesAsText];
if(metadata != nil && fieldValues != nil) {
ListItemDto * listItem = [[ListItemDto alloc] init];
listItem.id = [metadata objectForKey : @"id"];
listItem.uri = [metadata objectForKey : @"uri"];
listItem.type = [metadata objectForKey : @"type"];
listItem.title = [fieldValues objectForKey : @"Title"];
if(listItem.title == nil || [listItem.title isEqualToString : @""]){
listItem.title = [fieldValues objectForKey : @"FileLeafRef"];
}
listItem.created = [photoMeta objectForKey : @"Created"];
NSMutableDictionary * attributes = [[NSMutableDictionary alloc] init];
[attributes setObject: [fieldValues objectForKey : @"FileLeafRef"] forKey: @"FileLeafRef"];
[attributes setObject: [fieldValues objectForKey : @"FileRef"] forKey: @"FileRef"];
NSString * fileUrl = [[AppSettings SiteUrl] stringByAppendingString : [fieldValues objectForKey : @"FileRef"]];
[attributes setObject: fileUrl forKey: @"FileUrl"];
listItem.attributes = attributes;
[loadedItems addObject: listItem];
}
}
}
return loadedItems;
}
@end
Our manager-class loads the information about the photos and returns a list of ListItemDto (serves as kinda of View Model).
Displaying information in UITableView
Now that we've loaded our pictures let's display them in the list. For this we will use UITableView class, more precisely it would be the controller based on UITableView
(UITableViewController
).
Add a new Objective-C class named SPListViewController
. Specify 'UITableViewController
' class in Subclass of box.
Open SPListViewController.h file and add the following code:
@interface SPListViewController : UITableViewController
{
__weak NSHTTPCookie * rtFaCookie;
__weak NSHTTPCookie * fedAuthCookie;
NSArray * tableData;
}
- (id) initWithCookies : (NSHTTPCookie *) rtFa : (NSHTTPCookie *) fedAuth;
@end
Open SPListViewController.m file and add the following code:
@implementation SPListViewController
- (id) initWithCookies : (NSHTTPCookie *) rtFa : (NSHTTPCookie *) fedAuth
{
self = [super initWithNibName: nil bundle: nil];
if (self) {
rtFaCookie = rtFa;
fedAuthCookie = fedAuth;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
PhotosListManager * photosListMgr = [[PhotosListManager alloc] initWithCookies: rtFaCookie : fedAuthCookie];
tableData = [photosListMgr loadItems];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [tableData count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[PhotoItemCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier];
}
ListItemDto * listItem = [tableData objectAtIndex:indexPath.row];
NSString* imageUrl = [listItem.attributes objectForKey : @"FileUrl"];
cell.textLabel.text = listItem.title;
cell.imageView.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]]];
return cell;
}
@end
In the viewDidLoad
method, we just say our manager to load photos. In numberOfRowsInSection
method we return the number of elements in the section (for us it would be number of photos). In CellForRowAtIndexPath
method we create a list item. Here, we simply assign the photo title and image data to corresponding properties of UITableViewCell
item.
Almost done. It remains only to perform switching to SPListViewController
after successfull authentication in SharePoint 2013. Open LoginViewController.m file and add the following code to the loadSharePointList
method:
- (void)loadSharePointList : (NSHTTPCookie *) rtFaCookie : (NSHTTPCookie *) fedAuthCookie
{
[[self webView] setHidden:YES];
SPListViewController * listView = [[SPListViewController alloc] initWithCookies: rtFaCookie : fedAuthCookie];
listView.title = @"List Content";
UINavigationController * navigationController = [[UINavigationController alloc] initWithRootViewController:listView];
[self presentViewController : navigationController animated:YES completion: nil];
}
That's it! Run the application. After you enter the user's credentials and push Sign in button, you should get the following:
Thank you for reading. Any comments and suggestions would be really appreciated.
History
- April 03 2013: Initial publication.