views:

602

answers:

2

Using VS2008 and ASP.NET 3.5 (or VS 2010 / .NET 4.0?), how can I include a bit of dynamic ASP.NET server-side code in mostly-static JavaScript and CSS files?

I want to do this to avoid cloning entire JS or CSS files to vary just a small part of them multi-tenant sites. Later, I want to extend the solution to handle localization inside javascript/CSS, dynamic debugging/tracing support, and other cool things you can get by injecting stuff dynamically into JavaScript and CSS.

The hard part is that I don't want to lose all the cool things you get with static files, for example:

  • JS/CSS code coloring and intellisense
  • CSS-class "go to definition" support in the IDE
  • automatic HTTP caching headers based on date of underlying file
  • automatic compression by IIS

The server-side goodness of static files (e.g. headers/compression) can be faked via an HttpHandler, but retaining IDE goodness (intellisense/coloring/etc) has me stumped.

An ideal solution would meet the following requirements:

  1. VS IDE provides JS/CSS intellisense and code coloring. Giving up server-code intellisense is OK since server code is usually simple in these files.
  2. "go to defintion" still works for CSS classes (just like in static CSS files)
  3. send HTTP caching headers, varying by modified date of the underlying file.
  4. support HTTP compression like other static files
  5. support <%= %> and <script runat=server> code blocks
  6. URL paths (at least the ones that HTTP clients see) end with .JS or .CSS (not .ASPX). Optionally, I can use querystring or PathInfo to parameterize (e.g. choosing a locale), although in most cases I'll use vdirs for this. Caching should vary for different querystrings.

So far the best (hacky) solution I've come up with is this:

  • Switch the underlying CSS or JS files to be .ASPX files (e.g. foo.css.aspx or foo.js.aspx). Embed the underlying static content in a STYLE element (for CSS) or a SCRIPT element (for JS). This enables JS/CSS intellisense as well as allowing inline or runat=server code blocks.
  • Write an HttpHandler which:
    • looks at the URL and adds .aspx to know the right underlying ASPX to call
    • uses System.Net.HttpWebRequest to call that URL
    • strips out the containing STYLE or SCRIPT tags, leaving only the CSS or JS
    • adds the appropriate headers (caching, content type, etc.)
    • compresses the response if the client suports compression
  • Map *.CSS and *.JS to my handler.
  • (if IIS6) Ensure .JS and .CSS file extensions are mapped to ASP.NET

I'm already using a modified version of Darick_c's HttpCompression Module which handles almost all of above for me, so modifying it to support the solution above won't be too hard.

But my solution is hacky. I was wondering if anyone has a more lightweight approach for this problem which doesn't lose Visual Studio's static-file goodness.

I know I can also hack up a client-side-only solution where I split all JS and CSS into "vary" and "won't vary" files, but there's a performance and maintenance overhead to this kind of solution that I'd like to avoid. I really want a server-side solution here so I can maintain one file on the server, not N+1 files.

I've not tried VS10/.NET 4.0 yet, but I'm open to a Dev10/.net4 solution if that's the best way to make this scenario work.

Thanks!

+2  A: 

I have handled a similar problem by having a master page output a dynamic generated JSON object in the footer of each page.

I needed to have my js popup login dialog box support localization. So using JSON.NET for serialization, I created a public key/value collection property of the master page that pages could access in order place key/values into such as phrase key/localized phrase pairs. The master page then renders a dynamic JSON object that holds these values so that static js files could reference these dynamic values.

For the js login box I have the masterpage set the localized values. This made sense because the masterpage also includes the login.js file.

Tim Santeford
So in other words, you retained the static JS but had the page pre-populate a global variable which the static JS file subsequently re-used? This seems like a good solution for JS-- although it does make the JS non-self-contained which may make re-use and testing a little bit harder. I assume I could apply the same kind of approach to my CSS, by injecting a dynamically-generated STYLE element in my masterpage which overrode the CSS styles set in my main file.
Justin Grant
To make the static js file be self contained have the js file define a global array and populate it with defaults. Then allow the masterpage to overwrite the defaults as needed.
Tim Santeford
About the CSS injection. If the amount of css that differs only by one or two lines then I think injecting a short override into the masterpage is completely acceptable.
Tim Santeford
Hi Tim - this seems like the best approach I've seen so far, and not really hacky which is nice. Brian's answer is similar and has more detail, but yours came in first and has, IMHO, a more robust javascript solution, so you get the bouty. Thanks for your help!
Justin Grant
+1  A: 

I do commend you on your concern over the number of http requests being made from the client and the payload being returned. Too many people I know and work with overlook those easy optimizations. However, any time I run into the same issue you're having (which is actually quite often), I have found I've usually either taken a wrong turn somewhere or am trying to solve the problem the wrong way.

As far as your JS question goes, I think Frank Schwieterman in the comments above is correct. I'd be looking at ways to expose the dynamic parts of your JS through setters. A really basic example would be if you have want to display a customized welcome message to users on login. In your JS file, you can have a setMessage(message) method exposed. That method would then be called by the page including the script. As a result, you'd have something like:

<body onLoad="setMessage('Welcome' + <%= user.FirstName %>);">

This can obviously be expanded by passing objects or methods into the static JS file to allow you the functionality you desire.

In response to the CSS question, I think you can gain a lot from the approach Shawn Steward from the comments makes a good point. You can define certain static parts of your CSS in the base file and then redefine the parts you want to change in other files. As a result, you can then dictate the look of your website by which files you're including. Also, since you don't want to take the hit for extra http requests (keep in mind, if you set those files to be cached for a week, month, etc. it's a one time request), you can do something like combining the CSS files into a single file at compilation or runtime.

Something like the following links may be helpful in pointing you in the right direction:

http://geekswithblogs.net/rashid/archive/2007/07/25/Combine-Multiple-JavaScript-and-CSS-Files-and-Remove-Overheads.aspx

http://www.asp.net/learn/3.5-SP1/video-296.aspx?wwwaspnetrdirset=1

http://dimebrain.com/2008/04/resourceful-asp.html

By utilizing the combining at run or compile time you can gain the best of both world by allowing you to logically separate CSS and JS files, yet also gaining the reduction of payload and requests that comes with compressing and combining files.

Brian Hasden
Hi Brian - this is great info, thanks. I'm already combining my CSS and JS files (using a using a modified version of Darick_c's HttpCompression Module), so your solution fits nicely with what I already am using. I admit I am not a fan of your JS "setter" approach because it requires more defensive coding in cases where the external script doesn't load (e.g. network problems, weird timing issues, etc.). Tim's JS solution of setting global variables in the masterpage and having the script pull those vars seems safer. But yours is a good solution, and deserves a +1 ! Thanks again for the help.
Justin Grant
No problem. What changes did you make to the compression module? I'm using the ajax toolkit ToolkitScriptManager to combine my scripts together but I'm only using it for JS and am not currently using anything for compression or CSS files.
Brian Hasden