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:
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
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).
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.
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.
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.
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.
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.
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
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.
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 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.
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.
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).
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.
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.
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.
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)
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.
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.
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.
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.
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.
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:
Launch the Manage Your Server application (from Start
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.
If the wizard asks you to choose between "Typical configuration for a first server" and "Custom configuration," choose "Custom configuration," and then click Next.
Select "Application server (IIS, ASP.NET)," and then click Next.
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.
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.
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."
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:
Open IIS Manager (from Control Panel
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.
Add a new web site entry by right-clicking the Web Sites node and choosing New
Enter some descriptive name for the web site (e.g., its intended domain name) and click Next.
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.
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.
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.
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.
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.
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?
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
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!
The way to resolve this depends on your .NET Framework version. Let's first consider applications that target .NET 3.5.
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.
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
.
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
For Executable, put c:windowsmicrosoft.netframeworkv2.0.50727aspnet_isapi.dll
, or copy and paste the value from Executable in the existing .aspx
mapping.
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.
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
.
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 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.
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
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.
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.
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.
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.NET
bitnessversion
, 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.
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).
Take the following steps to install IIS onto Windows Server 2008 or Windows Server 2008 R2:
Open Server Manager (via Start
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.
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.
You should now get a page of information about IIS. Click Next.
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.
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.
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.
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).
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.
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:
Open IIS Manager (from Start
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.
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.
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.
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).
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).
.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.
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.
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
).
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.
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"/>
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.
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]
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.NET
bitness
clr
aspnet_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
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).
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
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."
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.
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.
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.
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.
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> <compilationdebug="true"
> <!-- assemblies node omitted --> </compilation> <!-- rest of Web.config omitted --> </system.web> </configuration>
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
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
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
.
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.
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
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/
.
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 yourProject
obj
configuration
Package
:
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).
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
.
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.).
18.119.143.32