I`ve promised to write about my implementation of the async file uploader for the web forms application. The task was to implement it as a server control that can be added and reused on different pages (actually, in order to replace the old teleric control).
I decided to use this plugin. And the only problem was with a url to upload the file. As it should be available from the different pages I could not use the [WebMethod] so I`ve implemented a http handler to upload the file. Plus few additional changes to the client side - show the amount of files, add an option to delete uploaded file etc.
Here is my implementation:
Here I`ve used the Session to store the target folder for files as this is safer. Hope this will help someone :)
I decided to use this plugin. And the only problem was with a url to upload the file. As it should be available from the different pages I could not use the [WebMethod] so I`ve implemented a http handler to upload the file. Plus few additional changes to the client side - show the amount of files, add an option to delete uploaded file etc.
Here is my implementation:
public class AsyncUploader : CompositeControl { private Panel _container; private Panel _uploadedFilesContainer; private HtmlInputFile _uploader; public string[] AllowedFileExtensions { get; set; } ////// Maximum file size in bytes /// public int MaxFileSize { get; set; } ////// Gets or sets the virtual path of the folder, where RadUpload will automatically save the valid files after the upload completes. /// Note that existing files with the same name will be overwritten. As such it is best to append a unique identifier to the folder. /// public string TargetPhysicalFolder { get; set; } public int MaxFilesCount { get; set; } protected string EscapedUniqueId { get { return Regex.Replace(UniqueID, "[$.\\s]", "_"); } } public ListFiles { get { EnsureChildControls(); var targetFolder = TargetPhysicalFolder; var result = new List (); foreach (var key in Page.Request.Form.AllKeys) { if (key.StartsWith(EscapedUniqueId + "savedFile_")) { var index = key.Replace(EscapedUniqueId + "savedFile_", ""); var savedFileName = Page.Request.Form[key]; result.Add(new UploadedFile { OriginalFileName = Page.Request.Form[EscapedUniqueId + "originalFile_" + index], SavedFileFullPath = Path.Combine(targetFolder, savedFileName), SavedFileName = savedFileName }); } } return result; } } protected override void OnPreRender(EventArgs e) { if (_container.ClientIDMode != ClientIDMode.Static) throw new ArgumentException("ClientIDMode"); var uniqueId = EscapedUniqueId; var options = new JavaScriptSerializer().Serialize(new { uniqueID = uniqueId, elementSelector = "#" + uniqueId, sessionId = Page.Session.SessionID, maxFiles = MaxFilesCount, maxFileSize = MaxFileSize, extensions = AllowedFileExtensions != null && AllowedFileExtensions.Any() ? AllowedFileExtensions.ToDelimitedString("|") : "*", submitButtonsSelector = _submitButtonsToDisable .Select(x => "#" + x.ClientID) .ToDelimitedString(", ") }); Page.Session[uniqueId + "_TargetFolder"] = TargetPhysicalFolder; var setUp = "".F( options); Page.ClientScript.RegisterClientScriptBlock(GetType(), "setup" + uniqueId, setUp); base.OnPreRender(e); } protected override void CreateChildControls() { _container = new Panel(); _container.ClientIDMode = ClientIDMode.Static; _container.ID = EscapedUniqueId; _uploader = new HtmlInputFile(); _uploader.Attributes.Add("class", "filePicker"); if (MaxFilesCount > 1) _uploader.Attributes.Add("multiple", "true"); _uploadedFilesContainer = new Panel(); _uploadedFilesContainer.Attributes.Add("class", "uploadedFiles"); _uploadedFilesContainer.ID = RandomString.Generate(20); var errorsContainer = new Panel(); errorsContainer.Attributes.Add("class", "uploadErrors"); _container.Controls.Add(_uploader); _container.Controls.Add(_uploadedFilesContainer); _container.Controls.Add(errorsContainer); this.Controls.Add(_container); base.CreateChildControls(); } protected override void OnLoad(EventArgs e) { //Register resources (here I`ve used Peter Blum) ClientScriptLibrary .RegisterEmbeddedResource( typeof(AsyncUploader), "jquery.ui.widget.js", ClientDependencyType.Javascript); ClientScriptLibrary .RegisterEmbeddedResource( typeof(AsyncUploader), "jquery.fileupload.js", ClientDependencyType.Javascript); ClientScriptLibrary .RegisterEmbeddedResource( typeof(AsyncUploader), "AsyncUploadFormItem.js", ClientDependencyType.Javascript); ClientScriptLibrary .RegisterEmbeddedResource( typeof(AsyncUploader), "AsyncUploadFormItem.css", ClientDependencyType.CSS); base.OnLoad(e); } }
public class UploadedFile { public string OriginalFileName { get; set; } public string SavedFileName { get; set; } public string SavedFileFullPath { get; set; } }
var setupfileUpload = function (options) { var $filePicker = $(options.elementSelector + ' .filePicker'); var $uploadedFiles = $(options.elementSelector + ' .uploadedFiles'); var $uploadErrors = $(options.elementSelector + ' .uploadErrors'); $filePicker.fileupload({ url: 'fileUpload.axd', dropZone: $filePicker, add: function (e, data) { cleanExceptionsPanel(); var count = $uploadedFiles.find('.sentFile').length; //client validation by file size and file type if (!isUploadLimit(options.maxFiles, count) || !isFileValid(data.files[0].size, data.files[0].name)) return; //to support duplicated files the div id should be unique for different files - and that Id should be passed to the handler. var divId = 'id' + (new Date()).getTime(); //submit the form with 2 additional parameters - where to save and file id data.formData = { targetFolder: options.targetFolder, fileId: divId, sessionId: options.sessionId, controlid: options.uniqueID }; var jqXHR = data.submit(); //append a div containing inputs for a given file var div = $(''); div.append('
public class FileUploadHandler : IHttpHandler, IReadOnlySessionState { private static readonly Logger _log = LogManager.GetCurrentClassLogger(); private void DeleteFile(HttpContext context) { // Make sure to sanitise the filename by calling Path.GetFileName. This will // prevent deletions from folders other than the target folder (which is known only // by the server) var filename = Path.GetFileName(context.Request.Form["filename"]); var controlId = context.Request.Form["controlId"]; var path = context.Session[controlId + "_TargetFolder"].ToString(); var targetFilename = Path.Combine(path, filename); if (File.Exists(targetFilename)) File.Delete(targetFilename); } private void UploadFile(HttpContext context) { Parse(context.Request.InputStream, Encoding.UTF8); var controlId = context.Request.Form["controlId"]; var fileId = context.Request.Form["fileId"]; var path = context.Session[controlId + "_TargetFolder"].ToString(); var targetFolder = Directory.CreateDirectory(path).FullName; var targetFilename = Path.Combine(targetFolder, _filename); // Handle existing files by incrementing counter int counter = 1; while (File.Exists(targetFilename)) { counter++; targetFilename = Path.Combine(targetFolder, Path.GetFileNameWithoutExtension(_filename) + counter + Path.GetExtension(_filename)); } using (var file = File.Create(targetFilename)) { file.Write(_fileContents, 0, _fileContents.Length); } context.Response.Write(new JavaScriptSerializer() .Serialize(new { OriginalFile = _filename, SavedFile = Path.GetFileName(targetFilename), FileId = fileId })); } public void ProcessRequest(HttpContext context) { var sessionId = context.Request.Form["sessionId"]; if (context.Session == null || context.Session.SessionID != sessionId) throw new InvalidOperationException("Wrong session state during the file upload operation"); if (context.Request.Form["deleteRequest"] != null) { DeleteFile(context); } else { UploadFile(context); } } private byte[] ToByteArray(Stream stream) { byte[] buffer = new byte[32768]; using (MemoryStream ms = new MemoryStream()) { while (true) { int read = stream.Read(buffer, 0, buffer.Length); if (read <= 0) return ms.ToArray(); ms.Write(buffer, 0, read); } } } private int IndexOf(byte[] searchWithin, byte[] serachFor, int startIndex) { int index = 0; int startPos = Array.IndexOf(searchWithin, serachFor[0], startIndex); if (startPos != -1) { while ((startPos + index) < searchWithin.Length) { if (searchWithin[startPos + index] == serachFor[index]) { index++; if (index == serachFor.Length) { return startPos; } } else { startPos = Array.IndexOf(searchWithin, serachFor[0], startPos + index); if (startPos == -1) { return -1; } index = 0; } } } return -1; } private void Parse(Stream stream, Encoding encoding) { // Read the stream into a byte array byte[] data = ToByteArray(stream); // Copy to a string for header parsing string content = encoding.GetString(data); // The first line should contain the delimiter int delimiterEndIndex = content.IndexOf("\r\n"); if (delimiterEndIndex > -1) { string delimiter = content.Substring(0, content.IndexOf("\r\n")); // Look for Content-Type Regex re = new Regex(@"(?<=Content\-Type:)(.*?)(?=\r\n\r\n)"); Match contentTypeMatch = re.Match(content); // Look for filename re = new Regex(@"(?<=filename\=\"")(.*?)(?=\"")"); Match filenameMatch = re.Match(content); // Did we find the required values? if (contentTypeMatch.Success && filenameMatch.Success) { // Set properties this._contentType = contentTypeMatch.Value.Trim(); this._filename = filenameMatch.Value.Trim(); // Get the start & end indexes of the file contents int startIndex = contentTypeMatch.Index + contentTypeMatch.Length + "\r\n\r\n".Length; byte[] delimiterBytes = encoding.GetBytes("\r\n" + delimiter); int endIndex = IndexOf(data, delimiterBytes, startIndex); int contentLength = endIndex - startIndex; // Extract the file contents from the byte array byte[] fileData = new byte[contentLength]; Buffer.BlockCopy(data, startIndex, fileData, 0, contentLength); this._fileContents = fileData; } } } private string _contentType; public string _filename; public byte[] _fileContents; public bool IsReusable { get { return false; } } }
Here I`ve used the Session to store the target folder for files as this is safer. Hope this will help someone :)
No comments:
Post a Comment