Как я могу выполнить преобразования XSLT в HttpModule? - PullRequest
3 голосов
/ 17 февраля 2010

Я пытался реализовать XSLT-преобразования на стороне сервера в виде HttpModule IIS. Мой основной подход заключается в установке нового фильтра в 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 ]

3 голосов
/ 17 февраля 2010

Когда вы закончите чтение данных в MemoryStream, позиция будет в конце потока. Перед отправкой потока в StreamReader / XmlReader необходимо сбросить позицию на 0.

stream.Position = 0;
/* or */
stream.Seek(0, SeekOrigin.Begin);
2 голосов
/ 17 февраля 2010

Я немного удивлен, что это работает вообще (даже после сброса положения потока). Я немного обшарил код HttpApplication, и, хотя я не до конца его понимаю, похоже, вы слишком поздно модифицировали выходной поток в процессе обработки запроса.

Если вы до сих пор не поняли этого, попробуйте подключить функцию второго обработчика к одному из событий после PostReleaseRequestState - либо UpdateRequestCache, либо PostUpdateRequestCache. Не подходит ни один из них, но читайте дальше!

По какой-то причине документация MSDN для 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.

Вы делаете что-то немного необычное - вы на самом деле не фильтруете поток ответов в традиционном смысле (когда вы передаете несколько байтов в другой поток), поэтому вам, возможно, придется сделать что-то немного хакерское, чтобы ваш текущие проектные работы.

Альтернативой может быть реализация этого с использованием IHttpHandler вместо модуля - здесь есть хороший пример . Он имеет дело с преобразованием вывода из запроса к базе данных, но его легко адаптировать к источнику данных файловой системы.

1 голос
/ 17 февраля 2010

Даже если вы не придерживаетесь msdn , вы должны реализовать 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();
}
...