Scott Hanselman

Deploying ASP.NET MVC on ASP.NET 2.0

July 8, '08 Comments [24] Posted in ASP.NET | ASP.NET MVC
Sponsored By

minefieldThis post will be filled with disclaimers and warnings. They are not just to CYA, but also to C my A and avoid my getting sacked.

I've been working with Shawn Burke and Rob Conery and the magical Eilon Lipton to try to figure out a way to get ASP.NET MVC running under ASP.NET 2.0 SP1. Shawn had this idea and told Rob "go figure this out." Rob was really busy doing the MVC Storefront so he told me "go figure this out, you like this kind of freaky stuff."

Take a moment to look at my post on How to set an IIS Application or AppPool to use ASP.NET 3.5 rather than 2.0 for background on the physical relationship between .NET 2.0 and .NET 3.5.  There's a lot of stuff going on between 2.0 and 3.5, even though the CLR is more or less the same.

I was brainstorming ways to get ASP.NET MVC running on a .NET 2.0 machine and there were a couple of ways. I could recompile/fork all the stuff at http://www.codeplex.net/aspnet and compile via #ifdefs or manually an ASP.NET 2.0  version. However, then I'd have to maintain multiple versions, it could get really messy really quick.

I ended up speaking to Brad Abrams and spent the last few weeks, on and off, talking to all the different groups that owned different parts of the framework. I expressed that I could get a Hello World ASP.NET MVC application working if I:

  • Developed on Visual Studio 2008
  • Targeted .NET 2.0 in Project Properties
  • Deployed the Application to a .NET 2.0 SP1 machine
  • Copied System.Core.dll local to the web apps /bin folder

It's that final step that we're not technically allowed to do, but I'm showing you how to do it in case you real;y want to ASP.NET MVC on a machine that you just can't put .NET 3.5 on yet.

Here's the disclaimers:

  1. This workaround is offered with exactly zero warranty or support. It's as-is, just an FYI on the blog. If this hack deletes files or kills your cat, you have been warned. No whining.
  2. In practice, no one really knows what might break.  Microsoft didn't test this.
  3. You must not redistribute System.Core.dll to a 3rd party. You mustn't bundle it or ship it with your application.
  4. When you update to .NET 3.5, remove your local copy of System.Core.dll
  5. Don't GAC System.Core.dll.
  6. Don't taunt System.Core.dll.
  7. Take a moment and read #3 again.
  8. This just flat might not work for you. Sorry.
  9. Don't do this to any machine that you don't own.
  10. I wanted to have an even ten, so this is a placeholder disclaimer in case I forgot one.

After all those sunny disclaimers, I do hope that this helps someone in some small way to get a development site running on .NET 2.0 while you prepare for an upgrade to .NET 3.5.

What To Do

From an ASP.NET MVC Web Application, you'll need to set your Project Target Framework to 2.0 in Project Properties:

image

NOTE: This doesn't enable all of 3.5, or even much of .NET 3.5. But it does enable System.Web.Mvc and it's supporting assemblies to run as they are the ones with the reference to System.Core. However, remember, at this point, you and your project are living in .NET 2.0 world.

Running 2.0 means you don't have LINQ to SQL or Entity Framework or anything. You'll need to use something else. In my example code I'm using Davy Brion's NHibernate Northwind Sample as it's a .NET 2.0 compiled solution and it'll be acting as my Model.

VS 2008 Will Try to Stop You

When you add references to System.Web.Routing, System.Web.Abstractions and System.Web.MVC you'll get warnings like this one. Click Yes. This is VS trying (rightfully) to protect you from confusion and assembly errors as we'll see, and if you click "yes" you're saying you really know what you're doing. From this point on, we're on our own.

image

After this, you can develop your now .NET 2.0 application as you like, using 2.0 technologies. Some of the cooler ASP.NET MVC stuff won't work, particularly in the Views if you try to use lambdas with HtmlHelpers or use the var keyword. This is because while your development machine's compiler is 3.5 since you're using VS2008, when you deploy your ASPX views to the server side, that machine has only .NET 2.0 SP1 and will compile those views with the 2.0 compilers.

For example, this line of code in a view that showed a list of products...

<% foreach (var product in ViewData.Model) { %>

...gave me this error.

CS0246: The type or namespace name 'var' could not be found (are you missing a using directive or an assembly reference?)

If you see errors like this, you'll know you're using 3.5isms that's not understood by the 2.0 compiler in your Views.

Deploying to an ASP.NET Machine

Since I'll be deploying on a machine with .NET 2.0 SP1, I'll use an XP SP2 Virtual Machine running IIS6. You can find a fantastic troubleshooting blog post by Omar Al Zabir on Deploying ASP.NET MVC on IIS 6 here. Suffice it to say, my routes use the .mvc extension, and I've associated .mvc with aspnet_isapi in the IIS6 management console.

I've also updated my routes to include the .mvc extension so IIS6 can "see" the requests:

routes.MapRoute("mvcroute", "{controller}.mvc/{action}/{id}"
    , new { controller="products", action = "Index", id = "" }
    , new { controller=@"[^\.]*"});

ASIDE: If you like, you can certainly enable pretty URLs using ISAPI_Rewrite and remapping requests to extensionless URLs to .mvc or you can configure IIS6 with a wildcard map. I prefer the former.

image

After I deploy my ASP.NET MVC application over to my .NET 2.0 SP1 machine, again, in this case running IIS6, I might see this YSOD indicating I don't have System.Core on that machine. This makes sense because this machine doesn't have .NET 3.5 installed.

Here's the moment of truth, and the moment we step from supported to unsupported. You can copy System.Core from your .NET 3.5 development machine (this is the machine running VS2008 that you're developing on) to the /bin folder on your .NET 2.0 SP1 machine. It's gotta be running .NET Framework 2.0 SP1 or this won't work.

image

System.Core is probably somewhere around "C:\windows\assembly\GAC_MSIL\System.Core\3.5.0.0__b77a5c561934e089" on your machine, but you're a bad person for even asking.

image

Copy that file into your bin folder on your deployment machine and you should be able to Refresh past this error page.

Things You Don't Get With This Hack

  • Support from Me or Microsoft (in that order)
  • Any .NET 3.5 feature like LINQ to SQL or LINQ to Entities
  • Many of the HtmlHelpers and cool things that work with Lambdas won't work with inline script in Views because those pages will be compiled on the server using the 2.0 compiler.
  • Good karma

Things You Appear to Get With This Hack

  • ASP.NET MVC seems to work
  • Anything you can do in ordinarily in .NET 2.0 works
  • Bragging rights
  • A spot on your immortal soul
  • The ability to show your boss that ASP.NET MVC is a good thing, and maybe get him/her to let you upgrade the server to ASP.NET 3.5 with the promise of even cooler stuff.

I was able to get a full Northwind Sample Site up using ASP.NET MVC Preview 3 with NHibernate against the Northwind Database in about two hours. This was my first NHibernate application of any size and I'm sure I did it wrong, but it works on my machine! Thanks again to Davy Brion's most excellent and thoughtful example, it was a great way for me to learn NHibernate (which has a bit of a learning curve).

image

Navigating Microsoft Legal

One thing that I wanted to add was that this was my first time navigating Microsoft Legal. I was dreading it. However, the LCA (Legal and Corporate Affairs) guy that helped me through it was exceedingly cool. Rather than what I expected - here's reasons why you CAN'T do this - he was more like, "what can we do to make this happen." I don't know if it's representative of Microsoft Legal in general, our division, or just this nice guy, but either way, it was cool and he's my go-to guy the next time I try something crazy.

Related Links

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Hanselminutes Podcast 120 - The Odd Couple - A Developer and a Designer talk about working with XAML

July 7, '08 Comments [4] Posted in Podcast | Silverlight | Windows Client | WPF
Sponsored By

XAML Icon My one-hundred-and-twentieth podcast is up. In this episode, I talk to Felix, a Designer, and Richard, a Developer about their Odd Couple relationship as they create Silverlight and WPF applications. They also speak frankly about their opinions as full time users of Blend and VS2008.

Subscribe: Subscribe to Hanselminutes Subscribe to my Podcast in iTunes

If you have trouble downloading, or your download is slow, do try the torrent with µtorrent or another BitTorrent Downloader.

Do also remember the complete archives are always up and they have PDF Transcripts, a little known feature that show up a few weeks after each show.

Telerik is our sponsor for this show.

Telerik's new stuff is pretty sweet, check out the ONLINE DEMO of their new ASP.NET AJAX suite. RadGrid handles sorting, filtering, and paging of hundreds of thousands of records in milliseconds, and the RadEditor loads up to 4 times faster and the navigation controls now support binding to web services on the client.

As I've said before this show comes to you with the audio expertise and stewardship of Carl Franklin. The name comes from Travis Illig, but the goal of the show is simple. Avoid wasting the listener's time. (and make the commute less boring)

Enjoy. Who knows what'll happen in the next show?

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Back to Basics - Do namespace using directives affect Assembly Loading?

July 4, '08 Comments [29] Posted in Back to Basics
Sponsored By

Jeff Atwood had a great post a while back called "Strong Opinions, Weakly Held." It's good to feel strongly about something, but important to be open to changing your opinion if you're faced with new evidence.

Last week a reader pointed me to this post at the Microsoft StyleCop blog that shows some interesting examples of using directives outside and inside the namespace declaration.

For example, this compiles fine:

using Guid = System.Guid;

namespace Microsoft.Sample
{
public class Guid
{
public Guid(string s){}
}

public class Program
{
public static void Main(string[] args)
{
Guid g = new Guid("hello");
}
}

}

However this one with the using moved inside the namespace doesn't compile:

namespace Microsoft.Sample
{
using Guid = System.Guid;

public class Guid
{
public Guid(string s){ }
}

public class Program
{
public static void Main(string[] args)
{
Guid g = new Guid("hello");
}
}
}

The code fails on the following compiler error, found on the line containing Guid g = new Guid("hello");

CS0576: Namespace 'Microsoft.Sample' contains a definition conflicting with alias 'Guid'

In the first example, there's an alias created, but it doesn't matter because the second Guid class is in local scope (there's no scope conflict) and the compiler chooses the inner Guid class.

In the second example, there are two "Guids" declared in the same scope and that's a conflict that the compiler can't resolve automatically. The style rule/argument the post makes is that you will only see these kinds of conflicts if you put your using directives inside your namespaces. To this, I say, "meh." Sure, if it makes you happy and you use lots of namespace aliases, sure, but it's an edge case. I simply prefer to have my namespaces outside.

Read Twice, Test Once

However, the second rule in the post said:

"However, placing the using statements [Ed. Note: They mean "directives"] within a namespace element allows the framework to lazy load the referenced assemblies at runtime. In some cases, if the referencing code is not actually executed, the framework can avoid having to load one or more of the referenced assemblies completely. This follows general best practice rule about lazy loading for performance."

This stopped me in my tracks. This rocks the very bedrock that my knowledge of the CLR stands on. I'm like, NO WAY, and then I oscillated back and forth between denial and acceptance. Then, I settled on denial. I don't buy it. A using directive is for aliasing and is a kind of syntactic sugar. Ultimately the IL is the same. Assembly loading won't be affected as the assembly manifest doesn't change.

Here's what my experiment showed. I believe it's true until I find out from someone on the CLR Loader team that it's not true. ;)

First Test

using System;
using System.Xml;

namespace Microsoft.Sample
{
public class Program
{
public static void Main(string[] args)
{
Guid g = Guid.NewGuid();
Console.WriteLine("Before XML usage");
Console.ReadLine();
Foo();
Console.WriteLine("After XML usage");
Console.ReadLine();
}

public static void Foo()
{
XmlDocument x = new XmlDocument();
}
}
}

I ran this program outside the debugger but compiled in debug mode. At the point there the first ReadLine() hits, the program pauses and waits for an Enter key. I loaded up Process Explorer and saw:

image

Then, I hit Enter, executing the Foo() method and new'ing up an XmlDocument. You can see that System.Xml just got loaded (specifically the native image) into the process.

image

Second Test

If I do the same thing with the usings INSIDE the namespace I get identical results.

namespace Microsoft.Sample
{
using System;
using System.Xml;

public class Program
{
public static void Main(string[] args)
{
Guid g = Guid.NewGuid();
Console.WriteLine("Before XML usage");
Console.ReadLine();
Foo();
Console.WriteLine("After XML usage");
Console.ReadLine();
}

public static void Foo()
{
XmlDocument x = new XmlDocument();
}
}
}

In fact, the only thing that changed the way the assemblies got loaded was switching to release mode. Running the app in release mode had all the assemblies in my trivial app loaded immediately. I thought it was weird for a second, but then realized it had nothing to do with debug vs. release. It was simply that the Foo() method was either inlined or there was a Tail Call Optimization as I explored in this post: Release IS NOT Debug: 64bit Optimizations and C# Method Inlining in Release Build Call Stacks.

I'm 99.99% sure at this point that using directives can't change your assembly loading behavior and I think I was right to be suspicious. However, I'm going to ask some people on the Fusion (assembly loader) and C# teams who are smarter than I and I'll update this post as I learn more!

However, the Back to Basics Tips here are:

  • Don't believe everything you read, even on a Microsoft Blog.
  • Don't believe this blog, either!
  • Decide for yourself with experiments if you need a tiebreaker!

And be ready to be wrong anyway! It only takes one negative experiment to disprove a theory. Of course, the real question is, what does the specification say? 

UPDATE #1: Ian Griffiths had a similar reaction and a similar test!

What do you think, Dear Reader?

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

The Weekly Source Code 30 - VB.NET with XML Literals as a View Engine for ASP.NET MVC

July 3, '08 Comments [12] Posted in ASP.NET | ASP.NET MVC | VB | XML
Sponsored By

I was literally in the middle of writing the post when I saw a message from Andrew Davey about how he had implemented the same idea! Of course, his is way better, so I got to code via subtraction. That means subtracting out the crap I had written in a few minutes and dig into his code.

There are no unique ideas, right? ;) Either way, it's fun when the same idea is being thought about simultaneously.

Here's the general concept. A few weeks back I was talking with Avner Aharoni, a Language Program Manager, and he had been kicking around the idea of VB9's XML Literals making friendlier Views within ASP.NET MVC.

I've blogged about VB9's rocking sweet XML support before. It lets you create XML like this. Note the lack of strings...the XML is there in the language and the compiler and intellisense are all aware of it.

 Dim books = <bookstore xmlns="http://examples.books.com">
<book publicationdate=<%= publicationdate %> ISBN=<%= isbn %>>
<title>ASP.NET Book</title>
<price><%= price %></price>
<author>
<first-name><%= a.FirstName %></first-name>
<last-name><%= a.LastName %></last-name>
</author>
</book>
</bookstore>

Views in ASP.NET MVC

Starting with the Northwind ASP.NET MVC Sample Code for Preview 3 that Phil updated, let's look at the List View:

<asp:Content ContentPlaceHolderID="MainContent" runat="server">
<h2><%=ViewData.CategoryName %></h2>
<ul>
<% foreach (Product product in ViewData.Model.Products.Model) { %>
<li id="prod<%= product.ProductID %>">
<%= product.ProductName %>
<span class="editlink"">
(<%= Html.ActionLink("Edit", new { Action="Edit", ID=product.ProductID })%>)
</span>
</li>
<% } %>
</ul>
<%= Html.ActionLink("Add New Product", new { Action="New" }) %>
</asp:Content>

This is straight from the ASPX page with inline C#.

ASP.NET MVC Views using VB9's XML Literal Support

imageNow, we thought it'd be cool/interesting/potentially-something if we could use the XML Literal support to get, as Andrew puts it "compiled, strongly typed, intellisense friendly views." Sure, we mostly get that with the ASPX pages, but perhaps this would be better/easier/something? Keep in mind here that we're playing.

Your opinions on if this is a good idea or something to move forward would be useful. Leave comments and I'll compile and give them directly on to the VB and MVC team(s)! Remember, it can look like however you think it should, so don't be constrained by my spike or Andrew's.

Here's why Andrew thinks it's cool, quoted from a post on the ALT.NET mailing list:

Some key features I've used.
- <%= From ... Select ... %> to create repeated elements
- VB's ternary "If" operator for conditional output
- X-linq to post process the HTML before sending it
- choose between indented and compressed XML output
- modules of functions as "controls" - it's so simple :)

Here's what my solution looks like in Visual Studio. See how the ListVB.aspx has no "+" sign. There's no code-behind .cs file there even though this is a C# project. The meat of the View is in another assembly (although you could conceivably do something freaky and get VB and C# to live in the same assembly (be sure to read the comments)).

Actually, the ListVB.aspx file is VB, not C# and refers not to to a code-behind, but another class in another DLL, specifically the VBViews assembly.

<%@ Page Language="VB" MasterPageFile="~/Views/Shared/Site.Master" 
AutoEventWireup="true" Inherits="VBViews.ListVB" Title="Products" %>
<%@ Import Namespace="NorthwindModel" %>
<%@ Import Namespace="System.Collections.Generic" %>
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
<h2><%=ViewData.Model.CategoryName%></h2>
<% = GetList() %>
</asp:Content>

Here's the Visual Basic code in the other assembly.

Imports System.Xml.Linq
Imports NorthwindDemo.Models

Partial Public Class ListVB
Inherits System.Web.Mvc.ViewPage

Function GetList() As XElement
Dim c As Category = CType(ViewData.Model, Category)
Return <ul><%= From product In c.Products _
Select _
<li id=<%= "prod" & product.ProductID %>>
<span class="editlink">
<a href=<%= "/Products/Edit/" & product.ProductID %>>
<%= product.ProductName %>
</a>
</span>
</li> %>
</ul>
End Function
End Class

This won't really be clear without some syntax highlighting to make the point, so here it is again, but this time as a screenshot. See now the VB code, XML and <% %> blocks are all together in same line? VB is just generating XElement's which in turn will turn into a string when "rendered" by the ASPX page.

The same VB9 code as before, except this time in color.

Andrew's Take #2 on the VB9 XML Literals as ASP.NET MVC Views

Andrew Davey's NRest project is more than just VB9 Views. It's a REST web framework for ASP.NET using the Nemerle programming language (using the May CTP). You can browser or GET his code with SVN here: http://svn.assembla.com/svn/nrest/trunk/. It's also a nicely laid out solution that uses the Ninject IOC but I'll cover that later. Do check out Andrew's screencast about his NRest project.

His code is a mix of C#, Nemerle and VB. The Website, Tests and Services are in C# and the Ninject modules are in Nemerle, along with the meat of the main NRest project. I think he could have used more of System.MVC, specifically the View Engines, that he did, but I'm still grokking his intent.

He's got a hierarchy in VB with a MainPageBase, Page, in order to achieve a kind of Master Pages:

Public Class MainPageBase(Of TChrome As MainChrome, TContent)
Inherits Page(Of TChrome, TContent)

Public Overrides Function GetHtml() As XElement
Return _
<html>
<head>
<title><%= Chrome.Title %></title>
<link href="/styles/demo.css" type="text/css" rel="Stylesheet"/>
<%= GetHeadContents().Elements() %>
</head>
<body>
<h1><%= Chrome.Title %></h1>
<%= GetBodyContents().Elements() %>
</body>
</html>
End Function

Public Overridable Function GetHeadContents() As XElement
Return <_></_>
End Function

Public Overridable Function GetBodyContents() As XElement
Return <_></_>
End Function

End Class

So a Hello World page in VB would be very simple, just this:

Public Class CustomerPage
Inherits MainPageBase(Of CustomerPageData)

Public Overrides Function GetBodyContents() As XElement
Return _
<_>
<p>Hello <%= Content.FirstName & " " & Content.LastName %></p>
</_>
End Function
End Class

All of this is a work in progress, but it's really cool that we're all trying to push the envelope and not afraid to try crazy stuff in order to make things better. It'll be cool for me to read this post in a year and either say "ew" or "cool!" depending on what direction we all went.

Have you done anything cool or crazy with Views and ViewEngines, Dear Reader?

Related Posts

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Back to Basics - This is not the object you're looking...wait, oh, it is the object

July 2, '08 Comments [27] Posted in Back to Basics | Learning .NET | Windows Client
Sponsored By

Downcasting is kind of something you usually want to avoid, but sometimes it's not easily avoided. It depends on the situation. Because it's not an idiom you'll find yourself doing every day, sometimes you'll forget to do it entirely and what you're looking for is right there under your nose.

The Problem

A buddy was trying to host the WebBrowser control in his WinForms application. It was a locally run application that was using the WebBrowser control to generate part of the UI. The user might click on a link, and you'd want to do something based on the click of an link/anchor in the HTML.

In this trivial example, I add the words "Add Comment" to a page I've navigated to within my WinForms Application.

image

First, we make some new elements within the WebBrowser control itself, like this.

HtmlElement div = webBrowser1.Document.CreateElement("div");
div.SetAttribute("style", "color:blue;");
webBrowser1.Document.Body.AppendChild(div);

HtmlElement anchor = webBrowser1.Document.CreateElement("a");
anchor.InnerText = "Add Comment";
anchor.Id = "lnkAddComment";
anchor.SetAttribute("href", "#");
anchor.Click += new HtmlElementEventHandler(doit);
div.AppendChild(anchor);

Notice that we can hook up a managed event handler to the anchor, and that the anchor's href attribute points to nothing, by setting it to "#".

anchor.Click += new HtmlElementEventHandler(doit);

My friend hooked up the event to doit() that had a standard EventHandler signature like this:

public void doit(object sender, HtmlElementEventArgs e)

You've likely seen this "object sender, SomeEventArgs e" method signature before. He started digging around in the HtmlElementEventArgs object, trying to find something he could use to known if the user had clicked on the link called lnkAddComment. He was bummed to find nothing he could use.

image 

There's a couple of thing to learn here. First, just hovering over the sender object in the debugger shows us a lot. We can see that the type of sender isn't just object, but rather System.Windows.Forms.HtmlElement. This is a CLR type, and not a JavaScript type or a type you'd know about if you were familiar with the DOM and were expecting something more DOM-like.

The DOM element in this case, is there, but it's a COM Object and is part of the IE DOM object model and you'd be better off using Visual Basic if you care deeply about getting into it as we've recently learned.

image

Downcast, Damned Spot!

However, it's an HtmlElement and it has and id property with the string "lnkAddComment," and that's useful. At this point one could down-cast it. That means, telling the compiler that we know more than it does and that we're totally sure and are willing to risk our necks.

HtmlElement foo = (HtmlElement)sender;
if (foo.Id == "lnkAddComment")
{
MessageBox.Show("woot");
}

There's some interesting things we can think about here. First, what if we're wrong? Well, bad things for one:

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.InvalidCastException: Unable to cast object of type 'Something.You.Did.Not.Plan.On' to type 'System.Windows.Forms.HtmlElement'.
   at WindowsFormsApplication1.Form1.doit(Object sender, HtmlElementEventArgs e) in C:\Yay\Form1.cs:line 32

And that sucks. However, in this case we are pretty darn sure, but we should be more defensive about it.

public void doit(object sender, HtmlElementEventArgs e)
{
if (sender is HtmlElement)
{
HtmlElement foo = (HtmlElement)sender;
if (foo != null && foo.Id == "lnkAddComment")
{
MessageBox.Show("woot");
}
}
}

But that is kind of verbose, we can try a defensive cast:

HtmlElement foo = sender as HtmlElement;
if (foo != null && foo.Id == "lnkAddComment")
{
MessageBox.Show("woot");
}

In this case, if sender isn't an HtmlElement, we'll get back null. The same code in VB is pretty clear also. See the TryCast?

Public Sub doit(ByVal sender As Object, ByVal e As HtmlElementEventArgs)
Dim foo As HtmlElement = TryCast(sender,HtmlElement)
If ((Not foo Is Nothing) AndAlso (foo.Id = "lnkAddComment")) Then
MessageBox.Show("woot")
End If
End Sub

The IL (Intermediate Language) instructions are interesting also as it's asking explicitly if that object is what I think it is:

L_0001: ldarg.1 
L_0002: isinst [System.Windows.Forms]System.Windows.Forms.HtmlElement

I suppose that means I could just ask myself, but that's largely stylistic as the resulting IL is virtually the same, with the defensive cast being a simpler.

But Why the Downcast at All?

At this point you might be asking, Why did I have to downcast at all? Doesn't that mean the EventHandler pattern is lame? Maybe, but here's the thinking as described by Jeffrey Richter on page 228 of his most excellent CLR via C#, 2nd Edition (with [contextual edits and emphasis] by me so it makes sense in this post).

A lot of people wonder why the event pattern requires the sender parameter to always be of type Object. After all, since the [HtmlElement] will be the only type raising an event with a [HtmlElementEventArgs] object, it makes more sense for the callback method to be prototyped like this:

void MethodName(HtmlElement sender, HtmlElementEventArgs e);

The pattern requires the sender parameter to be of type Object mostly because of inheritance. What if [HtmlElement] were used as a base class for [DerivedHtmlElemen]? In this case, the calIback method should have the sender parameter prototyped as an [DerivedHtmlElement] instead of HtmlElement, but this can't happen because [DerivedHtmlElement] just inherited the Click event. So the code that was expecting an [DerivedHtmlElement] to raise the event must still have to cast the sender argument to an [DerivedHtmlElement]. In other words, the cast is still required, so the sender parameter might as well be typed as Object.

The next reason for typing the sender parameter as Object is just flexibility. It allows the delegate to be used by multiple types that offer an event that passes a [HtmlElementEventArgs] object. For example, a [PopHtmlElement] class could use the delegate even if this class were not derived from [HtmlElement].

One more thing: the event pattern also requires that the delegate definition and the callback method name the EventArgs-derived parameter e. The only reason for this is to add additional consistency to the pattern, making it easier for developers to learn and implement the pattern. Tools that spit out source code (such as Microsoft Visual Studio) also know to call the parameter e.

If you're interested in this kind of stuff, you should totally buy his book.

Can we avoid the cast completely?

Why did we downcast in the first place? We wanted to get ahold of the HtmlElement.Id property so we could do a string comparison in order to tell if an object is the one we're looking for. Perhaps we can check for that object's identity using something a little cleaner than a string.

In this case, since we were the ones that created the anchor in the first place we can check the "object sender" against a saved reference to our anchor by checking object identity. Are these the same two objects? Is the object I added to the object model the same one that is coming back to me as the sender parameter to this Event Handler?

HtmlElement anchor;
public void doit(object sender, HtmlElementEventArgs e)
{
if (sender.Equals(anchor))
{
MessageBox.Show("woot");
}
}

Is this a good idea? What about using == instead? In this case, I CAN use == because HtmlElement has explicitly created equality operators, so when I'm using == to compare these two instances I'm ACTUALLY calling a static op_Equality(HtmlElement, HtmlElement) on the HtmlElement type. It's static because both side might be null and I can't call methods on null instances.

HtmlElement anchor;
public void doit(object sender, HtmlElementEventArgs e)
{
if (anchor == sender)
{
MessageBox.Show("woot");
}
}

However, while operator overloading is common in C++ it's generally considered to be unnecessarily obscure in C# and moreover, you just can't count on folks to be consistent. For example, when I say == do I mean value equality or reference equality? In the case of HtmlElement they mean reference equality. In C++ an overloaded == is usually done for deep value comparisons.

I will get this warning if I try anyway:

Possible unintended reference comparison; to get a value comparison, cast the right hand side to type 'System.Windows.Forms.HtmlElement' 

I like to use Equals() myself for clarity's sake, but touché! It's going to just do this internally anyway:

public override bool Equals(object obj)
{
return (this == (obj as HtmlElement));
}

Commenter Adam makes the excellent point that using Object.ReferenceEquals is explicitly clear in expressing intent. The result would be:

public void doit(object sender, HtmlElementEventArgs e)
{
if (Object.ReferenceEquals(anchor,sender))
{
MessageBox.Show("woot");
}
}

Of course, the implementation of ReferenceEquals is just this again. ;)

public static bool ReferenceEquals(object objA, object objB)
{
return (objA == objB);
}

What's the point?

Know your options, but above all, know your intentions and make sure that the code you're writing correctly expresses your intent.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.