I am trying to implement ng-flow https://github.com/flowjs/ng-flow
for file upload. It upload files in chunk, I successfully set this on client but I am not sure how to handle file on backend inside web api method.
public void Upload()
{
//how to handle file?
}
The request contain the following information
Here's the ASP.NET Web API controller that I've come up with to save the chunks and assemble them.
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Net;
using System.Net.Http;
using System.IO;
using System.Web;
using System.Web.Http;
namespace Api.Controllers
{
[RoutePrefix("api")]
public class FlowUploadController : ApiController
{
string root = Path.Combine(Path.GetTempPath(), "FlowUpload");
[Route("flowupload"), AcceptVerbs("GET")]
public object Upload(
int flowChunkNumber,
string flowIdentifier)
{
if (ChunkIsHere(flowChunkNumber, flowIdentifier))
{
return Request.CreateResponse(HttpStatusCode.OK);
}
else
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
}
[Route("flowupload"), AcceptVerbs("POST")]
public async Task<object> Upload()
{
// Check if the request contains multipart/form-data.
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
if (!Directory.Exists(root)) Directory.CreateDirectory(root);
var provider = new MultipartFormDataStreamProvider(root);
try
{
await Request.Content.ReadAsMultipartAsync(provider);
int chunkNumber = Convert.ToInt32(provider.FormData["flowChunkNumber"]);
int totalChunks = Convert.ToInt32(provider.FormData["flowTotalChunks"]);
string identifier = provider.FormData["flowIdentifier"];
string filename = provider.FormData["flowFilename"];
// Rename generated file
MultipartFileData chunk = provider.FileData[0]; // Only one file in multipart message
RenameChunk(chunk, chunkNumber, identifier);
// Assemble chunks into single file if they're all here
TryAssembleFile(identifier, totalChunks, filename);
// Success
return Request.CreateResponse(HttpStatusCode.OK);
}
catch (System.Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e);
}
}
private string GetChunkFileName(int chunkNumber, string identifier)
{
return Path.Combine(root, string.Format("{0}_{1}", identifier, chunkNumber.ToString()));
}
private void RenameChunk(MultipartFileData chunk, int chunkNumber, string identifier)
{
string generatedFileName = chunk.LocalFileName;
string chunkFileName = GetChunkFileName(chunkNumber, identifier);
if (File.Exists(chunkFileName)) File.Delete(chunkFileName);
File.Move(generatedFileName, chunkFileName);
}
private string GetFileName(string identifier)
{
return Path.Combine(root, identifier);
}
private bool ChunkIsHere(int chunkNumber, string identifier)
{
string fileName = GetChunkFileName(chunkNumber, identifier);
return File.Exists(fileName);
}
private bool AllChunksAreHere(string identifier, int totalChunks)
{
for (int chunkNumber = 1; chunkNumber <= totalChunks; chunkNumber++)
if (!ChunkIsHere(chunkNumber, identifier)) return false;
return true;
}
private void TryAssembleFile(string identifier, int totalChunks, string filename)
{
if (AllChunksAreHere(identifier, totalChunks))
{
// Create a single file
var consolidatedFileName = GetFileName(identifier);
using (var destStream = File.Create(consolidatedFileName, 15000))
{
for (int chunkNumber = 1; chunkNumber <= totalChunks; chunkNumber++)
{
var chunkFileName = GetChunkFileName(chunkNumber, identifier);
using (var sourceStream = File.OpenRead(chunkFileName))
{
sourceStream.CopyTo(destStream);
}
}
destStream.Close();
}
// Rename consolidated with original name of upload
filename = Path.GetFileName(filename); // Strip to filename if directory is specified (avoid cross-directory attack)
string realFileName = Path.Combine(root, filename);
if (File.Exists(filename)) File.Delete(realFileName);
File.Move(consolidatedFileName, realFileName);
// Delete chunk files
for (int chunkNumber = 1; chunkNumber <= totalChunks; chunkNumber++)
{
var chunkFileName = GetChunkFileName(chunkNumber, identifier);
File.Delete(chunkFileName);
}
}
}
}
}
This repository on Github has implementation of parsing and consolidating of flow js chunks on MVC.
It's very easy to implement. Just drop provided files in your project and your controller will look something like this
-- Disclaimer. I'm the author of this repo.