ASP.NET Team has done a lot with the razor engine, but have not yet released a cscss or csjs file type.
The methodology will be very simple. We will use normal cshtml files, but dedicate them to the script generation. For that and to take advantage of the completion of Visual Studio, we will use the proper html tag to identify the type of content we would like. Using cshtml will be convenient as we can:
- use view models to pre-fill our scripts
- use Partial conditional subviews in the script
- use take advantage of the formatting helpers of Visual Studio
For that we will use the service built in a previous post to generate HTML string from cshtml razor views.
Our process will be:
- define a structure for cshtml files that we can easily re-use and that is intuitive
- fill our cshtml view with the code we want to generate dynamically
- generate a HTML string from the cshtml view using the ViewToStringRendererService
- extract the relevant parts
- serve it as file content from the controller
Structure of our cshtml file
We want to take advantage of the multi-language cshtml views, but we want to be able to easily extract the output. Therefore what our file should render is a single node containing our script content. For
- a css file, we would generate a
<style>...style content...</style>output and you may add more options if you need to build different types of resources, such as SVG for example
A post-in-progress will show how to build a very useful translation tool for razor views (and translate all your website)
(cshtml being quite complex to highlight as it embeds different languages, this may not look so great on post, but copy-pasting in a cshtml will help)
This very stupid example is an example of the structure we will use:
Generate the HTML result of this razor view
For that we will simply register the above-referenced ViewToStringRendererService tool, inject it in our controller, and call it as
var vModel = new DynamicJS1ViewModel(); const string VIEW_NAME = "/Areas/MyFeature/Views/DynamicJS/js1.cshtml"; var viewString = await _ViewToStringRenderer.RenderViewToStringAsync(VIEW_AD, vModel);
From there we get the string result of the above view, we need now to extract the content of the script tag. We could either parse it as a XML file, or just use a Regex. I opted for a regular expression as it should cover most of the cases.
const string JsPattern = @"^<script[^>]*>(.*)</script>$"; // We want the '.' to match also new lines, so we use the option `RegexOptions.Singleline`. // We replace the whole string by the content of the script tag, so by "$1". You could even improve this and allow multiple Script tags var content = Regex.Replace(viewString, JsPattern, "$1", RegexOptions.Singleline);
For a css dynamic file:
const string CssPattern = @"^<style[^>]*>(.*)</style>$"; var content = Regex.Replace(viewString, CssPattern, "$1", RegexOptions.Singleline);
How to serve the script as file content from the controller
At last, we have the content, we now want to serve it as a file. This is quite straightforward:
Ensure to let charset to utf-8 to avoid any issue, or customize it to your specific needs. You may also prefer another similar method:
Below are the two Controller extension methods that I use to render the scripts
Once you have this, you don't need much more to understand how to concatenate different source files. To open a file from the wwwroot folder, you would do the following:
var filePath = Path.Combine(_Environment.WebRootPath, "/js/jsfile.js"); var fileContent = await System.IO.File.OpenText(filePath).ReadToEndAsync();
And then you can just concatenate file content by doing
var output = content + ";" + fileContent . I recommend to add a semicolon to avoid interferences between files, it happens quite often and is very difficult to debug 😃
If you want to use any other file from the solution, you may use:
var filePath = Path.Combine(Directory.GetCurrentDirectory(), fileName);
However ensure to be careful with security issues on
fileName variable if it is depending on request parameters.
When building dynamic resources, one issue you may reach for optimization is the cache. Ensure either to add custom query attributes so that if the context of your user changes, the file will be reloaded from a fresh content, or to add cache policies that meet your file constraints (medium, long durations).