如何在HttpModule中执行XSLT转换?

时间:2010-02-17 13:53:40

标签: c# iis xslt httpmodule

我一直在尝试将服务器端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());
        }

    }

}

3 个答案:

答案 0 :(得分:3)

当您完成将数据读入MemoryStream后,位置位于流的末尾。在将流发送到StreamReader / XmlReader之前,您需要将位置重置为0。

stream.Position = 0;
/* or */
stream.Seek(0, SeekOrigin.Begin);

答案 1 :(得分:2)

我对此工作有点惊讶(即使在重置了流的位置之后)。我稍微探讨了HttpApplication代码,虽然我不完全理解它,但看起来你可能在请求处理过程中太晚修改了输出流。

如果您还没有想到这一点,请尝试将第二个处理函数附加到PostReleaseRequestState之后的其中一个事件 - UpdateRequestCachePostUpdateRequestCache。两种声音都不是特别合适,但请继续阅读!

出于某种原因,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();
}