Scott Hanselman

ASP.NET MVC and the new IIS7 Rewrite Module

October 10, '08 Comments [9] Posted in ASP.NET | ASP.NET MVC | IIS
Sponsored By

Last year I noticed that there were 11 ways to get to my blog. Literally 11 different URLs and it wasn't helping me my ranking in the search engines. I wrote about this in detail and how I used ISAPI_Rewrite to fix it up.

Fast forward to this year and the IIS7 team has been taking advantage of IIS7's modular design to release a bunch of new modules out-of-band.

Both the newest ISAPI_Rewrite and Apache's standard mod_rewrite module uses distributed configuration files or .htaccess files.

Here's just part of my .htaccess file that makes sure that all the incoming URLs end up at the final canonical http://www.hanselman.com/blog/

RewriteRule /blog/default\.aspx http\://www.hanselman.com/blog/ [I,RP] 
RewriteCond Host: ^hanselman\.com
RewriteRule (.*) http\://www.hanselman.com$1 [I,RP]

RewriteCond Host: ^computerzen\.com
RewriteRule (.*) http\://www.hanselman.com$1 [I,RP]

RewriteCond Host: ^www.computerzen\.com
RewriteRule (.*) http\://www.hanselman.com/blog/ [I,RP]

After you've installed the IIS7 Rewrite module, you can bring rules in a couple ways. The nicest is by importing them directly. Notice the tree view in the screenshot below. It gets updated in as you type.

Note that the importer only really understands rules in the mod_rewrite syntax. It doesn't fully support ISAPI_Rewrite so some things like Host: and [I] aren't supported in this release, but I'm hoping (and I've formally asked) that they'll support them for the final RTW (Release to Web). If you have ISAPI_Rewrite rules, you can either convert them then manually edit them to tidy up (what I did), or you can convert them to mod_rewrite syntax first.

For example, in the rule importer UI I could have replaced the ISAPI_Rewrite directive "Host:" with "%{HTTP_HOST}" and "[I]" with "[NC]" (meaning case insensitive). Or, I can just edit the incorrectly imported rules.

image

This is useful for importing existing rules like mine, but it's still hard since we're talking obscure formats left and right. There's also an Add Rule wizard:

Add rule(s)

It's REALLY easy with the User Friendly URL dialog to interactively create mappings between the URLs your app uses and the URLs you want. See in the screenshot below how the combo-box is dynamically populated based on the example I put in the top text box?

Add rules to enable user friendly URLs (2)

The User Interface for this module is surprisingly deep in functionality. There's a Regular Expression tester built into it, which makes Regular Expressions suck by about -2.

Test Pattern

ASP.NET MVC and SEO

I noticed a post by Jason Young recently on ASP.NET and SEO (Search Engine Optimization). He's concerned about trailing slashes

Ultimately I don't think it's that big of a deal since the URLs that your application generates are always consistent. Your app is what teaches search engines what to ask for. As long as your application is generating URLs that look the way you want them, you're cool.

The only real problem happens when other humans link to you and they make a mistake. Perhaps they include a trailing slash when you don't want one. Still, not a huge deal, but if you feel strongly about it, that's where a rewrite module comes in useful. I think that the Rewrite module would fit Jason's requirements.

Matt Hawley at eXcentrics World wrote a Legacy Route using ASP.NET Routing which is a clever idea as well. He could have certainly used this Rewrite Module, but ultimately as long as you're returning HTTP 301's (redirect permanent) or HTTP 302 (temporary redirect) as you fine appropriate, then use what makes you happy.

What's really important is that both these guys respect the permalink. A 404 is never a good thing.

Every site is different, but if I add a basic rule like this to my ASP.NET MVC site…

image

…and request http://localhost/rewritetest/Home/Index, the resulting HTTP Headers look like this, as the module forces the trailing backslash (of course you could also force NO backslash if it makes you happy):

GET /rewritetest/Home/Index HTTP/1.1
Accept-Encoding: gzip, deflate
Host: localhost
Connection: Keep-Alive

HTTP/1.1 301 Moved Permanently
Content-Type: text/html; charset=UTF-8
Location: http://localhost/rewritetest/Home/Index/
Server: Microsoft-IIS/7.0
X-Powered-By: ASP.NET

I'm not a regular expression expert, but searching the web for "mod_rewrite" rules will keep you busy for next 50 years. Here my favorite reference for .htaccess and mod_rewrite rules.

Learning the basics of the IIS7 Rewrite Module:

Functionality reference

Video walkthrough

Enjoy.

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

PDC: Microsoft .NET Framework: Overview and Applications for Babies

October 9, '08 Comments [10] Posted in ASP.NET | ASP.NET Dynamic Data | ASP.NET MVC | PDC | Windows Client
Sponsored By

image The PDC 2008 schedule is locked down. Looks like my main talk is on the first day, 10/27 at 5:15pm-6:30pm. Come to the talk and we'll all go out for tacos afterwards, maybe a party. :)

The scheduling tool is a little wonky, but if you want to add my talk to your sessions, you can go to https://sessions.microsoftpdc.com/public/sessions.aspx, sign in, search for "hanselman" then click "Add to My Sessions" in the lower right corner.

Here's the title and abstract:

Microsoft .NET Framework: Overview and Applications for Babies

Join Scott Hanselman for this lots-of-code-minimal slides talk that walks through the sheer joy of building out a .NET Framework application with Visual Studio using many of the new advances in the .NET Framework 3.5SP1 and 4.0. We have a data layer with Entity Framework, use REST web services with WCF and ADO.NET Data Services, write an ASP.NET site for reporting using Dynamic Data and MVC. All the data will come from a WPF client application and a Silverlight application that the audience will run live! All this, plus it's an application that babies and toddlers will love!

The back story here is in July, Dan Fernandez and Brian Keller, who are both Pure Evil© had the idea to send folks who watch "This Week on C9" to my BabySmash Feedback page and vote up a phony feature, asking for "Massively Multiplayer Online BabySmash so Babies can Smash together."

That first episode is here and they had the idea at 16:50-18:25. On the following week's show they noticed (4:32-5:15) that the new MMO BabySmash feature became the #1 feature with a 588% increase.

Of course, I'm an idiot, and I didn't know ANY of this. I thought that people (insane people) really wanted this feature. Later, on another show, I came on This Week on Channel9 and learned of their evil (27:25-30:05). They TOTALLY got me.

Then I started thinking about PDC and figured, why not? Why not add some networked ideas to BabySmash to showcase the .NET Framework.

My talk at PDC will show a bunch of 3.5 SP1 features in BabySmash! like WPF, plus I've added ADO.NET Data Services, Entity Framework, ASP.NET MVC, Charts and Graphs, Silverlight, Virtual Earth, AJAX, and jQuery. I'll also show a Silverlight version of BabySmash that talks to the same server-side endpoints, and we'll all (the audience) run BabySmash Silverlight on our laptops during the talk (better than just checking your email, which is what you usually do in talks) and see if we can't crush my server live. Then I'll talk about new .NET 4.0 features that I could use to take the whole solution to the next level.

I'm hoping it'll be a fun way for us to see what is in 3.5SP1 and how to use it as well as what's coming in .NET 4.0 and how it could improve out application. It's definitely not a Northwind Demo.

I hope I see you there.

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

Meme Time: Growing Up Geek

October 4, '08 Comments [22] Posted in Musings
Sponsored By

Hanseldork? Ok, it's meme time. Now, you may be surprised to hear that under my cool exterior, I was a nerd in school. No, no, you say! It's not so! Surely you were the most popular kid, Scott? You weren't beaten brutally each day for having glasses that weighed more than your huge head? Nonsense!

Alas, it's true. It's probably true for you as well, Dear Reader. Or, that's what I like to tell myself. ;)

I've written before about how I got into computers, some of the OS's I've run, that I love gadgets, and how my fifth grade teacher took me from an "at risk youth" to a huge dork. I've even done a podcast with my Dad about what it's like to raise a nerd. Of course, the Nerds will Inherit the Earth, right?

Did you grow up a geek?

Let's see what you looked like as a young burgeoning nerd or nerdette.

I'm tagging Phil Haack, Rob Conery, Leon Bambrick, Greg Hughes, and Brad Wilson.

Of course, if you're not tagged, I want to see pics of you as a kid-nerd regardless. Go dig in that shoebox you know you still have. Call your Mom, look at the photo album, and link to this post or leave a link in the comments.

Let's see some nerdy childhood pictures folks!

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

Web Platform Installer: Trying to make it easier to setup for web development

October 3, '08 Comments [15] Posted in ASP.NET | IIS | Tools
Sponsored By

There's a renewed focus, in my opinion, to make things easier to find around The Big Blue Monster. I'm working with a bunch of folks on a more official version of http://www.smallestdotnet.com and some changes around making the .NET Framework easier to find, as a small example.

Getting a machine up to speed for Web Development is another thing that's kind of a hassle because you need to go get (and know to go get) IIS7, Visual Web Developer 2008 Express Edition, SQL Server 2008 Express Edition and the .NET Framework, yada yada yada.

There's a new site at http://www.microsoft.com/web and a new (beta) of the Microsoft Web Platform Installer (blog announcement). It's basically a super bootstrapper that keeps track of where to get stuff and organizes them as profiles.

Microsoft Web Platform Installer

If I select "Your Choice" I get a complete list from a catalog of things that can be downloaded. I can auto-select options from a dropdown like "PHP Developer" or "Classic ASP Developer." Cool that those options are there as well as ASP.NET Developer. There's a manifest that it downloads to get the latest versions of each of these.

Web Platform Installer Choose Components

On the Web Server tab, it'll pick the right IIS modules you'd need to get a site up, but it also shows as options some of the more interesting (and not well publicized) modules like ARR and BitRate Throttling that have been released since IIS7 came out.

If you're running a Web Development shop, it's certainly a quick way to get everything you'd need installed, including the free version of Visual Studio Web Express.

Check it out, and if you have any trouble or find anything interesting, you can report it directly to the team at the Web Platform Installer Forum. If you like it or hate it, let them now. It'd be interesting to see how extensible it can be and if they choose to extend it other developer products.

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

Plug-In Hybrids: ASP.NET WebForms and ASP.MVC and ASP.NET Dynamic Data Side By Side

October 1, '08 Comments [32] Posted in ASP.NET | ASP.NET Dynamic Data | ASP.NET MVC | Web Services
Sponsored By

I wanted to write this post because there seems to be a lot of confusion about how ASP.NET WebForms, MVC and Dynamic Data all fit together. Folks may have WebForms applications and want to start looking at ASP.NET MVC but aren't sure if they need to start from scratch or not. Folks might look at Dynamic Data but mistakenly assume it's just for scaffolding administrative sites.

When you're in Visual Studio and you hit "File | New Project" you're given a number of choices. This is a starting point, but it easy to assume that this point is a fork in the road and you can mix them up.

File New Project Dialog

You can (and should) feel free to have Hybrid applications. You can have single Web applications that have all of these inside them (if it makes you happy):

  • ASP.NET MVC
  • ASP.NET WebForms
  • ASP.NET Web Services (ASMX)
  • WCF Services
  • ASP.NET Dynamic Data
  • ASP.NET AJAX

Here's an extreme example. I'll pick ASP.NET MVC Application to start with, but it doesn't really matter. If you're confused as to what web.config entries are required, just make one of each kind of project and compare the files with your favorite diff tool (mine is Beyond Compare).

Ok, so first I've got a Hello World ASP.NET MVC application:

Visual Studio Solution with just ONE MVC application

I'll add a quick LINQ to SQL database connection to the AdventureWorksLT database so I have something to query.

Next, I'll throw a quick ASMX Web Service into this ASP.NET MVC application that returns some data. I create it via File | New Item and select Web Service. I make a quick LINQ expression and a smaller class SmallProduct (a LINQ projection) that is returned. I also make it ScriptService, so I could call it via AJAX if I liked.

namespace Overkill
{
public class SmallProduct
{
public string Name { set; get; }
public string Color { set; get; }
public decimal Price { set; get; }
}

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
[System.Web.Script.Services.ScriptService]
public class Products : System.Web.Services.WebService
{
[WebMethod]
public List<SmallProduct> GetProductsByColor(string color)
{
AdventureWorksDataContext d = new AdventureWorksDataContext();
return (from p in d.Products
where p.Color == color
select new SmallProduct
{
Name = p.Name, Color = p.Color, Price = p.ListPrice
}).ToList<SmallProduct>();
}
}
}

What does this have to do with ASP.NET MVC? Nothing. That's the point. This is an ASP.NET 2.0 style ASMX Web Service with an ASP.NET AJAX ScriptService attribute using a .NET 3.x LINQ Query to return the data, all living in an ASP.NET MVC application.

Why doesn't ASP.NET MVC grab the request? Two reasons. First, there's an option on RouteCollection called RouteExistingFiles. It's set to false by default which causes ASP.NET MVC to automatically skip routing when a file exists on disk.

if (!this.RouteExistingFiles)
{
string appRelativeCurrentExecutionFilePath = httpContext.Request.AppRelativeCurrentExecutionFilePath;
if (((appRelativeCurrentExecutionFilePath != "~/") && (this._vpp != null)) && (this._vpp.FileExists(appRelativeCurrentExecutionFilePath) || this._vpp.DirectoryExists(appRelativeCurrentExecutionFilePath)))
{
return null;
}
}

Because the default Route in Global.asax isn't greedy enough to care even if we were routing all requests:

public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);

}

However, in my personal experience, File.Exists is a very expensive operation (very is relative…it's expensive because it hits the disk at all). A best (and example of attention to detail) practice would be to put in an IgnoreRoute call for those pages, directories, and/or HttpHanders. For example:

routes.IgnoreRoute("{myWebForms}.aspx/{*pathInfo}");
routes.IgnoreRoute("{myWebServices}.asmx/{*pathInfo}");
routes.IgnoreRoute("myCustomHttpHandler.foo/{*pathInfo}");
routes.IgnoreRoute("Contents/{*pathInfo}");

Here I'm ignoring .aspx, .asmx, a custom HttpHandler with a custom extension and the whole of the Contents folder. I might even want to set routes.RouteExistingFiles = true which would turn off the File.Exists check and put ALL the pressure for routing on my routes. I'll need to be more careful and explicit, but that's rarely a bad thing. You could also just structure your site such that all your non-MVC things live in their own folder. It's up to you.

Now, let me add a WebForm and *gasp* drag a GridView into it. Wow, I'm a bad person, I've just used the Designer. I was productive so, but what price my immortal soul? ;)

WebForm with a GridView

Seriously, though, use what makes you happy. This grid is kind of lame, so I'll add some ASP.NET DynamicData. However, while you usually see Dynamic Data from a File | New Application point of view, I'm going to just bring a DynamicDataManager control onto the page. You'll also want to confirm that you have DynamicData controls listed in your web.config:

<pages>
<controls>
<add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
<add tagPrefix="asp" namespace="System.Web.UI.WebControls" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
<add tagPrefix="asp" namespace="System.Web.DynamicData" assembly="System.Web.DynamicData, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
</controls>
</pages>

I'll add a few things to my WebForms page:

<div>
<asp:DynamicDataManager ID="DynamicDataManager1" runat="server" />
<asp:GridView ID="GridView1" runat="server" DataSourceID="GridDataSource"
AutoGenerateColumns="false" DataKeyNames="ProductID" AllowPaging="true" AllowSorting="true">
<Columns>
<asp:DynamicField DataField="Name" />
<asp:DynamicField DataField="ProductNumber" />
<asp:DynamicField DataField="Color" />
<asp:DynamicField DataField="ListPrice" />
</Columns>
</asp:GridView>
<asp:LinqDataSource ID="GridDataSource" runat="server" EnableDelete="true"
ContextTypeName="Overkill.AdventureWorksDataContext" TableName="Products"/>
</div>

In the Global.asax.cs, I'll add these two lines to let the DynamicData system know that we're working on this DataContext:

MetaModel model = new MetaModel();
model.RegisterContext(typeof(AdventureWorksDataContext), new ContextConfiguration() { ScaffoldAllTables = false });

Then, the most important part, I'll want to bring in the ~\DynamicData folder, since that's where DynamicData finds all of its templates. For this example, I really only need ~\DynamicData\FieldTemplates as I'm only using the smallest bit of Dynamic Data functionality.

To do this easily and quickly, I usually make a throwaway new DynamicData Web Application in another instance of Visual Studio, making sure to give it the same name (and hence, namespace) as the one I'm working on. Then I just drag that project's DynamicData folder over into my original application, and ensure that all the designer files and code-behinds are included in the project (Show All files, then right click each one, Include in Project). The rumor is that there will be a quick way in the future to bring a fresh DynamicData folder into an existing app.

Now, I'll hit my page again and then I get shiny Dynamic Data goodness.

DynamicData

At this point I've got a WebForm, Dynamic Data, and a totally random unused WebService living inside an ASP.NET MVC application. Of course, now this begs the question "Is this an ASP.NET MVC application."

Oh, you wanted MVC used also? ;) I'll add a quick Products method to the HomeController:

public ActionResult Products(string color)
{
AdventureWorksDataContext d = new AdventureWorksDataContext();
var smallProducts = (from p in d.Products
where p.Color == color
select new SmallProduct
{
Name = p.Name,
Color = p.Color,
Price = p.ListPrice
}).ToList<SmallProduct>();
return View("Products", smallProducts);
}

Then a quick view, making sure it's derived from ViewPage<List<SmallProduct>>:

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="Products.aspx.cs" Inherits="Overkill.Views.Home.Products" %>
<%@ Import Namespace="Overkill" %>
<asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
<h2><%= Html.Encode(ViewData["Message"]) %></h2>
<p>
<ul>
<% foreach (SmallProduct p in ViewData.Model)
{ %>
<li><%=p.Name %> <%=p.Price.ToString("C")%></li>

<% } %>
</ul>
</p>
</asp:Content>

And that works also, visiting /Home/Products, ensuring there's a route that matches. I'll make this Route overly specific:

routes.MapRoute(
"ProductsWithColor",
"Home/Products/{color}",
new { controller = "Home", action = "Products", color = "Red" }
);

And it renders like this:

MVCList

I hope this helps and it's more clear now that it's just "an ASP.NET application."

You can feel free to mix and match. Not everyone can (or should) rewrite an existing ASP.NET application, so it is nice that everyone can use some new features in the same ASP.NET application as their existing functionality.

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.