How to generate a dynamic javascript or css file using ASP.NET Core Razor engine
asp.net-core javascript css .net dynamic-resource dynamic-javascript dynamic-css

How to generate a dynamic javascript or css file using ASP.NET Core Razor engine

Someday I wanted to push out of my cshtml most of the javascript and css styles. However I faced an issue when I needed to customize a bit the css or the javascript for a specific user, cookie value etc. This article explains how you can easily include a dynamic javascript file (or dynamic css), by using the power of the razor engine and cshtml files.

You may want to optimize the number of files that are loaded from your server by aggregating all js files into a single one, as well as for css. Why would you load admin-related scripts if the user is not admin, besides helping hackers to find potential security breachs? You may also not want to use lass/scss/less but still need to use dynamic colors/values, or even serve different splits of css/javascript depending on the user profile to reduce the load, and still serve a single file.

ASP.NET Team has done a lot with the razor engine, but have not yet released a cscss or csjs file type.

We can actually find an easy-to-use workaround, that will serve our needs, and do this exact same job. You may use it to create dynamic javascript or css files in ASP.NET Core and use C# language to build them, useful when you want to load parts from a SQL database without delaying your page response or serve dynamic JSON files.

Methodology

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:

  • build dynamic javascript, json or css resources
  • 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
  • take advantage of any translation tool for javascript files

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 javascript or json file, we would then generate a <script>...js content...</script> output
  • 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:

@model MyCustomJavascript_ViewModel
@using Microsoft.AspNetCore.Http
@{
    Texts["MyFeature.Message1"] = "Message 1 in english";
	Texts["MyFeature.Error"] = "Error";
    await TranslateAsync(skip : new [] { "en" });
	
	var goodHost = "example.com";
	var refererHost = HttpContext.Request.GetTypedHeaders().Referer.Host;

    var parameter1 = Context.Request.GetQueryParameter("parameter1").FirstOrDefault();
}
@if(refererHost == goodHost)
{
	<script>
		const msg1 = '@T_JS("MyFeature.Message1")';
		const prm1Value = '@parameter1.replace("'", "\\'")';
		
		
		
		if(prm1Value === 'rightvalue') 
			alert(msg1);
		else alert(prm1Value);
	</script>
}
else 
{
	<script>
		alert('@T_JS("MyFeature.Error")');
	</script>
}	

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.

For a javascript dynamic file:

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:

return controller.Content(content, "application/javascript; charset=utf-8");

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:

return File(content.ToByteArray(),"application/javascript; charset=utf8");

Below are the two Controller extension methods that I use to render the scripts

public static IActionResult JavascriptFromViewString(this Controller controller, string viewString)
{
	const string JsPattern = @"^<script[^>]*>(.*)</script>$";
	var content = Regex.Replace(viewString, JsPattern, "$1", RegexOptions.Singleline);
	return controller.Content(content, "application/javascript; charset=utf-8");
}

public static IActionResult CssFromViewString(this Controller controller, string viewString)
{
	const string CssPattern = @"^<style[^>]*>(.*)</style>$";
	var content = Regex.Replace(viewString, CssPattern, "$1", RegexOptions.Singleline);
	return controller.Content(content, "text/css; charset=utf-8");
}

Concatenate files

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.

Cache issues

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).


Send
Please sign-in to comment
.X0001-01-01_00-00