MVC6: Razor code in dynamic JS/CSS files

MVC6: Razor code in dynamic JS/CSS files

2016-08: updated to be valid for ASP.NET Core 1.0

Sometimes it would be really useful to include Razor/C# code in JavaScript or even in CSS files. Take configuration values from web.config or URLs to MVC controller actions as an example: instead of hard-code them into the JavaScript files, how nice would it be to generate JS files with Razor syntax dynamically on the server and return them to the client browser? Of course, you don’t need such functionality throughout your whole application and I’m a big fan of keeping things clean and separated, but it can be handy to have a „global“ dynamically generated JavaScript configuration file, that serves for your whole client-side application and gets its values rendered server-side in Razor.

CSHTML to JS

Now unfortunately (or not) you cannot put Razor syntax in plain *.js files, because they are static content and not touched by the compiler. But you can create a *.cshtml-file, that is returned as *.js-file to the client after compilation. The easiest way how to do this is with a custom route. Say, every action in a JavaScriptController should return a *.js file, then you could use the following setup in your MVC6 Startup class:

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "javascript",
        template: "JavaScript/{action}.js",
        defaults: new { controller = "JavaScript" });
    ...
});

Now if you call e.g. /JavaScript/config.js, the Config action will be called and the result will be returned transparently as JS file.

Content Type and <script> Tags

We are not done here. First, the content type of the response should be "text/javascript". Second, to have typing support and IntelliSense in Visual Studio, we have to enclose the JavaScript in our *.cshtml file in a <script> Tag, e.g.:

<script>
    var config = {
        myValue: '@MyConfig.Value'
    };
</script>

Now, this script Tags are returned to the client, but that’s usually not what we want. To get a plain JavaScript file, we have to get rid of the script Tags. Thus, we want to replace the script Tags by nothing and just return the compiled JavaScript… This could be done e.g. by writing a custom attribute, or alternatively by implementing a controller extension method:

public static class ControllerExtensions
{
    public static async Task<ActionResult> GetPlainJavaScriptAsync(
        this Controller controller, string viewName = null, object model = null)
    {
        const string JsPattern = @"^<script[^>]*>(.*)</script>$";
        var viewResult = await RenderPartialViewToStringAsync(controller, viewName, model);
        var content = Regex.Replace(viewResult, JsPattern, "$1", RegexOptions.Singleline);

        return new ContentResult
        {
            ContentType = new MediaTypeHeaderValue("text/javascript"),
            Content = content
        };
    }

    private static async Task<string> RenderPartialViewToStringAsync(
        this Controller controller, string viewName = null, object model = null)
    {
        viewName = viewName ?? controller.ControllerContext.ActionDescriptor.DisplayName;
        controller.ViewData.Model = model;

        using (StringWriter stringWriter = new StringWriter())
        {
            var engine = (ICompositeViewEngine)controller.HttpContext.RequestServices
                .GetService(typeof(ICompositeViewEngine));
            var viewEngineResult = engine.FindView(
                controller.ControllerContext, viewName, false);
            var viewContext = new ViewContext(
                controller.ControllerContext, viewEngineResult.View, controller.ViewData, 
                controller.TempData, stringWriter, new HtmlHelperOptions());

            await viewEngineResult.View.RenderAsync(viewContext);
            return stringWriter.GetStringBuilder().ToString();
        }
    }
}

This method resolves a given view, gets it compiled and returned as string and removes the script Tags. The result is returned with the correct content type. You can call it in your controller with:

public async Task<ActionResult> MyJavaScriptAction()
{
    return await this.GetPlainJavaScriptAsync("MyJavaScriptView");
}

CSS

It’s pretty the same for CSS. You just need a custom CSS output method in the ControllerExtentions class:

public static async Task<ActionResult> GetPlainCssAsync(
    this Controller controller, string viewName = null, object model = null)
{
    const string CssPattern = @"^<style[^>]*>(.*)</style>$";
    var viewResult = await RenderPartialViewToStringAsync(controller, viewName, model);
    var content = Regex.Replace(viewResult, CssPattern, "$1", RegexOptions.Singleline);

    return new ContentResult
    {
        ContentType = new MediaTypeHeaderValue("text/css"),
        Content = content
    };
}
Ich bin freiberuflicher Senior Full-Stack Web-Entwickler (Angular, TypeScript, C#/.NET) im Raum Frankfurt/Main. Mit Leidenschaft für Software-Design, Clean Code, moderne Technologien und agile Vorgehensmodelle.

0 Kommentare

Eine Antwort hinterlassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

*

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.