URL Rewriting And ASP.NET 2.0's Site Navigation

posted on 03/02/06 at 10:37:56 pm by Joel Ross

I'm going to be blogging about Tourney Logic and Tourneytopia a lot in the near future, so be forwarned. It's not because I'm pushing it on you. It's because that's what I'm doing right now. It's actually why I haven't been blogging a lot lately, but I recently ran across a few things that were complex enough that I want to write them down before I forget what I did to fix them.

So, today's post is brought to you by URL Rewriting. Specifically, we installed the Tourney Pool Manager in a subfolder: /Pools. Then we set up a sitemap file, which is based on our files (obviously). Here's a sample of our files: /Pools/Default.aspx, /Pools/SubmitPicks/Picks.aspx, /Pools/Pool/Standings.aspx, etc. You get the idea. But the installation needs to be able to support multiple pools from the same installation. In my mind, there were two logical ways to do this: First, we use querystrings: /Pools/Default.aspx?PoolId=NCAA. Not bad, but which looks better? That, or this; /Pools/NCAA/Default.aspx. That brings us to the second option - URL rewriting.

I found a nice library that provided Regex support. It would basically pull the Pool Id out of the URL and direct you to the correct page. Then, we handle the Application_BeginRequest event, and if the RawUrl and the actual URL are different, we know it was rewritten. We then pull the Pool Id out, and shove it in the Context.Items collection for later use. This is where the fun begins. Some background: We set up our site map based on the actual files, not with the Pool ids. We don't know the pool ids ahead of time, so we had entries in our site map like this: ~/Default.aspx, ~/SubmitPicks/Picks.aspx, ~/Pool/Standings.aspx.

Now to the issues. First, the simple one. If you use the Menu control, by default, the links will be exactly what's in your site map file. So we lose the pool id. We did that for a while, and stored the pool id in a cookie. That's fine, until people are in multiple pools. Or want to send a link to someone else. So we had to include the pool id in the links that were generated. Easy enough. There's an event when each item in the menu is databound. Handle that event, and change the URL to include the pool id, which, since the menu is only in our header and footer, it's all contained in our master page.

The Login control was next. It's roughly the same - except there's no event, so I just handled it's pre-render event, and changed the URLs we added to the template there. There's two for us - the login link itself, and then the account page, which is seen when you're logged in. The logout link is a post back, and we redirect to the RawUrl (which already includes the "pretty" url) once you're logged out.

Now for the tricky one. The breadcrumb. We think it's a nice thing to have on the page, so you can keep track of where you are. Here's the problem we had. Every page has a property on it - Page.SiteMap. It hands controls two things: the RootNode and the CurrentNode. The RootNode works fine because it's not based on where you are. But the CurrentNode is determined by the SiteMapProvider. We're using the XmlSiteMapProvider, and what it does is look at the Request.RawUrl and compare that to the site map file. If it finds a match, then that's the current node. If not, the current node is null.

Well, that presents a problem now, doesn't it? Our RawUrl isn't in the site map - we've got that extra pool id in it. So, for a while, it just wasn't showing up. Once we realized that, it's simply a matter of creating our own SiteMapProvider to handle it for us. Obviously we don't need to create it from scratch, so we inherited from the XmlSiteMapProvider, and just overrode FindSiteMapNode(string rawUrl). We take that raw URL, and remove the pool id from it. That allows the XmlSiteMapProvider to operate as usual. And of course, we had to override the databound event for the breadcrumb to change the URLs written to the page.

Now, some may be asking why we didn't just go further and change the URLs of the SiteMapNodes in a custom provider. Good question. I tried. And tried. And I got it working, but there was a slight problem. ASP.NET only goes back and reads the nodes from web.sitemap when the file is changed or the application is restarted. So if I went in and changed all of the URLs to have a particular pool id in it, then it would screw up all of the other pools. I tried - I even thought I had it working, but then I had URLs with multiple different pool ids in it at the same time. That was a problem. That's why I came up with what I did.

I'm deploying the last changes that fixed a bunch of navigation issues tonight. Most likely, by the time you read this, it'll be out. But if you go there fast enough (or have been there in the past few days), you may have seen that if you are logged in as an admin of a pool, that some pages didn't work. Because of the way I was overriding FindSiteMapNode, something wasn't happening that should have been, and you only noticed it when you were logged in as the pool admin. That's when I changed my override to just pull the pool id out of the rawurl and then check that new URL. All problems solved!

Technorati Tags: | | | |

Categories: ASP.NET