Deployment and Diagnostics
|
This section documents ASP.NET Web Forms on .NET Framework 4.8.1, the last and permanent version of Web Forms — the page life cycle and postback model, ViewState and control state, server controls, validation controls, master pages and themes, data-bound controls, and the provider-based security model — as described by the official documentation at Microsoft Learn and the ASP.NET previous-versions archive, which are the reference these pages are written and verified against. Web Forms receives security fixes only and has no forward path onto modern .NET (.NET Framework 4.8.1 is Microsoft’s last version of .NET Framework; Web Forms itself never shipped on .NET Core/.NET 5+). It remains supported for existing applications running on Windows but is not recommended for new development — see Choosing an ASP.NET Framework and Migrating to Modern ASP.NET for what that means in practice. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Running a Web Forms application in production is inseparable from IIS — this page covers the deployment and operational-diagnostics concerns specific to that combination, on top of the ordinary "watch the logs" practices that apply to any web application.
IIS application pools
Each IIS application pool is an isolated worker process (w3wp.exe) hosting one or more applications, with
its own recycling schedule, identity, and CLR settings:
| Setting | Why it matters for Web Forms |
|---|---|
Integrated pipeline |
The default and effectively only mode used today — see HTTP Pipeline, Handlers, and Configuration for classic-vs-integrated behavior. |
32-bit vs. 64-bit |
"Enable 32-Bit Applications" must match what any native-dependency assemblies were
built for; a mismatch throws a |
.NET CLR version |
|
Recycling |
Scheduled/idle-timeout/memory-threshold recycling drops all in-process state — |
Web Deploy (MSDeploy) and Visual Studio publishing profiles
Web Deploy (msdeploy.exe) synchronizes a built application, IIS site/app-pool settings, and optionally
databases, from a build machine (or CI agent) to a target server, either directly over the Web Management
Service or by producing a portable package:
msdeploy.exe -verb:sync -source:contentPath="C:\src\MyApp\obj\Release\Package\PackageTmp" ^
-dest:contentPath="MyApp",computerName="https://webserver:8172/msdeploy.axd",userName=deploy,password=%DEPLOY_PW%,authType=basic
A publish profile (.pubxml, created and edited through Visual Studio’s Publish dialog, or hand-edited)
captures a named deployment target’s settings — destination, credentials reference, whether to precompile,
which web.config transform to apply — so a repeatable msbuild /p:DeployOnBuild=true
/p:PublishProfile=Production can run from CI without Visual Studio installed.
Precompiled vs. updatable deployment
As introduced in
HTTP Pipeline, Handlers, and
Configuration, aspnet_compiler (invoked automatically by a Web Deploy publish, or run standalone) produces
either an updatable precompiled site (markup .aspx/.ascx files still deployed, tweakable in place
without recompiling; code-behind is precompiled into assemblies) or a fully non-updatable precompiled site
(everything, including markup, compiled to assemblies — nothing readable/editable remains on disk). Fully
precompiled is the safer default for anything beyond a small internal tool: it removes first-request
compilation latency, avoids leaking source-adjacent markup on the server, and prevents an accidental
production hotfix made by editing a file directly on the server (a workflow the Web Site Project model, see
Getting Started, made easy but which bypasses source control
entirely).
<customErrors> and error pages
<system.web>
<customErrors mode="RemoteOnly" defaultRedirect="~/Errors/Generic.aspx">
<error statusCode="404" redirect="~/Errors/NotFound.aspx" />
<error statusCode="500" redirect="~/Errors/ServerError.aspx" />
</customErrors>
</system.web>
mode="RemoteOnly" (the default) shows the detailed ASP.NET error/stack-trace page to requests from the local
machine only, and the friendly customErrors page to everyone else — mode="On" forces friendly pages
everywhere (including locally, useful when testing the error pages themselves), mode="Off" disables the
feature entirely and should never be set in production, since it leaks stack traces (and potentially
connection strings, in an unhandled exception message) to end users.
Page and application Trace
Page.Trace/<trace> in web.config gives per-request diagnostic output — request/response details,
control-tree size and ViewState contribution per control (relevant to
measuring ViewState weight), session/application
state dumps, and custom timed messages — either inline at the bottom of the page or collected centrally:
<system.web>
<trace enabled="true" requestLimit="40" pageOutput="false" localOnly="true" />
</system.web>
protected void Page_Load(object sender, EventArgs e)
{
Trace.Write("OrderPage", "Loading order " + Request.QueryString["id"]);
}
With pageOutput="false", trace output is collected application-wide and viewed at ~/Trace.axd instead of
appended to every page — the practical choice for anything beyond debugging a single page locally, since it
does not alter the page’s own rendered output.
Health monitoring
<healthMonitoring> is ASP.NET’s built-in event-based monitoring system — application lifecycle events,
unhandled exceptions, Membership/Forms-authentication failures, and custom WebBaseEvent-derived events — are
routed to one or more configured providers (SQL Server, email, the Windows event log, or a custom provider):
<system.web>
<healthMonitoring enabled="true">
<providers>
<add name="SqlWebEventProvider" type="System.Web.Management.SqlWebEventProvider"
connectionStringName="HealthMonitoring" buffer="false" />
</providers>
<rules>
<add name="All Errors Default" eventName="All Errors" provider="SqlWebEventProvider" />
</rules>
</healthMonitoring>
</system.web>
This predates, and covers roughly the same ground as, structured application logging in later frameworks — most current Web Forms applications either lean on this built-in system for baseline coverage or replace it
outright with a conventional logging library (log4net, NLog, Serilog) called directly from
Application_Error and other code.
ELMAH
ELMAH (Error Logging Modules and Handlers) is the de facto standard third-party error-logging package for
System.Web applications — a drop-in HttpModule (NuGet: elmah) that captures every unhandled exception
automatically, with zero code changes, and exposes a browsable log (~/elmah.axd) plus pluggable storage
(SQL Server, SQLite, Azure Table Storage, XML files, …):
<system.webServer>
<modules>
<add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" preCondition="managedHandler" />
</modules>
</system.webServer>
ELMAH and <healthMonitoring> are not mutually exclusive but overlap significantly in the "log unhandled
exceptions somewhere reviewable" use case — most teams pick one rather than running both.
Performance counters
ASP.NET registers a set of Windows Performance Counters under the ASP.NET Apps v4.0.30319 and ASP.NET
v4.0.30319 categories — Requests/Sec, Requests Queued, Requests Timed Out, % Managed Processor Time,
Errors Total/Sec, Sessions Active, Cache Total Hit Ratio — viewable live via Performance Monitor
(perfmon.exe) or captured for trend analysis by any monitoring agent that reads Windows performance counters.
Requests Queued climbing under load is the classic early signal of thread-pool starvation, frequently caused
by blocking synchronous I/O in code that should have been made asynchronous.
The classic "works on my machine" web.config differences
The most common source of "it worked in dev, broke in production" for Web Forms specifically: a web.config
<compilation debug="true"> left on in production (major performance cost — disables batch compilation and
some JIT optimizations, and leaks stack traces regardless of customErrors), a machineKey that is
auto-generated per-server rather than explicitly shared across a farm (breaking ViewState/Forms-auth validation
intermittently depending on which server handles a given request — see
Security), and connection strings or <appSettings> pointing at a
developer’s local database because a Web.Release.config transform was never actually wired up or tested.
Verifying the deployed, post-transform web.config on the target server — not just the source-controlled
one — after every release catches most of these before they become an incident.