Search code examples
azurexamarinxamarin.iosazure-blob-storagensurlsession

Does the WindowsAzure.Storage library for Xamarin use NSUrlSession for file uploads?


Problem Statement: We have a requirement to upload log data to Azure Storage from a Xamarin.IOS application. The logs are not created by the user of the application, and there's no constraint on the user to keep the application open for any amount of time after the logs are generated. We want to reliably upload our logs with a couple points in mind:

  • The user might send the app into the background
  • The file sizes can be up to 15MB
  • We don't care when we get them. We're open to scheduling a task for this.

In looking at potential solutions to this problem, the Xamarin documentation states that in iOS7+:

NSURLSession allows us to create tasks to:

  1. Transfer content through network and device interruptions.
  2. Upload and download large files ( Background Transfer Service ).

So it seems like NSURLSession is a good candidate for this sort of work, but I wonder if I am reinventing the wheel. Does the WindowsAzure.Storage client library respect app backgrounding with an upload implementation based on NSURLSession, or if I want to upload the data in the background, is it necessary to upload to an intermediate server I control with a POST method, and then relay data to Azure Storage? There doesn't seem to be any indication from the public Azure documentation that uploads can be done via scheduled task.


Solution

  • I got this working. I've simplified the classes and methods into a single method. Only the necessities are here.

    public void UploadFile(File playbackFile)
    {
        /// Specify your credentials
        var sasURL = "?<the sastoken>";
    
        /// Azure blob storage URL
        var storageAccount = "https://<yourstorageaccount>.blob.core.windows.net/<your container name>";
    
        /// specify a UNIQUE session name
        var configuration =
            NSUrlSessionConfiguration.CreateBackgroundSessionConfiguration("A background session name");
    
        /// create the session with a delegate to recieve callbacks and debug
        var session = NSUrlSession.FromConfiguration(
            configuration,
            new YourSessionDelegate(),
            new NSOperationQueue());
    
        /// Construct the blob endpoint
        var url = $"{storageAccount}/{playbackFile.Name}{sasURL}";
        var uploadUrl = NSUrl.FromString(url);
    
        /// Add any headers for Blob PUT. x-ms-blob-type is REQUIRED
        var dic = new NSMutableDictionary();
        dic.Add(new NSString("x-ms-blob-type"), new NSString("BlockBlob"));
    
        /// Create the request with NSMutableUrlRequest
        /// A default NSUrlRequest.FromURL() is immutable with a GET method
        var request = new NSMutableUrlRequest(uploadUrl);
        request.Headers = dic;
        request.HttpMethod = "PUT";
    
        /// Create the task
        var uploadTask = session.CreateUploadTask(
            request,
            NSUrl.FromFilename(playbackFile.FullName));
    
        /// Start the task
        uploadTask.Resume();
    }
    
    /// Delegate to recieve callbacks. Implementations are omitted for brevity
    public class YourSessionDelegate: NSUrlSessionDataDelegate
    { 
        public override void DidBecomeInvalid(NSUrlSession session, NSError error)
        {
            Console.WriteLine(error.Description);
        }
    
        public override void DidSendBodyData(NSUrlSession session, NSUrlSessionTask task, long bytesSent, long totalBytesSent, long totalBytesExpectedToSend)
        {
            Console.WriteLine(bytesSent);
        }
    
        public override void DidReceiveData(NSUrlSession session, NSUrlSessionDataTask dataTask, NSData data)
        {
            Console.WriteLine(data);
        }
    
        public override void DidCompleteWithError(NSUrlSession session, NSUrlSessionTask task, NSError error)
        {
            var uploadTask = task as NSUrlSessionUploadTask;
            Console.WriteLine(error?.Description);
        }
    
        public override void DidReceiveResponse(NSUrlSession session, NSUrlSessionDataTask dataTask, NSUrlResponse response, Action<NSUrlSessionResponseDisposition> completionHandler)
        {
            Console.WriteLine(response);
        }
    
        public override void DidFinishEventsForBackgroundSession(NSUrlSession session)
        {
            using (AppDelegate appDelegate = UIApplication.SharedApplication.Delegate as AppDelegate)
            {
                var handler = appDelegate.BackgroundSessionCompletionHandler;
                if (handler != null)
                {
                    appDelegate.BackgroundSessionCompletionHandler = null;
                    handler();
                }
            }
        }
    }
    

    Helpful documentation:

    Hopefully someone finds this useful and spends less time on this than I did. Thanks @SushiHangover for pointing me in the right direction.