BradVin's .Net Blog

Code, snippets, controls, utils, etc. Basically all things .net

The goal of this post : reduce the total size of a simple asp.net MVC page response.
Our measuring tools : Firefox running Firebug and the YSlow plugin
Source Code : Download here

Lets use a really simple and common scenario as the example. The steps to create this really simple example are:

  1. Create a new MVC project in visual studio.
  2. Dump some useful script files into the scripts folder and delete the others we dont need.
  3. Create a new .css file in the Content folder and cut half the css out of the site.css and paste it into this new file. (this is just to create more than 1 css file)
  4. Open the site.master file and add references to the new files in the head.

Why are we doing this? Well this is what you would most likely do when adding functionality to your new MVC site. You would add a few references to some scripts you will use in the views, and you would more than likely add some references to more css files. (I personally hate combining css into one large file, and instead prefer to break it up into logical files)

The resulting site.master would look something like this:

<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
<link href="../../Content/Site.css" rel="stylesheet" type="text/css" />
<link href="../../Content/Layout.css" rel="stylesheet" type="text/css" />
<script src="../../Scripts/jquery-1.3.2.js" type="text/javascript"></script>
<script src="../../Scripts/jquery.utils.js" type="text/javascript"></script>
<script src="../../Scripts/jqDnR.js" type="text/javascript"></script>
</head>
<body>
<!- extracted -->
</body>
</html>

Let's compile and run the MVC site. Open it in FireFox. Turn on firebug and see what u get. I got this Net breakdown:

Note at this stage the number of requests (6) and the total download size (135KB) and it took 4.11 seconds. Also notice that when you refresh the page, ALL the page components are downloaded AGAIN. Nothing is cached. This is not good. Now the YSlow breakdown :

 See our YSLOW score is 73!! WOW thats bad. You can expand each section to see exactly what you can do to improve the performance. Here it is expanded :

Now we are going to work through the page to improve our YSlow score and our overall download size. In this post I am going to go straight into the solution. I will use future posts to explain the code behind the changes and the reasons why I did what I did. I am also going to be using both code I have found on the web and some code I have written myself (with some ideas taken from the web). Again, all my code will be explained in future posts. This post is just an intro while giving a solution. (cause I hate blog series where you have to wait untill the last one to get the solution, especially seeing that I write about 1 post every 6 months - i promise i will try harder!)

I have put all the source together into my own Utils components. I have spoken about these a while ago and they have changed dramatically since then. There are 4 files you need to reference from the web project :

  1. Unity - Dependency injection framework used by the utils project
  2. Utils - common utils and helper classes incl. encryption wrappers, collections, string utils, a number of extension methods, etc etc
  3. Utils.Web - common web utils and helpers not specific to either webforms or MVC incl querystring helpers and a URL helper to name a few
  4. Utils.Web.MVC - common MVC specific classes and utils.

Please note that the source for the utils in this post has changed alot since the original blog posts, and someday I will write some more blog posts on the more interesting and useful classes inside.

Firstly, lets start with the easy one : gzip and cache the page response. I found some really good code at Kazi Manzur Rashid's blog. In his post he gives the solution to both caching and compressing the page response, by utilising 2 action flters. Just change the home controller in our project to the following :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Utils.Web.MVC.Filters;
namespace MvcApplication2.Controllers
{
[HandleError]
[CacheFilter]
[CompressFilter]
public class HomeController : Controller
{
public ActionResult Index()
{
ViewData["Message"] = "Welcome to ASP.NET MVC!";
return View();
}
public ActionResult About()
{
return View();
}
}
}

Our YSLOW score is now 75 and our total download size is 1K less. So we are getting there, but slowly...

Next, we need to do something about the CSS. We are now going to combine, gzip and cache the stylesheets. Follow these steps:

  1. Add a namespace reference in the web.config :
    <add namespace="Utils.Web.MVC"/>
  2. Add a httphandler to the web.config :
    <add verb="*" path="css.axd" type="Utils.Web.HttpHandlers.CSSHandler, Utils.Web" validate="false"/>
  3. Change the site.master. Cut out the old references to the stylesheets and replace with this in the head:
        <% Html.CSS().Add("~/Content/Site.css"); %>
    <% Html.CSS().Add("~/Content/Layout.css"); %>
    <%= Html.CSS().HTML %>

Cool. With a few changes to the HTML, we get the same response but with a few improvements:

  1. Our YSLOW score has jumped up to 83!
  2. We now have less requests (5) and our total file size is down to 130KB.
Finally, lets do something about the scripts. We are going to combine, gzip and cache the script files. Follow these steps:
  1. Add the httphandler to the web.config :
    <add verb="*" path="js.axd" type="Utils.Web.HttpHandlers.JSHandler, Utils.Web" validate="false"/>
  2. Change the site.master. Cut out the old script references from the head and at the bottom of the file just before the closing of the body tag, add the following code:
        <% Html.Scripts().Add("~/Scripts/jquery-1.3.2.js"); %>
    <% Html.Scripts().Add("~/Scripts/jquery.utils.js"); %>
    <% Html.Scripts().Add("~/Scripts/jqDnR.js"); %>
    <%= Html.Scripts().HTML %>

 Now what has changed? Well some major changes have occurred in the HTML output which has resulted in the following:

  1. Our YSLOW score is a whopping 98!!! WOW!! Thats more like it. The only thing we are not getting an A score for is the Content Delivery Network section.
  2. But more importantly, our number of requests has dropped to 3 and the total size is only 27KB!
  3. Our total transfer time is now only 2.08 seconds!

The HTML output is now the following:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>

Home Page

</title><link rel="stylesheet" type="text/css" href="/css.axd?f=%2fContent%2fSite.css%3fm%3d1%2c%2fContent%2fLayout.css%3fm%3d1&d=365" media="screen" /></head>

<body>
<div class="page">
<!-- extracted for example -->
</div>
<script type="text/javascript" src="/js.axd?f=%2fScripts%2fjquery-1.3.2.js%3fm%3d1%2c%2fScripts%2fjquery.utils.js%3fm%3d1%2c%2fScripts%2fjqDnR.js%3fm%3d1&d=365"></script>
</body>
</html>

The resulting firebug Net screenshot is:

And here is the YSlow screenshot:

Conclusion:

With very few (and simple) changes there has been a massive improvement:

  1. 6 requests down to 3
  2. 135K size down to 27K
  3. YSlow score from 73 up to 98
  4. Download time from 4.11s down to 2.08s

I also feel the changes have not been too hard to make and they still leave the view code in a very readable state (this is important in MVC). In my follow-on posts I want to go through the CSS and JS handlers that combine,gzip and cache the files. I will go through how your views and even your partial views can 'tell' the page what scripts or css to include and combine, and eliminate duplicates if necessary. No longer does the master page have to know about all the scripts that will be used within the whole site up front.

Download the source. (Check out the Test.aspx view and PartialTest.ascx partial view to see a more realistic example)

Posted by bradvin | 6 comment(s)
Filed under: , , , ,

 A while ago when I was learning LINQ I searched the web for a 'cheat sheet'. I needed a single document that covered most of the common scenarios w.r.t LINQ so that I would not need to google every time I was writing LINQ queries. I found one very good one : http://aspnetresources.com/blog/linq_sqo__cheat_sheet.aspx . But I wanted more (as usual) so I created my own. I was also new to lambda expressions and I wanted to learn the differences between syntax in a LINQ query written in 'query syntax' and 'lambda syntax' so I also included that as part of my cheat sheet. Every query is given in both syntaxes. Here is a screenshot:

Download the LINQ CHEAT SHEET

Please let me know what you think. Did it help you? Where could it be improved? What would you add?

You can also download the code that I used when creating the document. It also includes basic testing to make sure the queries of both syntaxes returned the same results. Beware as it may contain some bugs ;)

UPDATE (3 Nov 2008) :  I updated the cheat sheet document. Changes made:

  • made the left and right margins smaller
  • adjusted column widths to be equal
  • added new example under 'Ordering' section (thanks to RichardD - see comments)
  • added new example under 'Element Operators' section and updated notes (thanks to RichardD - see comments)

 

Posted by bradvin | 24 comment(s)
Filed under: , ,

A friend of mine found an excellent resource that I just had to share. It contains code snippets of examples of all the new features within C# 3.0. They are all taken from the book 'C# 3.0 in a Nutshell'. It looks like an excellent book for anyone wanting to learn the new features.

link : http://www.albahari.com/nutshell/code.aspx

An overview of what the book contains :


 

Posted by bradvin | with no comments
Filed under: ,

If you are a web developer you would definately have worked with the querystring. Most of the time you are just getting values from the querystring or adding querystring values to url's, but in some cases the querystring can really become a hassle to work with. A simple example of this can be seen in the following scenario :

I have a control that contains a list of data displayed in a table. The current URL has no querystring by default, and in that case the first item in the grid is selected. Each row in the table contains a link to the same page, but a querystring is used to determine the selected row. On the itemdatabound event of the repeater I was setting the URL of a hyperlink on each row. Now I know this is a really simple example and using the itemdatabound event is overkill, but it's only an example ;) Now when setting the URL, I have to worry about if the same querystring value already exists in the current URL and if so replace it, else add it. (I hope you see where I'm going here.)

Introducing the Querystring builder class. The class can be easily used to build up querystrings (from scratch, the existing URL or any string in fact). I also made it chainable (like the stringbuilder class). You can also encrypt and decrypt the querystring with ease (it uses my Cryptography classes). Here are a few examples of it's use:

//create a querystring from the current URL, add 'id','user' and 'sessionId' values and remove an 'action' value
//output : "?id=123&user=brad&sessionId=ABC"
string strQuery = QueryString.Current.Add("id", "123").Add("user", "brad").Add("sessionId", "ABC").Remove("action").ToString();

//take an existing string and replace the 'id' value if it exists (which it does)
//output : "?id=5678&user=tony"
strQuery = new QueryString("id=1234&user=tony").Add("id", "5678", true).ToString();

//create a querystring from the current URL, add an 'id' value and encrypt the result
//output : "?DhSbRo10vxUjYC5ChMXO5Q%3d%3d=dkxaLXpSg6aeM71fhHJ4ZQ%3d%3d"
strQuery = QueryString.Current.Add("id", "123").Encrypt("my key").ToString();

//takes a previous querystring value, decrypts it using the same key and gets the 'id' value
//output : "123"
strQuery = new QueryString(strQuery).Decrypt("my key")["id"];

Now obviously you cannot edit the current querystring, but the class is useful when manipulating the current querystring for other links or URL's. Here is the full code :

using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Collections.Specialized;

namespace Utils.Web
{
/// <summary>
/// A chainable query string helper class.
/// Example usage :
/// string strQuery = QueryString.Current.Add("id", "179").ToString();
/// string strQuery = new QueryString().Add("id", "179").ToString();
/// </summary>
public class QueryString : NameValueCollection
{
public QueryString() { }

public QueryString(string queryString)
{
FillFromString(queryString);
}

public static QueryString Current
{
get
{
return new QueryString().FromCurrent();
}
}

/// <summary>
/// extracts a querystring from a full URL
/// </summary>
/// <param name="s">the string to extract the querystring from</param>
/// <returns>a string representing only the querystring</returns>
public string ExtractQuerystring(string s)
{
if (!string.IsNullOrEmpty(s))
{
if (s.Contains("?"))
return s.Substring(s.IndexOf("?") + 1);
}
return s;
}

/// <summary>
/// returns a querystring object based on a string
/// </summary>
/// <param name="s">the string to parse</param>
/// <returns>the QueryString object </returns>
public QueryString FillFromString(string s)
{
base.Clear();
if (string.IsNullOrEmpty(s)) return this;
foreach (string keyValuePair in ExtractQuerystring(s).Split('&'))
{
if (string.IsNullOrEmpty(keyValuePair)) continue;
string[] split = keyValuePair.Split('=');
base.Add(split[0],
split.Length == 2 ? split[1] : "");
}
return this;
}

/// <summary>
/// returns a QueryString object based on the current querystring of the request
/// </summary>
/// <returns>the QueryString object </returns>
public QueryString FromCurrent()
{
if (HttpContext.Current != null)
{
return FillFromString(HttpContext.Current.Request.QueryString.ToString());
}
base.Clear();
return this;
}

/// <summary>
/// add a name value pair to the collection
/// </summary>
/// <param name="name">the name</param>
/// <param name="value">the value associated to the name</param>
/// <returns>the QueryString object </returns>
public new QueryString Add(string name, string value)
{
return Add(name, value, false);
}

/// <summary>
/// adds a name value pair to the collection
/// </summary>
/// <param name="name">the name</param>
/// <param name="value">the value associated to the name</param>
/// <param name="isUnique">true if the name is unique within the querystring. This allows us to override existing values</param>
/// <returns>the QueryString object </returns>
public QueryString Add(string name, string value, bool isUnique)
{
string existingValue = base[name];
if (string.IsNullOrEmpty(existingValue))
base.Add(name, HttpUtility.UrlEncodeUnicode(value));
else if (isUnique)
base[name] = HttpUtility.UrlEncodeUnicode(value);
else
base[name] += "," + HttpUtility.UrlEncodeUnicode(value);
return this;
}

/// <summary>
/// removes a name value pair from the querystring collection
/// </summary>
/// <param name="name">name of the querystring value to remove</param>
/// <returns>the QueryString object</returns>
public new QueryString Remove(string name)
{
string existingValue = base[name];
if (!string.IsNullOrEmpty(existingValue))
base.Remove(name);
return this;
}

/// <summary>
/// clears the collection
/// </summary>
/// <returns>the QueryString object </returns>
public QueryString Reset()
{
base.Clear();
return this;
}

/// <summary>
/// Encrypts the keys and values of the entire querystring acc. to a key you specify
/// </summary>
/// <param name="key">the key to use in the encryption</param>
/// <returns>an encrypted querystring object</returns>
public QueryString Encrypt(string key)
{
QueryString qs = new QueryString();
Utils.Cryptography.Encryption enc = new Utils.Cryptography.Encryption();
enc.Password = key;
for (var i = 0; i < base.Keys.Count; i++)
{
if (!string.IsNullOrEmpty(base.Keys[i]))
{
foreach (string val in base[base.Keys[i]].Split(','))
qs.Add(enc.Encrypt(base.Keys[i]), enc.Encrypt(HttpUtility.UrlDecode(val)));
}
}
return qs;
}

/// <summary>
/// Decrypts the keys and values of the entire querystring acc. to a key you specify
/// </summary>
/// <param name="key">the key to use in the decryption</param>
/// <returns>a decrypted querystring object</returns>
public QueryString Decrypt(string key)
{
QueryString qs = new QueryString();
Utils.Cryptography.Encryption enc = new Utils.Cryptography.Encryption();
enc.Password = key;
for (var i = 0; i < base.Keys.Count; i++)
{
if (!string.IsNullOrEmpty(base.Keys[i]))
{
foreach (string val in base[base.Keys[i]].Split(','))
qs.Add(enc.Decrypt(HttpUtility.UrlDecode(base.Keys[i])), enc.Decrypt(HttpUtility.UrlDecode(val)));
}
}
return qs;
}

/// <summary>
/// overrides the default
/// </summary>
/// <param name="name"></param>
/// <returns>the associated decoded value for the specified name</returns>
public new string this[string name]
{
get
{
return HttpUtility.UrlDecode(base[name]);
}
}

/// <summary>
/// overrides the default indexer
/// </summary>
/// <param name="index"></param>
/// <returns>the associated decoded value for the specified index</returns>
public new string this[int index]
{
get
{
return HttpUtility.UrlDecode(base[index]);
}
}

/// <summary>
/// checks if a name already exists within the query string collection
/// </summary>
/// <param name="name">the name to check</param>
/// <returns>a boolean if the name exists</returns>
public bool Contains(string name)
{
string existingValue = base[name];
return !string.IsNullOrEmpty(existingValue);
}

/// <summary>
/// outputs the querystring object to a string
/// </summary>
/// <returns>the encoded querystring as it would appear in a browser</returns>
public override string ToString()
{
StringBuilder builder = new StringBuilder();
for (var i = 0; i < base.Keys.Count; i++)
{
if (!string.IsNullOrEmpty(base.Keys[i]))
{
foreach (string val in base[base.Keys[i]].Split(','))
builder.Append((builder.Length == 0) ? "?" : "&").Append(HttpUtility.UrlEncodeUnicode(base.Keys[i])).Append("=").Append(val);
}
}
return builder.ToString();
}
}
}
Posted by bradvin | 10 comment(s)
Filed under: , ,

UPDATE : James Hart has an excellent post about jQuery 1.3 intellisense - you must check it out!

I know, I know, this has been done before. The reason I'm writing this post, however, is to improve on what I have found on the web. For anyone wondering what jQuery is, goto the jQuery site and also read this tutorial. Now onto the good stuff....

If you are like me and you've read the many articles about how to get other javascript libraries to work in VS2008, you'll know that all you really need to do is install the visual studio HOTFIX. This patches your VS and among other things, gets the javascript intellisense working nicely.

Before the hotfix:


After the hotfix:

You'll notice you can now use the jQuery $ shortcut function.

At this point I was delighted. and then I typed $(document). and I got no more intellisense, why???!!! After looking around some more, I found an article on the topic written by Rick Strahl : http://www.west-wind.com/WebLog/posts/251271.aspx. It helped explain things quite a bit and I recommend you read it. Basically, if you use the 'new' keyword in declaring jQuery objects, you get intellisense, so the following works nicely and you get intellisense:

var doc = new jQuery(document);
doc.ready(function() {
var div = new jQuery("#someDiv");
div.html("some text here!");
div.fadeOut(5000);
});

But I still wasn't satisfied. Why should I change the way I write my jQuery just so VS understands it? Yes I get intellisense, but I'm writing more code, and it's unnecessary code at that. Remember : jQuery is the "write less, do more, JavaScript library".

Then onto another point: what about one of the most powerful aspects of the jQuery architecture - the chainability? The following code

$("a").addClass("test").show().html("foo");
doesnt give you any intellisense whatsoever. In order to get intellisense, it has to be written as follows:
var a = new jQuery("a");
a.addClass("test");
a.show();
a.html("foo");

So intellisense for jQuery at this point is actually quite useless I thought. Because in order to use it, we have to write double the amount of code, and jQuery's chainability power isnt being utilised at all. :(
Another bummer was the fact that the method info for the jQuery functions was seriously lacking. Obviously this is because the jQuery library has been written to be as small as possible. But it would be nice if the functions were explained together with the paramaters (exactly like we are used to when writing c# code).

so back to google and back to searching for alternatives...
This is when I found this gem of an article written by James Hart : http://blogs.ipona.com/james/archive/2008/02/15/JQuery-IntelliSense-in-Visual-Studio-2008.aspx .  And it literally solved all my problems. Here is what the method info was before:

And this is how it looks afterwards:

Big improvement! And here is the intellisense in action (using chainability):

Awesome!!!! But how does it work? Basically, a new javascript is generated and referenced from the page. This new file is just a list of methods with no functionality and it 'tricks' VS into producing nice intellisense. It also uses XML documentation to provide useful parameter listings and return types. (You can read more about javascript documentation comments here: http://weblogs.asp.net/bleroy/archive/2007/04/23/the-format-for-javascript-doc-comments.aspx ). For ASPX pages, all you need to do is include the script inside an invisible placeholder :

<script type="text/javascript" src="jQuery.js" ></script>
<asp:PlaceHolder runat="server" visible="false">
<script type="text/javascript" src="JQuery.Intellisense.js" ></script>
</asp:PlaceHolder>
This is to stop the file from actually being included in the rendered page. Note that it must also be after the reference to the 'real' jQuery file.
And in other external .JS files that use jQuery code, add the following to the top of the file:
/// <reference path="jquery.intellisense.js"/>

So now I can write my jQuery code as I did before and I have excellent method information and intellisense. This works fine in external javascript files, plain ASPX pages, master pages, content pages and user controls.

So are there still some issues? Yes there are. Firstly the generated file is using an outdated jQuery version 1.2.1 and even more outdated jQuery documentation version 1.1.2. This is by no means the fault of the author, but rather the jQuery team who have not updated the XML documentation. Secondly, certain functions in jQuery have optional parameters and return different types based on those parameters e.g. the html() function. These functions unfortunately don't give me intellisense because the generator gives them a return type of "Object" in the XML comments. No problem, just edit the generated file and change it where it has <returns type="Object"></returns> to <returns type="jQuery"></returns>
A list of the functions I found that need changing are :

  • css
  • html
  • attr
  • text
  • val
  • height
  • width

Ok, so stricly speaking, it's not correct, because these functions will return a non jQuery object when nothing is passed in, but what the hell. I still get more intellisense than before. And besides, you must at least have some idea of what the functions do, and not rely on intellisense exclusively to write code ;)

You can download my JQuery.Intellisense file with the changes already made. It is using API version: 1.2.3 and Documentation version: 1.2.2.

jQuery Intellisense In 3 Easy Steps

  1. Install VS2008 hotfix
  2. Generate a JQuery.Intellisense.js stub file. Either goto http://www.infobasis.com/sandpit/jQuery-Intellisense/ and generate it, or download a more up to date one (including minor fixes) from here. save it to the same folder as your jQuery file
  3. Add a invisible placeholder into your header and reference the new .js file:
        <asp:PlaceHolder runat="server" visible="false">
            <script type="text/javascript" src="JQuery.Intellisense.js" ></script>
        </asp:PlaceHolder>
    or add a referenece at the top of your .js file
    /// <reference path="jquery.intellisense.js"/>

Download the whole website: JqueryIntellisenseWebsite.zip

Code samples:

simple jQuery page with full intellisense:

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>I have intellisense!</title>
<script type="text/javascript" src="jQuery.js" ></script>
<asp:PlaceHolder runat="server" visible="false">
<script type="text/javascript" src="JQuery.Intellisense.js" ></script>
</asp:PlaceHolder>

<script type="text/javascript">
$(function() {
$("#someDiv").hide().html("Some text here!!!").addClass("bold").fadeIn(1000).fadeOut(2000);
});
</script>

<style type="text/css">
.bold{ font-weight:bold; }
</style>
</head>
<body>
<form id="form1" runat="server">
<div id="someDiv">
</div>
</form>
</body>
</html>


simple master page :
<%@ Master Language="C#" AutoEventWireup="true" CodeFile="MasterPage.master.cs" Inherits="MasterPage" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Untitled Page</title>
<script type="text/javascript" src="jQuery.js" ></script>
<asp:PlaceHolder runat="server" visible="false">
<script type="text/javascript" src="JQuery.Intellisense.js" ></script>
</asp:PlaceHolder>
<asp:ContentPlaceHolder id="head" runat="server">
</asp:ContentPlaceHolder>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:ContentPlaceHolder id="ContentPlaceHolder1" runat="server">
</asp:ContentPlaceHolder>
</div>
</form>
</body>
</html>


simple content page :
<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" CodeFile="Default3.aspx.cs" Inherits="Default3" Title="Untitled 

Page" %>


<asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server">
<script type="text/javascript">
$(function() {
$("#someDiv").addClass("bold").fadeIn(2000).fadeOut(1000).html("gone");
});
</script>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
</asp:Content>


simple user control :
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="TestControl.ascx.cs" Inherits="TestControl" %>
<asp:PlaceHolder ID="PlaceHolder1" runat="server" visible="false">
<script type="text/javascript" src="JQuery.Intellisense.js" ></script>
</asp:PlaceHolder>
<script type="text/javascript">
$(function() {
$("#someDiv").hide().html("Some text here!!!").addClass("bold").fadeIn(1000).fadeOut(2000);
});
</script>
Posted by bradvin | 28 comment(s)
Filed under: , , ,

Have you ever wanted to send out meeting requests from your code? Well I wanted to do just that today. And I didnt want to reference the Outlook dll's. I simply wanted to send an email to an attendee's email address.

After much searching and alot of useless code I found a gem at http://chuckdotnet.blogspot.com. I took the code there and modified it slightly so that you can send the meeting request to multiple recipients (attendees) at once.

below is the method that creates the system.net.mailmessage object with all the necessary alternateviews, so that when opened in outlook it behaves exactly like a meeting request that was sent from within outlook. remember to add using System.Net.Mail;using System.Net.Mime; to the top of your code.


public static MailMessage CreateMeetingRequest(DateTime start, DateTime end, string subject, string summary,
string location, string organizerName, string organizerEmail, string attendeeName, string attendeeEmail)
{
MailAddressCollection col = new MailAddressCollection();
col.Add(new MailAddress(attendeeEmail, attendeeName));
return CreateMeetingRequest(start, end, subject, summary, location, organizerName, organizerEmail, col);
}

public static MailMessage CreateMeetingRequest(DateTime start, DateTime end, string subject, string summary,
string location, string organizerName, string organizerEmail, MailAddressCollection attendeeList)
{
MailMessage msg = new MailMessage();

// Set up the different mime types contained in the message
System.Net.Mime.ContentType textType = new System.Net.Mime.ContentType("text/plain");
System.Net.Mime.ContentType HTMLType = new System.Net.Mime.ContentType("text/html");
System.Net.Mime.ContentType calendarType = new System.Net.Mime.ContentType("text/calendar");

// Add parameters to the calendar header
calendarType.Parameters.Add("method", "REQUEST");
calendarType.Parameters.Add("name", "meeting.ics");

// Create message body parts
// create the Body in text format
string bodyText = "Type:Single Meeting\r\nOrganizer: {0}\r\nStart Time:{1}\r\nEnd Time:{2}\r\nTime Zone:{3}\r\nLocation: {4}\r\n\r\n*~*~*~*~*~*~*~*~*~*\r\n\r\n{5}";
bodyText = string.Format(bodyText,
organizerName,
start.ToLongDateString() + " " + start.ToLongTimeString(),
end.ToLongDateString() + " " + end.ToLongTimeString(),
System.TimeZone.CurrentTimeZone.StandardName,
location,
summary);

AlternateView textView = AlternateView.CreateAlternateViewFromString(bodyText, textType);
msg.AlternateViews.Add(textView);

//create the Body in HTML format
string bodyHTML = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2//EN\">\r\n<HTML>\r\n<HEAD>\r\n<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=utf-8\">\r\n<META NAME=\"Generator\" CONTENT=\"MS Exchange Server version 6.5.7652.24\">\r\n<TITLE>{0}</TITLE>\r\n</HEAD>\r\n<BODY>\r\n<!-- Converted from text/plain format -->\r\n<P><FONT SIZE=2>Type:Single Meeting<BR>\r\nOrganizer:{1}<BR>\r\nStart Time:{2}<BR>\r\nEnd Time:{3}<BR>\r\nTime Zone:{4}<BR>\r\nLocation:{5}<BR>\r\n<BR>\r\n*~*~*~*~*~*~*~*~*~*<BR>\r\n<BR>\r\n{6}<BR>\r\n</FONT>\r\n</P>\r\n\r\n</BODY>\r\n</HTML>";
bodyHTML = string.Format(bodyHTML,
summary,
organizerName,
start.ToLongDateString() + " " + start.ToLongTimeString(),
end.ToLongDateString() + " " + end.ToLongTimeString(),
System.TimeZone.CurrentTimeZone.StandardName,
location,
summary);

AlternateView HTMLView = AlternateView.CreateAlternateViewFromString(bodyHTML, HTMLType);
msg.AlternateViews.Add(HTMLView);

//create the Body in VCALENDAR format
string calDateFormat = "yyyyMMddTHHmmssZ";
string bodyCalendar = "BEGIN:VCALENDAR\r\nMETHOD:REQUEST\r\nPRODID:Microsoft CDO for Microsoft Exchange\r\nVERSION:2.0\r\nBEGIN:VTIMEZONE\r\nTZID:(GMT-06.00) Central Time (US & Canada)\r\nX-MICROSOFT-CDO-TZID:11\r\nBEGIN:STANDARD\r\nDTSTART:16010101T020000\r\nTZOFFSETFROM:-0500\r\nTZOFFSETTO:-0600\r\nRRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=11;BYDAY=1SU\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nDTSTART:16010101T020000\r\nTZOFFSETFROM:-0600\r\nTZOFFSETTO:-0500\r\nRRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=2SU\r\nEND:DAYLIGHT\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nDTSTAMP:{8}\r\nDTSTART:{0}\r\nSUMMARY:{7}\r\nUID:{5}\r\nATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=\"{9}\":MAILTO:{9}\r\nACTION;RSVP=TRUE;CN=\"{4}\":MAILTO:{4}\r\nORGANIZER;CN=\"{3}\":mailto:{4}\r\nLOCATION:{2}\r\nDTEND:{1}\r\nDESCRIPTION:{7}\\N\r\nSEQUENCE:1\r\nPRIORITY:5\r\nCLASS:\r\nCREATED:{8}\r\nLAST-MODIFIED:{8}\r\nSTATUS:CONFIRMED\r\nTRANSP:OPAQUE\r\nX-MICROSOFT-CDO-BUSYSTATUS:BUSY\r\nX-MICROSOFT-CDO-INSTTYPE:0\r\nX-MICROSOFT-CDO-INTENDEDSTATUS:BUSY\r\nX-MICROSOFT-CDO-ALLDAYEVENT:FALSE\r\nX-MICROSOFT-CDO-IMPORTANCE:1\r\nX-MICROSOFT-CDO-OWNERAPPTID:-1\r\nX-MICROSOFT-CDO-ATTENDEE-CRITICAL-CHANGE:{8}\r\nX-MICROSOFT-CDO-OWNER-CRITICAL-CHANGE:{8}\r\nBEGIN:VALARM\r\nACTION:DISPLAY\r\nDESCRIPTION:REMINDER\r\nTRIGGER;RELATED=START:-PT00H15M00S\r\nEND:VALARM\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n";
bodyCalendar = string.Format(bodyCalendar,
start.ToUniversalTime().ToString(calDateFormat),
end.ToUniversalTime().ToString(calDateFormat),
location,
organizerName,
organizerEmail,
Guid.NewGuid().ToString("B"),
summary,
subject,
DateTime.Now.ToUniversalTime().ToString(calDateFormat),
attendeeList.ToString());

AlternateView calendarView = AlternateView.CreateAlternateViewFromString(bodyCalendar, calendarType);
calendarView.TransferEncoding = TransferEncoding.SevenBit;
msg.AlternateViews.Add(calendarView);

// Adress the message
msg.From = new MailAddress(organizerEmail);
foreach (MailAddress attendee in attendeeList)
{
msg.To.Add(attendee);
}
msg.Subject = subject;

return msg;
}
Posted by bradvin | 23 comment(s)
Filed under: ,

ÜberUtils Series posts so far :

**** Please note that this code is using .NET 3.5 ****

Now onto the post - everyone I'm sure has used the system.configuration namespace before in a project. If you haven't then shame on you. If you have, then you know it should be used for storing things like :

  • admin email address
  • connection strings to a database
  • log file path
  • Tax amounts

and so on. I am a strong believer in NEVER hard code a setting. There is just no reason to do so. What happens if a setting changes down the line? Lets use a webmaster email as an example. You would have to locate wherever you use the email address (probably more than one place) and edit it. Then recompile and redeploy your changes. What a mission! Instead, just store it in the appsettings section of the .config file and edit the setting there whenever you need to.

I really do like using the appsettings and connectionstring sections in the .config files, but I think there is room for improvement. Wouldn't it be nice if appsettings was strongly typed for types other than string? Indeed it would. Here's an example: you store a "Testing" appsetting that determines if your website is in testing mode. If it is in testing mode, then you send all emails to Admin instead of sending emails to the actual users. Now every time you want to check if the site is in testing mode you have to convert the appsetting to a boolean first. Similarly with a Tax appsetting. You have to convert it to decimal every time before you can use it.

This then follows directly onto my next question : Wouldn't it be nice if appsettings and connectionstrings had default values? Yes again. Let's say the "testing" appsetting we spoke about earlier wasn't in the .config file. We would then want it to default to true. Maybe our tax appsetting should default to 0.14 and maybe we always use the same default connectionstring in our data access layer. Defaults would be great.

I address these two shortfalls with Utils.Configuration. Lets look at some examples:

bool bTesting = Utils.Configuration.ConfigurationManager.AppSettings["Testing", true];
this gets the testing appsetting with a deafult of true (if we forgot to add the appsetting into the .config file). Notice too how it's strongly typed to be boolean. You could've also done it this way :
bool bTesting = Utils.Configuration.ConfigurationManager.AppSettings.GetValue("Testing", true);

This goes the same for the other types : Decimals, Ints, Doubles, Datetimes and obviously strings.

decimal nTax = Utils.Configuration.ConfigurationManager.AppSettings["Tax", 0.14M];
The same applies for connectionstrings, except the output is obviously always a string. You can also check if a connection string exists so you can provide better error handling :
if (ConfigurationManager.ConnectionStrings.Exists("TestConn2"))
{
return ConfigurationManager.ConnectionStrings["TestConn2", "Driver={SQL Server};Server=myServerAddress;Database=myDataBase;Uid=myUsername;Pwd=myPassword;"];
}
download the assembly with unit tests here
Posted by bradvin | 3 comment(s)
Filed under: , ,

 ÜberUtils Series posts so far :

I figured it was about time I did another post in the UberUtils series seeing that I haven't written a post in over a month. So here is the next installment. This time it's for collections. I hear you asking "but the framework has every collection I will ever need", and this is true for 98% of the time. But every once in a while there comes a time where I find myself subtly changing the way an existing collection works, or wishing it could do something just slightly differently. As with any software development, there are many different ways to solve the same problem, so you probably find yourself getting around limitations with the built-in collections without even knowing it. (I know I have when I go back and look at code I've written).

The number one question I have found myself asking over the years with regards to the collection namespace is:

Is there a dictionary whereby I can access the values from both the key and an index?

The answer to this is NO. And if I'm wrong about this then please shoot me now for wasting so much time over the years on this code. You can overcome this limitation by using the other collections at your disposal, and with not so much code either. The thing is, I hate writing the same code over and over again (I actually refuse to do it), so why not write a generic dictionary that can access the values by index? And then just use that dictionary instead of the normal dictionary in the future.

Introducing the IndexedDictionary, an indexable dictionary. Here is a unit test to show the indexing feature :

        [TestMethod()]
public void SomeTest()
{
IndexedDictionary<string, int> col = new IndexedDictionary<string,int>();
col.Add("1", 1);
col.Add("2", 2);
col.AddAt(0, "0", 0); //add by index
col.Add("3", 3);
col.RemoveAt(2); //remove by index
string strList = string.Empty;
for (int i = 0; i < col.Count; i++)
{
strList += col[i]; //get by index
}
Assert.AreEqual(strList, "013");
}
As you can see, you can access the dictionary now by an index (aswell as your key). This is accomplished simply by inheriting from the Dictionary class and holding a List inside the class too :
    public class IndexedDictionary<TKey, TValue> : Dictionary<TKey, TValue>
{
protected List<TKey> m_col = new List<TKey>();

I have also added some extra features which can be toggled :

  • Replace Duplicate Keys - obviously you cannot have multiple entries in a dictionary with the same key, so what this does instead is replaces a value with the same key when adding to the collection
  • Throw Error On Invalid Remove - if disabled, when you try to remove an item that does not exist in the collection it just does nothing and does not throw an exception as it normally does

Immediately after this new collection came the NamedIndexedDictionary. This is a sub class of the IndexedDictionary class that takes a string as the key.

public class NamedIndexedDictionary<TValue> : IndexedDictionary<string, TValue>

The string key can also be set to be case insensitive or not, so calling col["abc"] is the same as col["ABC"].

Both have been very useful to me in the past and I hope someone else will find them useful too. Send me any other helpful collections you have created and I will add them too. 

Download here : collections.zip

 

Posted by bradvin | 5 comment(s)
Filed under: , , ,

I wrote a beginners article on how to create your own extension methods in .NET 3.5 and then how to go about testing them using new built-in features in VS2008. Please go read the article at the Code Project : http://www.codeproject.com/useritems/ExtensionUnitTests.asp.

If you are interested in extension methods (like I am obviously) and plan on writing your own, I think the article is a good starting point. I give suggestions on when to use them and when I think it is not appropriate. I also talk about some unexpected behaviors that occur when using extension methods on variables that are null. It then walks you through a step by step guide on how to write unit tests for the extension methods using the new unit testing features of VS2008 pro.

Please check it out and comment on how I could improve it and please remember to rate the article.

Posted by bradvin | with no comments
Filed under: , , ,

ÜberUtils Series posts so far :

So every developer has (or should have) a utilities class for strings. It seems the built-in string class never has enough (well for me in any case). So I hereby introduce my string utils class. It actually comprises of 3 files which are :

  1. Strings.cs (the actual string utils)
  2. SafeConvert.cs (a class for doing common conversions)
  3. Extensions/Strings.cs (extension methods using the string utils)

Here is the class diagram of the Strings class :

 

As you can see it has a nested class Regex which is also static. More on this later. Lets cover the string utility methods first (in 'logical' order):

  • IsEmpty - returns true if the object passed in is either null or has a length of zero (exactly like string.isNullOrEmpty but can take an object as input)
  • IsNumeric - returns true if we are dealing with a numeric value. Uses the regular expression : @"^\-?\(?([0-9]{0,3}(\,?[0-9]{3})*(\.?[0-9]*))\)?$". This matches a positive or negative value with any precision and scale (whole number or decimal). It also allows for left-padded zeros, commas as group separators or parenthesis to indicate negative number
  • IsEmail - returns true if an email. Uses the regular expression : @"([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)"
  • Trim - exactly like "abc".Trim() but adds checking for nulls
  • CutWhitespace - cuts all whitespace from a string aswell as trims it
    • eg. Strings.CutWhitespace(" 12  34   5 6  7   ") == "12 34 5 6 7"
  • CutEnd - chops the end n chars off the end of a string
    • eg. Strings.CutEnd("1234567890", 3) == "1234567"
  • CutStart - chops the first n chars off the beginning of a string
    • eg. Strings.CutStart("1234567890", 3) == "4567890"
  • Start - returns the first n chars of a string
    • eg. Strings.Start("1234567890", 3) == "123"
  • End - returns the last n chars of a string
    • eg. Strings.End("1234567890", 3) == "890"
  • GetOccurences - returns an array of strings that are found within another string based on a regular expression
    • eg. Strings.GetOccurences("say day bay toy", "[sdbt]ay") == new string[] {"say" , "day" , "bay"}
    • eg. Strings.GetOccurences("123asdasd 1sk 555 sdkfjsdfkl999", "\\d+") == new string [] {"123" , "1" , "555" , "999"}
  • OccurenceCount - returns the count of strings found within another string based on a regular expression
    • eg. Strings.OccurenceCount("the cat sat on the mat", "at") == 3
    • eg. Strings.OccurenceCount("abcabc", "a") == 2
  • Combine - combines a string array by a delimeter (or not) (DEPRICATED - read update and comments)
    • eg. Strings.Combine(Strings.GetOccurences("123asdasd 1sk 555 sdkfjsdfkl999", "\\d+"), ",") == "123,1,555,999"
    • eg. Strings.Combine(new string[] { "a", "b", "c", "d" }, ";") == "a;b;c;d"
  • ToPaddedNumber - returns a zero padded number (DEPRICATED - read update and comments)
    • eg. Strings.ToPaddedNumber("123", 5) == "00123"
  • XOR - performs a binary XOR operation on each char in the input string based on a key. Very simple form of encryption where XOR(XOR(input)) == input
    • eg. Strings.XOR(Strings.XOR("test", "key"), "key") == "test"
  • ToTitleCase - returns the title case of a string
    • eg. Strings.ToTitleCase("this is a title") == "This Is A Title"
  • ToFriendlyName - returns what I call a "friendly" version of a string. I use this mainly for converting a database field name into a user friendly name
    • eg. Strings.ToFriendlyName("IAmNotFriendly") == "I Am Not Friendly"
    • eg. Strings.ToFriendlyName("SomePrimaryKeyId") == "Some Primary Key"

Now onto the Regex class. The static Regex class just wraps regular expression functionality and contains a few commonly used expressions as constants. Here is the run down :

  •  IsExactMatch - returns true if a string is an exact match for a pattern
    • eg. Strings.Regex.IsExactMatch("test@google.com", Strings.Regex.REGEX_EMAIL) == true
  • Contains - returns true if a string contains a pattern
    • eg. Strings.Regex.Contains("here is my email : test@google.com", Strings.Regex.REGEX_EMAIL) == true
  • Replace - returns a string with a pattern replaced by another string
    • eg. Strings.Regex.Replace("1 23 a 456", @"\d+", "!") == "! ! a !"
  • GetMatch - returns the first match of pattern within a string
    • eg. Strings.Regex.GetMatch("Subject: Test Subject\r\n", @"Subject\s*\:\s*(?<SubjectReturn>.*)\r\n", "SubjectReturn") == "Text Subject"

Now onto the SafeConvert class. It contains the following methods :

  • ToBoolean - returns a boolean value from an object
  • ToInt - returns an integer value from an object
  • ToDecimal - returns a decimal value from an object
  • ToDouble - returns a double value from an object
  • ToHexString - returns a hexidecimal string representation of a byte array. This is used from Extensions\ByteArray.cs
  • ToStream - returns a System.IO.MemoryStream from a string

So thats version 1 of the strings utilities. I say version 1 because I will no doubt add to this over the next couple of posts.

Oh yes, and again we have a whole bunch of new extension methods :

  • Start
  • End
  • CutStart
  • CutEnd
  • OccurenceCount
  • GetOccurences
  • ReplaceAll - similar to Replace, but uses a regular expression to do the replacement
  • Split - similar to Split(char c) but takes a string pattern to split using regular expressions
  • Combine (DEPRICATED - read update and comments)
  • Join - an extension method for string arrays wrapping the string.Join method

Now I know some people might argue that this is extension method abuse, but look at how much more power my strings have :

 

... and anything that helps me code quicker and smarter is not abuse in my book - its smart coding!

Download the source code and unit tests here

UPDATE - thanks to Dan's comments we found a bug in the email regular expression whereby it would not allow the domain ".museum" so I changed the regex to
@"([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,8}|[0-9]{1,8})(\]?)" (changes in bold)
Please note that email validation seems to be a touchy point for many developers as can be seen over at haaked.com . I would suggest not to use ANY email validation like this for restricting comments or purchases online, as you would be limiting your site's reach. Source code and unit tests have been updated.

UPDATE - thanks to Scott Hanselman for pointing out that ToPaddedNumber is redundant as the string class has a PadLeft (as well a PadRight) method - DOH! Source code and unit tests have been updated.

UPDATE - thanks to Don and John for pointing out the fact that my Combine method is redundant as the string.Join method does the exact same thing. - oops ;)
I then renamed my extension method Combine to Join and changed it to wrap the string.Join functionality. Again Source and tests have been updated.

NOTE - I renamed the static extension classes so that you could include both the Utils and Utils.Extensions namespaces without getting the build error : 'Strings' is an ambiguous reference between 'Utils.Strings' and 'Utils.Extensions.Strings'. Please get the latest source.


Thanks for all comments and feedback and please keep it coming. Collaboration and a LOT of testing is the only way to produce robust,useful code!

Posted by bradvin | 22 comment(s)
Filed under: , , ,
More Posts Next page »