2010-02-17 8 views
3

J'ai essayé d'implémenter des transformations XSLT côté serveur comme IIS HttpModule. Mon approche de base consiste à installer un nouveau filtre à BeginRequest qui détourne les écritures dans un MemoryStream, puis à PreSendRequestContent pour transformer le document en utilisant XSLT et l'écrire dans le flux de sortie d'origine. Cependant, même sans effectuer la transformation, je fais clairement quelque chose de mal car le HttpModule semble fonctionner pour le premier chargement de la page et ensuite je n'obtiens aucune réponse du serveur jusqu'à ce que je redémarre le pool d'applications. Avec la transformation en place, je reçois une page vide la première fois, puis aucune réponse. Je fais clairement quelque chose de stupide mais c'est le premier code C# que j'ai écrit depuis des années (et ma première tentative à un HttpModule) et je n'ai aucune idée de ce que pourrait être le problème. Quelles erreurs fais-je? (Je l'ai fait remarquer la partie XSLT dans le code ci-dessous et décommenté une ligne qui écrit le contenu du cache de la réponse.)Comment puis-je effectuer des transformations XSLT dans un HttpModule?

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

Aucune infraction, mais votre échantillon est miles de cette fantaisie "Clean Code" chose. – Filburt

+0

@Filburt - s'il vous plaît soyez plus précis, ou vous ne faites que du spam. –

+0

@Jeff: Ok, juste deux points évidents: 1) Naming - (contexte HttpApplication) est trompeur. 2) entasser tout le matériel dans Init() qui, selon son nom, devrait être Init the Module. – Filburt

Répondre

3

Lorsque vous avez terminé la lecture des données dans votre MemoryStream la position est à la fin du flux. Avant d'envoyer le flux au StreamReader/XmlReader vous devez réinitialiser la position à 0.

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

La deuxième version a les arguments dans le mauvais ordre. J'ai maintenant modifié le code pour le faire et je reçois la sortie XSLT une fois. La deuxième fois que je charge la page, la valeur hexadécimale 0x1F est un caractère invalide Ligne 1, position 1, ce qui est certainement un pas en avant. – Rich

+0

Corrigé mon code .. c'est ce que je reçois sans intellisense;) Pour plus de débogage, vous devriez vider votre flux entrant sur le disque et d'examiner qu'il est en fait valide xml. Se pourrait-il que ce soit gzippé? –

+0

0x1F semble être la nomenclature de votre réponse codée utf-8. Vous pouvez indiquer à UTF8Encoding d'omettre la nomenclature: http://msdn.microsoft.com/en-us/library/s064f8w2.aspx – Filburt

2

Je suis un peu surpris que cela fonctionne du tout (même après la réinitialisation de la position du flux). J'ai tapoté un peu le code HttpApplication, et bien que je ne le comprenne pas complètement, il semble que vous modifiez le flux de sortie trop tard dans le processus de gestion des requêtes.

Si vous avez toujours pas compris cela, essayez d'attacher votre deuxième fonction de gestionnaire à l'un des événements après PostReleaseRequestState - soit UpdateRequestCache ou PostUpdateRequestCache. Ni l'un ni l'autre ne semble particulièrement approprié, mais lisez la suite!

Pour une raison quelconque, le MSDN documentation for HttpApplication n'inclut pas PreSendRequestContent dans sa liste d'événements, mais Reflector montre que ses gestionnaires ne sont pas appelés avant HttpResponse.Flush.

Si je lis le bon code, Response.Flush calcule la longueur du contenu avant les gestionnaires sont appelés, il pense que la réponse est vide quand il est à ce 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); 
} 

Il y en a des chemins alternatifs qui peuvent être appelés en fonction de votre point d'entrée et des conditions initiales, ce qui pourrait expliquer pourquoi cela fonctionne une partie du temps mais pas d'autres. Mais à la fin de la journée, vous ne devriez probablement pas modifier le flux de réponse une fois que vous êtes dans Flush. Vous faites quelque chose d'un peu inhabituel - vous ne filtrez pas vraiment le flux de réponse dans le sens traditionnel (où vous passez des octets à un autre flux), vous devrez peut-être faire quelque chose d'un peu hackish pour faire votre travail de conception actuel.

Une alternative serait d'implémenter ceci en utilisant un IHttpHandler au lieu d'un module - il y a a good example here. Il traite de la transformation de la sortie d'une requête de base de données, mais devrait être facile à adapter à une source de données de système de fichiers.

+0

J'ai essayé d'attacher les gestionnaires à d'autres événements mais en vain. Je pense que je pourrais faire quelque chose de mal avec les cours d'eau. Dans ce cas, une implémentation HttpHandler n'est pas la meilleure option car je veux être capable de transformer des fichiers, des services de données WCF, la sortie d'autres applications et ainsi de suite de façon transparente. Pour le moment, nous utilisons l'URL rewriter pour mapper des URL externes à un script PHP qui transmet les requêtes à un autre point de terminaison, puis transforme les réponses en utilisant libxslt mais les performances pourraient être meilleures et très loin d'être transparentes. – Rich

+0

Blast - Eh bien, je vais laisser ça ici pour repousser quelqu'un d'autre qui pourrait être aussi mal avisé. –

1

Même si vous ne respectez pas le msdn example, vous devez implémenter HttpApplication.EndRequest:

context.EndRequest += (sender, e) => { 
    HttpResponse response = HttpContext.Current.Response; 
    response.Flush(); 
}; 

propre

// ... 

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(); 
} 
+0

Ironiquement, c'est ainsi que j'ai commencé, mais j'ai ensuite changé pour la version lambda parce que je pensais que c'était plus propre. – Rich