Creating a NSUrlProtocol that uses NSUrlSession in Xamarin

In iOS 7.0 Apple introduced a new class suite, NSUrlSession, to manage the request-response web calls, replacing the NSUrlConnection class suite with more robust and granular control of how requests are controlled both at the session and individual request levels. More information about the NSUrlSession class suite can be found here.

Some of the advantages that the NSUrlSession class suite has over the NSUrlConnection class suite are:

  • Background uploads and downloads
  • Ability to pause and resume networking operations
  • Configurable container
  • Subclassable and private storage
  • Improved authentication handling
  • Rich delegate model
  • Uploads and downloads through the file system

Source

However, when extending the NSUrlProtocol class to define custom ways of handling URLs send from service calls or one of the web views (UIWebView or WKWebView), the NSUrlProtocolClient class uses methods that match the NSUrlConnection rather than the NSUrlSession for completing requests and loading data to the client. Below, I will be illustrating how implementing both classes is accomplished and what steps are necessary to enable the NSUrlSession to replace NSUrlConnection.

First, when extending the NSUrlProtocol class, you will need to implement several methods specified with the ExportAttribute used to bind the methods to their underlying objective-c methods.

public class CustomNSUrlProtocol : NSUrlProtocol
{
  [Export("canInitWithRequest:")]
  public static bool canInitWithRequest(NSUrlRequest request)
  {
  	return true;
  }
  [Export("canonicalRequestForRequest:")]
  public static new NSUrlRequest GetCanonicalRequest(NSUrlRequest request)
  {
  	return request;
  }
  [Export("initWithRequest:cachedResponse:client:")]
  public CustomNSUrlProtocol(NSUrlRequest request, NSCachedUrlResponse cachedResponse, NSUrlProtocolClient client)
  : base(request, cachedResponse, client)
  {
  }
}

Then, you have to register the NSUrlProtocol with the AppDelegate in the FinishedLoading method to add it to the request processing pipeline.

NSUrlProtocol.RegisterClass(new MonoTouch.ObjCRuntime.Class(typeof(CustomNSUrlProtocol)));

The methods used by the NSUrlConnection, usually through a delegate, to mark the end of a connection are ReceivedResponse, ReceivedData, and FinishedLoading for successful requests and FailedWithError for unsuccessful requests.

public class CustomNSUrlConnectionDataDelegate : NSUrlConnectionDataDelegate
{
  public NSUrlProtocol Handler{ get; private set; }
  public CustomNSUrlConnectionDataDelegate(NSUrlProtocol protocol)
  {
  	this.Handler = protocol;
  }
  public override void ReceivedResponse (NSUrlConnection connection, NSUrlResponse response)
  {
  	this.Handler.Client.ReceivedResponse(this.Handler, response, NSUrlCacheStoragePolicy.NotAllowed);
  }
  public override void FinishedLoading (NSUrlConnection connection)
  {
  	this.Handler.Client.FinishedLoading(this.Handler);
  }
  public override void ReceivedData (NSUrlConnection connection, NSData data)
  {
  	this.Handler.Client.DataLoaded(this.Handler,data);
  }
  public override void FailedWithError (NSUrlConnection connection, NSError error)
  {
  	this.Handler.Client.FailedWithError(this.Handler,error);
  }
}
public class CustomNSUrlProtocol : NSUrlProtocol
{
  .
  .
  .
  public override void StartLoading ()
  {
  	NSUrlConnection connection = new NSUrlConnection (Request, new CustomNSUrlConnectionDataDelegate());
  }
}

On the other hand, NSUrlSession uses a single callback function on its tasks (objects used to execute requests to a server in the NSUrlSession class suite) to handle both successful and unsuccessful requests.

public static class Session
{
  private static NSUrlSession _instance;

  public static NSUrlSession CurrentSession 
  {
    get 
    {
      if (_instance == null) 
      {
  	  _instance = NSUrlSession.FromConfiguration (NSUrlSessionConfiguration.DefaultSessionConfiguration, new NSUrlSessionDelegate (), null);
  	  //Set configuration for the NSUrlSession
  	}
  	return _instance;
    }
  }
}
public class CustomNSUrlProtocol : NSUrlProtocol
{
  .
  .
  .
  public override void StartLoading ()
  {
  	Session.CurrentSession.CreateDataTask (Request, (NSData data, NSUrlResponse response, NSError error) => {
  		if(response==null)
  		{
  			this.Client.FailedWithError(this,error);
  		}
  		else
  		{
  			this.Client.ReceivedResponse(this,response,NSUrlCacheStoragePolicy.Allowed);
  this.Client.DataLoaded(this,data);
  this.Client.FinishedLoading(this);
  		}
  	});
  }
}

A custom NSUrlSessionDelegate can be defined and used to specify authentication handling as well as other methods specified here. More granular task specific delegates exist for each task type. Each task level delegate inherits from NSUrlSessionDelegate and can be used to implement the session-level events along side the task-level events that are explicitly defined within the task delegates.

Using the NSUrlProtocol/NSUrlSession combination above, along with implimenting NTLM authentication logic, enabled me to load a SharePoint page including resources from three separate web applications, each requiring its own NTLM authentication handshake. Prior to implementing a custom NSUrlProtocol, using the NSUrlSession class suite, the requests made to load page resources (images, css, javascript, etc.) would result in either account lockouts or resources of the page failing to load.