It is always desirable to produce the smallest amount of client-code at any given time. That includes HTML, JavaScript and CSS files. The more client-code you produce, the longer it takes to download and render the web page. Let’s see how easy it is to reduce CSS files by 35 % at runtime, but first we have to set some rules for the feature.

  1. No changes to the original .css files must be made
  2. Must work on all .css files without exception
  3. No negative performance impact
  4. Use both the client's and the server's cache
  5. Update the cache when the .css files change 
  6. Simplest possible implementation

We are going to use a generic handler (.ashx) to reduce the .css files and serve them to the browser. The handler has to do 2 things. It must reduce the files and add them to the browsers cache. The method that reduces the file, removes all unnecessary whitespace and comments. The more whitespace and comments you use, the more it will reduce the file.

<%@ WebHandler Language="C#" Class="css" %>

 

using System;

using System.Web;

using System.IO;

using System.Text.RegularExpressions;

using System.Web.Caching;

 

public class css : IHttpHandler

{

 

  public void ProcessRequest(HttpContext context)

  {

    string file = context.Server.MapPath(context.Request.QueryString["path"]);   

    ReduceCSS(file, context);

    SetHeaders(file, context);

  }

 

  /// <summary>

  /// Removes all unwanted text from the CSS file,

  /// including comments and whitespace.

  /// </summary>

private void ReduceCSS(string file, HttpContext context)

{

  FileInfo fi = new FileInfo(file);

  // Make sure that it only accesses .css files

  if (!fi.Extension.Equals(".css", StringComparison.OrdinalIgnoreCase))

  {

    throw new System.Security.SecurityException("No access");

  }

 

  string body = fi.OpenText().ReadToEnd();

 

  body = body.Replace("  ", String.Empty);

  body = body.Replace(Environment.NewLine, String.Empty);

  body = body.Replace("\t", string.Empty);

  body = body.Replace(" {", "{");

  body = body.Replace(" :", ":");

  body = body.Replace(": ", ":");

  body = body.Replace(", ", ",");

  body = body.Replace("; ", ";");

  body = body.Replace(";}", "}");

  body = Regex.Replace(body, @"/\*[^\*]*\*+([^/\*]*\*+)*/", "$1");

  body = Regex.Replace(body, @"(?<=[>])\s{2,}(?=[<])|(?<=[>])\s{2,}(?=&nbsp;)|(?<=&ndsp;)\s{2,}(?=[<])", String.Empty);

 

  context.Response.Write(body);

}

>

>

>

 

  /// <summary>

  /// This will make the browser and server keep the output

  /// in its cache to eliminate negative performance impact.

  /// </summary>

  private void SetHeaders(string file, HttpContext context)

  {

    context.Response.ContentType = "text/css";  

    // Server-side caching

    context.Response.AddFileDependency(file);

    context.Response.Cache.VaryByParams["path"] = true;

    // Client-side caching

    context.Response.Cache.SetETagFromFileDependencies();

    context.Response.Cache.SetLastModifiedFromFileDependencies();

  }

>

>

>

 

  public bool IsReusable

  {

    get

    {

      return false;

    }

  }

 

}

Implementation

Download the css.ashx file below and add it to the root of your website. Then change the path to the stylesheet in the header of the web page to point to the css.ashx with the original .css file name as parameter:

<link rel="stylesheet" type="text/css" href="css.ashx?path=common.css" />

<link rel="stylesheet" type="text/css" href="css.ashx?path=folder/menu.css" />

Download

css.zip (0,82 KB)

Even though ViewState is unreadable by the human eye, it is nothing but a base64 encoded string that easily decodes by a piece of software like this one. With this knowledge in mind, you might want to look at how you can secure the ViewState to avoid tampering and maybe even identity theft in extreme cases.

Let’s take a look on how to secure the ViewState and at the same time, keeping it simple to implement.

Encryption

The first thing we need to do is to add encryption to the ViewState. This is done by editing the web.config only. We need to tell the web application to enable ViewStateMac. This will generate a hashed code and add it to the ViewState. If the hashcode is tampered with, any postback will fail. It is still not secure before we add encryption. That is also done in the web.config by telling the application to use the 3DES encryption algorithm. Add these two lines to the web.config’s <system.web> section:

<pages enableViewStateMac="true" />

<machineKey validation="3DES" />

The ViewState is now encrypted on an application wide basis, but it can still be compromised by a skilled hacker.

User key

To further enhance the security ASP.NET can add another level of encryption based on the actual user. If the user is authenticated by a logon form or similar authentication, you can use the username as the encryption key. Override the OnInit method of your web page or master page to add the username as key:

protected override void OnInit(EventArgs e)

{

  base.OnInit(e);

  if (User.Identity.IsAuthenticated)

  {

    base.ViewStateUserKey = User.Identity.Name;

  }

}

This works great if the user is authenticated by the server, but what about web sites where users don’t sign in, but still requires strong security like a webshop? You cannot make it as secure without the username of the authenticated user, but you can do something else to identify the user.

You can generate a key based on the IP-address, user-agent string and registered browser languages. It is not 100% tamper-proof but it will make it much harder to break the encryption. Here’s a method that generates a somewhat unique user key.

private string GenerateUserKey()

{

  System.Text.StringBuilder sb = new System.Text.StringBuilder();

  sb.Append(Request.UserHostAddress);

  sb.Append(Request.UserAgent);

 

  if (Request.UserLanguages != null)

  {

    foreach (string language in Request.UserLanguages)

    {

      sb.Append(language);

    }

  }

 

  return sb.ToString().GetHashCode().ToString();

}

This method creates one big string with information about the user’s browser environment and then returns the hashcode of that string. Now we just need to add it to the OnInit method like so:

protected override void OnInit(EventArgs e)

{

  base.OnInit(e);

  base.ViewStateUserKey = GenerateUserKey();

}

For this to be compromised, the hacker needs to know the users IP-address, user-agent string and what languages is registered in the browser. The method could also be extended to include more unique stuff from the session, cookies, cache or anything else you can use to identify the user. That’s up to you.