80

I am trying to figure this out. I was not getting any useful error messages with my code so I used something else to generate something. I have attached that code after the error message. I have found a tutorial on it but I do not know how to implement it with what I have. This is what I currently have:

public async Task<object> PostFile()
    {
        if (!Request.Content.IsMimeMultipartContent())
            throw new Exception();


        var provider = new MultipartMemoryStreamProvider();
        var result = new { file = new List<object>() };
        var item = new File();

        item.CompanyName = HttpContext.Current.Request.Form["companyName"];
        item.FileDate = HttpContext.Current.Request.Form["fileDate"];
        item.FileLocation = HttpContext.Current.Request.Form["fileLocation"];
        item.FilePlant = HttpContext.Current.Request.Form["filePlant"];
        item.FileTerm = HttpContext.Current.Request.Form["fileTerm"];
        item.FileType = HttpContext.Current.Request.Form["fileType"];

        var manager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
        var user = manager.FindById(User.Identity.GetUserId());

        item.FileUploadedBy = user.Name;
        item.FileUploadDate = DateTime.Now;

        await Request.Content.ReadAsMultipartAsync(provider)
         .ContinueWith(async (a) =>
         {
             foreach (var file in provider.Contents)
             {
                 if (file.Headers.ContentLength > 1000)
                 {
                     var filename = file.Headers.ContentDisposition.FileName.Trim('\"');
                     var contentType = file.Headers.ContentType.ToString();
                     await file.ReadAsByteArrayAsync().ContinueWith(b => { item.FilePdf = b.Result; });
                 }


             }


         }).Unwrap();

        db.Files.Add(item);
        db.SaveChanges();
        return result;

    }

Error:

Object {message: "The request entity's media type 'multipart/form-data' is not supported for this resource.", exceptionMessage: "No MediaTypeFormatter is available to read an obje…om content with media type 'multipart/form-data'.", exceptionType: "System.Net.Http.UnsupportedMediaTypeException", stackTrace: " at System.Net.Http.HttpContentExtensions.ReadAs…atterLogger, CancellationToken cancellationToken)"}exceptionMessage: "No MediaTypeFormatter is available to read an object of type 'HttpPostedFileBase' from content with media type 'multipart/form-data'."exceptionType: "System.Net.Http.UnsupportedMediaTypeException"message: "The request entity's media type 'multipart/form-data' is not supported for this resource."stackTrace: " at System.Net.Http.HttpContentExtensions.ReadAsAsync[T](HttpContent content, Type type, IEnumerable1 formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken) ↵ at System.Net.Http.HttpContentExtensions.ReadAsAsync(HttpContent content, Type type, IEnumerable1 formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken)

Code used to generate error message:

    [HttpPost]
    public string UploadFile(HttpPostedFileBase file)
    {

        if (file.ContentLength > 0)
        {
            var fileName = Path.GetFileName(file.FileName);
            var path = Path.Combine(HttpContext.Current.Server.MapPath("~/uploads"), fileName);
            file.SaveAs(path);


        }
        return "/uploads/" + file.FileName;
    }

Class:

public class File
{
    public int FileId { get; set; }
    public string FileType { get; set; }
    public string FileDate { get; set; }
    public byte[] FilePdf { get; set; }
    public string FileLocation { get; set; }
    public string FilePlant { get; set; }
    public string FileTerm { get; set; }
    public DateTime? FileUploadDate { get; set; }
    public string FileUploadedBy { get; set; }

    public string CompanyName { get; set; }
    public virtual ApplicationUser User { get; set; }
}
Boann
  • 48,794
  • 16
  • 117
  • 146
texas697
  • 5,609
  • 16
  • 65
  • 131
  • So it looks like you're POST-ing data from one backend to another. Any reason you're using multipart/form rather than JSON or raw data? – timothyclifford Feb 06 '15 at 16:10
  • no reason, it was what worked for me a while back. I posted my angular. I open for suggestions – texas697 Feb 06 '15 at 16:13
  • 4
    @timothyclifford Why not? Multipart is an efficient way to do this. The point of this QA page is not suggesting another way to do it instead of fixing the problem you have with the approach you are looking for, – Carlos López Marí Apr 13 '20 at 12:08

10 Answers10

110

I normally use the HttpPostedFileBase parameter only in Mvc Controllers. When dealing with ApiControllers try checking the HttpContext.Current.Request.Files property for incoming files instead:

[HttpPost]
public string UploadFile()
{
    var file = HttpContext.Current.Request.Files.Count > 0 ?
        HttpContext.Current.Request.Files[0] : null;

    if (file != null && file.ContentLength > 0)
    {
        var fileName = Path.GetFileName(file.FileName);

        var path = Path.Combine(
            HttpContext.Current.Server.MapPath("~/uploads"),
            fileName
        );

        file.SaveAs(path);
    }

    return file != null ? "/uploads/" + file.FileName : null;
}
Thomas C. G. de Vilhena
  • 13,819
  • 3
  • 50
  • 44
  • this is the errror message. Could not find a part of the path 'C:\Development\Transp\temp\uploads\02-Unicare.PNG'. – texas697 Feb 06 '15 at 17:52
  • the permissions are set just by using the windows file explorer? – texas697 Feb 06 '15 at 17:53
  • Now it seems the path you're using to save the received file is invalid. Check that the folder exists, and that your application has read/write permission on that folder (yes, you can do that using windows explorer). – Thomas C. G. de Vilhena Feb 06 '15 at 17:58
  • i changed the folder to App_Start and it worked! now this is actually storing the file on the server. If i do it this way how can i store the path and other properties in the database? – texas697 Feb 06 '15 at 18:07
  • I'm glad it worked. Regarding your new doubt, instead of updating this question, I suggest you create a new question here in StackOverflow showing how you're trying to save things in the database, and why it isn't working. – Thomas C. G. de Vilhena Feb 06 '15 at 19:48
  • exactly what i need! – Mohsen.Sharify May 08 '17 at 09:29
  • While it works, this approach is rather cumbersome. By default, you can't take advantage of model binding when using `multipart/form-data`, so good luck if you're sending more than just files. [@Redplane's answer](https://stackoverflow.com/a/53525796/1185136) is the way to go. – Rudey Jan 29 '20 at 00:31
  • @Rudey actually there is an option for model binding for sending additional information, but you'll have to send parameters encoded in the URL query string. This is a sample API method signature I'm using in one of my apps: `public SubmitImageResponse SubmitImage([FromUri] SubmitImageRequest data)` notice the [FromUri](https://learn.microsoft.com/en-us/dotnet/api/system.web.http.fromuriattribute?view=aspnetcore-2.2) attribute. – Thomas C. G. de Vilhena Jan 29 '20 at 11:43
  • In case you ended up here with a ASP Core project: https://stackoverflow.com/questions/53825794/webapi-is-it-possible-to-post-an-iformcollection-and-object-in-an-action – Carlos López Marí Apr 13 '20 at 13:21
76

This is what solved my problem
Add the following line to WebApiConfig.cs

config.Formatters.XmlFormatter.SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue("multipart/form-data"));
Nick Prozee
  • 2,823
  • 4
  • 22
  • 49
  • 3
    This only makes ASP.NET route `multipart/form-data` requests to your controllers. It won't be able to deserialize the form values and bind them to your method's parameters, because the `XmlFormatter` can't parse the `multipart/form-data` payload. – Rudey Jan 29 '20 at 00:28
22

You can use something like this

[HttpPost]
public async Task<HttpResponseMessage> AddFile()
{
    if (!Request.Content.IsMimeMultipartContent())
    {
        this.Request.CreateResponse(HttpStatusCode.UnsupportedMediaType);
    }

    string root = HttpContext.Current.Server.MapPath("~/temp/uploads");
    var provider = new MultipartFormDataStreamProvider(root);
    var result = await Request.Content.ReadAsMultipartAsync(provider);

    foreach (var key in provider.FormData.AllKeys)
    {
        foreach (var val in provider.FormData.GetValues(key))
        {
            if (key == "companyName")
            {
                var companyName = val;
            }
        }
    }

    // On upload, files are given a generic name like "BodyPart_26d6abe1-3ae1-416a-9429-b35f15e6e5d5"
    // so this is how you can get the original file name
    var originalFileName = GetDeserializedFileName(result.FileData.First());

    var uploadedFileInfo = new FileInfo(result.FileData.First().LocalFileName);
    string path = result.FileData.First().LocalFileName;

    //Do whatever you want to do with your file here

    return this.Request.CreateResponse(HttpStatusCode.OK, originalFileName );
}

private string GetDeserializedFileName(MultipartFileData fileData)
{
    var fileName = GetFileName(fileData);
    return JsonConvert.DeserializeObject(fileName).ToString();
}

public string GetFileName(MultipartFileData fileData)
{
    return fileData.Headers.ContentDisposition.FileName;
}
Dakshal Raijada
  • 1,261
  • 8
  • 18
  • Can you please show me what I need to do here. I don't quite understand – texas697 Feb 06 '15 at 16:31
  • The above code will stay in your Web API Controller that accepts multipart/form-data. Once the code verifies its a multipart content we get the file and extra data like "companyname", ... and you can process your file, save it and return the desired result. You can use angular/.net code to access this method – Dakshal Raijada Feb 06 '15 at 16:42
  • I plugged in what you have to see what happens but it throws the error at the first foreach loop. part of the issue is i dont quite understand how it is being converted to binary format. I created the tmp folder and made sure it has write permissions – texas697 Feb 06 '15 at 17:38
  • FormData could be null try this instead result.FormData.GetValues("companyname").FirstOrDefault(); – Dakshal Raijada Feb 06 '15 at 17:41
  • same error with : foreach (var val in result.FormData.GetValues("companyname").FirstOrDefault()) – texas697 Feb 06 '15 at 17:45
  • can you check if the folder to where the response is being written out has necessary permissions for your application to write.? – Dakshal Raijada Feb 06 '15 at 17:52
  • changed the folder to App_Start and it worked. So now that the formdata is getting to the controller how do i convert it to binary and store it in sql? i updated the post with my model – texas697 Feb 06 '15 at 18:10
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/70415/discussion-between-dakshal-raijada-and-texas697). – Dakshal Raijada Feb 06 '15 at 18:15
  • How can I change name of file and save with extension ?? – Aarsh Sep 26 '18 at 07:09
  • if I have a file attached, formdata is always with empty values (keys are available, but empty) – kurtanamo Dec 24 '18 at 08:35
  • Hi Dakshal, Tanuj here, my requirement is I don't want to actually upload the file, rather I want to convert the file into bytes and read them, how can I do that. `var result = await Request.Content.ReadAsMultipartAsync(provider);` So result variable has the file content I guess, but now I want to iterate over file content and may be store in byte array and loop through byte array and send it to s3 bucket. Please guide me. – Unbreakable Feb 13 '19 at 03:22
  • Hi , for any further questions I suggest you ask a new SO question. Thanks – Dakshal Raijada Feb 13 '19 at 08:36
  • This doesn't work when sending defined form data (e.g. a DTO struct) in the body of the form. – elliotwesoff Mar 17 '21 at 19:24
15

5 years later on and .NET Core 3.1 allows you to do specify the media type like this:

[HttpPost]
[Consumes("multipart/form-data")]
public IActionResult UploadLogo()
{
    return Ok();
}
Richard
  • 4,740
  • 4
  • 32
  • 39
11

Perhaps it is late for the party. But there is an alternative solution for this is to use ApiMultipartFormFormatter plugin.

This plugin helps you to receive the multipart/formdata content as ASP.NET Core does.

In the github page, demo is already provided.

Redplane
  • 2,971
  • 4
  • 30
  • 59
  • When I do send properties but do not the actual file to the API controller's action, serialization is not happening, I am receiving `null` as my model. If I do send a file or remove file property from model, serialization works fine. Is it as designed or I am missing something? – Roman.Pavelko Feb 03 '20 at 20:44
  • @Roman.Pavelko, can you create a minimal reproduction on git, it will be better to know what really happens :) – Redplane Feb 04 '20 at 03:24
  • Thanks. This saved me for this question: https://stackoverflow.com/questions/68188224/submit-javascript-formdata-with-xmlhttprequest-to-webforms-web-api-controller-cl – Terry Jul 02 '21 at 13:42
1

Here's another answer for the ASP.Net Core solution to this problem...

On the Angular side, I took this code example...

https://stackblitz.com/edit/angular-drag-n-drop-directive

... and modified it to call an HTTP Post endpoint:

  prepareFilesList(files: Array<any>) {

    const formData = new FormData();
    for (var i = 0; i < files.length; i++) { 
      formData.append("file[]", files[i]);
    }

    let URL = "https://localhost:44353/api/Users";
    this.http.post(URL, formData).subscribe(
      data => { console.log(data); },
      error => { console.log(error); }
    );

With this in place, here's the code I needed in the ASP.Net Core WebAPI controller:

[HttpPost]
public ActionResult Post()
{
  try
  {
    var files = Request.Form.Files;

    foreach (IFormFile file in files)
    {
        if (file.Length == 0)
            continue;
        
        string tempFilename = Path.Combine(Path.GetTempPath(), file.FileName);
        System.Diagnostics.Trace.WriteLine($"Saved file to: {tempFilename}");

        using (var fileStream = new FileStream(tempFilename, FileMode.Create))
        {
            file.CopyTo(fileStream);
        }
    }
    return new OkObjectResult("Yes");
  }
  catch (Exception ex)
  {
    return new BadRequestObjectResult(ex.Message);
  }
}

Shockingly simple, but I had to piece together examples from several (almost-correct) sources to get this to work properly.

Mike Gledhill
  • 27,846
  • 7
  • 149
  • 159
0

For me worked this way, hope it helps!

        [HttpPost("AddProduct")]

        public async Task<IActionResult> AddProduct( IFormFile files)
        {
            string fileGuid = Guid.NewGuid().ToString();
            var pathImage = Path.Combine(_hostEnvironment.ContentRootPath, "Images", fileGuid);
            var streamImage = new FileStream(pathImage, FileMode.Append);
            await files.CopyToAsync(streamImage);

            return Ok();

           
        }
Siumauricio
  • 101
  • 1
  • 5
0

For Asp.net MVC I need to validate and process before saving the file so I solve my problem by some more steps to give the file to a property. At first

HttpContext.Current.Request.Files[i]

is HttpPostedFile so I made a property with this type (I have multiple files and need more properties) so I assign file to property like below

if (HttpContext.Current.Request.Files.Count>0)
        {

            var files = HttpContext.Current.Request.Files;
            for (int i = 0; i < files.Keys.Count; i++)
            {
                if (files.Keys[i].ToLower() == "ShenasnameImage".ToLower())
                {
                    model.ShenasnameImage = files[i];
                    
                }
                if (files.Keys[i].ToLower() == "MelliCardImage".ToLower())
                {
                    model.MelliCardImage = files[i];
                    
                }
            }
        }

then I make validation and process for file properties and finally save files.

Shojaeddin
  • 1,851
  • 1
  • 18
  • 16
-2

check ur WebApiConfig and add this

GlobalConfiguration.Configuration.Formatters.XmlFormatter.SupportedMediaTypes.Clear();
Liu Jaguar
  • 21
  • 1
-5

You're getting HTTP 415 "The request entity's media type 'multipart/form-data' is not supported for this resource." because you haven't mention the correct content type in your request.

Chamath Jeevan
  • 5,072
  • 1
  • 24
  • 27