Scott Hanselman

New Modules for IIS7: Application Request Routing - Proxy and Load Balancing Module

July 15, '08 Comments [12] Posted in IIS
Sponsored By

image I really like the IIS7 team at Microsoft. They're cool people, but the what I like is that IIS7 is so freaking modular (I've talked about this at conferences before when showing how to use PHP and Ruby under IIS7 using FastCGI) that the group keeps bringing out new functionality as "OOB" or Out Of Band releases.

Bill Staples as a good post on how IIS7 ships software now. There's a big list of all the Release to Web (RTW) modules for IIS7. I demo'ed a the Bit Rate Throttler at TechEd and talked about it in a post on"Squeezing the most out of IIS7 Media Bit Rate Throttling which can help people save bandwidth money while hosting downloadable files/media.

This week the IIS7 team put out three new preview releases for download.

  • IIS7 PowerShell Provider CTP2
    • This provider marries the two technologies and makes administrating IIS7 feel more natural to PowerShell folks. To put it bluntly, you can "cd" into iis:\ as if it were a drive, the type "dir" to see your websites. Drink that in. It's the bomb. I loves me some Powershell.
  • URL Rewrite Module CTP1
    • Just what it sounds like. Unlike ISAPI_Rewrite (which I love) this is an HTTP Module rather than an ISAPI Filter, and it includes an integrated UI for management within the IIS Manager.
  • OSIApplication Request Routing CTP1
    • This is the real dark-horse release. It's got that bland "huh?" name that might cause you to just blow it off or ignore it in the middle of these three modules' release. However, it's deceptively powerful and worth checking out.
      • It requires the URL Rewrite Module above, and hugely builds on its functionality. If you get an error while installing ARR, you need to go install the URL Rewrite Module first.
      • IMPORTANT NOTE: You have to run the MSI from an Administrator Command Prompt. Just running the MSI by double clicking doesn't work. This is a known bug in this CTP. Bummer.

Application Request Routing is interesting. At first I thought it was like NLB (Network Load Balancing), that feature of NT 4.0 that used to be called "Wolfpack." I figured that the great Load Balancing Wars of the '90s were won, and the winner was hardware. I've used Cisco LocalDirector and F5's BigIP in my previous jobs.

ARR is basically a proxy module with load balancing capability that does its routing at Layer 7, rather than Layer 4. That means you make decisions at the HTTP level rather than the IP level. It sits on top of the URL rewrite module, so you can write routing or load balancing rules that can key off of HTTP Headers or Server Vars. You can do Client Affinity via cookie to differentiate between clients behind NAT.  These rules mean it could compliment a system that has an existing hardware load balancer.

If you're familiar with Apache, IIS7's ARR Module kind of combines the functionality you'd find in modproxy, modloadbalance, modproxyhttp along with some other goodness.

It's also a nice reverse proxy if you've ever wanted to do have a smarter IIS7 app router in your home to sit on the outside of your network and route traffic to machines or services inside.

For example, this screenshot shows a routing condition where we want to route folks who have .NET 3.5 on their systems to a separate server. Perhaps a beta site, or a site that has ClickOnce apps or some different functionality. It's totally up to you. You could route folks with certain cookie values, browsers, or  based on path requested.

image

If we had 3 machines in the farm, one IIS7+ARR for routing in front and two other IIS7 machines behind it, I could write a rule that said "don't route requests for images." In this example, I'll have the /images folder served by the ARR machine up front instead.

image

It also has Health Monitoring to check on boxes being down, and you can decide what "healthy" means to you.

ARR is a free download and it plus into IIS7 Manager using the new UI extensibility stuff in IIS7, so it just looks like part of IIS and is managed the same way you manage everything else.

Download

Check 'em out. I'm looking into how I can use ARR to expose my internal Subversion server in a more secure and easily configurable way.

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

July 22nd - Seattle/Redmond/Bellevue Nerd Dinner

July 15, '08 Comments [36] Posted in ASP.NET | Microsoft | Musings
Sponsored By

iStock_000002684567XSmall Are you in King County/Seattle/Redmond/Bellevue Washington and surrounding areas? Are you a huge nerd? Perhaps a geek? No? Maybe a spaz, dork, dweeb or wonk. Maybe you're in town for an SDR (Software Design Review) or the ASPInsiders meeting. Quite possibly you're just a normal person.

Regardless, why not join us for some Mall Food at the Crossroads Bellevue Mall Food Court on Tuesday, July 22nd around 6:30pm?

Here's some links to help you remember and add this to your calendar, or head over to http://nerddinner.events.live.com. There's photos of previous Nerd Dinners up on Flickr thanks to Orcmid.

Add to your calendar

I hope to see you there!

NOTE: Even though I told Live Events this was an Open To Anyone Event, it seems to want invitations. Just leave a comment here and show up on July 22nd at 6:30pm! Everyone is welcome, Microsoft employee or not. The more the merrier.

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 - Everyone Remember Where We Parked (that memory)!

July 11, '08 Comments [37] Posted in BabySmash | Back to Basics
Sponsored By

I added a new feature to BabySmash during lunch, so that if your (baby's) mouse wheel is over a shape and they scroll the wheel, the system will play a sound and zoom that object in or out. The mouse wheel events come REALLY fast, as do most mouse events.

The general idea is this. I've got the PInvoke/DllImport call to the PlaySound API and a couple of helper methods. If the WAV wasn't cached, I'd go get it and store it away. All this code was kind of written on auto-pilot, you know? It's called very quickly in the MouseWheel event and works fine...until it totally doesn't work at all.

I found that when I wheeled the mouse REALLY fast, sometimes it'd get a nasty burst of loud static instead of the nice WAV file playing as I expected.

I store my WAV files inside the resources of BabySmash.exe (for now) so I just hold them in memory. Initially I would pull them out of the resource every time, but then I added some basic caching. (I probably should have used Chad and Jeremy's cache, but anyway)

[DllImport("winmm.dll")]
public static extern bool PlaySound(byte[] data, IntPtr hMod, UInt32 dwFlags);

public static void PlayWavResource(string wav)
{
byte[] b = GetWavResource(wav);
PlaySound(b, IntPtr.Zero, SND_ASYNC | SND_MEMORY);
}

public static void PlayWavResourceYield(string wav)
{
byte[] b = GetWavResource(wav);
PlaySound(b, IntPtr.Zero, SND_ASYNC | SND_MEMORY | SND_NOSTOP);
}

private static byte[] GetWavResource(string wav)
{
//TODO: Is this valid double-check caching?
byte[] b = null;
if (cachedWavs.ContainsKey(wav))
b = cachedWavs[wav];
if (b == null)
{
lock (cachedWavsLock)
{
// get the namespace
string strNameSpace = Assembly.GetExecutingAssembly().GetName().Name;

// get the resource into a stream
using (Stream str = Assembly.GetExecutingAssembly().GetManifestResourceStream(strNameSpace + wav))
{
if (str == null)
throw new ArgumentException(wav + " not found!");
// bring stream into a byte array
var bStr = new Byte[str.Length];
str.Read(bStr, 0, (int)str.Length);
cachedWavs.Add(wav, bStr);
return bStr;
}
}
}
return b;
}

Anyway, I kind of forgot that byte was a value type and in a chat this afternoon Larry made this comment. You might remember that the man responsible for the PlaySound() API is none other than Larry Osterman, who I interviewed last year. Here's our chat transcript:

Larry Osterman‎‎:
My guess is that you're deleting the array b before the PlaySound has completed.
or rather the CLR is.

‎‎Scott Hanselman:
even though it's on the stack?
ah
I get it
the GC is getting to it

‎‎Larry Osterman‎‎:
when you say snd_async, it queues the actual playsound operation to a worker thread.
Yup, GC makes it go away.

When I started going really fast with dozens of calls to PlaySound() a second, I was piling these up and eventually hit the point where one byte[] that was being played would disappear (get garbage collected) and I'd hear the sound of zeros being played. Which sounds much like static. (kidding) ;) I could have made the sound play synchronously, but that doesn't fit well with BabySmash's free-form maniacal button pressing.

Larry suggested I copy the WAV files to a temporary location so they'd be "pinned" down, as there wasn't really a good way to pin these in memory that either of us could come up with. Here's what I did. I grabbed a TempFileName, put the WAV files on disk there and switched the call to PlaySound to the filename overloaded version, rather than the byte[] version. I use TempFileCollection which is helpful because it automatically tries to delete the temporary files when its finalizer runs.

[DllImport("winmm.dll", SetLastError = true)]
static extern bool PlaySound(string pszSound, IntPtr hmod, UInt32 fdwSound);

public void PlayWavResource(string wav)
{
string s = GetWavResource(wav);
PlaySound(s, IntPtr.Zero, SND_ASYNC);
}

public void PlayWavResourceYield(string wav)
{
string s = GetWavResource(wav);
PlaySound(s, IntPtr.Zero, SND_ASYNC | SND_NOSTOP);
}

TempFileCollection tempFiles = new TempFileCollection();

private string GetWavResource(string wav)
{
//TODO: Is this valid double-check caching?
string retVal = null;
if (cachedWavs.ContainsKey(wav))
retVal = cachedWavs[wav];
if (retVal == null)
{
lock (cachedWavsLock)
{
// get the namespace
string strNameSpace = Assembly.GetExecutingAssembly().GetName().Name;

// get the resource into a stream
using (Stream str = Assembly.GetExecutingAssembly().GetManifestResourceStream(strNameSpace + wav))
{
string tempfile = System.IO.Path.GetTempFileName();
tempFiles.AddFile(tempfile,false);
var bStr = new Byte[str.Length];
str.Read(bStr, 0, (int)str.Length);
File.WriteAllBytes(tempfile, bStr);
cachedWavs.Add(wav, tempfile);
return tempfile;
}
}
}
return retVal;
}

It's coarse, but it works, and now I can move on to some cleanup with this bug behind me. The Back to Basics lesson for me is:

  • Don't forget there is a Garbage Collector out there, doing just that.
    • It's easy to forget all about it, but it's so important to know who has a finger on your memory when you're moving back and forth over the unmanaged/managed boundary.
  • Edge cases are no fun.
    • There are always edge cases, race conditions and deadlocks to be found, and I'm sure I've got more left to find! (notice my lack of surety around the lock() call in the comments?)
    • Know your patterns for best practices or, better yet, know where to go to find the answers.
  • Your software typically runs exactly as you wrote it.
    • Even though this was a GC doing something I didn't expect, it was doing its job with the code I provided it. Given how I was using the byte array, it's very deterministic in its behavior.
  • Know what's going wrong before you try to fix it.
    • Once I understood the bug, I was able to reproduce the bug much more easily. "Flakies" are scary, but Bugs are just bugs. If I tried to fix it without understanding it, I'd just be programming by coincidence. (And I may still be! That's the rub.)

Have a nice day!

Technorati Tags: ,,

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

Learning WPF with BabySmash - MVC or MVP and the Benefits of a Designer

July 10, '08 Comments [14] Posted in BabySmash | Windows Client | WPF
Sponsored By

NOTE: If you haven't read the first post in this series, I would encourage you do to that first, or check out the BabySmash category. Also check out http://windowsclient.net/ for more developer info on WPF.

BACKGROUND: This is one of a series of posts on learning WPF. I wrote an application for my 2 year old using WPF, but as I'm a Win32-minded programmer, my working app is full of Win32-isms. It's not a good example of a WPF application even though it uses the technology. I'm calling on community (that's you, Dear Reader) to blog about your solutions to different (horrible) selections of my code. You can get the code http://www.codeplex.com/babysmash. Post your solutions on your blog, in the comments, or in the Issue Tracker and we'll all learn WPF together. Pick a single line, a section, or subsystem or the whole app!

The sheer breadth and depth of WPF is really overwhelming some times. I find myself finding three or four different ways to accomplish basically the same thing and not knowing which one to choose. One wants to balance maintainability and a sense of code aesthetic (Code Smell) as well as focusing on extensibility. All this combined with my own "hacker's impatience" for new features has made messing with BabySmash! a very interesting exercise.

Last week I did a podcast with Felix Corke and Richard Griffin from Conchango on the Developer/Designer relationship and how they navigate their way through the development process and the tools that enable them or hamper their progress.

So far BabySmash has been just basic shapes drawn by me with some help from you, Dear Reader. And let me just say, few of you are designers from the looks of the XAML you've been sending me. ;)

Here's BabySmash before Felix got to the XAML and before I refactored it to support working with him.

image

Here it is now (and it's got animations and interactions, but you'll have to run it to see):

babysmashv2

There's also a bunch of new features that have been made possible by refactoring the project to an MVP (Model-View-Presenter) pattern. But let's start at the beginning.

The Benefits of a Designer and Separated Concerns

I opened my email yesterday and discovered a file sent by Flex called "allshapes1.xaml."

ASIDE: I'm starting to realize that just like regular non-WPF .NET folks can't do everything in Visual Studio, this is especially true when coding in WPF. I'm regularly bouncing between these tools:

I busted out KaXAML and was greeted with this!

default.xaml - Kaxaml

imageAfter I finished crying and wallowing in self pity over my own lack of design skills, I set to figuring out how to exploit benefit from Felix's abilities. When I showed Felix and Richard my initial version of BabySmash last month in Olso, I was using Figures and Shapes and Geometries as my Units of Work. They both suggested I consider UserControls as a better way to work, especially when a Designer gets involved. I mostly ignored them, as I was getting along OK.

However, when Felix's art showed up, it clicked for me. I took each of his shapes and made them UserControls, then modified my factory (FigureGenerator) to make UserControls instead.

UserControls as Unit of Work

The UserControls are self-contained, easy to make from Blend, and as soon as I made one Visual Studio prompted me to reload the project (as Blend and Visual Studio share the SAME csproj/vbproj/sln files) and there it was.

The UserControls compartmentalized everything (duh) nicely so I could start thinking about animations without messing up my business logic or cluttering up my (already cluttered) code.

Adding Animation

Felix drew faces on the shapes, so I asked if he'd include animation. Minutes later he had markup in the XAML to make the eyes blink. Animation in WPF is declarative and time-based (not frame-based). He inserted some key frames and set the animation to repeat forever. Now, the shapes blink occasionally and I didn't have to write any code or worry about threading.

Even better, when I do another animations from code, his animations continue! This means, the shape's faces blink, the shapes fade away after a way, and if you click on them they'll jiggle. Three different animations done in different ways, all happening simultaneously.

Take the Square for example. You start with the basic shape. Notice is has an "x:Name." We can refer to anything with a name later, either in Code or XAML.

<Rectangle x:Name="Body" StrokeThickness="10" Stroke="#ff000000" Width="207" Height="207">
</Rectangle>

Then, he gives it a nice radial fill. Note that he's doing all this work in Blend. I find the XAML building up interesting, myself.

<Rectangle x:Name="Body" StrokeThickness="10" Stroke="#ff000000" Width="207" Height="207">
<Rectangle.Fill>
<RadialGradientBrush MappingMode="Absolute" GradientOrigin="110.185547,455" Center="110.185547,455" RadiusX="98.5" RadiusY="98.5">
<RadialGradientBrush.Transform>
<MatrixTransform Matrix="1,0,-0,-1,-6.685547,558.5" />
</RadialGradientBrush.Transform>
<GradientStop Offset="0" Color="#ffff00ff"/>
<GradientStop Offset="1" Color="#ff9d005c"/>
</RadialGradientBrush>
</Rectangle.Fill>
</Rectangle>

Then he added a face. Note the the Face and Eyes are named.

<Canvas x:Name="Face">
<Canvas x:Name="Eyes">
<Path Fill="#ff000000" Data="...snip..."/>
<Path Fill="#ff000000" Data="...snip..."/>
</Canvas>
<Path Fill="#ff000000" Data="...snip..."/>
</Canvas>

Then he makes the animation a Resource, and sets a Trigger that starts the animation when we're loaded:

<UserControl.Resources>
<Storyboard x:Key="Eyes">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="Eyes"
Storyboard.TargetProperty="(UIElement.Opacity)"
RepeatBehavior="Forever">
<SplineDoubleKeyFrame KeyTime="00:00:02.1000000" Value="1"/>
<SplineDoubleKeyFrame KeyTime="00:00:02.1000000" Value="0"/>
<SplineDoubleKeyFrame KeyTime="00:00:02.300000" Value="0"/>
<SplineDoubleKeyFrame KeyTime="00:00:02.300000" Value="1"/>
<SplineDoubleKeyFrame KeyTime="00:00:10.300000" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</UserControl.Resources>
<UserControl.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard Storyboard="{StaticResource Eyes}"/>
</EventTrigger>
</UserControl.Triggers>

This background animation all lives in the XAML and works without my code having to do anything.

I wanted an interaction animation. I could have probably done it in XAML with a trigger, but in code it was just this. I took the animations from the WPF AnimationBehaviors project on CodePlex but did them with code rather than in XAML so they can apply to any UserControl including ones that I haven't added yet.

void AllControls_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
UserControl f = sender as UserControl;
if (f != null && f.Opacity > 0.1) //can it be seen?
{
Animation.ApplyRandomAnimationEffect(f, Duration.Automatic);
PlayLaughter();
}
}

Still there are a half-dozen different ways to do things, so I'm still trying to find balance. I can see one going too far left or right and doing everything in XAML or Code even when it's uncomfortable. BabySmash is a hybrid until someone can help me understand better when to do one over the other.

MVP and MultiMonitor

Both Ian Griffiths and Karl Shifflett said I needed to refactor things such that I wasn't putting all the logic in the MainWindow's code behind file. They said that WPF lends itself to an MVP (Model-View-Presenter) pattern, even if you're not really strict about it.

I realized I was going to need to do this soon as my first attempt at Multi-Monitor sucked so bad I ended up yanking it. Initial revisions of BabySmash! had a MainWindow class and all the logic in the code-behind, just like I would have in a WinForms application. The application start up and the Application class would spin through all the Monitors making a window for each one. This had a number of problems:

  • The shapes would only appear on the monitor whose Window had focus. You had to change focus with the mouse.
  • You could close a Window or two on secondary monitors without closing all of them.

Now, there's a single Controller class that manages as many Windows as it needs to. The app starts up like this:

private void Application_Startup(object sender, StartupEventArgs e)
{
Controller c = new Controller();
c.Launch();
}

And Controller.Launch looks like:

public void Launch()
{
foreach (WinForms.Screen s in WinForms.Screen.AllScreens)
{
MainWindow m = new MainWindow(this)
{
WindowStartupLocation = WindowStartupLocation.Manual,
Left = s.WorkingArea.Left,
Top = s.WorkingArea.Top,
Width = s.WorkingArea.Width,
Height = s.WorkingArea.Height,
WindowStyle = WindowStyle.None,
Topmost = true
};
m.Show();
m.WindowState = WindowState.Maximized;
windows.Add(m);
}
}

Pretty simple to start. I should have smelt that something was wrong with the initial plan as I felt like I was "chasing my tail" in code, trying to get things to work in the original pattern. When I switched to this pattern things just became easy.

Now, Why is this Model View Presenter and not Model View Controller (especially considering I called the class Controller)? Well, Phil does a good job answering this:

With MVC, it’s always the controller’s responsibility to handle mouse and keyboard events. With MVP, GUI components themselves initially handle the user’s input, but delegate to the interpretation of that input to the presenter. This has often been called “Twisting the Triad”, which refers to rotating the three elements of the MVC triangle and replacing the “C” with “P” in order to get MVP.

Now I need to go learn more about Supervising Controller and Passive View as Martin Fowler suggested retiring the MVP pattern in favor of those two variants. The code is still in a sloppy stage (up on CodePlex) but I'd love to have someone (Phil?) who is familiar with pure instances of these patterns to help me tidy it up. I didn't take testing into consideration before (mistake) and I need to get back on the Righteous Path otherwise the technical debt is going to crush me. That's what I get for not going TDD from the start.

The MainWindow's code-behind is just a bunch of small methods that delegate off to the Controller. If there are n MainWindows there's still just the single Controller. MainWindow is full of these kinds of things:

protected override void OnKeyUp(KeyEventArgs e)
{
base.OnKeyUp(e);
e.Handled = true;
controller.ProcessKey(this, e);
}

Every method is less than 20 lines, and most are really simple and boring, which is good.

private void AddFigure(FrameworkElement uie, string s)
{
FigureTemplate template = FigureGenerator.GenerateFigureTemplate(s);
foreach (MainWindow m in this.windows)
{
UserControl f = FigureGenerator.NewUserControlFrom(template);
m.AddUserControl(f);

f.Width = 300;
f.Height = 300;
Canvas.SetLeft(f, Utils.RandomBetweenTwoNumbers(0, Convert.ToInt32(m.ActualWidth - f.Width)));
Canvas.SetTop(f, Utils.RandomBetweenTwoNumbers(0, Convert.ToInt32(m.ActualHeight - f.Height)));

Storyboard storyboard = Animation.CreateDPAnimation(uie, f,
UIElement.OpacityProperty,
new Duration(TimeSpan.FromSeconds(Settings.Default.FadeAfter)));
if (Settings.Default.FadeAway) storyboard.Begin(uie);

IHasFace face = f as IHasFace;
if (face != null)
{
face.FaceVisible = Settings.Default.FacesOnShapes ? Visibility.Visible : Visibility.Hidden;
}
f.MouseLeftButtonDown += HandleMouseLeftButtonDown;
}
FiguresCount++;
PlaySound(template);
}

So far my biggest problems are moving things around, trying to decide "who is responsible for what." Given Animations, Sounds, Shapes, Faces, and all that, where and who is responsible for what, while keeping an eye open for extensibility.

The Little Niceties - Enabling a TextBox if a CheckBox IsChecked

One little aside to end on. Just when I'm getting really pissed at WPF and I'm ready to give up, something simple and cool happens where I realize I'm starting to "get" it.

For example. I've got this Options Dialog that you might remember Jason Kemp refactored. All the controls live inside a Grid and that Grid has a "DataContext" that is my Settings object. All the controls get bound to the object and I don't have to do any loading of values or pulling of values. It just works.

I added that last checkbox and a new option where I wanted to Fade Shapes Away in x seconds. I wanted to disable the TextBox if the Checkbox was not checked. This is the kind of typical operation you might find yourself writing code for in WinForms. You'd hook up events to watch if it's Checked or not, then set the Enabled property of the TextBox, and you also have to watch for the initial load of state. It's not hard in WinForms, but it's irritating, tedious and it's in two places in the code behind.

 Baby Smash! - Options (2)

Even though the DataContext (the thing we are data-binding to) is the Settings object, I can bind objects together by using the ElementName. Check this out. Look at the TextBox's IsEnabled property.

<StackPanel Orientation="Horizontal" 
Grid.Row="6" Grid.ColumnSpan="2" HorizontalAlignment="Stretch">
<CheckBox x:Name="FadeChecked" Margin="15,0,0,0"
IsChecked="{Binding Path=Default.FadeAway,Mode=TwoWay}" >
Fade Shapes Away in</CheckBox>
<TextBox Margin="5,0,0,0"
Text="{Binding Path=Default.FadeAfter}"
IsEnabled="{Binding ElementName=FadeChecked,Path=IsChecked,Mode=TwoWay}"
Height="20" Width="25" />
<TextBlock>secs.</TextBlock>
</StackPanel>

It's a tiny victory, but it made me happy.

Related Links

Technorati Tags: ,

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

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

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