Creating a NSUrlProtocol that uses NSUrlSession in Xamarin
Nov 14, 2014
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
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.’