Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HTTP handlers and javascript bundling in VS 2012

I am currently trying to setup a project to implement localization on javascript files (as described here) but at the same time I'd like to bundle and minify the javascript in the project. I followed a tutorial on bundling and minification here

I have been able to get both working separately, but when I try to get them working together I cannot get the localisation working properly. I think this is because bundling creates it's own route handling for the bundled/minified javascript it generates, so the httpHandler I have defined in the webconfig gets ignored. I keep getting javascript errors saying "CustomTranslate is not defined".

I am trying to do this because we are building a number of controls using ExtJS, but we need to be able to apply localisation to those controls. Any help/ideas on how I can get them to work together would be appreciated.

I am not using MVC, but doing this in asp.net in Visual Studio 2012.

Here is my code:

BundleConfig.cs

namespace TranslationTest
{
    public class BundleConfig
    {
        public static void RegisterBundles(BundleCollection bundles)
        {
            //default bundles addeed here...

            bundles.Add(new ScriptBundle("~/bundles/ExtJS.axd").Include("~/Scripts/ExtJS/ext-all.js", "~/Scripts/ExtJS/TestForm.js"));

        }
    }
}    

web.config:

<globalization uiCulture="auto" />
<httpHandlers>
  <add verb="*" path="/bundles/ExtJS.axd" type="TranslationTest.ScriptTranslator, TranslationTest" />
</httpHandlers>

Default.aspx

<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="TranslationTest._Default" %>

<asp:Content runat="server" ID="BodyContent" ContentPlaceHolderID="MainContent">
    <script src="/bundles/ExtJS.axd"></script>
</asp:Content>    

TestForm.js:

Ext.require([
       'Ext.form.*',
       'Ext.layout.container.Column',
       'Ext.tab.Panel'
]);

Ext.onReady(function () {

    Ext.QuickTips.init();

    var bd = Ext.getBody();

    bd.createChild({ tag: 'h2', html: 'Form 1' });


    var simple = Ext.create('Ext.form.Panel', {
        url: 'save-form.php',
        frame: true,
        title: 'Simple Form',
        bodyStyle: 'padding:5px 5px 0',
        width: 350,
        fieldDefaults: {
            msgTarget: 'side',
            labelWidth: 75
        },
        defaultType: 'textfield',
        defaults: {
            anchor: '100%'
        },

        items: [{
            fieldLabel: CustomTranslate(FirstName),
            name: 'first',
            allowBlank: false
        }, {
            fieldLabel: CustomTranslate(LastName),
            name: 'last'
        }, {
            fieldLabel: CustomTranslate(Company),
            name: 'company'
        }, {
            fieldLabel: CustomTranslate(Email),
            name: 'email',
            vtype: 'email'
        }, {
            xtype: 'timefield',
            fieldLabel: CustomTranslate(Time),
            name: 'time',
            minValue: '8:00am',
            maxValue: '6:00pm'
        }],

        buttons: [{
            text: CustomTranslate(Save)
        }, {
            text: CustomTranslate(Cancel)
        }]
    });

    simple.render(document.body);


});

Currently the FirstName, LastName, etc are all stored in resource files, as in the linked example above.

ScriptTranslator.cs

namespace TranslationTest
{
    public class ScriptTranslator : IHttpHandler
    {
        #region IHttpHandler Members

        public bool IsReusable
        {
            get { return false; }
        }

        public void ProcessRequest(HttpContext context)
        {
            string relativePath = context.Request.AppRelativeCurrentExecutionFilePath.Replace(".axd", string.Empty);
            string absolutePath = context.Server.MapPath(relativePath);
            string script = ReadFile(absolutePath);
            string translated = TranslateScript(script);

            context.Response.Write(translated);

            Compress(context);
            SetHeadersAndCache(absolutePath, context);
        }

        #endregion

        private void SetHeadersAndCache(string file, HttpContext context)
        {
            context.Response.AddFileDependency(file);
            context.Response.Cache.VaryByHeaders["Accept-Language"] = true;
            context.Response.Cache.VaryByHeaders["Accept-Encoding"] = true;
            context.Response.Cache.SetLastModifiedFromFileDependencies();
            context.Response.Cache.SetExpires(DateTime.Now.AddDays(7));
            context.Response.Cache.SetValidUntilExpires(true);
            context.Response.Cache.SetCacheability(HttpCacheability.Public);
        }

        #region Localization

        private static Regex REGEX = new Regex(@"CustomTranslate\(([^\))]*)\)", RegexOptions.Singleline | RegexOptions.Compiled);

        private string TranslateScript(string text)
        {
            MatchCollection matches = REGEX.Matches(text);
            ResourceManager manager = new ResourceManager(typeof(TranslationTest.App_GlobalResources.text));

            foreach (Match match in matches)
            {
                object obj = manager.GetObject(match.Groups[1].Value);
                if (obj != null)
                {
                    text = text.Replace(match.Value, CleanText(obj.ToString()));
                }
            }
            return text;
        }

        private static string CleanText(string text)
        {
            text = text.Replace("'", "\\'");
            text = text.Replace("\\", "\\\\");
            return text;
        }

        private static string ReadFile(string absolutePath)
        {
            if (File.Exists(absolutePath))
            {
                using (StreamReader reader = new StreamReader(absolutePath))
                {
                    return reader.ReadToEnd();
                }
            }
            return null;
        }

        #endregion

        #region Compression

        private const string GZIP = "gzip";
        private const string DEFLATE = "deflate";

        private static void Compress(HttpContext context)
        {
            if (IsEncodingAccepted(DEFLATE, context))
            {
                context.Response.Filter = new DeflateStream(context.Response.Filter, CompressionMode.Compress);
                SetEncoding(DEFLATE, context);
            }
            else if (IsEncodingAccepted(GZIP, context))
            {
                context.Response.Filter = new GZipStream(context.Response.Filter, CompressionMode.Compress);
                SetEncoding(GZIP, context);
            }
        }

        private static bool IsEncodingAccepted(string encoding, HttpContext context)
        {
            return context.Request.Headers["Accept-encoding"] != null && context.Request.Headers["Accept-encoding"].Contains(encoding);
        }

        private static void SetEncoding(string encoding, HttpContext context)
        {
            context.Response.AppendHeader("Content-encoding", encoding);
        }

        #endregion

    }
}

global.asax.cs

namespace TranslationTest
{
    public class Global : HttpApplication
    {
        void Application_Start(object sender, EventArgs e)
        {
            Microsoft.Web.Optimization.BundleTable.Bundles.EnableDefaultBundles();

            BundleConfig.RegisterBundles(System.Web.Optimization.BundleTable.Bundles);
            AuthConfig.RegisterOpenAuth();
        }
    }
}

I hope I've covered everything, but please let me know if there's anything missing. Thanks in advance!!

like image 361
Paul Avatar asked Jun 17 '13 10:06

Paul


People also ask

What is bundling and minification in JavaScript?

Bundling and minification are two techniques you can use in ASP.NET 4.5 to improve request load time. Bundling and minification improves load time by reducing the number of requests to the server and reducing the size of requested assets (such as CSS and JavaScript.)

What is difference between bundling and minification?

Both bundling and minification are the two separate techniques to reduce the load time. The bundling reduces the number of requests to the Server, while the minification reduces the size of the requested assets.

Which of the following options allows you to combine multiple files such as CSS and JavaScript into a single file in MVC?

Bundling is a new feature in ASP.NET 4.5 that makes it easy to combine or bundle multiple files into a single file. You can create CSS, JavaScript and other bundles.

How to enable bundling and minification in MVC 5?

Bundling and minification can be enabled or disabled in two ways: either setting the value of the debug attribute in the compilation Element in the Web. config file or setting the enableOptimizations property on the BundleTable class. In the following example, debug is set to true in web.


1 Answers

Ok, I've set up everything in your example and I've got it to work but you need to use the IBundleTransform interface. The details of everything I did are posted below..

I had to create a class to handle the bundle transformation (i.e the translation) instead of allowing the default behaviour.

public class JsLocalizationTransform : IBundleTransform
    {
        public JsLocalizationTransform(){}

        #region IBundleTransform Members

        public void Process(BundleContext context, BundleResponse response)
        {
            string translated = TranslateScript(response.Content);

            response.Content = translated;
        }

        #endregion

        #region Localization

        private static Regex REGEX = new Regex(@"CustomTranslate\(([^\))]*)\)", RegexOptions.Singleline | RegexOptions.Compiled);

        private string TranslateScript(string text)
        {
            MatchCollection matches = REGEX.Matches(text);
            ResourceManager manager = new ResourceManager(typeof(TranslationTest.App_GlobalResources.text));

            foreach (Match match in matches)
            {
                object obj = manager.GetObject(match.Groups[1].Value);
                if (obj != null)
                {
                    text = text.Replace(match.Value, CleanText(obj.ToString()));
                }
            }

            return text;
        }

        private static string CleanText(string text)
        {
            //text = text.Replace("'", "\\'");
            text = text.Replace("\\", "\\\\");

            return text;
        }
        #endregion

    }

Then in BundleConfig.RegisterBundles method you need to create and add the bundle like this:

var extjsBundle = new Bundle("~/bundles/ExtJS").Include("~/Scripts/ExtJS/ext-all.js", "~/Scripts/ExtJS/TestForm.js");
    extjsBundle.Transforms.Clear();
    extjsBundle.Transforms.Add(new JsLocalizationTransform());
    extjsBundle.Transforms.Add(new JsMinify());
    bundles.Add(extjsBundle);

I could then remove the HttpHandler from web.config as that gets configured automatically through the bundler. I also had to make some changes to the Application_Start method in global.asax.cs

void Application_Start(object sender, EventArgs e)
        {
            //Microsoft.Web.Optimization.BundleTable.Bundles.EnableDefaultBundles(); 
            BundleTable.EnableOptimizations = true; //Added this line..
            BundleConfig.RegisterBundles(System.Web.Optimization.BundleTable.Bundles);
            AuthConfig.RegisterOpenAuth();
        }

Because the JSLocalisationTransform class is handling the bundle transformation and translation, I completely removed the ScriptTranslator class.

Hope that helps.

like image 61
Grant Clements Avatar answered Sep 22 '22 19:09

Grant Clements