Chapter 16. Deployment

Deployment is the process of installing your web application onto a live public web server so that it can be accessed by real users. If you've deployed any ASP.NET application before, you'll be pleased to know that deploying an ASP.NET MVC 2 application is virtually the same deal. The main new consideration has to do with routing (with ASP.NET MVC 1, many folks got stuck trying to use extensionless URLs on IIS 6), but this is easily handled when you know how. Beyond that, Visual Studio 2010 introduces some new deployment features that are well worth knowing about.

This chapter covers the following:

  • Server requirements for hosting ASP.NET MVC 2 applications

  • How to build your application for production use, including using MVC Framework-specific build tasks

  • Installing IIS 6, 7, and 7.5 onto Windows Server and getting ASP.NET MVC 2 applications to run in them

  • IIS's request handling architecture, how routing fits into it, and what this means for handling extensionless URLs

  • An overview of Visual Studio 2010's new packaging and publishing features that can help to automate your deployment process

Server Requirements

To run ASP.NET MVC 2 applications, your server needs the following:

  • IIS version 5.1 or later, with ASP.NET enabled

  • The .NET Framework—either version 3.5 SP1 or 4.0, depending on which .NET Framework version your application targets

Live web sites should really only be hosted on a server operating system, which for ASP.NET MVC means Windows Server 2003 (which runs IIS 6), Windows Server 2008 (which runs IIS 7), or Windows Server 2008 R2 (which runs IIS 7.5).

Note

Notice that ASP.NET MVC 2 itself isn't on the list of server requirements. That's because you don't have to install it separately on the server. All you have to do is include System.Web.Mvc.dll (version 2.0.0.0) in your in folder. It was designed this way to make deployment easier, especially in shared hosting scenarios, than if you had to install any assemblies into the server's Global Assembly Cache (GAC). You'll hear more about the precise steps later in this chapter.

Requirements for Shared Hosting

To deploy an ASP.NET MVC 2 application to a shared web host, your hosting account must support ASP.NET and have the .NET Framework version 3.5 or 4 (depending on which .NET version you're targeting) installed on the server. That's all—you don't need to find a hosting company that advertises specific support for ASP.NET MVC 2, since you'll deploy the MVC Framework yourself by putting its assembly into your in folder.

If your hosting company uses IIS 7 or later in its default integrated pipeline mode (explained later), you'll be able to use clean, extensionless URLs with no trouble. But if it uses IIS 6, read the "Deploying to IIS 6 on Windows Server 2003" section later in this chapter, because there are several different ways to make routing work on IIS 6, and your host might not support all of them.

Building Your Application for Production Use

Before we get into the real business of setting up IIS to host your application, I want to point out a couple of compilation and build options you can use to maximize performance and detect errors before they happen at runtime.

Controlling Dynamic Page Compilation

One particular Web.config setting that you should pay attention to during deployment is <compilation>:

<configuration>
  <system.web>
    <compilation debug="true">
      ...
    </compilation>
  </system.web>
</configuration>

When the Web Forms view engine loads and compiles one of your ASPX or ASCX view files at runtime, it chooses between debug and release compilation modes according to the debug flag. If you leave the default setting in place (i.e., debug="true"), then the compiler does the following:

  • Makes sure you can step through the code line by line in the debugger by disabling a number of possible code compilation optimizations

  • Compiles each ASPX/ASCX file separately when it's requested, rather than compiling many in a single batch (producing many more temporary assemblies, which unfortunately consume more memory)

  • Turns off request timeouts (letting you spend a long time in the debugger)

  • Instructs browsers not to cache any static resources served by WebResources.axd

All these things are helpful during development and debugging, but adversely affect performance on your production server. Naturally, the solution is to flip this switch off when deploying to the production server (i.e., set debug="false"). If you're deploying to IIS 7.x, you can use IIS Manager's .NET Compilation configuration tool (Figure 16-1), which edits this and other Web.config settings on your behalf.

Using IIS 7's .NET Compilation tool to turn off the debug ASPX compilation mode

Figure 16-1. Using IIS 7's .NET Compilation tool to turn off the debug ASPX compilation mode

Tip

If you're using Visual Studio 2010, you can avoid this manual step. You can use the configuration file transformation feature to set debug to false automatically as part of your deployment process. You'll learn more about how to do this near the end of this chapter.

Detecting Compiler Errors in Views Before Deployment

As you know, ASPX and ASCX files are compiled on the fly as they are needed on the server. They aren't compiled by Visual Studio when you select Build

Detecting Compiler Errors in Views Before Deployment

If you want to verify that all your views can compile without errors, then you can enable a special project option called MvcBuildViews. Open your ASP.NET MVC application's project file (YourApp.csproj) in a text editor such as Notepad, and change the MvcBuildViews option from false to true:

<MvcBuildViews>true</MvcBuildViews>

Save the updated .csproj file and return to Visual Studio. Now whenever you compile your application, Visual Studio will run a postbuild step that also compiles all the .aspx, .ascx, and .Master views, which means you'll be notified of any compiler errors.

Detecting Compiler Errors in Views Only When Building in Release Mode

Be aware that enabling this postbuild step will make compilation take significantly longer. You might prefer to enable this option only when building in Release mode. That will help you to catch compiler errors before deploying, without suffering longer compile times during day-to-day development.

To do this, open your application's .csproj file in Notepad, find the <Target> node called AfterBuild (it's near the end of the file), and then change its Condition attribute as follows:

<Target Name="AfterBuild" Condition="'$(Configuration)'=='Release'">
  <AspNetCompiler VirtualPath="temp" PhysicalPath="$(ProjectDir)" />
</Target>

Note that once you've done this, the <MvcBuildViews> node will be ignored and can even be removed entirely.

IIS Basics

IIS is the web server built into most editions of the Windows operating system.

  • Version 5 is built into Windows Server 2000. However, the .NET Framework 3.5 and later does not support Windows Server 2000, so you cannot use it with ASP.NET MVC 2.

  • Version 5.1 is built into Windows XP Professional. However, IIS 5.1 is intended for use during development only, and should not be used as a production server.

  • Version 6 is built into Windows Server 2003.

  • Version 7 is built into Windows Server 2008 and Windows Vista Business/Enterprise/Ultimate editions. However, Vista is a client operating system and is not optimized for server workloads.

  • Version 7.5 is built into Windows Server 2008 R2 and Windows 7 Professional/Enterprise/Ultimate editions. Of course, Windows 7 is a client operating system and is not optimized for server workloads.

In summary, it's almost certain that your production web server will run IIS 6 or IIS 7.x. This chapter focuses exclusively on those options. First, I'll quickly cover the basic theory of IIS web sites, virtual directories, bindings, and application pools. After that, you'll find detailed guides to deploying ASP.NET MVC 2 applications to a variety of IIS and .NET Framework versions.

Understanding Web Sites and Virtual Directories

All versions of IIS (except 5.1) can host multiple independent web sites simultaneously. For each web site, you must specify a root path (a folder either on the server's file system or on a network share), and then IIS will serve whatever static or dynamic content it finds in that folder.

To direct a particular incoming HTTP request to a particular web site, IIS allows you to configure bindings. Each binding maps all requests for a particular combination of IP address, TCP port number, and HTTP hostname to a particular web site (see Figure 16-2). You'll learn more about bindings shortly.

IIS 7 Manager displaying a list of simultaneously hosted web sites and their bindings

Figure 16-2. IIS 7 Manager displaying a list of simultaneously hosted web sites and their bindings

As an extra level of configuration, you can add virtual directories at any location in a web site's folder hierarchy. Each virtual directory causes IIS to take content from some other file or network location, and serve it as if it were actually present at the virtual directory's location under the web site's root folder (see Figure 16-3). It's a bit like a folder shortcut (or if you've used Linux, it's similar to a symbolic link).

How virtual directories are displayed in IIS 7 Manager (in Content view mode)

Figure 16-3. How virtual directories are displayed in IIS 7 Manager (in Content view mode)

For each virtual directory, you can choose whether or not to mark it as an independent application. If you do, it gets its own separate application configuration, and if it hosts an ASP.NET application, its state becomes independent from its parent web site's state. It can even run a different version of ASP.NET than its parent web site.

IIS 6 introduced application pools (usually called app pools) as a mechanism to enable greater isolation between different web applications running in the same server. Each app pool runs a separate worker process, which can run under a different identity (affecting its level of permission to access the underlying OS), and defines rules for maximum memory usage, maximum CPU usage, process-recycling schedules, and so on. Each web site (or virtual directory marked as an independent application) is assigned to one of these app pools. If one of your applications crashes, then the web server itself and applications in other app pools won't be affected.

Binding Web Sites to Hostnames, IP Addresses, and Ports

Since the same server might host multiple web sites, it needs a system to dispatch incoming requests to the right one. As mentioned previously, you can bind each web site to one or more combinations of the following:

  • Port number (in production, of course, most web sites are served on port 80)

  • Hostname

  • IP address (only relevant if the server has multiple IP addresses—e.g., if it has multiple network adapters)

For hostname and IP address, you can choose not to specify a value. This gives the effect of a wildcard—matching anything not specifically matched by a different web site.

If multiple web sites have the same binding, then only one of them can run at any particular time. Virtual directories inherit the same bindings as their parent web site.

Deploying Your Application

At a minimum, deploying your application means copying its files to your server and then configuring IIS to serve them. Of course, if you have another application component, such as a database, you'll need to set that up too, and perhaps deploy your data schema and any initial data. (You could be using any database system, so that's beyond the scope of this chapter.)

Later in this chapter you'll learn about WebDeploy and Visual Studio 2010's built-in packaging and publishing features that can simplify and automate deployment. But first, in case you can't use WebDeploy or you need to customize the process, let's see how to do it manually.

Manually Copying Application Files to the Server

When running, an ASP.NET MVC application uses exactly the same set of files that an ASP.NET Web Forms application does:[102]

  • Its compiled .NET assemblies (i.e., those in the in folder)

  • Configuration and settings files (e.g., Web.config and any *.settings files)

  • Uncompiled view templates (*.aspx, *.ascx, and *.Master)

  • Global.asax (this tells ASP.NET which compiled class represents your global HttpApplication)

  • Any static files (e.g., images, CSS files, and JavaScript files)

  • Optionally, the *.pdb files in your in folder, which enable extra debugging information (these are rarely deployed to production servers)

These are the files you need to deploy to your web server. You don't need to deploy the files that are merely aspects of development, and for security reasons it's better to avoid deploying them. So, don't deploy the following:

  • C# code files (*.cs, including Global.asax.cs or any other "code behind" files)

  • Project and solution files (*.sln, *.suo, *.csproj, or *.csproj.user)

  • The obj folder

  • Anything specific to your source control system (e.g., .svn folders if you use Subversion, or the .hg or .git folders if you use Mercurial or Git)

Tip

Instead of manually collecting and filtering all the files to deploy, consider setting up an automated build process that fetches, compiles, and prepares your application for deployment. If your automated build process is connected directly to your source control system so that it can run a build after every commit, this is called continuous integration (CI). Two popular free CI servers for .NET are CruiseControl.NET (http://ccnet.thoughtworks.com/) and TeamCity (www.jetbrains.com/teamcity/). Of course, Microsoft's Team Foundation Server can also run automated builds.

Where Should I Put My Application?

You can deploy your application to any folder on the server. When IIS first installs, it automatically creates a folder for a web site called Default Web Site at c:Inetpubwwwroot, but you shouldn't feel any obligation to put your application files there. It's very common to host applications on a different physical drive from the operating system (e.g., in e:websitesexample.com). It's entirely up to you, and may be influenced by concerns such as how you plan to back up the server.

Bin-Deploying ASP.NET MVC 2

ASP.NET MVC 2's runtime consists of a single .NET assembly: System.Web.Mvc.dll. When you installed ASP.NET MVC 2 on your development workstation, the installer added this assembly into your workstation's Global Assembly Cache (GAC). That's how your application can run on your development workstation without needing its own separate copy of System.Web.Mvc.dll.

However, it's the opposite story on your production web server. System.Web.Mvc.dll isn't included in .NET 3.5 SP1 or .NET 4, so it's not going to be in your server's GAC by default. It's possible to install it into the GAC on your server (e.g., by running the ASP.NET MVC 2 installer there), but there's really no point—and in shared web hosting scenarios you probably don't have permission to do that anyway. For deployment, it's much easier and tidier just to include System.Web.Mvc.dll in your in folder. This is called bin-deploying it.

You can use any method to get System.Web.Mvc.dll into your deployed application's in folder, but the easiest is to make it get copied there as part of the compilation process. In Solution Explorer, expand your ASP.NET MVC project's References node, right-click System.Web.Mvc, and then choose Properties. In the Properties pane, set Copy Local to True, as shown in Figure 16-4.

Telling the compiler to include System.Web.Mvc.dll in your in folder

Figure 16-4. Telling the compiler to include System.Web.Mvc.dll in your in folder

Now, after you next compile, System.Web.Mvc.dll will be in your application's in folder. This won't make any difference on your development workstation where that assembly is in the GAC anyway, but on your production server it's usually essential.

Note

If you've previously deployed ASP.NET MVC 1 applications, you might be wondering about System.Web.Abstractions.dll and System.Web.Routing.dll. ASP.NET MVC 1 worked on .NET 3.5 (without SP1) as long as you also bin- or GAC-deployed those two extra assemblies. However, ASP.NET MVC 2 is only supported on .NET 3.5 SP1 or later, which includes System.Web.Abstractions.dll and System.Web.Routing.dll in the GAC, so you don't need to think about deploying them manually.

Deploying to IIS 6 on Windows Server 2003

To get started with deploying your application to Windows Server 2003, you first need to install IIS and the relevant version of the .NET Framework. Take the following steps to install IIS:

  1. Launch the Manage Your Server application (from Start

    Deploying to IIS 6 on Windows Server 2003
  2. Click "Add or remove a role," and then click Next to skip past the introduction screen. It may take a moment to detect your network settings.

  3. If the wizard asks you to choose between "Typical configuration for a first server" and "Custom configuration," choose "Custom configuration," and then click Next.

  4. Select "Application server (IIS, ASP.NET)," and then click Next.

  5. Check Enable ASP.NET, and then click Next. Click Next again after you've read the summary, and then the system will proceed to install and configure IIS.

  6. Click Finish.

Next, download and install the .NET Framework runtime from Microsoft (see http://microsoft.com/net/). If your application targets .NET 4, then obviously you need .NET 4 on the server. If your application targets .NET 3.5, then you must specifically install .NET 3.5 SP1 (.NET 4 alone won't do—it doesn't configure IIS to run .NET 3.5 SP1 applications). It's fine to install both .NET Framework versions on the same server.

Note

Because .NET 4 has very good backward compatibility, you should in theory be able to run your ASP.NET MVC 2 application as a .NET 4 application even if it was meant to target .NET 3.5. However, for the sake of reliability, it makes sense to run your live application on the same .NET Framework version that you developed and tested it with.

You may be asked to restart your server at this point. Next, check that IIS is installed and working by opening a browser on the server and visiting http://localhost/. You should receive a page entitled "Under Construction."

Adding and Configuring a New MVC Web Site in IIS Manager

If you haven't already done so, copy your application files to some folder on the server now. Remember to include only the file types that are needed to run the application (listed previously).

Take the following steps to configure IIS to serve your application:

  1. Open IIS Manager (from Control Panel

    Adding and Configuring a New MVC Web Site in IIS Manager
  2. In the left-hand column, expand the node representing your server, expand Web Sites, right-click any entries that you don't need (e.g., Default Web Site), and use the Stop or Delete option to make sure those entries don't interfere.

  3. Add a new web site entry by right-clicking the Web Sites node and choosing New

    Adding and Configuring a New MVC Web Site in IIS Manager
  4. Enter some descriptive name for the web site (e.g., its intended domain name) and click Next.

  5. Enter details of your intended IP, port, and hostname bindings. If it will be the only web site on the server, you can leave all the default values as they are. If you'll be hosting multiple sites simultaneously, you need to enter a unique combination of bindings. Of course, you almost certainly will want to use the default TCP port (80) for public Internet applications; otherwise, people will find your URLs confusing. Click Next.

  6. Specify the folder to which you've deployed your application files (i.e., the one that contains Web.config and has in as a subdirectory). Leave "Allow anonymous access" checked, unless you intend to use Windows Authentication (not suitable for public Internet applications). Click Next.

  7. For access permissions, enable Read and "Run scripts." You don't need to enable Execute (even though the description mentions ISAPI), because by default, aspnet_isapi.dll is marked as a "script engine." Click Next, and then Finish.

  8. Finally, and very importantly, open your new web site's Properties dialog (right-click the web site name and choose Properties), go to the ASP.NET tab, and choose the correct ASP.NET version. If you're targeting .NET 4, choose 4.0.30319. If you're targeting .NET 3.5, choose 2.0.50727.

Note

The version number I just gave wasn't a typo! If you're targeting .NET 3.5 (e.g., because you're using Visual Studio 2008), then you need to choose ASP.NET version 2.0.50727. In fact, there isn't an option for .NET 3.0 or 3.5. That's because the .NET Framework 3.5 actually still uses the same CLR as version 2.0 (version 3.5 has a new C# compiler and a new set of framework class library assemblies, but no new CLR), so IIS doesn't even know there's a difference. However, .NET 4 does include a new CLR, so IIS has a different version number for it.

At this point, check your configuration by opening a browser on the server and visiting http://localhost/ (you might need to amend this URL if you've bound to a specific port or hostname, or if you're deploying to a virtual directory). Don't use a browser running on your normal workstation just yet—if there are errors, you'll only get the complete error information when your browser is actually running on the server.

If you're running on .NET 4 and everything is working properly, your site's home page will appear. Success! But if you're targeting .NET 3.5, you can expect to get either a 403 Forbidden error or a 404 Not Found error at this point (Figure 16-5), because more configuration is required to support extensionless URLs. Read on for more information about why this is and how to deal with it. If you're facing a different error, look out for the troubleshooting steps in a few pages.

With .NET 3.5, IIS 6 will not serve extensionless URLs without further configuration.

Figure 16-5. With .NET 3.5, IIS 6 will not serve extensionless URLs without further configuration.

How IIS 6 Processes Requests

To understand your options for making IIS 6 work with routing and extensionless URLs, you must first take a step back and think about how IIS 6 processes requests in general. Without a basic understanding of this, you're likely to run into trouble and end up with 404 Not Found instead of the responses you were expecting.

You know that IIS uses the incoming port number, hostname, and IP address to match a request to a particular web site, but how does it decide what to do next? Should it serve a static file directly from disk, or should it invoke some web application platform to return dynamic content?

Making Extensionless URLs Work on IIS 6

IIS 6 doesn't support integrated pipeline mode (explained in the section about IIS 7)—it only supports classic pipeline mode, which dates back to IIS 5. In this mode, you serve dynamic content by mapping particular URL file name extensions to particular ISAPI extensions.[103]

IIS parses a file name extension from the URL (e.g., in http://hostname/folder/file.aspx?foo=bar, the file name extension is .aspx) and dispatches control to the corresponding ISAPI extension. To configure ISAPI extension mappings in IIS 6 Manager, right-click your site name, and then go to Properties

Making Extensionless URLs Work on IIS 6
IIS 6 Manager's mapping from .aspx to aspnet_isapi.dll

Figure 16-6. IIS 6 Manager's mapping from .aspx to aspnet_isapi.dll

When you install the .NET Framework (or if you run aspnet_regiis.exe), the installer automatically sets up mappings from *.aspx, *.axd, *.ashx, and a few other file name extensions to a special ISAPI extension called aspnet_isapi.dll. That's how the core ASP.NET platform gets involved in handling a request: the request must match one of these file name extensions, and then IIS will invoke aspnet_isapi.dll, an unmanaged ISAPI DLL that transfers control to the managed ASP.NET runtime, which is hosted by the .NET CLR in a different process.

Traditionally, this system has worked fine for ASP.NET server pages, because they are actual files on disk with an .aspx file name extension. However, it's much less suitable for the core routing system, in which URLs need not correspond to files on disk and often don't have any file name extension at all.

Remember that the core routing system is built around a .NET HTTP module class called UrlRoutingModule. That HTTP module is supposed to consider each request and decide whether to divert control into one of your controller classes. But this is .NET code, so it only runs during requests that involve ASP.NET (i.e., ones that IIS has mapped to aspnet_isapi.dll). So, unless the requested URL has an appropriate file name extension, aspnet_isapi.dll will never be invoked, which means that UrlRoutingModule will never be invoked, which means that IIS will simply try to serve that URL as a static file from disk. Since there isn't (usually) any such file on disk, you'll get a 404 Not Found error. So much for clean, extensionless URLs!

Extensionless URLs on IIS 6 with .NET 3.5

The way to resolve this depends on your .NET Framework version. Let's first consider applications that target .NET 3.5.

Note

Unless you're actually planning to deploy to IIS 6 and .NET 3.5—the most awkward of all ASP.NET MVC 2 deployment scenarios—there's really no need for you to read this long and detailed subsection. Most readers can skip ahead a few pages to "Extensionless URLs on IIS 6 with .NET 4," or further to "Deploying to IIS 7 on Windows Server 2008."

Whenever you create a new ASP.NET MVC 2 application that targets .NET 3.5, your Web.config file specifically instructs the ASP.NET runtime to let UrlRoutingModule intercept every request that goes through aspnet_isapi.dll:

<system.web>
  <httpModules>
    <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, ..." />
  </httpModules>
</system.web>

As long as ASP.NET handles a request, UrlRoutingModule will make sure your routing configuration is respected. But how can you make ASP.NET handle requests whose URL doesn't include .aspx? There are two main options:

  • Use a wildcard map: Configure IIS to process all requests using ASP.NET, regardless of the URL. This is very easy to set up, and is the solution I'd recommend in most cases.

  • Use a file name extension in your URLs: Put .aspx into all your route entries' URL patterns (e.g., {controller}.aspx/{action}/{id}), or use some other custom extension such as .mvc and map this to aspnet_isapi.dll. This causes IIS to map those requests into ASP.NET. The drawback is of course that it spoils otherwise clean URLs.

Note

In the previous edition of this book, I also explained a further option: using URL rewriting to trick ASP.NET into handling all requests that have no file name extension. This can yield slightly better performance. However, I'm omitting it now because it's excessively complicated to set up, the performance difference will be negligible for the vast majority of web sites, and it gives no benefit if you're using .NET 4 or IIS 7. In case you do want to investigate this option, I've explained how to do it on my blog at http://tinyurl.com/ygu4ptg.

Using a Wildcard Map

This is the simplest solution to achieve extensionless URLs with IIS 6, and it's the one I would recommend unless you have special requirements. It works by telling IIS to process all requests using aspnet_isapi.dll, so no matter what file name extension appears in a URL (or if no extension appears at all), the routing system gets invoked and can redirect control to the appropriate controller.

To set this up, open IIS Manager, right-click your application or virtual directory, and go to Properties

Extensionless URLs on IIS 6 with .NET 3.5
  1. For Executable, put c:windowsmicrosoft.netframeworkv2.0.50727aspnet_isapi.dll, or copy and paste the value from Executable in the existing .aspx mapping.

  2. Uncheck "Verify that file exists" (since your extensionless URLs don't correspond to actual files on disk).

That's it! You should now find that your extensionless URLs work perfectly.

Disadvantages of Using Wildcard Maps

Since IIS now uses ASP.NET to handle all requests, aspnet_isapi.dll takes charge even during requests for static files, such as images, CSS files, and JavaScript files. This will work; the routing system will recognize URLs that correspond to files on disk and will skip them (unless you've set RouteExistingFiles to true), and then ASP.NET will use its built-in DefaultHttpHandler to serve the file statically. This leads to two possibilities:

  • If you intercept the request (e.g., using an IHttpModule or via Application_BeginRequest()) and then send some HTTP headers, modify caching policy, write to the Response stream, or add filters, then DefaultHttpHandler will serve the static file by transferring control to a built-in handler class called StaticFileHandler. This is significantly less efficient than IIS's native static file handling: it doesn't cache files in memory—it reads them from disk every time; it doesn't serve the Cache-Control/expiry headers that you might have configured in IIS, so browsers won't cache the static files properly; and it doesn't use HTTP compression.

  • If you don't intercept the request and modify it as described previously, then DefaultHttpHandler will pass control back to IIS for native static file handling.[104] This is much more efficient than StaticFileHandler (e.g., it sends all the right content expiration headers), but there's still a slight performance cost from going into and then back out of managed code.

If the slight performance cost doesn't trouble you—perhaps because it's an intranet application that will only ever serve a limited number of users—then you can just stop here and be satisfied with a simple wildcard map. However, if you demand maximum performance for static files, you need to switch to a different deployment strategy, or at least exclude static content directories from the wildcard map.

Excluding Certain Subdirectories from a Wildcard Map

To improve performance, you can instruct IIS to exclude specific subdirectories from your wildcard map. For example, if you exclude /Content, then IIS will serve all of that folder's files natively, bypassing ASP.NET entirely. Unfortunately, this option isn't exposed by IIS Manager; you can only edit wildcard maps at a per-directory level by editing the metabase directly—for example, by using the command-line tool adsutil.vbs, which is installed by default in c:InetpubAdminScripts.

It's quite easy. First, use IIS Manager to find out the identifier number of your application, as shown in Figure 16-7.

Using IIS 6 Manager to determine the identifier number of a web site

Figure 16-7. Using IIS 6 Manager to determine the identifier number of a web site

Next, open a command prompt, change the directory to c:InetpubAdminScripts, and then run the following:

adsutil.vbs SET /W3SVC/105364569/root/Content/ScriptMaps ""

replacing 105364569 with the identifier number of your application. This eliminates all wildcard (and non-wildcard) maps for the /Content folder, so all its files will be served natively. Of course, you can substitute any other directory path in place of /Content.

Tip

If you really prefer to set this up with IIS Manager rather than adsutil.vbs, you can do so, but IIS Manager behaves very strangely. First, you must mark the /Content directory as an "application" (right-click the directory, go to Properties

Using IIS 6 Manager to determine the identifier number of a web site

Using a Traditional ASP.NET File Name Extension

If you don't mind having .aspx in your URLs, this solution is fairly easy to set up, and doesn't interfere with IIS's handling of static files. Simply add .aspx immediately before a forward slash in all your route entries. For example, use URL patterns like {controller}.aspx/{action}/{id} or myapp.aspx/{controller}/{action}/{id}. Of course, you're equally able to use any other file name extension registered to aspnet_isapi.dll, such as .ashx. Once you've made this change, you'll need to compile and deploy your updated application files to your server.

Note

Don't put .aspx inside curly brace parameter names (e.g., don't try to use {controller.aspx} as a URL pattern), and don't put .aspx into any Defaults values (e.g., don't set { controller = "Home.aspx" }). This is because .aspx isn't really part of the controller name—it just appears in the URL pattern to satisfy IIS.

This technique avoids the need for a wildcard map. It means that aspnet_isapi.dll is only invoked for requests into your application, not for static files (which have different file name extensions)—but unfortunately it tarnishes your otherwise clean URLs.

Using a Custom File Name Extension

If you're keen to have URLs that feature .mvc instead of .aspx (or to use any other custom extension—you're not limited to three characters), this is pretty easy to arrange as long as your hosting gives you access to IIS Manager so you can register a custom ISAPI extension.

Update all of your route entries' URL patterns as described previously in the "Using a Traditional ASP.NET File Name Extension" section, but use your own custom URL extension instead of .aspx. Then, after recompiling and deploying the updated files to your server, take the following steps to register your custom file name extension with IIS.

In IIS Manager, right-click your application or virtual directory, go to Properties

Using IIS 6 Manager to determine the identifier number of a web site
  • For Executable, enter c:windowsmicrosoft.netframeworkv2.0.50727aspnet_isapi.dll (or copy and paste whatever value appears in the same slot for the existing .aspx mapping).

  • For Extension, enter .mvc (or whatever extension you've used in the route entries).

  • For Verbs, leave "All verbs" selected, unless you specifically want to filter HTTP methods.

  • Leave "Script engine" checked, unless you also enable the Execute permission for your application (in which case it doesn't matter).

  • Make sure that "Verify that file exists" is not checked (since your URLs don't correspond to actual files on disk).

  • Click OK, and keep clicking OK until you've closed all the property windows.

You should now be able to open http://localhost/home/index.mvc (or whatever corresponds to your new routing configuration) in a browser on the server.

Extensionless URLs on IIS 6 with .NET 4

If you're using .NET 4 on IIS 6, then in most cases extensionless URLs will work without needing you to add a wildcard map or any other extra manual configuration. This is because .NET 4 adds two enhancements:

  • UrlRoutingModule no longer has to be referenced by your Web.config file, because .NET 4's machine-wide configuration now associates it with all web applications by default. Specifically, drive:WindowsMicrosoft.NETFrameworkv4.0.30319Configweb.config's <system.web>/<httpModules> node has an entry called UrlRoutingModule-4.0.

  • .NET 4 registers a global wildcard map so that IIS 6 maps all URLs with no file name extension into ASP.NET. For example, /home/about will be mapped to ASP.NET (and hence into UrlRoutingModule and then your application), whereas /content/styles.css won't be mapped and will be served natively by IIS. This gives both convenience and performance.

This should be sufficient for most applications. However, if your URLs sometimes include dot characters (e.g., http://hostname/users/bob.smith/), then you'll need to manually configure a regular wildcard map (see the preceding instructions), because those URLs won't be mapped into ASP.NET.

Tip

If for some reason you don't want .NET 4's new global wildcard map in IIS 6, you can disable it by creating a DWORD registry value called HKEY_LOCAL_MACHINE SOFTWARE Microsoft ASP.NET 4.0.30319 EnableExtensionlessUrls with value 0, as described on Thomas Marquardt's blog at http://tinyurl.com/yg44xyt. After editing the registry, restart your server or run iisreset from a command prompt.

Troubleshooting IIS 6 Errors

If you're unable to bring up your site's home page, consider the following advice:

  • If your root URL returns the message "Server Application Unavailable," check that the IIS worker process has permission to read files in the application's directory. In IIS 6 Manager, right-click your web site name and then choose Permissions. Ensure that IIS_WPG has permission to read, execute, and list folder contents.

  • If your root URL returns a 404 Not Found error, or if it returns an error saying "Directory Listing Denied," or if it returns an actual directory listing, then it's likely that your ASP.NET MVC application was never invoked. To resolve this

    • Make sure ASP.NET is enabled on the server.[105] In IIS Manager, under Web Service Extensions, be sure to allow ASP.NET version 2.0.50727 or 4.0.30319, or both, depending on which you're using (remember, .NET 3.5 uses the 2.0.50727 CLR).

    • If your intended ASP.NET version isn't on the list of web service extensions, then either you haven't installed the correct version of the .NET Framework or it just isn't associated with IIS (perhaps because you installed .NET before you installed IIS). Install the correct version of the .NET Framework, or if you've already done that, then run aspnet_regiis.exe -i, which you can find in the folder WINDOWSMicrosoft.NETbitnessversion, where bitness is either Framework or Framework64 (the latter if you want to run in 64-bit mode), and version is v2.0.50727 or v4.0.30319 depending on which .NET Framework version you're using.

    • If you're targeting .NET 3.5, make sure you have properly configured a wildcard map, or have otherwise made extensionless URLs work as described earlier in this chapter.

The preceding steps can also resolve certain 403 Access Denied errors.

  • If you get an ASP.NET yellow screen of death saying "Parser Error Message," then it's likely that you're trying to run your application under the wrong .NET Framework version. Check the "Version Information" line near the bottom of the error screen, and also consider the rest of the parser error message.

    • If it says "Unrecognized attribute 'type'," then you probably have your application configured to run under ASP.NET 1.1 by mistake. In IIS Manager, go back to the application's ASP.NET tab and make sure you've selected ASP.NET version 2.0.50727 or 4.0.30319.

    • If it says "Child nodes not allowed," then you probably have the .NET Framework 2 installed and selected, but haven't installed the correct .NET Framework version (3.5 SP1 or 4). Install it, and then make sure you've selected it on your application's ASP.NET tab.

    • If it says "Unrecognized attribute 'targetFramework'," then you're probably trying to deploy a .NET 4 application but haven't configured it to run under .NET 4. Go back to your application's ASP.NET tab and check that you've selected version 4.0.30319. If that version doesn't appear in the drop-down list, install the .NET Framework version 4 first.

Deploying to IIS 7.x on Windows Server 2008/2008 R2

After all that detailed information about IIS 6, you'll be pleased to hear that it's much easier to deploy ASP.NET MVC 2 applications to Windows Server 2008 and IIS 7.x. This is mainly because of IIS 7.x's integrated pipeline mode, which allows the routing system to get involved in processing all requests, regardless of file name extensions, while still serving static files natively (i.e., not through ASP.NET).

Installing IIS 7.x on Windows Server 2008/2008 R2

Take the following steps to install IIS onto Windows Server 2008 or Windows Server 2008 R2:

  1. Open Server Manager (via Start

    Installing IIS 7.x on Windows Server 2008/2008 R2
  2. In the left-hand column, right-click Roles and choose Add Roles. If it displays the Before You Begin page, click Next to skip past it.

  3. From the list of possible roles, select Web Server (IIS). (If at this point you're shown a pop-up window listing requirements for installing IIS, simply click Add Required Features.) Then click Next.

  4. You should now get a page of information about IIS. Click Next.

  5. On the Role Services page, under the Application Development heading, click to enable ASP.NET. A pop-up window will appear, listing other features required to install ASP.NET; click Add Required Role Services.

  6. Review the list of role services, and select any others that you need for your particular application. For example, if you intend to use Windows Authentication, enable it now. Don't enable any extra services that you don't expect to use. The goal is to minimize the surface area of your server.[106] Click Next.

  7. On the confirmation screen, review the list of features and services to be installed, and then click Install. The wizard will now install IIS and enable ASP.NET.

  8. When installation has completed, click Close on the results page.

At this point, you can test that your IIS installation is working by opening a browser on the server and visiting http://localhost/. You should find that it displays an IIS 7-branded welcome page.

Next, download and install the .NET Framework—either version 3.5 SP1 or 4 depending on which framework version you developed and tested your application against (or if you wish, install both .NET 3.5 SP1 and .NET 4).

Note

If you're using Windows Server 2008 R2, then you don't need to download .NET 3.5 SP1. It's already included in the operating system—you just need to enable it. Open Server Manager, and then select Features in the left-hand pane. Click Add Features, enable ".NET Framework 3.5.1 Features," click Next, and then click Install. After installation is completed, click Close.

Adding and Configuring a New MVC Web Site in IIS 7.x

If you haven't already done so, copy your application files to some folder on the server now. Remember to include only the file types that are needed to run the application (listed previously).

Take the following steps to configure IIS 7.x to serve your application:

  1. Open IIS Manager (from Start

    Adding and Configuring a New MVC Web Site in IIS 7.x
  2. In the left-hand column, expand the node representing your server, and expand its Sites node. For any unwanted sites already present in the list (e.g., Default Web Site), either right-click and choose to remove them, or select them and use the right-hand column to stop them.

  3. Add a new web site by right-clicking Sites and choosing Add Web Site. Enter a descriptive value for "Site name," and specify the physical path to the folder where you've already put your application files. If you wish to bind to a particular hostname or TCP port, enter the details here. When you're ready, click OK.

  4. IIS will create a new app pool for your new web site. The app pool will have the same name as your web site, and will run in integrated pipeline mode by default (which is usually what you want). By default, the new app pool will run .NET CLR 2.0, which is exactly what you want if your application targets .NET 3.5.[107] However, if your application targets .NET 4, then you need to go to your app pool settings (in IIS Manager's left-hand pane, select Application Pools and then double-click the entry corresponding to your new web site) and set the .NET Framework version to 4.0.30319.

That should do it! Try running it by opening a browser on the server and visiting http://localhost/ (amend this URL if you've bound the web site to a specific port or hostname, or are deploying to a virtual directory). If you're having problems, or if you want to run in classic pipeline mode, read on for further instructions.

How IIS 7.x Processes Requests in Classic Pipeline Mode

IIS 7.x's classic pipeline mode handles requests in pretty much the same way as IIS 6: it maps requests to ISAPI handlers based on file name extensions parsed from the URL. This is harder to work with and omits certain performance benefits, so you should only choose classic mode if you need to use a legacy ISAPI module that doesn't work in integrated mode.

You can switch into classic mode using the Application Pools configuration screen (Figure 16-8).

Configuring an app pool to run in integrated or classic pipeline mode

Figure 16-8. Configuring an app pool to run in integrated or classic pipeline mode

In this mode, you must manually map requests to aspnet_isapi.dll just as with IIS 6. To set up a wildcard map, select your web site in IIS Manager and then open Handler Mappings. Click Add Wildcard Script Map, give it any name you like, and for Executable enter the following:

c:WindowsMicrosoft.NETFrameworkv4.0.30319aspnet_isapi.dll

amending this as required for your server and application (e.g., change the drive letter if needed, replace Framework with Framework64 if you have a 64-bit server and don't plan to run in 32-bit mode, and replace v4.0.30319 with v2.0.50727 if you're targeting .NET 3.5).

Note

.NET 4 makes extensionless URLs work by default on IIS 6, but it still doesn't make them work by default in IIS 7.x classic mode. Although .NET 4 registers a map from *. to ASP.NET (attempting to match all requests with no file name extension), IIS 7.x doesn't support this mapping syntax, so it has no effect. If you want to make the *. map work on IIS 7.x, download and install a hotfix from http://support.microsoft.com/kb/980368. Otherwise, you need to create your own wildcard map as in the preceding instructions.

How IIS 7.x Processes Requests in Integrated Pipeline Mode

IIS 7 introduced a radically different pipeline mode, integrated pipeline mode, in which .NET is a native part of the web server. In this mode, it's no longer necessary to use an ISAPI extension to invoke .NET code—IIS 7.x itself can invoke HTTP modules and HTTP handlers (i.e., .NET classes that implement IHttpModule or IHttpHandler) directly from their .NET assemblies. Integrated mode is the default for all IIS 7.x app pools and should even work with most old-style, unmanaged ISAPI extensions (if not, you can go back to classic mode).

In integrated mode, IIS still selects handlers (either ISAPI extensions or .NET IHttpHandler classes) in terms of file name extensions parsed from the URL. Again, you can configure this using the Handler Mappings configuration screen. The difference for ASP.NET is that it no longer needs to go through aspnet_isapi.dll—you can now have a direct mapping from *.aspx to System.Web.UI.PageHandlerFactory, which is the .NET class responsible for compiling and running ASP.NET Web Forms server pages. Other ASP.NET extensions (e.g., *.ashx) are mapped to different .NET IHttpHandler classes. When you enable ASP.NET on your web server, all these mappings are automatically set up for you.

How Integrated Mode Makes Extensionless URLs Easy

To recap, an IHttpHandler class represents the endpoint for handling a request, so each request can be handled by only one such handler (which one is determined by URL file name extension). By comparison, IHttpModule classes plug into the request handling pipeline, so you can have any number of such modules involved in servicing a single request. On IIS 7.x, that's true even for requests that don't end up being handled by ASP.NET.

Since UrlRoutingModule is an IHttpModule (not an IHttpHandler), it can be involved in servicing all requests, irrespective of file name extensions and handler mappings. When invoked, UrlRoutingModule allows the routing system to try matching the incoming request against your routing configuration, and if it matches an entry, to divert control toward one of your controller classes (or to a custom I RouteHandler).

Why Extensionless URLs Work on IIS 7.x Integrated Pipeline Mode with .NET 3.5

If you create a new ASP.NET MVC 2 web application that targets .NET 3.5, your Web.config file has a <system.webServer> node that configures UrlRoutingModule to participate in all requests, as follows:

<system.webServer>
  <modules runAllManagedModulesForAllRequests="true">
    <remove name="ScriptModule"/>
    <remove name="UrlRoutingModule"/>
    <add name="ScriptModule" type="System.Web.Handlers.ScriptModule, ..."/>
    <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, ... "/>
  </modules>
</system.webServer>

The <system.webServer> node is where IIS 7 stores and retrieves its configuration data for your application.[108] So, when you deploy to IIS 7 in integrated mode, extensionless routing just works without requiring any manual configuration.

Why Extensionless URLs Work in IIS 7.x Integrated Pipeline Mode with .NET 4

If you create a new ASP.NET MVC 2 web application that targets .NET 4, you won't find any reference to UrlRoutingModule inside your Web.config file, but that's only because UrlRoutingModule is already referenced by .NET 4's default machine-wide configuration and therefore applies to all .NET 4 integrated mode web applications anyway.

Specifically, drive:WindowsSystem32inetsrvconfigapplicationHost.config's <system.webServer>/<modules> node has an entry that references UrlRoutingModule. As it happens, this entry is configured only to apply during requests that map to an ASP.NET handler, so to support extensionless URLs, your application's Web.config file will contain the following line to enable UrlRoutingModule during all requests:

<modules runAllManagedModulesForAllRequests="true"/>

Tip

Some developers are concerned about the performance implications of including managed code in the pipeline for all requests (even requests for images and other static files). In practice, very few web sites suffer problems because of this, partly because most sites don't have much traffic, and partly because IIS 7's kernel mode caching will actually intercept most static file requests and serve them without touching any managed code anyway (despite the runAllManagedModulesForAllRequests setting).

If you're really keen to keep managed code away from static file requests, and yet still route extensionless URLs, then you can either host your static files in a separate ASP.NET-free application, or you can use the IIS 7.x hotfix from http://support.microsoft.com/kb/980368, which maps only extensionless URLs into ASP.NET. But don't worry about this unless you've actually observed performance problems due to having managed modules running during static file requests.

Further IIS 7.x Deployment Considerations

Even though ASP.NET MVC applications are likely to work right away with IIS 7.x, I should also point out a key way in which IIS 7.x differs from Visual Studio's built-in web server.

If you use integrated pipeline mode, and if you have any custom IHttpModule or IHttpHandler classes registered in your Web.config file under <system.web>—for example:

<system.web>
  <httpHandlers>
    <add verb="*" path="*.blah" validate="false"
         type="MyMvcApp.MySpecialHandler, MyMvcApp"/>
  </httpHandlers>
  <httpModules>
    <add name="MyHttpModule" type="MyMvcApp.MyHttpModule, MyMvcApp"/>
  </httpModules>
</system.web>

then even though they worked in Visual Studio's built-in server (and would in IIS 6 or IIS 7.x classic mode), they won't take effect in IIS 7.x integrated mode. You must also register them in the <system.webServer> section. Either use IIS Manager's Modules and Handlers tools to register them, or edit Web.config manually, noticing that the syntax is slightly different:

<system.webServer>
    <validation validateIntegratedModeConfiguration="true" />
    <modules runAllManagedModulesForAllRequests="true">
        <add name="MyHttpModule"
type="MyMvcApp.MyHttpModule, MyMvcApp" />
    </modules>
    <handlers>
        <add name="MyHandler" path="*.blah" verb="*"
             type="MyMvcApp.MySpecialHandler" />
    </handlers>
</system.webServer>

IIS wants to make sure you understand that in integrated mode, it only considers modules and handlers that are registered under <system.webServer>. So, if you leave any handlers or modules under <system.web>, it will throw the error "An ASP.NET setting has been detected that does not apply in Integrated managed pipeline mode." You must either remove the old module/handler registrations, or set validateIntegratedModeConfiguration="false" on <system.webServer>/<validation>, which lets IIS 7.x simply ignore those old registrations.[109]

Troubleshooting IIS 7.x Errors

Here are some error messages you might face when deploying an ASP.NET MVC 2 application to IIS 7.x, along with likely resolutions. I've personally had all of the following problems:

  • If your application's root URL returns 403.14 Forbidden or a directory listing (and other actions' URLs return 404 Not Found), it's usually because the request isn't being mapped into ASP.NET at all. Possible resolutions include the following:

    • Ensure that you've installed whatever version of the .NET Framework you're targeting (3.5 SP1 or 4). Ensure that it's associated with IIS by running the following in an administrative mode command prompt:

      %windir%Microsoft.NETFrameworkv4.0.30319aspnet_regiis.exe -ir

      (Of course, you may need to amend this: on a 64-bit operating system such as Windows Server 2008 R2, replace Framework with Framework64, and if you're targeting .NET 3.5, replace v4.0.30319 with v2.0.50727.)

    • If you're running in classic pipeline mode, ensure that ASP.NET's ISAPI extension is allowed. In IIS Manager, select your server node and then open ISAPI and CGI Restrictions. There may be up to four entries (corresponding to 32 bit or 64 bit and CLR 2 or CLR 4)—make sure your chosen one is set to Allowed.

    • If you're running in classic pipeline mode, make sure you've followed the preceding instructions to set up a wildcard map (or have enabled extensionless URLs in some other way). Also make sure the wildcard map refers to the correct version of aspnet_isapi.dll. The handler mapping's "Executable" path should end with WindowsMicrosoft.NETbitnessclraspnet_isapi.dll, where bitness is either Framework or Framework64 (if you have a 64-bit operating system, you should choose Framework64 unless you also configure the app pool to run in 32-bit mode by setting its Enable 32-Bit Applications option to True), and clr is either v2.0.50727 or v4.0.30319, depending on which .NET Framework version you're targeting.

    • If you're trying to deploy to IIS 7.5 on Windows 7 for development purposes, you may also need to click Start, type turn windows features on or off, press Enter, and then enable Internet Information Services

      Troubleshooting IIS 7.x Errors
  • If you get the error 500.19 Internal Server Error, make sure your application's worker process can read your application directory. In IIS Manager, right-click your web site name, choose Edit Permissions, and then switch to the Security tab. Give Read, Execute, and List Folder Contents permissions to IIS_IUSRS (the default group of IIS worker process identities) and IUSR (the default account that serves static files during anonymous requests).

  • If your ASP.NET MVC action methods run and render their views successfully, but all your images and CSS styles seem to be missing, ensure that you've turned on IIS's Static Files feature. From Server Manager, choose Roles, and then under Web Server (IIS) click Add Role Services. Under Common HTTP Features, enable Static Content, and then click Next/Install to complete the wizard.

  • If your static files still aren't working, or if your root URL unexpectedly redirects to your Forms Authentication login URL, then make sure that IUSR has permission to read your application folder.

  • If you get an ASP.NET yellow screen of death saying "Parser Error Message," it's likely that your application is configured to run under the wrong .NET Framework version (see the "Version Information" at the bottom of the page). Make sure you've installed the correct .NET Framework version and your app pool is set to use it (remember, choose v2.0.50727 for .NET 3.5 and v4.0.30319 for .NET 4).

Deploying to IIS 7.5 on Windows Server 2008 R2 Core

Windows Server 2008 R2 Core is an edition of Windows Server 2008 R2 with almost every component stripped out or turned off by default. This is intended to minimize resource usage and the potential attack surface by eliminating all unnecessary services. It's so minimal that its UI consists only of a command-line prompt (that's right: there's no Windows Explorer, Server Manager, or Control Panel).

ASP.NET MVC 2 works perfectly well on Windows Server 2008 R2 core, with the following caveats:

  • IIS and .NET installation is somewhat less obvious—you have to get very intimate with the command-line prompt.

  • At the time of writing, there's no .NET 4 package for Windows Server 2008 R2 Core, so your application must target .NET 3.5, or you must wait for .NET 4 support (I don't know when or if this will happen).

To install IIS 7.5 on Windows Server 2008 R2 Core, follow the instructions on Ruslan Yakushev's blog, at http://tinyurl.com/yaqpngz. You don't necessarily need to install PowerShell support, but I expect you will want IIS remote management support so that you can set up web sites using a GUI (otherwise, it's command line all the way!). Check that IIS is working by opening a web browser on your own workstation and navigating to http://x.x.x.x/, where x.x.x.x is your server's IP address or hostname. You should see the IIS 7-branded welcome screen.

Next, download and install IIS Remote Manager from www.iis.net/expand/IISManager onto your own workstation. You should be able to launch this and connect to the remote IIS instance. Choose File

Deploying to IIS 7.5 on Windows Server 2008 R2 Core

If you've made it this far, you're well on the way! Next, you'll need to install .NET 3.5 SP1 on the server, so enter the following commands into its prompt:

start/wocsetupNetFx3-ServerCore
dism /online /enable-feature /featurename:NetFx3-ServerCore-WOW64

You may be forced to restart the server at this point. Once the command prompt returns, restart the IIS remote management service by issuing the following command:

net start wmsvc

Next, copy your ASP.NET MVC 2 application files to some directory on the server (e.g., via a file share or USB key). To skip the need to configure Access Control List (ACL) permissions, you might like to put them into some folder under c:inetpubwwwroot.

From here on, you can use IIS Remote Manager on your local workstation to create and configure a new IIS 7.5 web site on the remote server by following the same instructions I presented earlier, in the section "Deploying to IIS 7.x on Windows Server 2008/2008 R2."

Note

IIS Remote Manager can't edit file permissions (i.e., ACLs) on the remote server. If you have to edit ACLs, you can use the cacls or icacls command-line tools. For usage information, just enter either of those command names into the server's prompt.

Automating Deployments with WebDeploy and Visual Studio 2010

So far, I've assumed that each time you want to deploy your ASP.NET MVC 2 application, you're willing to copy the application's files to your server manually (remembering to filter out *.cs, *.csproj, and other file types you don't want to deploy), adjust any database connection strings or other configuration settings in Web.config, and apply suitable ACL permissions all by hand. This process is both time-consuming and error-prone.

Visual Studio 2008 has the ability to publish a web application: it sends a filtered set of application files (i.e., just those needed at runtime) to a file system directory, an FTP site, or a Front Page Server Extensions (FPSE)-enabled IIS instance. However, on its own, this is a limited solution, because

  • You may also need to adjust connection strings or other Web.config settings to match different deployment environments.

  • You may also need to configure ACLs on the server, run SQL scripts to update your database, set values in the server's Windows registry, and so on.

  • As a developer, you might not have direct access to production web servers (this is the case in most medium or large corporations), and the IT professionals who do the deployments might not want to run Visual Studio.

  • If you have a central build server (or a CI server), then that should be the only source of builds deployed to QA, staging, or production web servers. As a matter of consistency, you wouldn't also want developers to run ad hoc builds in Visual Studio and then push those builds up your servers.

Visual Studio 2010 goes a long way toward overcoming these issues with a range of new packaging and publishing features all built on a technology called WebDeploy. Of course, every development team has its own special requirements and procedures to follow, so it tries to be flexible.

  • For the simplest scenarios, it offers online "one-click publishing" of your application directly from Visual Studio 2010 on a developer workstation to IIS 6, 7, or 7.5 on a separate server. The publishing process automatically filters your files to deploy only those needed at runtime, can update connection strings or other Web.config settings to match different deployment environments, instructs IIS to apply the correct ACL permissions to files and folders as they're deployed, and can run SQL scripts to update database schemas and data.

  • For more complex scenarios, it offers an offline mode—you generate a deployment "package" that can later be "imported" onto an IIS instance. For example, you could set up your CI server to generate these packages using MsBuild, and then an IT professional (or an automated process) could later push a package onto one or more IIS instances (either through the IIS Manager GUI, or again from the command line). The package includes your application's files, environment-specific Web.config settings, instructions to apply ACL permissions, run SQL scripts, write registry settings, and so on.

Note

This is not the same as a web deployment project—a Visual Studio project type available since 2005 that can replace Web.config sections as a postbuild step and generate an .msi installer for your web application. WebDeploy is a newer and more powerful technology.

No doubt, many of you have more complex requirements than even WebDeploy can handle. For example, you will still need your own strategy for rolling back if the deployment goes wrong. And if you deploy to many servers on a load-balanced web farm, you still have to make your own rollout plans. For example, do you deploy to all servers at once and accept some downtime, or do you deploy sequentially and deal with data synchronization issues?

This book isn't primarily about server administration or managing build infrastructure (whole books and indeed jobs are dedicated to that), so the next few pages will be far from an exhaustive reference. My goal is just to show you, as an ASP.NET MVC developer, an outline of what's possible so that you don't waste weeks reinventing your own duplicate deployment infrastructure.

Transforming Configuration Files

The Web.config settings you use during development are often not the same as those used on QA or production web servers. For example, you'll often need different database connection strings, different settings for the debug switch (mentioned earlier in this chapter), and different <customErrors> settings.

As an automatic way to update Web.config settings as part of the packaging/publishing process, Visual Studio 2010 introduces a feature called config transforms. When you create an ASP.NET MVC 2 project with Visual Studio 2010, you'll notice that Web.config contains two subfiles, Web.Debug.config and Web.Release.config (see Figure 16-9). This is an IDE feature, so it works even if you're targeting .NET 3.5.

Web.config with its transform files

Figure 16-9. Web.config with its transform files

By default, Web.Debug.config is just a placeholder and doesn't do anything. But here's what's in Web.Release.config (comments removed):

<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <system.web>
    <compilation xdt:Transform="RemoveAttributes(debug)" />
  </system.web>
</configuration>

This is an instruction to remove the debug attribute from Web.config's compilation node (so that dynamic page compilation runs in "release" mode, which is the default). This transformation doesn't apply when you run your site locally—it only affects the results of publishing or packaging.

Let's consider how you could create a custom solution profile called QA that not only removes the debug attribute but also modifies a database connection string and a custom <appSettings> value. First, in your main Web.config file, you might define a connection string and a couple of custom setting as follows:

<?xml version="1.0"?>
<configuration>
  <appSettings>
    <add key="UploadedImagesDiskPath" value="c:devmysiteuploadedImages"/>
    <add key="MaxUploadedImageSizeKilobytes" value="2048"/>
</appSettings>
  <connectionStrings>
    <add name="ApplicationServices" providerName="System.Data.SqlClient"
         connectionString="data source=.SQLEXPRESS;Integrated Security=SSPI;" />
  </connectionStrings>
  <system.web>
    <compilation debug="true">
   <!-- assemblies node omitted -->
    </compilation>

   <!-- rest of Web.config omitted -->
  </system.web>
</configuration>

Note

If you're unsure how to access these configuration values in your code, see the section titled "Configuration" in Chapter 17.

Next, to create a custom profile that applies when deploying to a QA[110] server, go to Build

Web.config with its transform files
Creating a new solution configuration

Figure 16-10. Creating a new solution configuration

Click OK and close the pop-up. Next, in Solution Explorer, right-click your main Web.config file and then choose Add Config Transforms. Visual Studio will create Web.QA.config to sit alongside the other transform files shown previously in Figure 16-9. Since it's a duplicate of Web.Release.config, it already contains an instruction to remove the debug attribute. Here's how you can update it to modify your connection string and a custom setting as well:

<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <appSettings>
    <add key="UploadedImagesDiskPath" value="e:QADataPublicImages"
         xdt:Transform="SetAttributes" xdt:Locator="Match(key)"/>
  </appSettings>
  <connectionStrings>
    <add name="ApplicationServices"
      connectionString="someOtherConnectionString"
      xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
  </connectionStrings>
  <system.web>
    <compilation xdt:Transform="RemoveAttributes(debug)" />
  </system.web>
</configuration>

Again, this won't change anything when you run your site locally (not even if you compile with QA as your active solution configuration)—it only affects publishing and packaging. So, the easiest way to see this working is to "publish" your application to some empty folder on your own hard disk.

First, make sure that QA is your active solution configuration (see Build

Creating a new solution configuration

The output will be just the files needed to run your application, and at the top level, there will be only one Web.config file (the others, such as Web.QA.config, won't be there), containing the following:

<?xml version="1.0"?>
<configuration>
  <!-- configSections node omitted -->

  <appSettings>
    <add key="UploadedImagesDiskPath" value="e:QADataPublicImages"/>
    <add key="MaxUploadedImageSizeKilobytes" value="2048"/>
  </appSettings>

  <connectionStrings>
    <add name="ApplicationServices" providerName="System.Data.SqlClient"
         connectionString="someOtherConnectionString" />
  </connectionStrings>

  <system.web>
    <compilation> <!-- Notice the absence of the "debug" attribute -->
      <!-- assemblies node omitted -->
    </compilation>

    <!-- rest of file omitted -->
  </system.web>
</configuration>

For more details about config transform syntax (including the many xdt:Transform verbs such as SetAttributes, Remove, InsertAfter, etc.), see http://tinyurl.com/ydde2vd.

Note

If you're wondering why Microsoft didn't use XSLT as a way of transforming Web.config files (these files are XML, after all), it's simply because xdt:Transform instructions are far easier to use when you're just tweaking values in an XML file rather than radically changing the whole document's shape. If you are keen on XSLT, though, you can actually use xdt:Transform="XSLT(file path)" to run XSLT transformations on specific Web.config nodes.

Automating Online Deployments with One-Click Publishing

You've seen how to publish to a folder on your own hard disk, but what about getting those files onto a server and configuring IIS to serve the application?

"Online one-click publishing" is a streamlined way of deploying from Visual Studio directly to IIS. This is mainly useful when you don't have a CI server doing your builds—perhaps when you're working on smaller projects or deploying to shared hosting.

If your target IIS instance is already configured appropriately (I'll explain how to set this up in a moment), then you can publish to it by choosing Build

Automating Online Deployments with One-Click Publishing
One-click publishing

Figure 16-11. One-click publishing

Internally, it builds a deployment package (with a transformed Web.config file) and transfers it to IIS's web deployment handler. IIS then unpacks this package, copies the contained files to the target web site's folder, applies any ACL settings, and runs any other deployment steps specified by the package. Altogether, this is very convenient compared to manual deployment.

If you're deploying to shared web hosting that supports this publishing mechanism, that should be all you need to do. However, if you're in charge of the IIS instance in question, you'll first need to have installed WebDeploy to the server and have enabled its deployment handler. For details, see http://learn.iis.net/page.aspx/516/configure-the-web-deployment-handler/. This web page doesn't go into much detail about how to install the IIS management service, so you might also want to consult http://learn.iis.net/page.aspx/159/configuring-remote-administration-and-feature-delegation-in-iis-70/.

Automating Offline Deployments with Packaging

As I explained earlier, it's often not desirable to publish from Visual Studio on a developer's workstation directly to a production server. As a developer, you may not have permission to do that. Or, you might only want to deploy the output from a build server or CI server.

To handle more complex scenarios, you can "package" your application for later deployment. You may give this package to an IT professional who does have access to install it on a server, or you may have an automated process that sends it directly from a build server to IIS on some other server.

You can easily generate a package directly from Visual Studio 2010 by right-clicking your ASP.NET MVC project's name in Solution Explorer and then choosing Build Deployment Package. By default, this produces the following files in yourProjectobjconfigurationPackage:

  • YourSiteName.deploy.cmd: A DOS batch file that can install the package

  • YourSiteName.deploy-readme.txt: Information about the DOS batch file

  • YourSiteName.SetParameters.xml: A file in which a server administrator can edit connection strings or other custom parameters before command-line deployment

  • YourSiteName.SourceManifest.xml: More metadata about the package

  • YourSiteName.zip: Your application's files, plus information about parameters (e.g., connection strings) that can be supplied as part of the deployment process

To customize packaging further, right-click your project's name in Solution Explorer and then choose Package/Publish Settings (Figure 16-12).

Choose Package/Publish Settings.

Figure 16-12. Choose Package/Publish Settings.

Instead of generating packages using Visual Studio, you can generate them from the Visual Studio command prompt using a command such as the following (replacing QA with Release, Debug, or any other solution configuration):

msbuild projName.csproj /T:Package /P:Configuration=QA;PackageLocation=C:Deploy.zip

The command-line option is very handy if you want a build server or CI server to generate deployment packages.

Whichever way you generate the package, you can later import it to IIS in one of two ways:

  • Using IIS Manager: You must first have installed WebDeploy onto the target server (see the preceding instructions regarding one-click publishing). Then, using IIS Manager on the target server, select the site to which you want to deploy, and then choose Import Application from the Actions pane. Choose your deployment package's ZIP file, and follow the wizard.

    You can also import using IIS Remote Manager on your own workstation. For this to work, the target server must also be running the IIS management service (again, see the preceding instructions regarding one-click publishing).

  • Using the command line: For example, copy the package to the target server (not just the ZIP file; also copy the other generated files), and then copy the DOS batch file previously generated by the packaging tool—for example:

    YourSiteName.deploy.cmd /Y

    Here, the /Y option means "yes, seriously." If you omit this, you'll just get a handy page of usage information that describes various other command-line switches you can use. For example, the /M switch lets you specify a remote server to be the deployment target (this is somewhat harder to get working, because you then need to deal with authentication too).

WebDeploy is a powerful technology. It can write registry settings, recycle IIS applications, set ACL permissions, and synchronize folder contents, even to remote machines. Plus, you can declare deployment parameters so that IIS Manager will prompt for custom settings as part of the import process, and it will then update Web.config with the supplied values. This last option is useful if you need to distribute a single package to multiple IIS administrators who must each specify their own disk paths, connection strings, encryption keys, or other settings.

For more about WebDeploy, see its web site at www.iis.net/download/WebDeploy.

Summary

In this chapter, you considered many of the issues you'll face when deploying an ASP.NET MVC 2 application to a production web server. These include the process of installing IIS, deploying your application files, and making the routing system play nicely with the web server. You also learned about WebDeploy and its support in Visual Studio 2010, which can eliminate many manual steps from deployment, saving time and avoiding mistakes. It was a brief guide, but hopefully you'll now be well equipped for most deployment scenarios.

If you want to become a genuine IIS expert, there's much more you can learn about application health monitoring, process recycling, trust levels, throttling bandwidth/CPU/memory usage, and so on. You can consult a dedicated IIS administration resource for more details about these.



[102] ASP.NET MVC projects by default use the classic precompilation model that's been available since ASP.NET 1.0, not the unpopular dynamic compilation option that was introduced with ASP.NET 2.0. That's why ASP.NET MVC applications don't need any C# code files on the server.

[103] Internet Services API (ISAPI) is IIS's old plug-in mechanism. It allows unmanaged C/C++ DLLs to run as part of the request handling pipeline.

[104] Actually, IIS will invoke each registered wildcard map in turn until one handles the request. If none does, then it will use its native static file handler.

[105] If you're using Internet Explorer, make sure the page isn't just cached in your browser. Press F5 for a proper refresh.

[106] This is partly to protect you in the event that vulnerabilities are subsequently discovered in obscure IIS features and services, but more importantly to reduce your chances of accidentally misconfiguring the server in some way that exposes more than you intended.

[107] As I explained in the note earlier in this chapter under the instructions for deploying to IIS 6, the .NET Framework 3.5 does not have a CLR of its own—it runs on the CLR from .NET 2.0. However, .NET 4 does have its own separate CLR.

[108] Unlike earlier versions of IIS, which stored configuration information in a separate "metabase" (which isn't so easy to deploy).

[109] This is beneficial if you want the same Web.config file to work properly in IIS 7.x (integrated), Visual Studio's built-in web server, and IIS 6.

[110] This stands for "quality assurance." For this example, it's just an arbitrary name. In your company, you may use different names for your different deployment environments (e.g., Test, Staging, Integration, Production, Live, etc.).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.143.32