pete
pete

Reputation: 25081

How can I overwrite/update a file that is currently being served by IIS?

The problem:

My company puts out a monthly newsletter which I host on our internal website. I have a page for the author of the newsletter to upload the latest version. Once the author has uploaded the latest newsletter, he sends a broadcast email to announce the new newsletter. Employees invariably check the new newsletter and send feedback to the author with corrections that need to be made.

Once the author has made the necessary corrections (typically within an hour of sending the broadcast email), he revisits my page and replaces the latest version with the updated newsletter.

Immediately following the replacement (or update, if you will) of the newsletter, anyone attempting to access it gets a 500 - Internal Server Error.

My IT guy who maintains the server cannot delete/rename/move the file because of a permissions error and has to do a lot of convoluted things to get the file deleted (and once the file is deleted, the author of the newsletter can re-upload the corrected copy and it works fine.

My IT guy and I are pretty sure that the problem stems from that I'm trying to replace the file while IIS is actively serving it to users (which I thought of and thought that I had coded against happening).

The code that runs the replacement is as follows:

Protected Sub ReplaceLatestNewsletter()
    Dim dr As DataRow
    Dim sFile As String
    Dim mFileLock As Mutex

    Try
        If Me.Archives.Rows.Count > 0 Then
            dr = Me.Archives.Rows(0)
            sFile = dr("File").ToString

            If dr("Path").ToString.Length > 0 Then
                mFileLock = New Mutex(True, "MyMutexToPreventReadsOnOverwrite")

                Try
                    mFileLock.WaitOne()
                    System.IO.File.Delete(dr("Path").ToString)
                Catch ex As Exception
                    lblErrs.Text = ex.ToString
                Finally
                    mFileLock.ReleaseMutex()
                End Try
            End If

            fuNewsletter.PostedFile.SaveAs(Server.MapPath("~/Newsletter/archives/" & sFile))
        End If
    Catch ex As Exception
        lblErrs.Text = ex.ToString
    End Try

    dr = Nothing
    sFile = Nothing
    mFileLock = Nothing
End Sub

I thought the Mutex would take care of this (although after re-reading documentation I'm not sure I can actually use it like I'm trying to). Other comments on the code above:

In any case, I'm pretty sure that the code above is not establishing an exclusive lock on the file so that the file can be overwritten safely.

Ultimately, I would like to make sure that the following happens:

  1. If IIS is currently serving the file, wait until IIS has finished serving it.
  2. Before IIS can serve the file again, establish an exclusive lock on the file so that no other process, thread, user (etc.) can read from or write to the file.
  3. Either delete the file entirely and write a new file to replace it or overwrite the existing file with the new content.
  4. Remove the exclusive lock so that users can access the file again.

Suggestions?

Also, can I use a Mutex to get a mutually exclusive lock on a file in the Windows filesystem?

Thank you in advance for your assistance and advice.

EDIT:

The way that the links for the newsletter are generated is based on the physical filename. The method used is:

  1. Get all PDF files in the "archives" directory. For each file:
  2. Parse the date of publication from the filename.
  3. Store the date, the path to the file, the filename, and a URL to each file in a DataRow in a DataTable
  4. Sort the DataTable by date (descending).
  5. Output the first row as the current issue.
  6. Output all subsequent rows as "archives" organized by year and month.

UPDATE:

In lieu of not being able to discern when all existing requests for that file have completed, I took a closer look at the first part of @Justin's answer ("your mutex will only have an effect if the process that reads from the file also obtains the same mutex.")

This led me to Configure IIS7 to server static content through ASP.NET Runtime and the linked article in the accepted answer.

To that end, I have implemented a handler for all PDF files which implements New Mutex(True, "MyMutexToPreventReadsOnOverwrite") to ensure that only one thread is doing something with the PDF at any given time.

Thank you for you answer, @Justin. While I did not wind up using the implementation you suggested, your answer pointed me towards an acceptable solution.

Upvotes: 4

Views: 5096

Answers (1)

Justin
Justin

Reputation: 86729

Your mutex will only have an effect if the process that reads from the file also obtains the same mutex. What is the method used to serve up the file? Is ASP.Net used or is this just a static file?

My workflow would be a little different:

  1. Write the new newsletter to a new file
  2. Have IIS start serving up the new file instead of the old one for the given Newsletter url
  3. Delete the old file once all existing requests for that file have completed

This requires no locking and also means that we don't need to wait for requests for the current file be completed (something which could potentially take an indefinite amount of time if people keep on making new requests). The only interesting bit is step 2 which will depend on how the file is served - the easiest way would probably be to either set up a HTTP redirect or use URL rewriting

HTTP Redirect

A HTTP Redirect is where the server tells the client to look in a different place when it gets a request for a given resource so that the browser URL is automatically updated to match the new location. For example if the user requested http://server/20120221.pdf then they could be automatically redirected to another URL such as http://server/20120221_v2.pdf (the URL shown in the browser would change however the URL they need to type in would not).

You can do this in IIS 7 using the httpRedirect configuration element, for example:

<configuration>
   <system.webServer>
      <httpRedirect enabled="true" exactDestination="true" httpResponseStatus="Found">
         <!-- Note that I needed to add a * in for IIS to accept the wildcard even though it isn't used in this case -->
         <add wildcard="*20120221.pdf" destination="20120221_v2.pdf" />
      </httpRedirect>
   </system.webServer>
</configuration>

The linked page shows how to change these settings from ASP.Net

Url Rewriting

Alternatively IIS can be set up to automatically serve up the content of a different file for a given URL without the client (the browser) ever knowing the difference. This is called URL rewriting and can be done in IIS using something like this however it does require that additional components be installed to IIS to work.

Using a HTTP Redirect is probably the easiest method.

Upvotes: 4

Related Questions