I found the documentation regarding handling large uploads in a ASP.Net Core app confusing. I didn’t find a lot of code examples, and even none at all using F#.
Eventually I found an
example
I could get inspiration from and build a working solution with.
It should work with whatever ASP.Net Core based framework you use, as long as
you can get a handle on the Microsoft.AspNetCore.Http.HttpContext
instance.
You will need to clarify some things before you put the code in production. For example I’m not sure the code
handles contentDisposition.FileNameStar
correctly. This means that even though it’s useful to share this code, it is done without any warranty and
you should do your homework to validate the code works as you expect and fits your requirements.
The function that will extract the file from the payload and save the file on
disk is uploadFile
and it uses the small helper function cleanupHeaderValue
to remove quotes from header values:
let cleanupHeaderValue (s:string) =
Microsoft.Net.Http.Headers.HeaderUtilities.RemoveQuotes(s).Value
// returns true when it worked through all sections
let rec uploadFile(reader:MultipartReader)(section:MultipartSection) = async {
let hasContentDispositionHeader,contentDisposition = System.Net.Http.Headers.ContentDispositionHeaderValue.TryParse(section.ContentDisposition)
if (hasContentDispositionHeader) then
let hasFileName = not <| System.String.IsNullOrEmpty(contentDisposition.FileName)
// not sure FileNameStar is handled correctly below, do you homework and check!
let hasFileNameStar = not <| System.String.IsNullOrEmpty(contentDisposition.FileNameStar)
if (contentDisposition.DispositionType.Equals("form-data") &&
(hasFileName || hasFileNameStar))
then
let filePath = System.IO.Path.GetFullPath(Path.Combine(System.Environment.CurrentDirectory, "UploadedFiles"))
// BEWARE: you should sanitize the filename extract from the headers, eg to ensure it doesn't include a partial path
use fileStream = System.IO.File.Create(Path.Combine(filePath, cleanupHeaderValue contentDisposition.FileName))
do! section.Body.CopyToAsync(fileStream)|> Async.AwaitTask;
let! nextSection = reader.ReadNextSectionAsync() |> Async.AwaitTask
if isNull nextSection then
return true
else
return! uploadFile reader nextSection
else return false
else
return false
}
To use it, you just have to get the MultipartReader
and MultipartSection
from the HttpContext
.
Here’s how I call it from another async
function:
async {
// first you need to get a handle on the Microsoft.AspNetCore.Http.HttpContext instance (not shown)
....
// then
let boundary =
Microsoft.Net.Http.Headers.HeaderUtilities.RemoveQuotes(
Microsoft.Net.Http.Headers.MediaTypeHeaderValue.Parse(httpContext.Request.ContentType).Boundary
).Value
let reader = new MultipartReader(boundary, httpContext.Request.Body);
let! section = reader.ReadNextSectionAsync() |> Async.AwaitTask
if section <> null then
return! uploadFile reader nextSection
else
return true
}
The code above also handles multiple files sent in the same request (which in my case was a XMLHttpRequest).
Special thanks to Abel Braaksma on the F# slack for his feedback!