I have a working solution, with two caveats:
First, it will always render the trace output too early, because it's too late to do that in a Page.ProcessRequest() override (the Response
object has already been cleaned up), so we're forced to do it during the Render
phase, which means we'll miss some messages (most notably EndRender
).
Implementing that behavior in a control exacerbates the problem, since we'd have to ensure our control is the last thing to render on the page in order to avoid missing more messages. For that reason, I chose to implement a custom page class instead of a custom control class. If you absolutely need a control class, it should be easy to convert (but leave me a word here if you need help).
Second, the profiler object that owns the data, HttpRuntime.Profile
, is internal
to the System.Web
assembly, and of course the trace rendering routine is private
to the Page
class. So we have to abuse reflection, break encapsulation, and basically be evil in order to do what you want. If the ASP.NET trace implementation changes in the slightest, we're SOL.
That said, here's the traceable page class:
using System;
using System.Reflection;
using System.Web;
using System.Web.UI;
namespace StackOverflow.Bounties.Web.UI
{
public class TraceablePage : Page
{
/// <summary>
/// Gets or sets whether to render trace output.
/// </summary>
public bool EnableTraceOutput
{
get;
set;
}
/// <summary>
/// Abuses reflection to force the profiler's page output flag
/// to true during a call to the page's trace rendering routine.
/// </summary>
protected override void Render(HtmlTextWriter writer)
{
base.Render(writer);
if (!EnableTraceOutput) {
return;
}
// Allow access to private and internal members.
BindingFlags evilFlags
= BindingFlags.Instance | BindingFlags.Static
| BindingFlags.Public | BindingFlags.NonPublic;
// Profiler profiler = HttpRuntime.Profile;
object profiler = typeof(HttpRuntime)
.GetProperty("Profile", evilFlags).GetGetMethod(true)
.Invoke(null, null);
// profiler.PageOutput = true;
profiler.GetType().GetProperty("PageOutput", evilFlags)
.GetSetMethod(true).Invoke(profiler, new object[] { true });
// this.ProcessRequestEndTrace();
typeof(Page).GetMethod("ProcessRequestEndTrace", evilFlags)
.Invoke(this, null);
// profiler.PageOutput = false;
profiler.GetType().GetProperty("PageOutput", evilFlags)
.GetSetMethod(true).Invoke(profiler, new object[] { false });
}
}
}
And here's its test page, which uses an AutoPostBack
check box to demonstrate its behavior across postbacks:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="TestTracePage.aspx.cs"
Inherits="StackOverflow.Bounties.Web.UI.TestTracePage" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>TraceablePage Test</title>
</head>
<body>
<form id="form" runat="server">
<h2>TraceablePage Test</h2>
<p>
<asp:CheckBox id="enableTrace" runat="server"
AutoPostBack="True" Text="Enable trace output"
OnCheckedChanged="enableTrace_CheckedChanged" />
</p>
</form>
</body>
</html>
And the code behind:
using System;
using System.Web.UI;
namespace StackOverflow.Bounties.Web.UI
{
public partial class TestTracePage : TraceablePage
{
protected void enableTrace_CheckedChanged(object sender, EventArgs e)
{
EnableTraceOutput = enableTrace.Checked;
}
}
}
The test page renders like this on first load:
Checking the box posts back and renders the trace output:
Clearing the check box again suppresses the trace output, as expected.