"
 
 
 
ASP.NET (snapshot 2017) Microsoft documentation and samples

File uploads in ASP.NET Core

By Steve Smith

ASP.NET MVC actions support uploading of one or more files using simple model binding for smaller files or streaming for larger files.

View or download sample from GitHub

Uploading small files with model binding

To upload small files, you can use a multi-part HTML form or construct a POST request using JavaScript. An example form using Razor, which supports multiple uploaded files, is shown below:

In order to support file uploads, HTML forms must specify an enctype of multipart/form-data. The files input element shown above supports uploading multiple files. Omit the multiple attribute on this input element to allow just a single file to be uploaded. The above markup renders in a browser as:

File upload form
File upload form

The individual files uploaded to the server can be accessed through (xref:)Model Binding using the IFormFile interface. IFormFile has the following structure:

[!WARNING] Don’t rely on or trust the FileName property without validation. The FileName property should only be used for display purposes.

When uploading files using model binding and the IFormFile interface, the action method can accept either a single IFormFile or an IEnumerable<IFormFile> (or List<IFormFile>) representing several files. The following example loops through one or more uploaded files, saves them to the local file system, and returns the total number and size of files uploaded.

[!INCLUDE GetTempFileName]

[!code-csharpMain]

   1:  using System.Collections.Generic;
   2:  using System.IO;
   3:  using System.Linq;
   4:  using System.Threading.Tasks;
   5:  using Microsoft.AspNetCore.Http;
   6:  using Microsoft.AspNetCore.Mvc;
   7:   
   8:  namespace FileUploadSample.Controllers
   9:  {
  10:      public class UploadFilesController : Controller
  11:      {
  12:          #region snippet1
  13:          [HttpPost("UploadFiles")]
  14:          public async Task<IActionResult> Post(List<IFormFile> files)
  15:          {
  16:              long size = files.Sum(f => f.Length);
  17:   
  18:              // full path to file in temp location
  19:              var filePath = Path.GetTempFileName();
  20:   
  21:              foreach (var formFile in files)
  22:              {
  23:                  if (formFile.Length > 0)
  24:                  {
  25:                      using (var stream = new FileStream(filePath, FileMode.Create))
  26:                      {
  27:                          await formFile.CopyToAsync(stream);
  28:                      }
  29:                  }
  30:              }
  31:   
  32:              // process uploaded files
  33:              // Don't rely on or trust the FileName property without validation.
  34:   
  35:              return Ok(new { count = files.Count, size, filePath});
  36:          }
  37:          #endregion
  38:      }
  39:  }

Files uploaded using the IFormFile technique are buffered in memory or on disk on the web server before being processed. Inside the action method, the IFormFile contents are accessible as a stream. In addition to the local file system, files can be streamed to Azure Blob storage or Entity Framework.

To store binary file data in a database using Entity Framework, define a property of type byte[] on the entity:

Specify a viewmodel property of type IFormFile:

[!NOTE] IFormFile can be used directly as an action method parameter or as a viewmodel property, as shown above.

Copy the IFormFile to a stream and save it to the byte array:

[!NOTE] Use caution when storing binary data in relational databases, as it can adversely impact performance.

Uploading large files with streaming

If the size or frequency of file uploads is causing resource problems for the app, consider streaming the file upload rather than buffering it in its entirety, as the model binding approach shown above does. While using IFormFile and model binding is a much simpler solution, streaming requires a number of steps to implement properly.

[!NOTE] Any single buffered file exceeding 64KB will be moved from RAM to a temp file on disk on the server. The resources (disk, RAM) used by file uploads depend on the number and size of concurrent file uploads. Streaming is not so much about perf, it’s about scale. If you try to buffer too many uploads, your site will crash when it runs out of memory or disk space.

The following example demonstrates using JavaScript/Angular to stream to a controller action. The file’s antiforgery token is generated using a custom filter attribute and passed in HTTP headers instead of in the request body. Because the action method processes the uploaded data directly, model binding is disabled by another filter. Within the action, the form’s contents are read using a MultipartReader, which reads each individual MultipartSection, processing the file or storing the contents as appropriate. Once all sections have been read, the action performs its own model binding.

The initial action loads the form and saves an antiforgery token in a cookie (via the GenerateAntiforgeryTokenCookieForAjax attribute):

The attribute uses ASP.NET Core’s built-in (xref:)Antiforgery support to set a cookie with a request token:

[!code-csharpMain]

   1:  using Microsoft.AspNetCore.Antiforgery;
   2:  using Microsoft.AspNetCore.Http;
   3:  using Microsoft.AspNetCore.Mvc.Filters;
   4:  using Microsoft.Extensions.DependencyInjection;
   5:   
   6:  namespace FileUploadSample.Filters
   7:  {
   8:      #region snippet1
   9:      public class GenerateAntiforgeryTokenCookieForAjaxAttribute : ActionFilterAttribute
  10:      {
  11:          public override void OnActionExecuted(ActionExecutedContext context)
  12:          {
  13:              var antiforgery = context.HttpContext.RequestServices.GetService<IAntiforgery>();
  14:   
  15:              // We can send the request token as a JavaScript-readable cookie, 
  16:              // and Angular will use it by default.
  17:              var tokens = antiforgery.GetAndStoreTokens(context.HttpContext);
  18:              context.HttpContext.Response.Cookies.Append(
  19:                  "XSRF-TOKEN",
  20:                  tokens.RequestToken,
  21:                  new CookieOptions() { HttpOnly = false });
  22:          }
  23:      }
  24:      #endregion
  25:  }

Angular automatically passes an antiforgery token in a request header named X-XSRF-TOKEN. The ASP.NET Core MVC app is configured to refer to this header in its configuration in Startup.cs:

[!code-csharpMain]

   1:  using Microsoft.AspNetCore.Builder;
   2:  using Microsoft.AspNetCore.Hosting;
   3:  using Microsoft.Extensions.DependencyInjection;
   4:  using Microsoft.Extensions.Logging;
   5:   
   6:  namespace FileUploadSample
   7:  {
   8:      public class Startup
   9:      {
  10:          #region snippet1
  11:          public void ConfigureServices(IServiceCollection services)
  12:          {
  13:              // Angular's default header name for sending the XSRF token.
  14:              services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
  15:   
  16:              services.AddMvc();
  17:          }
  18:          #endregion
  19:   
  20:          // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
  21:          public void Configure(IApplicationBuilder app,
  22:              IHostingEnvironment env,
  23:              ILoggerFactory loggerFactory)
  24:          {
  25:              loggerFactory.AddConsole(LogLevel.Debug);
  26:              loggerFactory.AddDebug();
  27:              if (env.IsDevelopment())
  28:              {
  29:                  app.UseDeveloperExceptionPage();
  30:              }
  31:              app.UseStaticFiles();
  32:              app.UseMvcWithDefaultRoute();
  33:          }
  34:      }
  35:  }

The DisableFormValueModelBinding attribute, shown below, is used to disable model binding for the Upload action method.

[!code-csharpMain]

   1:  using System;
   2:  using System.Linq;
   3:  using Microsoft.AspNetCore.Mvc.Filters;
   4:  using Microsoft.AspNetCore.Mvc.ModelBinding;
   5:   
   6:  namespace FileUploadSample.Filters
   7:  {
   8:      #region snippet1
   9:      [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
  10:      public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
  11:      {
  12:          public void OnResourceExecuting(ResourceExecutingContext context)
  13:          {
  14:              var formValueProviderFactory = context.ValueProviderFactories
  15:                  .OfType<FormValueProviderFactory>()
  16:                  .FirstOrDefault();
  17:              if (formValueProviderFactory != null)
  18:              {
  19:                  context.ValueProviderFactories.Remove(formValueProviderFactory);
  20:              }
  21:   
  22:              var jqueryFormValueProviderFactory = context.ValueProviderFactories
  23:                  .OfType<JQueryFormValueProviderFactory>()
  24:                  .FirstOrDefault();
  25:              if (jqueryFormValueProviderFactory != null)
  26:              {
  27:                  context.ValueProviderFactories.Remove(jqueryFormValueProviderFactory);
  28:              }
  29:          }
  30:   
  31:          public void OnResourceExecuted(ResourceExecutedContext context)
  32:          {
  33:          }
  34:      }
  35:      #endregion
  36:  }

Since model binding is disabled, the Upload action method doesn’t accept parameters. It works directly with the Request property of ControllerBase. A MultipartReader is used to read each section. The file is saved with a GUID filename and the key/value data is stored in a KeyValueAccumulator. Once all sections have been read, the contents of the KeyValueAccumulator are used to bind the form data to a model type.

The complete Upload method is shown below:

[!INCLUDE GetTempFileName]

[!code-csharpMain]

   1:  using System;
   2:  using System.Globalization;
   3:  using System.IO;
   4:  using System.Text;
   5:  using System.Threading.Tasks;
   6:  using FileUploadSample.Filters;
   7:  using FileUploadSample.Models;
   8:  using FileUploadSample.ViewModels;
   9:  using Microsoft.AspNetCore.Http;
  10:  using Microsoft.AspNetCore.Http.Features;
  11:  using Microsoft.AspNetCore.Mvc;
  12:  using Microsoft.AspNetCore.Mvc.ModelBinding;
  13:  using Microsoft.AspNetCore.WebUtilities;
  14:  using Microsoft.Extensions.Logging;
  15:  using Microsoft.Net.Http.Headers;
  16:   
  17:  namespace FileUploadSample.Controllers
  18:  {
  19:      public class StreamingController : Controller
  20:      {
  21:          private readonly ILogger<StreamingController> _logger;
  22:   
  23:          // Get the default form options so that we can use them to set the default limits for
  24:          // request body data
  25:          private static readonly FormOptions _defaultFormOptions = new FormOptions();
  26:   
  27:          public StreamingController(ILogger<StreamingController> logger)
  28:          {
  29:              _logger = logger;
  30:          }
  31:   
  32:          [HttpGet]
  33:          [GenerateAntiforgeryTokenCookieForAjax]
  34:          public IActionResult Index()
  35:          {
  36:              return View();
  37:          }
  38:   
  39:          #region snippet1
  40:          // 1. Disable the form value model binding here to take control of handling 
  41:          //    potentially large files.
  42:          // 2. Typically antiforgery tokens are sent in request body, but since we 
  43:          //    do not want to read the request body early, the tokens are made to be 
  44:          //    sent via headers. The antiforgery token filter first looks for tokens
  45:          //    in the request header and then falls back to reading the body.
  46:          [HttpPost]
  47:          [DisableFormValueModelBinding]
  48:          [ValidateAntiForgeryToken]
  49:          public async Task<IActionResult> Upload()
  50:          {
  51:              if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
  52:              {
  53:                  return BadRequest($"Expected a multipart request, but got {Request.ContentType}");
  54:              }
  55:   
  56:              // Used to accumulate all the form url encoded key value pairs in the 
  57:              // request.
  58:              var formAccumulator = new KeyValueAccumulator();
  59:              string targetFilePath = null;
  60:   
  61:              var boundary = MultipartRequestHelper.GetBoundary(
  62:                  MediaTypeHeaderValue.Parse(Request.ContentType),
  63:                  _defaultFormOptions.MultipartBoundaryLengthLimit);
  64:              var reader = new MultipartReader(boundary, HttpContext.Request.Body);
  65:   
  66:              var section = await reader.ReadNextSectionAsync();
  67:              while (section != null)
  68:              {
  69:                  ContentDispositionHeaderValue contentDisposition;
  70:                  var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out contentDisposition);
  71:   
  72:                  if (hasContentDispositionHeader)
  73:                  {
  74:                      if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
  75:                      {
  76:                          targetFilePath = Path.GetTempFileName();
  77:                          using (var targetStream = System.IO.File.Create(targetFilePath))
  78:                          {
  79:                              await section.Body.CopyToAsync(targetStream);
  80:   
  81:                              _logger.LogInformation($"Copied the uploaded file '{targetFilePath}'");
  82:                          }
  83:                      }
  84:                      else if (MultipartRequestHelper.HasFormDataContentDisposition(contentDisposition))
  85:                      {
  86:                          // Content-Disposition: form-data; name="key"
  87:                          //
  88:                          // value
  89:   
  90:                          // Do not limit the key name length here because the 
  91:                          // multipart headers length limit is already in effect.
  92:                          var key = HeaderUtilities.RemoveQuotes(contentDisposition.Name);
  93:                          var encoding = GetEncoding(section);
  94:                          using (var streamReader = new StreamReader(
  95:                              section.Body,
  96:                              encoding,
  97:                              detectEncodingFromByteOrderMarks: true,
  98:                              bufferSize: 1024,
  99:                              leaveOpen: true))
 100:                          {
 101:                              // The value length limit is enforced by MultipartBodyLengthLimit
 102:                              var value = await streamReader.ReadToEndAsync();
 103:                              if (String.Equals(value, "undefined", StringComparison.OrdinalIgnoreCase))
 104:                              {
 105:                                  value = String.Empty;
 106:                              }
 107:                              formAccumulator.Append(key, value);
 108:   
 109:                              if (formAccumulator.ValueCount > _defaultFormOptions.ValueCountLimit)
 110:                              {
 111:                                  throw new InvalidDataException($"Form key count limit {_defaultFormOptions.ValueCountLimit} exceeded.");
 112:                              }
 113:                          }
 114:                      }
 115:                  }
 116:   
 117:                  // Drains any remaining section body that has not been consumed and
 118:                  // reads the headers for the next section.
 119:                  section = await reader.ReadNextSectionAsync();
 120:              }
 121:   
 122:              // Bind form data to a model
 123:              var user = new User();
 124:              var formValueProvider = new FormValueProvider(
 125:                  BindingSource.Form,
 126:                  new FormCollection(formAccumulator.GetResults()),
 127:                  CultureInfo.CurrentCulture);
 128:   
 129:              var bindingSuccessful = await TryUpdateModelAsync(user, prefix: "",
 130:                  valueProvider: formValueProvider);
 131:              if (!bindingSuccessful)
 132:              {
 133:                  if (!ModelState.IsValid)
 134:                  {
 135:                      return BadRequest(ModelState);
 136:                  }
 137:              }
 138:   
 139:              var uploadedData = new UploadedData()
 140:              {
 141:                  Name = user.Name,
 142:                  Age = user.Age,
 143:                  Zipcode = user.Zipcode,
 144:                  FilePath = targetFilePath
 145:              };
 146:              return Json(uploadedData);
 147:          }
 148:          #endregion
 149:   
 150:          private static Encoding GetEncoding(MultipartSection section)
 151:          {
 152:              MediaTypeHeaderValue mediaType;
 153:              var hasMediaTypeHeader = MediaTypeHeaderValue.TryParse(section.ContentType, out mediaType);
 154:              // UTF-7 is insecure and should not be honored. UTF-8 will succeed in 
 155:              // most cases.
 156:              if (!hasMediaTypeHeader || Encoding.UTF7.Equals(mediaType.Encoding))
 157:              {
 158:                  return Encoding.UTF8;
 159:              }
 160:              return mediaType.Encoding;
 161:          }
 162:      }
 163:  }

Troubleshooting

Below are some common problems encountered when working with uploading files and their possible solutions.

Unexpected Not Found error with IIS

The following error indicates your file upload exceeds the server’s configured maxAllowedContentLength:

HTTP 404.13 - Not Found
The request filtering module is configured to deny a request that exceeds the request content length.

The default setting is 30000000, which is approximately 28.6MB. The value can be customized by editing web.config:

This setting only applies to IIS. The behavior doesn’t occur by default when hosting on Kestrel. For more information, see Request Limits <requestLimits>.

Null Reference Exception with IFormFile

If your controller is accepting uploaded files using IFormFile but you find that the value is always null, confirm that your HTML form is specifying an enctype value of multipart/form-data. If this attribute is not set on the <form> element, the file upload will not occur and any bound IFormFile arguments will be null.





Comments ( )
<00>  <01>  <02>  <03>  <04>  <05>  <06>  <07>  <08>  <09>  <10>  <11>  <12>  <13>  <14>  <15>  <16>  <17>  <18>  <19>  <20>  <21>  <22>  <23
Link to this page: //www.vb-net.com/AspNet-DocAndSamples-2017/aspnetcore/mvc/models/file-uploads.htm
<SITEMAP>  <MVC>  <ASP>  <NET>  <DATA>  <KIOSK>  <FLEX>  <SQL>  <NOTES>  <LINUX>  <MONO>  <FREEWARE>  <DOCS>  <ENG>  <CHAT ME>  <ABOUT ME>  < THANKS ME>