我一直在尝试将服务器端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());
}
}
}
答案 0 :(得分:3)
当您完成将数据读入MemoryStream后,位置位于流的末尾。在将流发送到StreamReader / XmlReader之前,您需要将位置重置为0。
stream.Position = 0;
/* or */
stream.Seek(0, SeekOrigin.Begin);
答案 1 :(得分:2)
我对此工作有点惊讶(即使在重置了流的位置之后)。我稍微探讨了HttpApplication
代码,虽然我不完全理解它,但看起来你可能在请求处理过程中太晚修改了输出流。
如果您还没有想到这一点,请尝试将第二个处理函数附加到PostReleaseRequestState
之后的其中一个事件 - UpdateRequestCache
或PostUpdateRequestCache
。两种声音都不是特别合适,但请继续阅读!
出于某种原因,MSDN documentation for HttpApplication
在其事件列表中不包含PreSendRequestContent
,但Reflector显示其处理程序在HttpResponse.Flush
之前不会被调用。
如果我正确阅读代码,Response.Flush
会在调用处理程序之前计算内容长度,因此它认为在获得此代码时响应为空:
if (contentLength > 0L) {
byte[] bytes = Encoding.ASCII.GetBytes(Convert.ToString(contentLength, 0x10) + "\r\n");
this._wr.SendResponseFromMemory(bytes, bytes.Length);
this._httpWriter.Send(this._wr);
this._wr.SendResponseFromMemory(s_chunkSuffix, s_chunkSuffix.Length);
}
根据您的入口点和初始条件,可能会调用一些备用路径,这可能解释了为什么这会在某些时候起作用而不是其他起作用。但是在一天结束时,您可能不应该在Flush
进入时修改响应流。
你正在做一些不寻常的事情 - 你并没有真正过滤传统意义上的响应流(你将一些字节传递给另一个流),所以你可能不得不做一些有点hackish的事情让你的目前的设计工作。
另一种方法是使用IHttpHandler
而不是模块来实现这一点 - a good example here。它涉及从数据库查询转换输出,但应该很容易适应文件系统数据源。
答案 2 :(得分:1)
即使您不坚持msdn example,您也应该实施 HttpApplication.EndRequest :
context.EndRequest += (sender, e) => {
HttpResponse response = HttpContext.Current.Response;
response.Flush();
};
<强>清洁器强>
// ...
public void Init(HttpApplication application)
{
// ...
application.EndRequest += (new EventHandler(this.Application_EndRequest));
}
private void Application_EndRequest(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Current.Response.Flush();
}