假 定某组用户在运行某 Web 应用程序的一个个性化版本,并且该应用程序需要一些远程调试或分析。您不能中断服务,但仍需要了解发生的情况。有了性能计数器和内置的跟踪功能可能还不 够。所部署的 ASP.NET 页面有可能根本没有包含或启用跟踪指令。在这种情况下,您需要进行远程干预,注入跟踪代码,这是最基本的要求。
还有一种情形,就是需要临时更改多种页面。对于单个页面,您只需创建旧页面的一个副本并替换它即可。不过,更新多个页面可能就比较困难了,并且维护工作也可能及其复杂。
第三种情形,就是企业策略禁止对源代码进行写操作。在这种情况下,源代码可读,但不可修改。不过,您可以向站点添加扩展。
最后一种情形,也是最复杂并且我真的遇到过的一种情形,即公司运行的 Web 应用程序的源代码由于某种原因不再可用。
可以看出,需要在不访问源代码的情况下修改页面的运行时行为的情形非常多。那么接下来该怎么办呢?
可行的方法
有许多方法可以在不接触源代码的情况下修改正在运行的 ASP.NET 页面。图 1 列出了几种方法。不是所有的方法在任何情形下都有效,而且有些方法可以一起使用。有的方法可能需要编写新的 HTTP 模块或 HTTP 处理程序,有的方法则可能要求对 web.config 文件进行更改。
方法 | 实现 |
访问控件树 |
HTTP 模块 |
修改页面基类 |
web.config |
控件替换 |
web.config |
构建提供程序 |
程序集 |
重定向页面 |
HTTP 处理程序 |
重写页面 |
HTTP 处理程序或 URL 重写 |
每个 ASP.NET 请求的处理都需要一个专用组件,即 HTTP 处理程序。当在应用程序生命周期内触发 PostMapRequestHandler 事件时,可引用负责处理当前请求的 HTTP 处理程序。
Code pieces:
Code
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Drawing;
/// <summary>
/// Summary description for Class3
/// </summary>
public class Class3: IHttpModule
{
public Class3()
{
//
// TODO: Add constructor logic here
//
}
#region IHttpModule Members
public void Dispose()
{
}
HttpApplication _context;
public void Init(HttpApplication context)
{
_context = context;
context.PostMapRequestHandler += new EventHandler(context_PostMapRequestHandler);
context.EndRequest += new EventHandler(context_EndRequest);
}
void context_EndRequest(object sender, EventArgs e)
{
}
void context_PostMapRequestHandler(object sender, EventArgs e)
{
if (_context.Context.Handler is Page)
{
(_context.Context.Handler as Page).PreInit += new EventHandler(Class3_PreInit);
(_context.Context.Handler as Page).Load += new EventHandler(Class3_Load);
(_context.Context.Handler as Page).Unload += new EventHandler(Class3_Unload);
}
}
void Class3_Unload(object sender, EventArgs e)
{
}
void Class3_Load(object sender, EventArgs e)
{
string ctrlName = "iambox";
Control ctl = FindControl(sender as Control, ctrlName);
if (ctl != null)
{
ctl.Parent.Controls.Remove(ctl);
}
}
void Class3_PreInit(object sender, EventArgs e)
{
(sender as Page).Response.Write("How are you?");
Label l = new Label();
l.Text = "changed by this Label!";
l.ForeColor = Color.Blue;
l.Font.Size = 30;
(sender as Page).Controls.AddAt(0, l);
}
#endregion
Control FindControl(Control ctl, string ctrlName)
{
if (ctl.ID != null)
{
if (ctl.ID.Equals(ctrlName, StringComparison.CurrentCultureIgnoreCase))
return ctl;
}
Control controlToFind = null;
foreach (Control c in ctl.Controls)
{
controlToFind = FindControl(c, ctrlName);
if (controlToFind != null)
break;
}
return controlToFind;
}
}
Code
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Web.Caching;
using System.Data.SqlClient;
/// <summary>
/// Summary description for Class2
/// </summary>
public class Class2 : IHttpHandler
{
public Class2()
{
//
// TODO: Add constructor logic here
//
}
#region IHttpHandler Members
public bool IsReusable
{
get { return true; }
}
public virtual void ProcessRequest(HttpContext context)
{
context.Response.Write("Hello!");
}
#endregion
}
HandlerTest.ashx
Code
<%@ WebHandler Language="VB" Class="Handler" %>
Public Class Handler
Implements IHttpHandler
ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
Get
Return True
End Get
End Property
Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
' Set up the response settings
context.Response.ContentType = "image/jpeg"
context.Response.Cache.SetCacheability(HttpCacheability.Public)
context.Response.BufferOutput = False
' Setup the Size Parameter
Dim size As PhotoSize = PhotoSize.Original
Select Case context.Request.QueryString("Size")
Case "S"
size = PhotoSize.Small
Case "M"
size = PhotoSize.Medium
Case "L"
size = PhotoSize.Large
Case Else
size = PhotoSize.Original
End Select
' Setup the PhotoID Parameter
Dim id As Int32 = 1
Dim stream As IO.Stream = Nothing
If ((Not (context.Request.QueryString("PhotoID")) Is Nothing) _
AndAlso (context.Request.QueryString("PhotoID") <> "")) Then
id = [Convert].ToInt32(context.Request.QueryString("PhotoID"))
stream = PhotoManager.GetPhoto(id, size)
Else
id = [Convert].ToInt32(context.Request.QueryString("AlbumID"))
stream = PhotoManager.GetFirstPhoto(id, size)
End If
' Get the photo from the database, if nothing is returned, get the default "placeholder" photo
If (stream Is Nothing) Then
stream = PhotoManager.GetPhoto(size)
End If
' Write image stream to the response stream
Dim buffersize As Integer = (1024 * 16)
Dim buffer() As Byte = New Byte((buffersize) - 1) {}
Dim count As Integer = stream.Read(buffer, 0, buffersize)
Do While (count > 0)
context.Response.OutputStream.Write(buffer, 0, count)
count = stream.Read(buffer, 0, buffersize)
Loop
End Sub
End Class
Code
<httpModules>
<add name="test" type="Class3"/>
</httpModules>
<httpHandlers>
<add verb="*" path="pageForbiddenhandler.aspx,ES Major Defects.xlsx" type="System.Web.HttpForbiddenHandler"/>
<add verb="*" path="pagehandlertest.aspx" type="Class2"/>
</httpHandlers>