问题
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());
}
}
}
回答1:
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.
stream.Position = 0;
/* or */
stream.Seek(0, SeekOrigin.Begin);
回答2:
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 - either UpdateRequestCache or PostUpdateRequestCache. Neither sounds especially suitable, but read on!
For some reason, the MSDN documentation for HttpApplication doesn't include PreSendRequestContent
in its list of events, but Reflector shows that its handlers don't get called until HttpResponse.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:
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);
}
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.
回答3:
Even if you don't stick to the msdn example you should implement HttpApplication.EndRequest:
context.EndRequest += (sender, e) => {
HttpResponse response = HttpContext.Current.Response;
response.Flush();
};
cleaner
// ...
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();
}
来源:https://stackoverflow.com/questions/2281173/how-can-i-perform-xslt-transformations-in-an-httpmodule