Reputation: 1095
I use the following code to receive uploaded files and store them in the uploadedFiles folder on the server.
public class CustomMultipartFormDataStreamProvider : MultipartFormDataStreamProvider
{
public CustomMultipartFormDataStreamProvider(string path) : base(path) { }
public override string GetLocalFileName(HttpContentHeaders headers)
{
return headers.ContentDisposition.FileName.Replace("\"", string.Empty);
}
}
[HttpPost]
public async Task<HttpResponseMessage> ReceiveFileupload()
{
if (!Request.Content.IsMimeMultipartContent("form-data"))
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
// Prepare CustomMultipartFormDataStreamProver in which our multipart form data will be loaded
string fileSaveLocation = HttpContext.Current.Server.MapPath("~/UploadedFiles");
CustomMultipartFormDataStreamProvider provider = new CustomMultipartFormDataStreamProvider(fileSaveLocation);
List<string> files = new List<string>();
Duck duck = null;
try
{
// Read all contents of multipart message into CustomMultipartFormDataStreamProvider
await Request.Content.ReadAsMultipartAsync(provider);
// Parse an ID which is passed in the form data
long id;
if (!long.TryParse(provider.FormData["id"], out id))
{
return Request.CreateResponse(HttpStatusCode.ExpectationFailed, "Couldn't determine id.");
}
duck = db.Ducks.Find(id);
if (null == duck)
return Request.CreateResponse(HttpStatusCode.NotAcceptable, "Duck with ID " + id + " could not be found.");
// Loop through uploaded files
foreach (MultipartFileData file in provider.FileData)
{
// File ending needs to be xml
DoSomething();
// On success, add uploaded file to list which is returned to the client
files.Add(file.LocalFileName);
}
// Send OK Response along with saved file names to the client.
return Request.CreateResponse(HttpStatusCode.OK, files);
}
catch (System.Exception e) {
return Request.CreateResponse(HttpStatusCode.InternalServerError, e);
}
}
Now I want to rename the uploaded files. As I understand, storing and renaming must be done in a critical section because when another user uploads a file with the same name at the same time, the first will be overwritten.
That's how I imagine a solution, but await
is not allowed in a lock statement.
lock(typeof(UploadController)) {
await Request.Content.ReadAsMultipartAsync(provider); //That's the line after which the uploaded files are stored in the folder.
foreach (MultipartFileData file in provider.FileData)
{
RenameFile(file); // Rename according to file's contents
}
}
How can I guarantee that the files will be stored and renamed atomically?
Upvotes: 1
Views: 2655
Reputation: 33538
Instead of using the original name and then renaming, why not generate a name on the fly in your upload code?
e.g. If the file is presented as "foo.xml
" you write it to disk as "BodyPart_be42560e-863d-41ad-8117-e1b634e928aa
"?
Also be aware that if you upload a file to "~/UploadedFiles"
anyone will be able to download it using a URL like www.example.com/UploadedFiles/name.xml
- you should store your files in ~/App_Data/
to prevent this.
Why not remove your override of the GetLocalFileName
method, and simply use the original in the MultipartFileStreamProvider base class?
public virtual string GetLocalFileName(HttpContentHeaders headers)
{
if (headers == null)
{
throw Error.ArgumentNull("headers");
}
return String.Format(CultureInfo.InvariantCulture, "BodyPart_{0}", Guid.NewGuid());
}
This should save each file as a unique name without having to later rename.
Upvotes: 4