SFTP File Upload Using ASP.Net Web Api and AngularJS

Introduction

The Angular-file-upload directive by nervgh is an awesome lightweight AngularJS directive that handles file upload for you and lets you upload files asynchronously to the server. This article will provide you with a basic understanding of how to upload files using this directive together with the .NET WebAPI service on a SFTP server. For the purpose of this tutorial, I'll keep everything as simple as possible since the focus here is on connecting AngularJS and async upload with a .NET WebAPI service and not on additional functionality that can be built afterwards around the angular-file-upload.

Background

From the Unix man page: "sftp is an interactive file transfer program, similar to ftp that does all operations over an encrypted ssh transport".

Why is SFTP better than FTP

In FTP all data is passed back and forth between the client and server without the use of encryption. This makes it possible for an evesdropper to listen in and retrieve your confidential information including login details. With SFTP all the data is encrypted before it is sent across the network.

Using the code

A brief description of how to use the article or code. The class names, the methods and properties, any tricks or tips.

HTML Code

  1. @{  
  2.     ViewBag.Title = "SFTP Uplaod Using WebApi and AngularJS";  
  3. }  
  4.   
  5. <html id="ng-app" ng-app="app">  
  6. <head>  
  7.     <title>Simple example</title>  
  8.     <link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" />  
  9.     <script src="http://code.jquery.com/jquery-1.8.3.min.js"></script>  
  10.     <script src="http://netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>  
  11.     <script src="http://code.angularjs.org/1.1.5/angular.min.js"></script>  
  12.     <script src="~/Scripts/angular-file-upload.js"></script>  
  13.     <script src="~/Scripts/controllers.js"></script>  
  14.     <style>  
  15.         .my-drop-zone {  
  16.             border: dotted 3px lightgray;  
  17.         }  
  18.   
  19.         .nv-file-over {  
  20.             border: dotted 3px red;  
  21.         }  
  22.         .another-file-over-class {  
  23.             border: dotted 3px green;  
  24.         }  
  25.   
  26.         html, body {  
  27.             height: 100%;  
  28.         }  
  29.     </style>  
  30. </head>  
  31. <body ng-controller="AppController" nv-file-drop="" uploader="uploader" filters="queueLimit, customFilter">  
  32.     <div class="container">  
  33.         <div class="row">  
  34.             <div class="col-md-3">  
  35.                 <h3>Select files</h3>  
  36.                 <div ng-show="uploader.isHTML5">  
  37.                     <div nv-file-drop="" uploader="uploader" options="{ url: '/foo' }">  
  38.                         <div nv-file-over="" uploader="uploader" over-class="another-file-over-class" class="my-drop-zone">  
  39.                             Another drop zone with its own settings  
  40.                         </div>  
  41.                     </div>  
  42.                 </div>  
  43.                 Single  
  44.                 <input type="file" nv-file-select="" uploader="uploader" />  
  45.             </div>  
  46.             <div class="col-md-9" style="margin-bottom: 40px">  
  47.                 <h3>Upload queue</h3>  
  48.                 <p>Queue length: {{ uploader.queue.length }}</p>  
  49.                 <table class="table">  
  50.                     <thead>  
  51.                         <tr>  
  52.                             <th width="50%">Name</th>  
  53.                             <th ng-show="uploader.isHTML5">Size</th>  
  54.                             <th ng-show="uploader.isHTML5">Progress</th>  
  55.                             <th>Status</th>  
  56.                             <th>Actions</th>  
  57.                         </tr>  
  58.                     </thead>  
  59.                     <tbody>  
  60.                         <tr ng-repeat="item in uploader.queue">  
  61.                             <td><strong>{{ item.file.name }}</strong></td>  
  62.                             <td ng-show="uploader.isHTML5" nowrap>{{ item.file.size/1024/1024|number:2 }} MB</td>  
  63.                             <td ng-show="uploader.isHTML5">  
  64.                                 <div class="progress" style="margin-bottom: 0;">  
  65.                                     <div class="progress-bar" role="progressbar" ng-style="{ 'width': item.progress + '%' }"></div>  
  66.                                 </div>  
  67.                             </td>  
  68.                             <td class="text-center">  
  69.                                 <span ng-show="item.isSuccess"><i class="glyphicon glyphicon-ok"></i></span>  
  70.                                 <span ng-show="item.isCancel"><i class="glyphicon glyphicon-ban-circle"></i></span>  
  71.                                 <span ng-show="item.isError"><i class="glyphicon glyphicon-remove"></i></span>  
  72.                             </td>  
  73.                             <td nowrap>  
  74.                                 <button type="button" class="btn btn-success btn-xs" ng-click="item.upload()" ng-disabled="item.isReady || item.isUploading || item.isSuccess">  
  75.                                     <span class="glyphicon glyphicon-upload"></span> Upload  
  76.                                 </button>  
  77.                                 <button type="button" class="btn btn-warning btn-xs" ng-click="item.cancel()" ng-disabled="!item.isUploading">  
  78.                                     <span class="glyphicon glyphicon-ban-circle"></span> Cancel  
  79.                                 </button>  
  80.                                 <button type="button" class="btn btn-danger btn-xs" ng-click="item.remove()">  
  81.                                     <span class="glyphicon glyphicon-trash"></span> Remove  
  82.                                 </button>  
  83.                             </td>  
  84.                         </tr>  
  85.                     </tbody>  
  86.                 </table>  
  87.                 <div>  
  88.                     <div>  
  89.                         Queue progress:  
  90.                         <div class="progress" style="">  
  91.                             <div class="progress-bar" role="progressbar" ng-style="{ 'width': uploader.progress + '%' }"></div>  
  92.                         </div>  
  93.                     </div>  
  94.                     <button type="button" class="btn btn-success btn-s" ng-click="uploader.uploadAll()" ng-disabled="!uploader.getNotUploadedItems().length">  
  95.                         <span class="glyphicon glyphicon-upload"></span> Upload all  
  96.                     </button>  
  97.                     <button type="button" class="btn btn-warning btn-s" ng-click="uploader.cancelAll()" ng-disabled="!uploader.isUploading">  
  98.                         <span class="glyphicon glyphicon-ban-circle"></span> Cancel all  
  99.                     </button>  
  100.                     <button type="button" class="btn btn-danger btn-s" ng-click="uploader.clearQueue()" ng-disabled="!uploader.queue.length">  
  101.                         <span class="glyphicon glyphicon-trash"></span> Remove all  
  102.                     </button>  
  103.                 </div>  
  104.             </div>  
  105.         </div>  
  106.     </div>  
  107. </body>  
  108. </html>  
AngularJS Controller Code
  1. 'use strict';  
  2.   
  3. angular  
  4. .module('app', ['angularFileUpload'])  
  5. .controller('AppController', ['$scope''FileUploader'function($scope, FileUploader) {  
  6.     // Uploader Plugin Code  
  7.    
  8.     var uploader = $scope.uploader = new FileUploader({  
  9.         url: window.location.protocol + '//' + window.location.host +   
  10.              window.location.pathname + '/api/Upload/UploadFile'  
  11.     });  
  12.   
  13.     // FILTERS  
  14.   
  15.     uploader.filters.push({  
  16.         name: 'extensionFilter',  
  17.         fn: function (item, options) {  
  18.             var filename = item.name;  
  19.             var extension = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase();  
  20.             if (extension == "pdf" || extension == "doc" || extension == "docx" ||   
  21.                 extension == "rtf")  
  22.                 return true;  
  23.             else {  
  24.                 alert('Invalid file format. Please select a file with pdf/doc/docs or  
  25.                 rtf format and try again.');  
  26.                 return false;  
  27.             }  
  28.         }  
  29.     });  
  30.   
  31.     uploader.filters.push({  
  32.         name: 'sizeFilter',  
  33.         fn: function (item, options) {  
  34.             var fileSize = item.size;  
  35.             fileSize = parseInt(fileSize) / (1024 * 1024);  
  36.             if (fileSize <= 5)  
  37.                 return true;  
  38.             else {  
  39.                 alert('Selected file exceeds the 5MB file size limit.   
  40.                        Please choose a new file and try again.');  
  41.                 return false;  
  42.             }  
  43.         }  
  44.     });  
  45.   
  46.     uploader.filters.push({  
  47.         name: 'itemResetFilter',  
  48.         fn: function (item, options) {  
  49.             if (this.queue.length < 5)  
  50.                 return true;  
  51.             else {  
  52.                 alert('You have exceeded the limit of uploading files.');  
  53.                 return false;  
  54.             }  
  55.         }  
  56.     });  
  57.    
  58.     // CALLBACKS  
  59.    
  60.     uploader.onWhenAddingFileFailed = function (item, filter, options) {  
  61.         console.info('onWhenAddingFileFailed', item, filter, options);  
  62.     };  
  63.     uploader.onAfterAddingFile = function (fileItem) {  
  64.         alert('Files ready for upload.');  
  65.     };  
  66.   
  67.     uploader.onSuccessItem = function (fileItem, response, status, headers) {  
  68.         $scope.uploader.queue = [];  
  69.         $scope.uploader.progress = 0;  
  70.         alert('Selected file has been uploaded successfully.');  
  71.     };  
  72.     uploader.onErrorItem = function (fileItem, response, status, headers) {  
  73.         alert('We were unable to upload your file. Please try again.');  
  74.     };  
  75.     uploader.onCancelItem = function (fileItem, response, status, headers) {  
  76.         alert('File uploading has been cancelled.');  
  77.     };  
  78.   
  79.     uploader.onAfterAddingAll = function(addedFileItems) {  
  80.         console.info('onAfterAddingAll', addedFileItems);  
  81.     };  
  82.     uploader.onBeforeUploadItem = function(item) {  
  83.         console.info('onBeforeUploadItem', item);  
  84.     };  
  85.     uploader.onProgressItem = function(fileItem, progress) {  
  86.         console.info('onProgressItem', fileItem, progress);  
  87.     };  
  88.     uploader.onProgressAll = function(progress) {  
  89.         console.info('onProgressAll', progress);  
  90.     };  
  91.           
  92.     uploader.onCompleteItem = function(fileItem, response, status, headers) {  
  93.         console.info('onCompleteItem', fileItem, response, status, headers);  
  94.     };  
  95.     uploader.onCompleteAll = function() {  
  96.         console.info('onCompleteAll');  
  97.     };  
  98.   
  99.     console.info('uploader', uploader);  
  100. }]); 
Web API Controller
  1. [System.Web.Http.HttpPost]  
  2. public void UploadFile()  
  3. {  
  4.     if (HttpContext.Current.Request.Files.AllKeys.Any())  
  5.     {  
  6.         var httpPostedFile = HttpContext.Current.Request.Files["file"];  
  7.   bool folderExists = Directory.Exists(HttpContext.Current.Server.MapPath("~/UploadedDocuments"));  
  8.         if (!folderExists)  
  9.             Directory.CreateDirectory(HttpContext.Current.Server.MapPath("~/UploadedDocuments"));  
  10.         var fileSavePath = Path.Combine(HttpContext.Current.Server.MapPath("~/UploadedDocuments"),  
  11.                                         httpPostedFile.FileName);  
  12.         httpPostedFile.SaveAs(fileSavePath);  
  13.   
  14.         if (File.Exists(fileSavePath))  
  15.         {  
  16.             //AppConfig is static class used as accessor for SFTP configurations from web.config  
  17.             using (SftpClient sftpClient = new SftpClient(AppConfig.SftpServerIp,   
  18.                                                          Convert.ToInt32(AppConfig.SftpServerPort),  
  19.                                                          AppConfig.SftpServerUserName,   
  20.                                                          AppConfig.SftpServerPassword))  
  21.             {  
  22.                 sftpClient.Connect();  
  23.                 if (!sftpClient.Exists(AppConfig.SftpPath + "UserID"))  
  24.                 {  
  25.                     sftpClient.CreateDirectory(AppConfig.SftpPath + "UserID");  
  26.                 }  
  27.   
  28.                 Stream fin = File.OpenRead(fileSavePath);  
  29.                 sftpClient.UploadFile(fin, AppConfig.SftpPath + "/" + httpPostedFile.FileName,   
  30.                                       true);  
  31.                 fin.Close();  
  32.                 sftpClient.Disconnect();  
  33.             }  
  34.         }  
  35.     }  

Points of Interest

1. extensionFilter is used to allow only PDF, Word and RTF documents.



2. The file size limit is also implemented by allowing file upto 5 MB.



3. Number of files allowed in upload queue is also implemented by filter.



4. In the WebAPI, an UploadedDocuments folder is created to get the file into the webserver from the client.

5. Drag and drop is also implemented along with a default browse button.

For the complete source code, please see https://github.com/m-hassan-tariq/SFTPFileUploadUsingWebAPIandAngularJS