Tuesday, May 26, 2009

ASP.NET MVC Output Caching

I've found what I believe to be a bug in ASP.NET MVC's caching model inasmuch as it appears to be caching GET's when caching is not specified. However, even though I think that it's a bug it's way more likely that this is something that I haven't understood or something that I've done wrong: The first rule of programming: It's my fault.

I've written a wizard style registration process for a site. It's fairly simple and stores the stage at which the user is at in a session state variable. Here is the code: 

[OutputCache(Duration=0, VaryByParam="None")]
public ActionResult Register()
    string registerStage = GetStage();

    switch (registerStage)
        case "1":
            return View("Register1");
        case "2":
            return View("Register2", UserDetailsVM);
        case "3":
            return View("Register3", UserDetailsVM);
        case "4":
            return View("CancelClicked");
            return View("NotFound");

What I found was that without the [OutputCache(Duration=0, VaryByParam="None")] attribute it appeared that the view was being cached.

If I removed this attribute and called the action via a browser it would work and break points inside the action would be hit the first time I navigated to the action/page. If I then navigated to a web form page (this is a hybrid web form / mvc application) and then returned to this page the Register action would not be hit. Added the [OutputCache(Duration=0,...] fixed the problem.

Friday, May 22, 2009

Normalizing Email Addresses

I was going to write a post here describing this function, why I wrote it, and what it does. But then I re-read the comments that I'd added to the function and the examples I'd included in the code and said "Hey, this is perfectly documented and doesn't need any further explanation."

/// <summary>
/// Takes an email address and converts it to a normalized base form.
/// This is needed for some email addresses such as gmail where you can
/// add dots anywhere in the name as well as a + sign and tag anything
/// extra on to the email address.
/// Example email addresses to normalize:
/// is.this.an.email@gmail.com
/// is.this.an.email+extrainfo@gmail.com
/// isthisanemail+extrainfo@gmail.com
/// These should all normalize to:
/// isthisanemail@gmail.com
/// </summary>
/// <param name="emailAddress">Any email address</param>
/// <returns>A modified/normalized email address or the original string</returns>
static public string NormalizeEmailAddress(string emailAddress)
    // So far we only know about gmail.com that does this
        string[] parts = emailAddress.Split('@');
        // If there are more than 2 @'s in the address then this is an
        // invalid email address so don't try and do anything to it.
        if (parts.Count() == 2)
            string[] beforeAtParts = parts[0].Split('+');
            return beforeAtParts[0].Replace(".","") + "@" + parts[1];

    return emailAddress;

Ironically I love this gmail feature and I use it all the time and I think that most people use it for the good. Unfortunately there's a small element of the population who will use this for multiple registrations on sites so that they can troll and spam so I am doing this conversion as a preventitive measure. Note that on the sites that I've implemented this on I've still allowed the users to register with any address that they want. However, when comparing a registration attempt against a site that does not allow registrations with the same email address then this will prevent multiple registrations by the same person.

Tuesday, May 19, 2009

VS2010 Beta now available

Jason Zander has just written Announcing VS2010 / .NET Framework 4.0 Beta 1 in his blog and I have just read it.

I look forward to moving to VS2010 and can see that it will have productivity benefits but I'm not going to rush out and install it and definitely not try the beta - it just doesn't have that much appeal. I thought that VS2008 and C# 3.0 were a fantastic jump from the previous VS2005/C# 2.0 version and brought enormous benefits especially in the new language features. VS2010 feels like a tooling release and not a language feature release.

My feeling is that C# has evolved as follows (although I'm sure that I've missed some features):

Baseline: C++

C# 1.0 - Gives you managed code but does not have all the features of C++.

C# 2.0 - Adds back generics to the language and now C# is only missing default parameters to get back to C++.

C# 3.0 - Adds LINQ, Extension Methods, and Lambdas - this is an enormous boost to the language and suddenly makes it compete with languages such as LISP.

C# 4.0 - Adds back default parameters so now most (all?) C++ features are in the language.

For me, the big jump so far has been C# 3.0.

Make no mistake, I'm looking forward to C# 4.0 and VS2010 but it doesn't have a killer feature like LINQ to pull me in there quickly.

Add (20 May 2009): If you were using C# 2.0 and were given a choice between upgrading it with either the new features of C# 3.0 or the new features of C# 4.0 which would you choose?

Monday, May 18, 2009

Which button was clicked in the ASP.NET MVC View?

In an ASP.NET MVC view you might have multiple "submit" buttons inside a <form> such as:

<% using (Html.BeginForm()) {%>
       <input type="submit" value="Create" name="create" />
       <input type="submit" value="Cancel" name="cancel" />
<% } %>

The Html helper extension method BeginForm() will emit an opening and closing <form> tag that encloses the input fields.

Your controller that accepts the POST verb will look something like this:

        public ActionResult Create(FormCollection collection)
                string create = Request.Form["create"];
                string cancel = Request.Form["cancel"];

                return RedirectToAction("Index");
                return View();

If the Create button was clicked then the create variable will be set to "Create" (because "Create" is the value set to the input named "create") and the cancel button will be set to null. The opposite is true if the Cancel button is clicked.

Sunday, May 17, 2009

HP MediaSmart Windows Home Server

I recently bought this HP MediaSmart Windows Home Server from Amazon:

Which image do you think looks like it was more professionally produced? The one that I took above with my digital camera or the Amazon image below?

 You can see that I have the bottom 3 drive bay lights lit up because I don't have a drive in the top slot.
The machine comes with a 750Gb drive and I stuck a 1.5Tb and 1.0Tb drive into it to expand the storage and add some redundancy. When I stuck the first drive in (1.5Tb Seagate) I powered the server down and then pulled out the drive bay tray, stuck in the drive and replaced it before power it up again. It worked perfectly. The reason that I didn't stick the new hard drive in while the machine was running was because (1) I had never done it before and (2) I remember reading that Scott Hanselman said that he wasn't brave enough to do it and I get the impression that he's braver than me when it comes to trying potentially unsafe changes to a server. However, when the second hard drive arrived I decided to throw caution to the wind and stuck it in while the machine was powered on and it worked like a dream.
If you're going to get this then I recommend that you go for the cheaper version like I did. The only difference between the cheap one and the more expensive one is that you're getting a second 750Gb hard drive but the price difference is $150 to $200. For $117 I picked up a 1.5TB Seagate drive so less money and double the space and considering how easy it is to stick in this is a no brainer.

Unable to reach NetBIOS name

Just been trying to troubleshoot why I couldn't ping any of the other computers on my network from one of my laptops. I still haven't fully solved the problem but I've managed to get the piece of software to work that I needed to get to work by following an excellent tip from amcmillan after asking "Can ping machine by IP but not by name" on the new Server Fault web site. The tip was a link to this Microsoft troubleshooting guide.
Here is my markedup flowchart that has got me to the next step:

Thursday, May 14, 2009

Professional ASP.NET MVC 1.0

Just bought and now reading Professional ASP.NET MVC 1.0 by Rob Conery, Scott Hanselman, Phil Haack, Scott Guthrie. I don't often buy computer books but I read the first chapter from a PDF that is available online and was suitably impressed with it and since I am working in ASP.NET MVC knew I needed the knowledge so went ahead and got it.

It's well worth it. If you're doing any ASP.NET MVC work and you need a tutorial or reference book on the subject then this one is excellent. I like the Product Team Asides where they describe what they discovered during the development of this framework and why they took certain approaches.

Something which I'm curious about though is why would four Microsoft employees writing about Microsoft technology publish a book through Wrox Press and not through Microsoft Press?

Thursday, May 7, 2009

Setting up a dropdown list in ASP.NET MVC

When adding a dropdownlist to a view in MVC there are two approaches that I've taken so far. Both of these approaches involve the same snippet of code in the view:

<% = Html.DropDownList("ModelData")%>

ModelData is the item in the ViewData[] collection that holds your list. You set this value in the Controller.

The Controller code creates or fetches a collection of items for the drop down and then assigns them to an item in the ViewData collection:

ViewData["ModelData"] = dropDownList;

The drop down list needs to be an IEnumerable. I have taken two approaches to creating this. The first is to create a dictionary of int's and string's which is the logical backing to a dropdown list.

Dictionary<int, string> dictionaryList = FetchDictionaryOfDropDownListItems();
ViewData["ModelData"] = new SelectList(dictionaryList, "Key", "Value");

Note that if you don't supply the "Key" and "Value" fields for the second and third parameters of the SelectList() constructor then both the integer (key) values and the string values from the Dictionary will appear in your dropdown list. By supplying these values you are telling the SelectList which are the values in your dropdown list and which is the data to display.

The second approach was to create a list of SelectListItems:

IEnumerable<SelectListItem> dropDownList = new listOfObjects.Select(a => new SelectListItem { Text = a.MyText, Value = a.MyValue });
ViewData["ModelData"] = dropDownList;

I'm using LINQ on an IEnumerable that I called "listOfObjects" which has objects that have at the minimum a MyText and MyValue public property. I'm assigning these to the SelectListItem's Text and Value properties. The Text property is what you'll see in the dropdown list and the Value is the backing value for each item, usually an ordered list of integers.

Yield return in C#

I was reading some of the sample code that comes with the Professional ASP.NET MVC 1.0 book and came across the yield return statement which I've seen before but never used.

It appears to be mostly syntactic sugar but may make the code more readable so I'm going to try and start using it to see if it improves the code smell.

Here is an example of how you might code something using a "classic" iterator:

        public static IEnumerable<string> FindStringsUsingClassic(string[] stringArray)
            List<string> stringList = new List<string>();
            foreach (string s in stringArray)
                if (s.StartsWith("f"))
            return stringList;

and here is the same code using yield return:

        private static IEnumerable<string> FindStringsUsingYield(string[] stringArray)
            foreach (string s in stringArray)
                    yield return s;

Slightly less code but is it more readable and understandable? I don't know yet...

Those are contrived examples because if you're using C# 3.0 you would or should opt for the following:

        public static IEnumerable<string> FindStringsUsingLINQ(string[] stringArray)
            return stringArray.Where(a => a.StartsWith("f"));