Sometimes, it is necessary for users of your site to be able to upload files to Document Libraries in an asynchronous manner. They could be creating user-generated content and require a space to upload a file to reference in their content, for instance. Using some JavaScript and an ASP.NET WebAPI controller, we can accomplish this fairly simply.
The HTML
The first thing we’ll need is an input that allows users to select a file. This’ll be a simple HTML input the “file” type. You can put this HTML (along with the associated JavaScript) anywhere on the page, either via Content Blocks in the backend, or via a Sitefinity widget. In my case, I’m using an MVC widget.
<input type="file" id="fileUploadInput" name="fileUploadInput" />
The JavaScript
That’s the easy part. Once we have the input in place, we need to write some JavaScript that’ll be used to listen for when a user selects a file to automatically trigger the upload. The JavaScript will watch for any changes to the input, and trigger the upload when it detects a change. We’ll be making use of jQuery’s ajax function here.
(function ($) { "use strict"; var $fileUploadInput = $("#fileUploadInput"); $fileUploadInput.on("change", function () { var newFile = $fileUploadInput[0].files[0]; if (newFile === undefined || newFile === null) { return; } var formData = new FormData(); formData.append("newFile", newFile); $.ajax({ url: "/api/Files/Upload", type: "POST", data: formData, cache: false, contentType: false, processData: false }) .done(function (fileUrl) { // Handle returned fileUrl here. }) .always(function () { $fileUploadInput.val(""); }); }); })(jQuery);
There’s a fair bit going on here, so let’s break it down. First, you’ll notice that we’re wrapping our JavaScript in a self-executing anonymous function for closure (check out this blog post for an explanation). Next we grab our HTML file input and then attach a method to its “on change” event. Whenever a user selects a file, our function will get called.
Within our on-change method is where the real meat and potatoes of the JavaScript is. From the file input we grab the selected file, and validate that one exists and is present. Now that we have the file, we need to send it back to the server in order for it to be uploaded into a Document Library of Sitefinity. We will make a POST to the server to an ASP.NET WebAPI controller (detailed below).
In order to make the POST and get the selected file to the server, you have to do some special setup as opposed to just calling $.post and passing the file along as part of the POST parameters. On line 12 we create a new FormData object and append our file to it. This is the data that we send via the $.ajax call (line 17) to contact the server. We specify the URL to the WebAPI controller method, and set cache, contentType, and processData all to false.
Once all that is set up, you can create any number of Promises to suit your needs. In this example, I have two: done() and always(). In the done() method, it accepts the URL to the file as a parameter (which is what our controller returns), and that can be used to provide the user who uploaded the file with a link to copy/paste or use as they see fit.
The Controller
Initial Setup
Finally we have our controller. This not only provides a means for our asynchronous JavaScript method to contact the server, but also handles the process of creating a new Document item and uploading it to a specific Document Library in Sitefinity.
- In the root of your web project (likely called SitefinityWebApp if it hasn’t been renamed), add an “Api” folder
- Within Api, create a new file: FilesController.cs
A Note on Security
An important consideration when creating public-facing API methods is handling who is allowed to use them. Since this is a method that anyone can post a file to, the method itself is responsible for handling security. In this case, the only security rule is “the uploading user must be logged into Sitefinity.” Your application’s security concerns can vary greatly, and be far more complex than what this simple scenario entails.
Code
With that in mind, here is the full code for FilesController.cs We’ll break it down right after the snippet block.
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Web; using System.Web.Http; using Telerik.Sitefinity.GenericContent.Model; using Telerik.Sitefinity.Libraries.Model; using Telerik.Sitefinity.Modules.Libraries; using Telerik.Sitefinity.Security; using Telerik.Sitefinity.Security.Claims; using Telerik.Sitefinity.Security.Model; namespace SitefinityWebApp.Api { public class FilesController : ApiController { private readonly LibrariesManager _librariesManager = LibrariesManager.GetManager(); [HttpPost] public string Upload() { var currentUserId = ClaimsManager.GetCurrentUserId(); if (currentUserId == Guid.Empty) { throw new UnauthorizedAccessException(); } User currentUser = UserManager.GetManager().GetUser(currentUserId); HttpFileCollection files = HttpContext.Current.Request.Files; if (files.Count <= 0) { throw new InvalidOperationException("No file was detected to upload."); } HttpPostedFile newFile = files[0]; DocumentLibrary documentLibrary = this.GetDocumentLibrary(); string fileExtension = Path.GetExtension(newFile.FileName); string fileName = files[0].FileName; string urlName = this.GetUniqueUrlName(documentLibrary, fileName); Document document = _librariesManager.CreateDocument(); document.Author = currentUser.UserName; document.DateCreated = DateTime.UtcNow; document.Owner = currentUser.Id; document.Parent = documentLibrary; document.Title = urlName; document.UrlName = urlName; _librariesManager.Upload(document, newFile.InputStream, fileExtension); _librariesManager.RecompileItemUrls(document); _librariesManager.Lifecycle.Publish(document); document.ApprovalWorkflowState = "Published"; _librariesManager.SaveChanges(); Document liveDocument = _librariesManager.Lifecycle.GetLive(document) as Document; if (liveDocument == null) { throw new InvalidOperationException("Document could not be uploaded properly."); } return liveDocument.MediaUrl; } private static string GetUniqueUrlName(DocumentLibrary documentLibrary, string fileName) { List<string> existingUrlNames = documentLibrary.ChildContentItems .Where(c => c.Status == ContentLifecycleStatus.Live && c.Visible) .Select(c => c.UrlName.ToString()).ToList(); string safeTitle = fileName.GetSafeUrlName(); while (existingUrlNames.Contains(safeTitle)) { safeTitle += "-"; } return safeTitle; } private DocumentLibrary GetDocumentLibrary() { const string documentLibraryName = "My Document Library"; DocumentLibrary documentLibrary = _librariesManager.GetDocumentLibraries() .FirstOrDefault(l => l.Title == documentLibraryName); if (documentLibrary == null) { throw new NotImplementedException (string.Format("The Document Library \"{0}\" does not exist to upload new files in.", documentLibraryName)); } return documentLibrary; } } }
Our asynchronous call starts at the Upload() method. Our class inherits from ApiController and has decorated Upload() with the [HttpPost] attribute. This enables the JavaScript asynchronous $.ajax() call to get into this method with the provided file. The start of the method simply validates that a file was uploaded and that the user performing the upload is logged in as an existing Sitefinity user.
As you can see, our method has no input parameters. The file we uploaded instead arrives within the HttpContext.Current.Request.Files collection (line 30). Starting at line 38 we dig into Sitefinity’s code to create a Document. Here, we fetch the DocumentLibrary that we want to store our file in. In this example we’re grabbing a library by name and getting the same one every time (all frontend users upload to the same DocumentLibrary).
Once we have that we get the file details (name, extension, url name) and begin creating the Document. We set up the current user as the Author/Owner and assign values to other basic fields as well. In addition, the property on Document that you assign the DocumentLibrary to is called “Parent” (line 47). You assign it the DocumentLibrary object itself, not just the ID. Once our Document’s properties are set, we go through the process of actually uploading the file to Sitefinity and publishing the Document so that we have a Live version to work with (lines 51-55).
Finally, we retrieve the Live version of our just-published Document and return its URL. This is the “fileUrl” that our done() Promise method retrieves in the JavaScript. You can, of course, alter the Upload() controller method to return whatever you like, but for our scenario here we simply return the URL.
That’s It!
With our HTML, JavaScript, and C# in place, our asynchronous file upload feature is ready to rock. Users can now select a file via the HTML input, and then get a valid URL as a response, with their file now fully uploaded and ready to use. In my next post I will show you how to use this feature to extend the functionality of a Kendo Editor to allow dynamic insertion of file URLs on the fly.
The post How to Upload Files to your Sitefinity Document Library Asynchronously appeared first on Falafel Software Blog.