2009-12-04 4 views
9

Je développe actuellement un service WCF RESTful. Dans le cadre de la validation des données POST, je lance des exceptions si la requête XML n'est pas conforme à nos règles métier.WCF + REST: Où sont les données de demande?

L'objectif est d'envoyer un courrier électronique au personnel concerné si une demande est considérée comme non valide. Mais, avec les en-têtes de requête entrants, la méthode et l'URI, je voudrais également envoyer le XML qui a été posté.

Je n'ai pas réussi à trouver un moyen d'accéder à ces données. La WCF détruit-elle réellement le corps/les données de la requête avant que j'aie eu la chance d'y accéder ou ai-je oublié quelque chose?

Votre aide est appréciée car je ne comprends pas pourquoi je ne peux pas accéder aux données de la demande.

Répondre

9

Ceci malheureusement n'est pas supporté - nous avions un besoin similaire, et l'avons fait en appelant les membres internes avec la réflexion. Nous l'utilisons simplement dans un gestionnaire d'erreurs (afin que nous puissions vider la requête brute), mais cela fonctionne bien. Je ne le recommanderais pas pour un système que vous ne possédez pas et n'utilisez pas (par exemple, ne livrez pas ce code à un client), car il peut changer à tout moment avec un service pack ou autre.

public static string GetRequestBody() 
{ 
    OperationContext oc = OperationContext.Current; 

    if (oc == null) 
     throw new Exception("No ambient OperationContext."); 

    MessageEncoder encoder = oc.IncomingMessageProperties.Encoder; 
    string contentType = encoder.ContentType; 
    Match match = re.Match(contentType); 

    if (!match.Success) 
     throw new Exception("Failed to extract character set from request content type: " + contentType); 

    string characterSet = match.Groups[1].Value; 

    object bufferedMessage = operationContextType.InvokeMember("request", 
     BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetField, 
     null, oc, null); 

    //TypeUtility.AssertType(bufferedMessageType, bufferedMessage); 

    object messageData = bufferedMessageType.InvokeMember("MessageData", 
     BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetProperty, 
     null, bufferedMessage, null); 

    //TypeUtility.AssertType(jsonBufferedMessageDataType, messageData); 

    object buffer = jsonBufferedMessageDataType.InvokeMember("Buffer", 
     BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty, 
     null, messageData, null); 

    ArraySegment<byte> arrayBuffer = (ArraySegment<byte>)buffer; 

    Encoding encoding = Encoding.GetEncoding(characterSet); 

    string requestMessage = encoding.GetString(arrayBuffer.Array, arrayBuffer.Offset, arrayBuffer.Count); 

    return requestMessage; 
} 
+1

vache sainte!C'est encore pire que ma solution :-) –

+0

D'accord, l'avantage est que nous n'avons pas à faire passer une deuxième copie de la couche de transport avec un inspecteur de message à chaque requête. De cette façon, nous pouvons obtenir le tampon original directement à partir du code de service, et seulement en cas de problème. D'où ma prudence d'origine. :) J'aurais aimé qu'ils l'exposent simplement sur le WebOperationContext, mais après l'avoir démonté, je vois pourquoi ils ne le font pas (surtout quand vous considérez des requêtes streamées de taille arbitraire). – nitzmahone

+0

Merci de répondre. Je comprends maintenant pourquoi vous prenez cette approche. Il est intéressant de savoir que pour comprendre pourquoi la WCF fonctionne comme elle le fait, il faut creuser dans la mise en œuvre. Cela va en quelque sorte à l'encontre du but d'essayer d'abstraire la complexité! –

9

Donc, si vous déclarez votre contrat quelque chose comme:

[WebInvoke(Method = "POST", UriTemplate = "create", ResponseFormat=WebMessageFormat.Json)] 
int CreateItem(Stream streamOfData); 

(vous pouvez utiliser XML au lieu) Le streamOfData devrait être le corps d'un HTTP POST. Vous pouvez le désérialiser en utilisant quelque chose comme:

StreamReader reader = new StreamReader(streamId); 
String res = reader.ReadToEnd(); 
NameValueCollection coll = HttpUtility.ParseQueryString(res); 

Cela fonctionne comme ça pour nous, au moins. Vous souhaiterez peut-être utiliser une approche différente pour obtenir la chaîne dans un XMLDocument ou quelque chose. Cela fonctionne pour nos messages JSON. Peut-être pas la solution la plus élégante, mais ça marche.

J'espère que cela aide.

Glenn

+1

Glenn, Merci pour votre réponse. J'ai actuellement un contrat d'opération qui désérialise immédiatement le xml posté dans un objet. Même si j'ai accès au nouvel objet à traiter, j'aimerais quand même que la requête brute soit disponible. Juste une représentation de chaîne simple du corps. Merci! – RossG

2

Essayez ceci,

OperationContext.Current.RequestContext.RequestMessage 
2

Voilà comment vous le faites sans réflexion:

using (var reader = OperationContext.Current.RequestContext.RequestMessage.GetReaderAtBodyContents()) { 
    if (reader.Read()) 
     return new string (Encoding.ASCII.GetChars (reader.ReadContentAsBase64())); 
       return result; 
    } 
} 

Si le lecteur est un HttpStreamXmlDictionaryReader (comme dans mon cas), la La mise en œuvre de la méthode par la classe ReadContentAsBase64(byte[] buffer, int index, int count) passe simplement ces paramètres à la méthode Stream.Rea d.

Une fois que j'ai le byte[], je convertis les octets en une chaîne via le codage ASCII. Pour une implémentation correcte, vous pouvez utiliser le type de contenu & codage à partir des en-têtes du message à faire par spécification HTTP.

+0

Votre solution ne fonctionne pas si Message.State est déjà défini sur Read - vous obtenez une exception InvalidOperationException "Ce message ne peut pas prendre en charge l'opération car il a été lu." – Dai

+0

Voici comment vous en prenez soin: http://stackoverflow.com/questions/2184806/read-wcf-message-body-twice-message-cannot-be-read –

0

Vous pouvez arrêter le HttpApplication.Request.InputStrea m dans un HttpModule personnalisé du service WCF, lire le flux et définir à nouveau sa position sur 0 dans le gestionnaire d'événements personnalisé de HttpModule. Ensuite, stockez-le en session et accédez-y plus loin dans le OperationContract réel.

Par exemple:

public class CustomModule : IHttpModule 
{ 
    public void Dispose() 
    { 

    } 

    public void Init(HttpApplication context) 
    { 
     context.AcquireRequestState +=context_AcquireRequestState; 
    } 

    void context_AcquireRequestState(object sender, EventArgs e) 
    { 
     HttpApplication application = sender as HttpApplication; 
     Stream str = application.Request.InputStream; 
     StreamReader sr = new StreamReader(str); 
     string req = sr.ReadToEnd(); 
     str.Position = 0; 
     application.Session["CurrentRequest"] = req; 
    } 
}