@ayende You ought to try Mercurial. in reply to ayende 2 weeks ago
26
Oct

How to validate a URL in .NET

System.Uri.TryCreate.

You don’t need to use regular expressions.

More generally, if you are trying to do something extremely common, the chances are that whatever framework you’re using, there’s a method or function somewhere in there which will do it for you. And it will almost certainly do it much better than your home-brewed solution will.

08
Sep

Paths and file locations in ASP.NET

There are loads of ways to find the path — either the URL or the physical path — to a page, user control or other file in an ASP.NET application. Unfortunately, however, the documentation doesn’t do a brilliant job of explaining them to you. There are also several different scenarios, depending on whether you are using conventional web forms, or URL rewriting, or Server.Transfer, or ASP.NET MVC. So I thought I’d better write down an overview of some of them for reference.

Scenario 1: direct request for a web form.

Just suppose for a minute that you have been contracted to rewrite Wikipedia in ASP.NET. So, for instance, you may end up with the page “What Wikipedia is Not” (aka “WP:NOT” or “Wikipedia’s attempt to get into the Guinness Book of Records for the most lies per kilobyte on a web page”) at http://en.wikipedia.org/wiki.aspx/WP:NOT.

In this case, you have several different properties of HttpContext.Current.Request containing different representations of it.

  • Request.RawUrl = "/wiki.aspx/WP:NOT" represents the path and query string parts of the URL. In this case, of course, there is no query string, but if there were, you might see it set to something like "/wiki.aspx/WP:NOT?mode=edit".
  • Request.Path = "/wiki.aspx/WP:NOT" represents the path part of the URL.
  • Request.FilePath = "/wiki.aspx" represents the part of the path to the file (in this case wiki.aspx) that is handling the request.
  • Request.PathInfo = "/WP:NOT" is a diff of Request.Path and Request.FilePath, giving the extraneous bit of the path that does not refer to a file in the file system.
  • Request.PhysicalPath = "c:\inetpub\wwwroot\wiki.aspx" is the physical path to the file that is servicing the request.

Case 2: Server.Transfer() and Server.Execute()

Sometimes, you may want to transfer control from one file to another. Let us suppose, for instance, that you decide to use several Web forms: one for articles, one for special pages, and one for article history. You do a few simple checks in wiki.aspx and decide to transfer control to another file, say, article.aspx, using Server.Transfer(). Then, another property of Request comes into play.

  • Request.CurrentExecutionFilePath = "/article.aspx" represents the path to the file that is currently handling the current part of the request.
  • Request.FilePath = "/wiki.aspx", however, remains unchanged.
  • Request.PhysicalPath = "c:\inetpub\wwwroot\wiki.aspx" also remains unchanged.
  • Request.AppRelativeCurrentExecutionFilePath = "~/article.aspx" is the same as Request.CurrentExecutionFilePath, but relative to the root of the web application, as defined in IIS. If your application were rooted at, say, "/wiki" then Request.CurrentExecutionFilePath would be "/wiki/article.aspx".
  • Everything else remains unchanged.

Note that Request.CurrentExecutionFilePath is always in use: if there has been no call to Server.Transfer it will be the same as Request.FilePath.

Case 3: URL rewriting

So you have this lovely new ASP.NET version of Wikipedia up and running, it works much more smoothly, has much less downtime, and runs on only a dozen or so servers rather than a hundred. Then, you start getting hate mail from irate Wikipedians, many of whom are open source zealots who are definitely not NPOV on Microsoft Windows. Jimbo and the Arbitration Committee get involved and demand you rewrite those URLs to cover up the fact that the Wikimedia Foundation has gone over to the Dark Side.

So, you take the original URL http://en.wikipedia.org/wiki/WP:NOT and transmogrify it into http://en.wikipedia.org/wiki.aspx?ns=Wikipedia&pg=What_Wikipedia_is_Not using a discreet call to Context.RewritePath.

Suddenly, everything changes!

  • Request.RawUrl = "/wiki/WP:NOT" represents the original path and query string parts of the URL. In actual fact, Request.RawUrl always represents exactly what you typed into your browser.
  • Request.Path = "/wiki.aspx" represents the path part of the URL.
  • Request.FilePath = "/wiki.aspx" represents the part of the path to the file (in this case wiki.aspx) that is handling the request.
  • Request.PathInfo is blank. When you use URL rewriting you have to point to a real file: you can’t use a PathInfo — that’s why you need to use a query string instead.
  • Request.CurrentExecutionFilePath = "/wiki.aspx" until you call Server.Transfer, when it changes.
  • Request.QueryString = "ns=Wikipedia&pg=What_Wikipedia_is_Not" is of course changed after the URL rewrite.
  • Request.PhysicalPath = "c:\inetpub\wwwroot\wiki.aspx" is, again, the physical path to the file that is servicing the request.

Case 4: ASP.NET MVC

So how on earth, you may be asking, does all this work with ASP.NET MVC? After all, it doesn’t use Web forms in the same way — URLs map to controllers, which then decide which views to render themselves.

Well here’s the skinny:

  • Request.RawUrl = "/wiki/WP:NOT" contains the raw URL (path and query string) as before.
  • Request.Path, Request.FilePath, and Request.CurrentExecutionFilePath, all contain the “path” part of the URL without the query string. They will all be set to "/wiki/WP:NOT"
  • Request.PathInfo is blank. ASP.NET MVC handles path info through the routing engine and passes it in the parameters for your controller.
  • Request.PhysicalPath = "c:\inetpub\wwwroot\wiki\WP:NOT" is NOT the physical path to the file that is servicing the request. Controllers may decide to render one of any number of views or other results, and they need not even be Web forms — they could be raw text content (from a ContentResult), or a redirect (from a RedirectResult or a RedirectToRouteResult) or a JSON string (from a JsonResult) and they aren’t associated with a physical file on the filesystem at all.

Case 5: ASP.NET MVC with URL rewriting and/or Server.Transfer

I shall leave this one as an exercise for the reader. No doubt there is someone, somewhere, who is doing this, for reasons that completely befuddle me. After all, I’d have thought that the whole MVC pattern renders URL rewriting and Server.Transfer pretty much redundant.

Case 6: Requests for a directory’s home page

This is much the same as the above, except that ASP.NET inserts the name of the home page — typically default.aspx — into Request.RawUrl, and, by extension, everything else. Obviously, this does not apply to ASP.NET MVC.

30
Apr

Why I hate web.config

One thing that is vital when deploying web applications is that you should be able to reduce the process of deploying upgrades and changes to as few steps as possible. Furthermore, every step should be a no-brainer — so simple that the scope for fat fingering something is strictly limited.

This kind of thing is acceptable:

  1. Get the appropriate stable build from your daily build server.
  2. FTP it onto the web server into a directory in an appropriate location. (Even better: have an option in your build script to do this automatically.)
  3. Change the IIS settings to point to the new version.
  4. You’re done!

Now in order to do this effectively, you need to build some foundations into your project. You need to isolate every setting that varies between your production environment and your developer box and put them in a separate location outside the website’s hierarchy that does not change from build to build.

These settings are purely concerned with server-specific configuration settings. They change from one machine to the next and will be different between developer machines and the production server. Examples include connection strings, SMTP server details, custom errors and trace settings. They aren’t necessarily stored in your source control, except as a sample file for documentation purposes, and they should definitely not be deployed afresh to the server with every build.

There are other settings that are tied much more closely to the code itself. Examples include HTTP handlers and modules, assemblies referenced in the <compilation> section, and all the additional stuff that ASP.NET Ajax or ASP.NET 3.5 adds to tell it that you’re using the C# 3.0 compiler, not the C# 2.0 compiler. These settings may change from one build to the next, but they are the same on every machine where they are used. They are, to all intents and purposes, code, and should be treated as such, kept in your source control, and deployed unmodified to the server with every new build.

Unfortunately, web.config mixes the two willy-nilly in a thoroughly cavalier way, with the result that there are several additional, more complex and error-prone steps that you need to take:

  1. Locate the previous build.
  2. Copy the web.config file into the new build.
  3. Merge in the changes manually.

These steps are less straightforward and provide much more scope for error. What if you forget to do them, make a dog’s dinner of merging in the changes, or worse, introduce some subtle and mysterious bug that isn’t there on your development machine?

ASP.NET 2.0 added a new feature to sort this mess out. You can now specify an alternative file for your <appSettings> section. By doing <appSettings file="..\myappsettings.config" /> you can even specify a file outside your web application root. Whoopee! Problem solved!

Not so fast. What about the settings that don’t fit in to <appSettings>? For example, connection strings now go in the <connectionStrings> section; custom errors should be enabled on the server but disabled on your development box; tracing should be enabled on your development machine but not on the server; and so on.

It turns out that these too have an option to allow you to reference external files. You can set, say, <connectionStrings configSource="blah" /> to put your connection strings in a separate file. Unfortunately, unlike with <appSettings>, you can’t put this outside your application root.

Meh. Why not??? This is a major pain in the neck — especially for <connectionStrings>.

To make matters worse, there are some elements that straddle both camps. <compilation> is the most obvious example. It needs to have the attribute debug="true" on a development server, but in production you will need to insert debug="false" for improved performance. However, within your <compilation> element, you have a list of additional assembly references for things such as the ASP.NET Ajax extensions. And you can’t put these in a separate file.

All in all, configSource and <appSettings file="blah" /> go some of the way towards solving the deployment problem. Unfortunately, they still have limitations that are awkward and hobble the process and are a major annoyance.

14
Jan

Missing ASP.NET tab in IIS on Windows Server 2003

One thing that’s been at the back of my mind since I upgraded to Windows Server 2003 is that the ASP.NET tab in IIS had taken a walk. Even running aspnet_regiis -i did nothing to solve the problem. Up till now I had no cause to fix it, but today I had to troubleshoot one of our apps that is (still!) using ASP.NET 1.1. This meant I needed to configure it to run version 1.1 in order to debug it using Visual Studio 2003. Might as well get to the bottom of this while I’m at it.

It turns out the problem is something to do with VMWare Server, which I also have running on the same machine, running a couple of instances of Ubuntu for my PHP work. For some reason this conflicts with the ASP.NET tab on Windows Server 2003. Fortunately, a quick Google search led me to this post, which has a fix described in the comments.

So, if you have the same problem:

  1. Stop IIS using iisreset /stop
  2. Open the file C:\WINDOWS\system32\inetsrv\MetaBase.xml in Notepad.
  3. Find and delete the line that says Enable32BitAppOnWin64="TRUE"
  4. Restart IIS using iisreset /start
  5. If you still don’t see your ASP.NET tab, aspnet_regiis -i should now work.