How To Accept a File POST

C#asp.net Mvc-4

C# Problem Overview


I'm using asp.net mvc 4 webapi beta to build a rest service. I need to be able to accept POSTed images/files from client applications. Is this possible using the webapi? Below is how action I am currently using. Does anyone know of an example how this should work?

[HttpPost]
public string ProfileImagePost(HttpPostedFile profileImage)
{
    string[] extensions = { ".jpg", ".jpeg", ".gif", ".bmp", ".png" };
    if (!extensions.Any(x => x.Equals(Path.GetExtension(profileImage.FileName.ToLower()), StringComparison.OrdinalIgnoreCase)))
    {
        throw new HttpResponseException("Invalid file type.", HttpStatusCode.BadRequest);
    }

    // Other code goes here

    return "/path/to/image.png";
}

C# Solutions


Solution 1 - C#

I'm surprised that a lot of you seem to want to save files on the server. Solution to keep everything in memory is as follows:

[HttpPost("api/upload")]
public async Task<IHttpActionResult> Upload()
{
    if (!Request.Content.IsMimeMultipartContent())
        throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); 

    var provider = new MultipartMemoryStreamProvider();
    await Request.Content.ReadAsMultipartAsync(provider);
    foreach (var file in provider.Contents)
    {
        var filename = file.Headers.ContentDisposition.FileName.Trim('\"');
        var buffer = await file.ReadAsByteArrayAsync();
        //Do whatever you want with filename and its binary data.
    }

    return Ok();
}

Solution 2 - C#

see http://www.asp.net/web-api/overview/formats-and-model-binding/html-forms-and-multipart-mime#multipartmime, although I think the article makes it seem a bit more complicated than it really is.

Basically,

public Task<HttpResponseMessage> PostFile() 
{ 
    HttpRequestMessage request = this.Request; 
    if (!request.Content.IsMimeMultipartContent()) 
    { 
        throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); 
    } 

    string root = System.Web.HttpContext.Current.Server.MapPath("~/App_Data/uploads"); 
    var provider = new MultipartFormDataStreamProvider(root); 

    var task = request.Content.ReadAsMultipartAsync(provider). 
        ContinueWith<HttpResponseMessage>(o => 
    { 

        string file1 = provider.BodyPartFileNames.First().Value;
        // this is the file name on the server where the file was saved 

        return new HttpResponseMessage() 
        { 
            Content = new StringContent("File uploaded.") 
        }; 
    } 
    ); 
    return task; 
} 

Solution 3 - C#

See the code below, adapted from this article, which demonstrates the simplest example code I could find. It includes both file and memory (faster) uploads.

public HttpResponseMessage Post()
{
    var httpRequest = HttpContext.Current.Request;
    if (httpRequest.Files.Count < 1)
    {
        return Request.CreateResponse(HttpStatusCode.BadRequest);
    }

    foreach(string file in httpRequest.Files)
    {
        var postedFile = httpRequest.Files[file];
        var filePath = HttpContext.Current.Server.MapPath("~/" + postedFile.FileName);
        postedFile.SaveAs(filePath);
        // NOTE: To store in memory use postedFile.InputStream
    }

    return Request.CreateResponse(HttpStatusCode.Created);
}

Solution 4 - C#

The ASP.NET Core way is now here:

[HttpPost("UploadFiles")]
public async Task<IActionResult> Post(List<IFormFile> files)
{
    long size = files.Sum(f => f.Length);

    // full path to file in temp location
    var filePath = Path.GetTempFileName();

    foreach (var formFile in files)
    {
        if (formFile.Length > 0)
        {
            using (var stream = new FileStream(filePath, FileMode.Create))
            {
                await formFile.CopyToAsync(stream);
            }
        }
    }

    // process uploaded files
    // Don't rely on or trust the FileName property without validation.

    return Ok(new { count = files.Count, size, filePath});
}

Solution 5 - C#

Here is a quick and dirty solution which takes uploaded file contents from the HTTP body and writes it to a file. I included a "bare bones" HTML/JS snippet for the file upload.

Web API Method:

[Route("api/myfileupload")]        
[HttpPost]
public string MyFileUpload()
{
    var request = HttpContext.Current.Request;
    var filePath = "C:\\temp\\" + request.Headers["filename"];
    using (var fs = new System.IO.FileStream(filePath, System.IO.FileMode.Create))
    {
        request.InputStream.CopyTo(fs);
    }
    return "uploaded";
}

HTML File Upload:

<form>
    <input type="file" id="myfile"/>  
    <input type="button" onclick="uploadFile();" value="Upload" />
</form>
<script type="text/javascript">
    function uploadFile() {        
        var xhr = new XMLHttpRequest();                 
        var file = document.getElementById('myfile').files[0];
        xhr.open("POST", "api/myfileupload");
        xhr.setRequestHeader("filename", file.name);
        xhr.send(file);
    }
</script>

Solution 6 - C#

I used Mike Wasson's answer before I updated all the NuGets in my webapi mvc4 project. Once I did, I had to re-write the file upload action:

    public Task<HttpResponseMessage> Upload(int id)
    {
        HttpRequestMessage request = this.Request;
        if (!request.Content.IsMimeMultipartContent())
        {
            throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.UnsupportedMediaType));
        }

        string root = System.Web.HttpContext.Current.Server.MapPath("~/App_Data/uploads");
        var provider = new MultipartFormDataStreamProvider(root);

        var task = request.Content.ReadAsMultipartAsync(provider).
            ContinueWith<HttpResponseMessage>(o =>
            {
                FileInfo finfo = new FileInfo(provider.FileData.First().LocalFileName);

                string guid = Guid.NewGuid().ToString();

                File.Move(finfo.FullName, Path.Combine(root, guid + "_" + provider.FileData.First().Headers.ContentDisposition.FileName.Replace("\"", "")));

                return new HttpResponseMessage()
                {
                    Content = new StringContent("File uploaded.")
                };
            }
        );
        return task;
    }

Apparently BodyPartFileNames is no longer available within the MultipartFormDataStreamProvider.

Solution 7 - C#

Toward this same directions, I'm posting a client and server snipets that send Excel Files using WebApi, c# 4:

public static void SetFile(String serviceUrl, byte[] fileArray, String fileName)
{
    try
    {
        using (var client = new HttpClient())
        {
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                using (var content = new MultipartFormDataContent())
                {
                    var fileContent = new ByteArrayContent(fileArray);//(System.IO.File.ReadAllBytes(fileName));
                    fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
                    {
                        FileName = fileName
                    };
                    content.Add(fileContent);
                    var result = client.PostAsync(serviceUrl, content).Result;
                }
        }
    }
    catch (Exception e)
    {
        //Log the exception
    }
}

And the server webapi controller:

public Task<IEnumerable<string>> Post()
{
    if (Request.Content.IsMimeMultipartContent())
    {
        string fullPath = HttpContext.Current.Server.MapPath("~/uploads");
        MyMultipartFormDataStreamProvider streamProvider = new MyMultipartFormDataStreamProvider(fullPath);
        var task = Request.Content.ReadAsMultipartAsync(streamProvider).ContinueWith(t =>
        {
            if (t.IsFaulted || t.IsCanceled)
                    throw new HttpResponseException(HttpStatusCode.InternalServerError);

            var fileInfo = streamProvider.FileData.Select(i =>
            {
                var info = new FileInfo(i.LocalFileName);
                return "File uploaded as " + info.FullName + " (" + info.Length + ")";
            });
            return fileInfo;

        });
        return task;
    }
    else
    {
        throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "Invalid Request!"));
    }
}

And the Custom MyMultipartFormDataStreamProvider, needed to customize the Filename:

PS: I took this code from another post http://www.codeguru.com/csharp/.net/uploading-files-asynchronously-using-asp.net-web-api.htm

public class MyMultipartFormDataStreamProvider : MultipartFormDataStreamProvider
{
    public MyMultipartFormDataStreamProvider(string path)
        : base(path)
    {

    }

    public override string GetLocalFileName(System.Net.Http.Headers.HttpContentHeaders headers)
    {
        string fileName;
        if (!string.IsNullOrWhiteSpace(headers.ContentDisposition.FileName))
        {
            fileName = headers.ContentDisposition.FileName;
        }
        else
        {
            fileName = Guid.NewGuid().ToString() + ".data";
        }
        return fileName.Replace("\"", string.Empty);
    }
}

Solution 8 - C#

[HttpPost]
public JsonResult PostImage(HttpPostedFileBase file)
{
    try
    {
        if (file != null && file.ContentLength > 0 && file.ContentLength<=10485760)
        {
            var fileName = Path.GetFileName(file.FileName);                                        

            var path = Path.Combine(Server.MapPath("~/") + "HisloImages" + "\\", fileName);

            file.SaveAs(path);
            #region MyRegion
            ////save imag in Db
            //using (MemoryStream ms = new MemoryStream())
            //{
            //    file.InputStream.CopyTo(ms);
            //    byte[] array = ms.GetBuffer();
            //} 
            #endregion
            return Json(JsonResponseFactory.SuccessResponse("Status:0 ,Message: OK"), JsonRequestBehavior.AllowGet);
        }
        else
        {
            return Json(JsonResponseFactory.ErrorResponse("Status:1 , Message: Upload Again and File Size Should be Less Than 10MB"), JsonRequestBehavior.AllowGet);
        }
    }
    catch (Exception ex)
    {

        return Json(JsonResponseFactory.ErrorResponse(ex.Message), JsonRequestBehavior.AllowGet);

    }
}

Solution 9 - C#

Here are two ways to accept a file. One using in memory provider MultipartMemoryStreamProvider and one using MultipartFormDataStreamProvider which saves to a disk. Note, this is only for one file upload at a time. You can certainty extend this to save multiple-files. The second approach can support large files. I've tested files over 200MB and it works fine. Using in memory approach does not require you to save to disk, but will throw out of memory exception if you exceed a certain limit.

private async Task<Stream> ReadStream()
{
	Stream stream = null;
	var provider = new MultipartMemoryStreamProvider();
	await Request.Content.ReadAsMultipartAsync(provider);
	foreach (var file in provider.Contents)
	{
		var buffer = await file.ReadAsByteArrayAsync();
		stream = new MemoryStream(buffer);
	}

	return stream;
}

private async Task<Stream> ReadLargeStream()
{
	Stream stream = null;
	string root = Path.GetTempPath();
	var provider = new MultipartFormDataStreamProvider(root);
	await Request.Content.ReadAsMultipartAsync(provider);
	foreach (var file in provider.FileData)
	{
		var path = file.LocalFileName;
		byte[] content = File.ReadAllBytes(path);
		File.Delete(path);
		stream = new MemoryStream(content);
	}

	return stream;
}

Solution 10 - C#

This question has lots of good answers even for .Net Core. I was using both Frameworks the provided code samples work fine. So I won't repeat it. In my case the important thing was how to use File upload actions with Swagger like this:

File upload button in Swagger

Here is my recap:

ASP .Net WebAPI 2

  • To upload file use: MultipartFormDataStreamProvider see answers here
  • How to use it with Swagger

.NET Core

Solution 11 - C#

I had a similar problem for the preview Web API. Did not port that part to the new MVC 4 Web API yet, but maybe this helps:

https://stackoverflow.com/questions/9290342/rest-file-upload-with-httprequestmessage-or-stream

Please let me know, can sit down tomorrow and try to implement it again.

Solution 12 - C#

API Controller :

[HttpPost]
public HttpResponseMessage Post()
{
    var httpRequest = System.Web.HttpContext.Current.Request;

    if (System.Web.HttpContext.Current.Request.Files.Count < 1)
    {
        //TODO
    }
    else
    {

    try
    { 
        foreach (string file in httpRequest.Files)
        { 
            var postedFile = httpRequest.Files[file];
            BinaryReader binReader = new BinaryReader(postedFile.InputStream);
            byte[] byteArray = binReader.ReadBytes(postedFile.ContentLength);

        }

    }
    catch (System.Exception e)
    {
        //TODO
    }

    return Request.CreateResponse(HttpStatusCode.Created);
}

Solution 13 - C#

Complementing Matt Frear's answer - This would be an ASP NET Core alternative for reading the file directly from Stream, without saving&reading it from disk:

public ActionResult OnPostUpload(List<IFormFile> files)
    {
        try
        {
            var file = files.FirstOrDefault();
            var inputstream = file.OpenReadStream();

            XSSFWorkbook workbook = new XSSFWorkbook(stream);
        
            var FIRST_ROW_NUMBER = {{firstRowWithValue}};

            ISheet sheet = workbook.GetSheetAt(0);
            // Example: var firstCellRow = (int)sheet.GetRow(0).GetCell(0).NumericCellValue;

            for (int rowIdx = 2; rowIdx <= sheet.LastRowNum; rowIdx++)
               {
                  IRow currentRow = sheet.GetRow(rowIdx);

                  if (currentRow == null || currentRow.Cells == null || currentRow.Cells.Count() < FIRST_ROW_NUMBER) break;

                  var df = new DataFormatter();                

                  for (int cellNumber = {{firstCellWithValue}}; cellNumber < {{lastCellWithValue}}; cellNumber++)
                      {
                         //business logic & saving data to DB                        
                      }               
                }
        }
        catch(Exception ex)
        {
            throw new FileFormatException($"Error on file processing - {ex.Message}");
        }
    }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPhilView Question on Stackoverflow
Solution 1 - C#GlenoView Answer on Stackoverflow
Solution 2 - C#Mike WassonView Answer on Stackoverflow
Solution 3 - C#Brent MatzelleView Answer on Stackoverflow
Solution 4 - C#Matt FrearView Answer on Stackoverflow
Solution 5 - C#James LawrukView Answer on Stackoverflow
Solution 6 - C#Steve StokesView Answer on Stackoverflow
Solution 7 - C#Daniel MeloView Answer on Stackoverflow
Solution 8 - C#user3722373View Answer on Stackoverflow
Solution 9 - C#Filix MogilevskyView Answer on Stackoverflow
Solution 10 - C#MajorView Answer on Stackoverflow
Solution 11 - C#RemyView Answer on Stackoverflow
Solution 12 - C#Tiago MediciView Answer on Stackoverflow
Solution 13 - C#Pedro CoelhoView Answer on Stackoverflow