Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overwriting ASP.NET MVC active stylesheet bundle

I have a stylesheet in my application ~/Content/theme/style.css. It is referenced in my application using standard bundling as such:

bundles.Add(new StyleBundle("~/Content/css").Include(
 "~/Content/font-awesome/font-awesome.css",
 "~/Content/theme/style.css"));

Now, I have used a Sass compiler (Libsass) to allow me to change the output style.css file to a customised user output file as required.

So basically I do something like this.

CompilationResult compileResult = SassCompiler.CompileFile(Server.MapPath(Path.Combine(WebConfigSettings.RootSassPath, "style.scss"), options: new CompilationOptions {
    SourceMap = true,
    SourceMapFileUrls = true
});

and then I save like this.

string outputPath = Server.MapPath(WebConfigSettings.ThemeOutputPath);
if (System.IO.File.Exists(outputPath))
    System.IO.File.Copy(outputPath, string.Format("{0}.bak", outputPath), true);

System.IO.File.WriteAllText(Server.MapPath(WebConfigSettings.ThemeOutputPath), compileResult.CompiledContent);

However intermittently I receive the following dreaded access error: "The process cannot access the file C:....\style.css" because it is being used by another process." (Note: This occurs at the File.WriteAllText line)

This doesn't make sense because I do not open any streams to the file and perform what I assume to be a single atomic operation using File.WriteAllText.

Now I have also noticed that this error is particularly likely when I use two different browsers to modify this file consecutively.

My assumption is that one of two things is happening.

Either:

a. The bundling packager is somehow locking the file because it has been modified while it updates the bundles and not releasing the lock or

b. Because two different connections access the file somehow a lock persists across them.

So, has anyone run into anything similar? Any suggestions on how I might be able to fix this issue?

PS: I have tried using HttpRuntime.UnloadAppDomain(); as a hacky way to try and release any locks on the file but this doesn't seem to be helping.

like image 640
Maxim Gershkovich Avatar asked Nov 07 '22 16:11

Maxim Gershkovich


1 Answers

Your web server itself will get a read lock on the file(s) when they are served. So, if you are going to be writing files at the same time, collisions will be inevitable.

Option 1

Write to disk in a retry loop and ignore this exception. The files are likely to be available for writing within a very short time span.

Option 2

Avoid the web server locking the files by serving them yourself from a cache.

From this answer:

...if you are updating these [files] a lot, you're really defeating IIS's caching mechanisms here. And it is not healthy for the web server to be serving files that are constantly changing. Web servers are great at serving static files.

Now if your [files] are so dynamic, perhaps you'll need to serve it through a server-side program instead.

Since you mentioned in a comment that your end users are changing the files, I would suggest doing the following to ensure there is no chance of a locking conflict:

  1. Use an action method to serve the content of the bundle.
  2. By default, read the files from disk.
  3. When an end user loads the "edit" functionality of the application, load the content from the file(s) into a cache. Your action method that serves the content should check this cache first, serving it if available, and serve the file(s) from disk if not.
  4. When the end user saves the content, compile the content, write it to disk, then invalidate the cache. If the user doesn't save, the cache will just time out eventually and the files will be read from disk again by end users.

See How can I add the result of an ASP.NET MVC controller action to a Bundle? for some potential solutions on how to serve the bundle from an action method. I would probably use a solution similar to this one (although the caching strategy might need to be different).

Alternatively, you could make the cache reload every time it is empty in a user request and update both the files and cache during the "save" operation which would probably be simpler and reduce the chance of a file lock issue to zero, but wouldn't scale as well.

like image 96
NightOwl888 Avatar answered Nov 14 '22 22:11

NightOwl888