Uploading Files In ASP.net Core

Uploading files in ASP.net core is largely the same as standard full framework MVC, with the large exception being how you can now stream large files. We will go over both methods of uploading a file in ASP.net core.

Model Binding IFormFile (Small Files)

When uploading a file via this method, the important thing to note is that your files are uploaded in their entirety before execution hits your controller action. What this means is that the disk on your server holds a temporary file while you decide where to push it. With this small files this is fine, larger files you run into issues of scale. If you have many users all uploading large files you are liable to run out of ram (where the file is stored before moving it to disk), or disk space itself.

For your HTML, it should look something like this :

The biggest thing to note is that the the encoding type is set to “multipart/form-data”, if this is not set then you will go crazy trying to hunt down why your file is showing up in your controller.

Your controller action is actually very simple. It will look something like :

Note that the name of the parameter “files” should match the name on the input in HTML.

Other than that, you are there and done. There is nothing more than you need to do.

Streaming Files (Large Files)

Instead of buffering the file in its entirety, you can stream the file upload. This does introduce challenges as you can no longer use the built in model binding of ASP.net core. Various tutorials out there show you how to get things working with massive pieces of code, but I’ll give you a helper class that should alleviate most of the work. Most of this work is taken from Microsoft’s tutorial on file uploads here. Unfortunately it’s a bit all over the place with helper classes that you need to dig around the web for.

First, take this helper class and stick it in your project. This code is taken from a Microsoft project here.

Next, you can need this extension class which again is taken from Microsoft code but moved into a helper so that it can be reused and it allows your controllers to be slightly cleaner. It takes an input stream which is where your file will be written to. This is kept as a basic stream because the stream can really come from anywhere. It could be a file on the local server, or a stream to AWS/Azure etc

Now, you actually need to create a custom action attribute that completely disables form binding. This is important otherwise C# will still try and load the contents of the request regardless. The attribute looks like :

Next, your controller should look similar to the following. We create a stream and pass it into the StreamFile extension method. The output is our FormValueProvider which we use to bind out model manually after the file streaming. Remember to put your custom attribute on the action to force the request to not bind.

In my particular example I have created a stream that writes to a temp file, but obviously you can do anything you want with it. The model I am using is the following :

Again, nothing special. It’s just showing you how to bind a view model even when you are streaming files.

My HTML form is pretty standard :

And that’s all there is to it. Now when I upload a file, it is streamed straight to my target stream which could be an upload stream to AWS/Azure or any other cloud provider, and I still managed to get my view model out too. The biggest downside is ofcourse that the viewmodel details are not available until the file has been streamed. What this means is that if there is something in the viewmodel that would normally determine where the file goes, or the name etc, this is not available to you without a bit of rejigging (But it’s definitely doable).



  1. I came here from your comment in docs.microsoft.com for file uploads. Looking through what you’ve pieced together here, I notice the lack of the antiforgery token attribute and the disabling of the binding attribute. Was that intentional as they are probably a good thing but not necessary? I see you discuss the binding issue but no attribute. And testing your code here appears to work just fine without it.

    1. Woops. Two things, you do need the disabling of binding attribute (This is very important otherwise the form is bound on the request anyway, will fix this, I had it earlier but didn’t copy it across into the blog post!). Secondly, the antiforgery token can be used here regardless as long as it’s sent in a header rather than the body (If you are using something like Angular you are likely doing this already).

      1. in my testing, I came to that conclusion as well regarding the non-binding attribute. thanks.

  2. The problem of FileStreamingHelper.StreamFile method that it doesn’t take into account case when Multipart HTTP request contain several files inside form. Right now it has single Stream as input parameter.

    1. Hey Alex

      That was my issue as well. All you need to do is not to pass in a target stream and loop through the files in the request in the FileStream method. I do this:

      If you want to cater for different store locations create overloads of the method. I think that would be the best

    1. Hi Dan,

      All the source code is contained in this post, but let me know if you have any particular issues getting up and running.

  3. Does this mean that the HTTP request will be formed in such a way that one large file will be divided into multiple sections in request body and the server side code will try and get one section (part of a large file) at a time from the client (browser) ?

  4. “What this means is that if there is something in the viewmodel that would normally determine where the file goes, or the name etc, this is not available to you without a bit of rejigging (But it’s definitely doable).”

    Any tips about how I would do this?

  5. Hi Wade,
    Thanks for this tutorial 🙂

    I have an error and don’t manage to know why :
    IOException: Unexpected end of Stream, the content may have already been read by another component.
    It happens in the FileStreamingHelper when I try to do the reader.ReadNextSectionAsync.

    I only use a simple dotnet core 2 razor pages application.
    Can you help me to resolve this please ?

      1. Hi,
        Got same problem I use MVC Controller. (.net core 1.1)
        I check DisableFormValueModelBinding attribute is call before controller.
        but it not work.

      2. I post by Postman like this

        POST /api/UserManage/UploadUserProfile HTTP/1.1
        Host: localhost:4661
        Content-Type: multipart/form-data; boundary=—-WebKitFormBoundary7MA4YWxkTrZu0gW
        Cache-Control: no-cache
        Postman-Token: 648876bd-d815-5531-a781-1ffcad5aaf54

        Content-Disposition: form-data; name=”
        “; filename=”239657-2.jpg”
        Content-Type: image/jpeg


  6. Thanks for this post, I notice though that there are no using statements for your code so I am not sure what namespaces I need to pull from to get this to work, I gather the ones from the GitHub on Microsoft aspnet core should work? . I too spent some time looking at the spaghetti that is supposed to help on the fileuploads in aspnetcore documentation. I am guessing that I will need some angular pieces to get this to work properly, where can I find an example of how to put it all together? I need to build a file upload as part of a project I am working on, once the files are uploaded I have to run some processes on them for publishing but this is a large part of what I need to get my project working.

  7. API has changed the return value for HeaderUtilities.RemoveQuotes.

    ASP.NET Core < 2.0: Returns String https://docs.microsoft.com/en-us/dotnet/api/microsoft.net.http.headers.headerutilities.removequotes?view=aspnetcore-1.1

    ASP.NET Core 2.0: Returns StringSegment https://docs.microsoft.com/en-us/dotnet/api/microsoft.net.http.headers.headerutilities.removequotes?view=aspnetcore-2.0

    Add ".Value" where the build breaks – HasValue check not explicitly needed because all those instances of StringSegment are already in string.IsNullOrEmpty() checks.

    1. Thanks for that! I’ve gone through and added “.Value” where needed (On a side note, how annoying is that change!). And left comments incase someone gives this a go on .NET Core <2.0

  8. Just like the Microsoft guide that I came from you give the source code for a class but don’t include the using’s at the top of the class.
    Like the MultipartRequestHelper class there are 2 options for MediaTypeHeaderValue (either System.Net.Http.Headers or Microsoft.Net.Http.Headers), I don’t know which one to use.

    I get an error on this line “if (string.IsNullOrWhiteSpace(boundary))” saying Cannot convert from “Microsoft.Extensions.Primitives.StringSegment to string plus other errors with the class.

    1. Hi Paul,

      For that particular header, it’s supposed to use Microsoft.Net.Http.Headers. I’ve added in the full namespace to the call to make it easier.

      For the second part of your comment, Microsoft recently updated a couple of API’s so they no longer return strings. I’ve updated the code to reflect this and left comments incase someone is still running .NET Core 1.X.

  9. Is there a way to get the file name prior to streaming? It would be neater to have that so as not to create a temp file that requires renaming later.

    1. What I found is this:

      var webRoot = _env.ContentRootPath;
      var uploadPath = webRoot + "\\uploads";

      var filename = Request.Form.Files[0].FileName;
      using (var stream = System.IO.File.Create(uploadPath + "\\" + filename))
      formModel = await Request.StreamFile(stream);

      But using this creates the following error:

      IOException: Unexpected end of Stream, the content may have already been read by another component at

      83 (var section = await reader.ReadNextSectionAsync();)

      The error occurs even if I change the extension of filename to .temp.

      1. I’ve narrowed it down to this:

        var filename = Request.Form.Files[0].FileName;

        Accessing Request before creating the stream creates the error, which would make sense. Is there another way to get the filename ahead of time?

Leave a Reply

Your email address will not be published. Required fields are marked *