5

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.

Maxim Gershkovich
  • 45,951
  • 44
  • 147
  • 243
  • Your compiler spits the error while writing the file. right? – Abdul Samad Sep 19 '17 at 10:40
  • No, sorry if I wasn't clear, the error occurs at the line System.IO.File.WriteAllText. I do not use the SassCompiler to write the actual file. – Maxim Gershkovich Sep 19 '17 at 10:41
  • It's necessary to write the `style.css` file ? You could compile sass on call and inject it in the bundle – GGO Oct 05 '17 at 15:39
  • If you're building MVC with Visual Studio, there are tons of tools, extensions and plugins to help you transpile SASS and LESS files into CSS files automatically. Web Essentials is 1 of them. If you like Bundle stuff all in one place, you can try BundleTransformer Nuget package. – David Liang Oct 05 '17 at 18:31
  • Yes of course, I understand this and I use those tools but my particular use case requires the ability to modify the Sass files at runtime by end users. – Maxim Gershkovich Oct 07 '17 at 01:48

2 Answers2

1

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.

Community
  • 1
  • 1
NightOwl888
  • 55,572
  • 24
  • 139
  • 212
  • Thank you so much, this was exactly the sort of explanation I was looking for. The tradeoff I am finding myself in is not wanting to over-complicate the approach (this feature only really get used by me - even though it is available to end users). I have gone with the approach of just ignoring the error and doing a retry loop, seems to be working for now. Thanks again... – Maxim Gershkovich Oct 10 '17 at 10:00
0

When one page rendered on browser, then the optimizer process the bundled css and jqueries into caching. So when once the page got cashed, one page re-request the browser first will check for page cached contents, if not present then only it make service call. There is only two solutions for your question less or sass type css usage.

  1. turn off bundling
  2. Less,coffeescript,scss & sass bundling
Saneesh kunjunni
  • 548
  • 5
  • 17