如何在 HttpModule 中执行 XSLT 转换?
我一直在尝试将服务器端 XSLT 转换实现为 IIS HttpModule。我的基本方法是在 BeginRequest 处安装一个新的过滤器,将写入转移到 MemoryStream 中,然后在 PreSendRequestContent 处安装一个新的过滤器,使用 XSLT 转换文档并将其写入原始输出流。然而,即使没有执行转换,我显然也做错了一些事情,因为 HttpModule 似乎适用于第一个页面加载,然后我根本没有从服务器得到任何响应,直到我重新启动应用程序池。转换完成后,我第一次得到一个空白页面,然后没有任何响应。我显然在做一些愚蠢的事情,但这是我多年来编写的第一个 C# 代码(也是我第一次尝试 HttpModule),我不知道问题是什么。我犯了什么错误? (我已注释掉下面代码中的 XSLT 部分,并取消注释将缓存内容写入响应的行。)
using System;
using System.IO;
using System.Text;
using System.Web;
using System.Xml;
using System.Xml.Xsl;
namespace Onyx {
public class OnyxModule : IHttpModule {
public String ModuleName {
get { return "OnyxModule"; }
}
public void Dispose() {
}
public void Init(HttpApplication application) {
application.BeginRequest += (sender, e) => {
HttpResponse response = HttpContext.Current.Response;
response.Filter = new CacheFilter(response.Filter);
response.Buffer = true;
};
application.PreSendRequestContent += (sender, e) => {
HttpResponse response = HttpContext.Current.Response;
CacheFilter cache = (CacheFilter)response.Filter;
response.Filter = cache.originalStream;
response.Clear();
/* XmlReader xml = XmlReader.Create(new StreamReader(cache), new XmlReaderSettings() {
ProhibitDtd = false,
ConformanceLevel = ConformanceLevel.Auto
});
XmlWriter html = XmlWriter.Create(response.OutputStream, new XmlWriterSettings() {
ConformanceLevel = ConformanceLevel.Auto
});
XslCompiledTransform xslt = new XslCompiledTransform();
xslt.Load("http://localhost/transformations/test_college.xsl", new XsltSettings() {
EnableDocumentFunction = true
}, new XmlUrlResolver());
xslt.Transform(xml, html); */
response.Write(cache.ToString());
response.Flush();
};
}
}
public class CacheFilter : MemoryStream {
public Stream originalStream;
private MemoryStream cacheStream;
public CacheFilter(Stream stream) {
originalStream = stream;
cacheStream = new MemoryStream();
}
public override int Read(byte[] buffer, int offset, int count) {
return cacheStream.Read(buffer, offset, count);
}
public override void Write(byte[] buffer, int offset, int count) {
cacheStream.Write(buffer, offset, count);
}
public override bool CanRead {
get { return cacheStream.CanRead; }
}
public override string ToString() {
return Encoding.UTF8.GetString(cacheStream.ToArray());
}
}
}
I've been trying to implement server-side XSLT transformations as an IIS HttpModule. My basic approach is to install a new filter at BeginRequest that diverts writes into a MemoryStream, and then at PreSendRequestContent to transform the document using XSLT and write it to the original output stream. However, even without performing the transformation I'm clearly doing something wrong as the HttpModule appears to work for the first page load and then I get no response from the server at all until I restart the application pool. With the transformation in place I get an empty page the first time and then no response. I'm clearly doing something stupid but this is the first C# code I'd written in years (and my first attempt at an HttpModule) and I have no idea what the problem might be. What mistakes am I making? (I've commented out the XSLT part in the code below and uncommented a line that writes the contents of the cache to the response.)
using System;
using System.IO;
using System.Text;
using System.Web;
using System.Xml;
using System.Xml.Xsl;
namespace Onyx {
public class OnyxModule : IHttpModule {
public String ModuleName {
get { return "OnyxModule"; }
}
public void Dispose() {
}
public void Init(HttpApplication application) {
application.BeginRequest += (sender, e) => {
HttpResponse response = HttpContext.Current.Response;
response.Filter = new CacheFilter(response.Filter);
response.Buffer = true;
};
application.PreSendRequestContent += (sender, e) => {
HttpResponse response = HttpContext.Current.Response;
CacheFilter cache = (CacheFilter)response.Filter;
response.Filter = cache.originalStream;
response.Clear();
/* XmlReader xml = XmlReader.Create(new StreamReader(cache), new XmlReaderSettings() {
ProhibitDtd = false,
ConformanceLevel = ConformanceLevel.Auto
});
XmlWriter html = XmlWriter.Create(response.OutputStream, new XmlWriterSettings() {
ConformanceLevel = ConformanceLevel.Auto
});
XslCompiledTransform xslt = new XslCompiledTransform();
xslt.Load("http://localhost/transformations/test_college.xsl", new XsltSettings() {
EnableDocumentFunction = true
}, new XmlUrlResolver());
xslt.Transform(xml, html); */
response.Write(cache.ToString());
response.Flush();
};
}
}
public class CacheFilter : MemoryStream {
public Stream originalStream;
private MemoryStream cacheStream;
public CacheFilter(Stream stream) {
originalStream = stream;
cacheStream = new MemoryStream();
}
public override int Read(byte[] buffer, int offset, int count) {
return cacheStream.Read(buffer, offset, count);
}
public override void Write(byte[] buffer, int offset, int count) {
cacheStream.Write(buffer, offset, count);
}
public override bool CanRead {
get { return cacheStream.CanRead; }
}
public override string ToString() {
return Encoding.UTF8.GetString(cacheStream.ToArray());
}
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
当您将数据读入 MemoryStream 后,该位置位于流的末尾。在将流发送到 StreamReader/XmlReader 之前,您需要将位置重置为 0。
When you are done reading the data into your MemoryStream the position is at the end of the stream. Before sending the stream to the StreamReader/XmlReader you need to reset the position to 0.
我有点惊讶这竟然有效(即使在重置流的位置之后)。我稍微研究了一下 HttpApplication 代码,虽然我不完全理解它,但看起来您可能在请求处理过程中太晚修改了输出流。
如果您仍然没有弄清楚这一点,请尝试将第二个处理程序函数附加到
PostReleaseRequestState
-UpdateRequestCache
或PostUpdateRequestCache
。听起来都不太合适,但请继续阅读!由于某种原因,
HttpApplication 的 MSDN 文档
的事件列表中不包含
PreSendRequestContent
,但 Reflector 显示其处理程序在HttpResponse.Flush
之前不会被调用。如果我正确地阅读了代码,
Response.Flush
会在调用处理程序之前计算内容长度,因此当它到达以下代码时,它会认为响应为空:根据您的入口点和初始条件,可能会调用一些备用路径,这可能解释了为什么它有时有效,但有时无效。但归根结底,一旦进入
Flush
,您可能就不应该修改响应流。你正在做一些有点不寻常的事情 - 你并没有真正过滤传统意义上的响应流(将一些字节传递到另一个流),所以你可能需要做一些有点黑客的事情才能使你当前的设计工作。
另一种方法是使用
IHttpHandler
而不是模块来实现它 - 有 这里是一个很好的例子。它处理数据库查询的转换输出,但应该很容易适应文件系统数据源。I'm a little surprised this works at all (even after resetting the stream's position). I poked around the
HttpApplication
code a bit, and though I don't fully understand it, it looks like you may be modifying the output stream too late in the request handling process.If you still haven't figured this out, try attaching your second handler function to one of the events after
PostReleaseRequestState
- eitherUpdateRequestCache
orPostUpdateRequestCache
. Neither sounds especially suitable, but read on!For some reason, the MSDN documentation for
HttpApplication
doesn't includePreSendRequestContent
in its list of events, but Reflector shows that its handlers don't get called untilHttpResponse.Flush
.If I'm reading the code right,
Response.Flush
calculates the content length before the handlers are called, so it thinks the response is empty when it gets to this code:There are some alternate paths that may get called depending on your entry point and initial conditions, and that might explain why this works some of the time but not others. But at the end of the day you probably shouldn't be modifying the response stream once you're in
Flush
.You're doing something a little unusual - you're not really filtering the response stream in the traditional sense (where you pass some bytes along to another stream), so you may have to do something a bit hackish to make your current design work.
An alternative would be to implement this using an
IHttpHandler
instead of a module - there's a good example here. It deals with transforming output from a database query, but should be easy to adapt to a file system data source.即使您不坚持使用 msdn 示例,您也应该实施HttpApplication.EndRequest:
更干净
Even if you don't stick to the msdn example you should implement HttpApplication.EndRequest:
cleaner