Sunday 26 June 2016

Setting up Bundling and Minification for ASP.NET Web Pages C# & VB

It can be quite frustrating when you receive complaint from customer regarding the application that you have developed perform very slow. You have tried to tune your application, move some of the non-business processing logic to javascript, added 3rd party javascript libraries to simplify the implementation and it just doesn't load fast enough. End up your application need to load up a lot of javascript files with some of them are quite large in file size.

To solve this, you may want to use bundling and minification techniques which are available on ASP.NET 4.5 onward. Thanks to my mentor Serena Yeoh for introducing this. The bundling is to combine all javascript and css files into 1 file of javascript and 1 file of css. This is to reduce the number of requests to the server to fetch different javscript and css files. Minification is to reduce the size of the javascript and css file. Check out here http://www.asp.net/mvc/overview/performance/bundling-and-minification to know more about bundling and minification.

If you create a web forms or MVC project, by default the bundling has been configured for you. So for those who creates ASP.NET Web Pages project, then this post is for you. So let's get started.

1) Create a Web Application project.

Visual Studio - Add New Project Window

2) Under "Select a template", choose "empty" and click OK button.

Visual Studio - New ASP.NET Project Window

3) NuGet "Microsoft ASP.NET Web Pages" and "Microsoft ASP.NET Web Optimization Framework".

Manage NuGet Packages - Microsoft ASP.NET Web Pages
Microsoft ASP.NET Web Pages

Manage NuGet Packages - Microsoft ASP.NET Web Optimization Framework
Microsoft ASP.NET Web Optimization Framework

4) Place the following configuration in your web configuration file. Change the namespace value "WPBDemo" to your web application project's namespace.

[web.config]
<configSections>
  <sectionGroup name="system.web.webPages.razor"
                type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
    <section name="host"
             type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
             requirePermission="false" />
    <section name="pages"
             type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
             requirePermission="false" />
  </sectionGroup>
</configSections>
<system.web.webPages.razor>
  <host factoryType="System.Web.WebPages.Razor.WebRazorHostFactory, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
  <pages pageBaseType="System.Web.WebPages.WebPage">
    <namespaces>
      <add namespace="System.Web.Optimization" />
      <add namespace="WPBDemo" />
    </namespaces>
  </pages>
</system.web.webPages.razor>

5) Create a folder named "App_Start" in your web application project and create a class file named "BundleConfig.cs".

BundleConfig.cs

6) In your "BundleConfig.cs" file. Create a static method with no return type called "RegisterBundles" that contain 1 parameter with datatype "BundleCollection".

[C#]
public static void RegisterBundles(BundleCollection bundles)
{
   
}

[VB]
Public Shared Sub RegisterBundles(ByVal bundles As BundleCollection)
   
End Sub

7) In the RegisterBundles method, assuming you have AngularJS and Bootstrap added to your web application. Add a new instance of ScriptBundle for javascript files and add a new instance of StyleBundle for css files. You can add as many ScriptBundle or StyleBundle as you want. The ScriptBundle and StyleBundle instance that you defined here will be used in your razor page.

[C#]
bundles.Add(new ScriptBundle("~/bundles/demo").Include(
              "~/Scripts/angular.js",
              "~/Scripts/angular-resource.js",
              "~/Scripts/angular-animate.js",
              "~/Scripts/angular-sanitize.js",
              "~/Scripts/angular-ui/ui-bootstrap-tpls.js"
            ));

bundles.Add(new StyleBundle("~/content/css").Include(
            "~/Content/bootstrap.css",
            "~/Content/ui-bootstrap-csp.css"
          ));

[VB]
bundles.Add(new ScriptBundle("~/bundles/demo").Include(
              "~/Scripts/angular.js",
              "~/Scripts/angular-resource.js",
              "~/Scripts/angular-animate.js",
              "~/Scripts/angular-sanitize.js",
              "~/Scripts/angular-ui/ui-bootstrap-tpls.js"
            ))

bundles.Add(new StyleBundle("~/content/css").Include(
            "~/Content/bootstrap.css",
            "~/Content/ui-bootstrap-csp.css"
          ))

8) To register the files that you have configured to bundle, you can either create a startup class or Global.asax file and use either 1 of them to register the bundle. In the following code snippet, Global.asax is used.

[C#]
protected void Application_Start(object sender, EventArgs e)
{
    BundleConfig.RegisterBundles(BundleTable.Bundles);
}

[VB]
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
    BundleConfig.RegisterBundles(BundleTable.Bundles)
End Sub

9) For your web page to request for the bundled files. Place the following code into your .cshtml (C#) or .vbhtml (VB) file. The string value passed in are based on what you have defined in "BundleConfig.cs" file.

[Razor]
@Scripts.Render("~/bundles/demo")
@Styles.Render("~/Content/css")

10) For the bundling and minification to take effect, run your application in release mode or execute the following code in "RegisterBundles" method.

[C#]
BundleTable.EnableOptimizations = true;

[VB]
BundleTable.EnableOptimizations = true

When you run your application in debug mode, you will noticed through Internet Explorer's Network tab that on page load, it will attempt to send request for every javascript file needed by this web page. Whereas if you run your application in release mode, only 1 request is send for javascript and 1 request is send for css. Total estimated packet size is 1.72MB in debug mode and 430,78KB in release mode. With fewer requests made to the server and smaller packet size, page loading time improves.

Internet Explorer - Network Tab - Release Mode
Run in release mode.

Internet Explorer - Network Tab - Debug Mode
Run in debug mode.
Note: If and only if you noticed that after you published your project but the javascript or css file did not minified properly, Remove all javascript file that ends with min.js and css files that ends with .min.css from your published folder.

You may refer to the demo application here https://1drv.ms/u/s!Aj2AA7hoIWHmgm17PymRScvyhCAB




Send and Download File with WCF Stream in C# & VB

Ever faced a situation where you need to send or receive file through WCF but somehow the file gets corrupted when downloading through byte array or when sending large file in byte array causing your application to used up very high memory. To solve this, you may want to consider implementing WCF Stream. You may refer to the following link for more detail about WCF Stream https://msdn.microsoft.com/en-us/library/ms751463(v=vs.110).aspx.

To get started, implement services and contracts for sending and receiving file in stream through WCF. For the service to send or return more than 1 parameter together with Stream class, you need to implement MessageContract instead of DataContract. For more detail about MessageContract, refer to this link https://msdn.microsoft.com/en-us/library/ms730255(v=vs.110).aspx.

[C#]
[MessageContract]
public class DownloadFileResponse
{
    [MessageHeader]
    public string FileName { get; set; }

    [MessageBodyMember]
    public Stream Stream { get; set; }
}

[MessageContract]
public class SaveFileRequest
{
    [MessageHeader]
    public string FileName { get; set; }

    [MessageBodyMember]
    public Stream Stream { get; set; }

}

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class FileService : IFileService
{
    public void SaveFile(SaveFileRequest saveFileRequest)
    {
        // Do your stuff
    }

    public DownloadFileResponse DownloadFile()
    {
        // Do your stuff
    }
}

[ServiceContract]
public interface IFileService
{
    [OperationContract]
    void SaveFile(SaveFileRequest saveFileRequest);

    [OperationContract]
    DownloadFileResponse DownloadFile();
}

[VB]
<MessageContract>
Public Class DownloadFileResponse
    <MessageHeader>
    Private _fileName As String
    Public Property FileName() As String
        Get
            Return _fileName
        End Get
        Set(ByVal value As String)
            _fileName = value
        End Set
    End Property

    <MessageBodyMember>
    Private _stream As Stream
    Public Property Stream() As Stream
        Get
            Return _stream
        End Get
        Set(ByVal value As Stream)
            _stream = value
        End Set
    End Property
End Class

<MessageContract>
Public Class SaveFileRequest
    <MessageHeader>
    Private _fileName As String
    Public Property FileName() As String
        Get
            Return _fileName
        End Get
        Set(ByVal value As String)
            _fileName = value
        End Set
    End Property

    <MessageBodyMember>
    Private _stream As Stream
    Public Property Stream() As Stream
        Get
            Return _stream
        End Get
        Set(ByVal value As Stream)
            _stream = value
        End Set
    End Property
End Class

<ServiceBehavior(InstanceContextMode:=InstanceContextMode.PerCall, ConcurrencyMode:=ConcurrencyMode.Single)>
Public Class FileService
    Implements IFileService

    Public Sub SaveFile(ByVal saveFileRequest As SaveFileRequest) Implements IFileService.SaveFile
        ' Do your stuff
    End Sub

    Public Function DownloadFile() As DownloadFileResponse Implements IFileService.DownloadFile
        ' Do your stuff
    End Function
End Class

<ServiceContract>
Public Interface IFileService
    <OperationContract>
    Sub SaveFile(ByVal saveFileRequest As SaveFileRequest)

    <OperationContract>
    Function DownloadFile() As DownloadFileResponse
End Interface

Do take note that when retrieving the stream from a physical file through File class, make sure to close and dispose the File class after moved the content into a stream to prevent the file being locked by your application.

There are few things you can configure in web configuration file. To send or receive file in stream, change the attribute "transferMode" to "Streamed" in binding element. By default, "transferMode" is "buffered", what this mean is WCF will hold the content in memory until the file is transferred, in which will lead to very high memory usage if transferring a large file. For more details about "transferMode" attribute, you may refer here https://msdn.microsoft.com/en-us/library/system.servicemodel.transfermode(v=vs.110).aspx

The second attribute that you can configured is "maxReceivedMessageSize". This attribute is for your application to receive large file send from another application through WCF. Refer here https://msdn.microsoft.com/en-us/library/ms731361(v=vs.110).aspx if you want to know what else can you configured in binding element.

[web.config]
<bindings>
  <basicHttpBinding>
    <binding name="basicHttp"
             transferMode="Streamed"
             maxReceivedMessageSize="2147483647"></binding>
  </basicHttpBinding>
</bindings>

Make sure to assign the binding that you have configured to your service, otherwise it will not take effect. To assign the binding, set your binding name to the attribute "bindingConfiguration" under endpoint element.

[web.config]
<services>
  <service name="WCFSDemoVB.Services.FileService"
           behaviorConfiguration="DefaultServiceBehavior">
    <endpoint name="basicHttpFileService"
              address=""
              binding="basicHttpBinding"
              bindingConfiguration="basicHttp"
              contract="WCFSDemoVB.Services.Contracts.IFileService" />
    <endpoint address="mex"
              binding="mexHttpBinding"
              contract="IMetadataExchange" />
  </service>
</services>

Once done, you can now create a client application to send or receive file with stream through the WCF service that you have implemented. 

The following samples are developed in layered based on http://serena-yeoh.blogspot.com/2014/03/layered-architecture-solution-guidance.html: C#: https://1drv.ms/u/s!Aj2AA7hoIWHmgmw_UVGt9PBYtaeR
VB: https://1drv.ms/u/s!Aj2AA7hoIWHmgmsNk_zKLGQj4YyB