Explicat
Explicat

Reputation: 1095

Renaming concurrent uploaded files in ASP.NET

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

Answers (1)

SilverlightFox
SilverlightFox

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.

Update:

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

Related Questions