2009-12-04 16 views
9

Attualmente sto sviluppando un servizio RESTful WCF. Nell'ambito della convalida dei dati POST, sto lanciando eccezioni se la richiesta XML non è conforme alle nostre regole aziendali.WCF + REST: dove sono i dati della richiesta?

L'obiettivo è inviare un'e-mail allo staff appropriato in caso di richiesta non valida. Ma, insieme alle intestazioni delle richieste in entrata, al metodo e all'URI, vorrei anche inviare l'XML che è stato pubblicato.

Non sono stato in grado di trovare un modo per accedere a questi dati. WCF sta effettivamente distruggendo il corpo/i dati della richiesta prima che io abbia la possibilità di accedervi o mi manchi qualcosa?

Il tuo aiuto è apprezzato perché sono confuso sul motivo per cui non riesco ad accedere ai dati della richiesta.

risposta

9

Questo sfortunatamente non è supportato - abbiamo avuto un bisogno simile, e lo abbiamo fatto chiamando i membri interni con la riflessione. Lo usiamo solo in un gestore di errori (quindi possiamo eseguire il dump della richiesta non elaborata), ma funziona correttamente. Non lo consiglierei a un sistema che non possiedi e gestisci (ad esempio, non spedire questo codice a un cliente), poiché può cambiare in qualsiasi momento con un service pack o altro.

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

Holy cow!È persino peggio della mia soluzione :-) –

+0

Concordato: il vantaggio è che non è necessario introdurre una seconda copia dal livello di trasporto con un'ispettore messaggi su ogni richiesta. In questo modo possiamo ottenere il buffer originale direttamente dal codice del servizio e solo quando c'è un problema. Quindi la mia cautela originale. :) Vorrei che lo esponessimo appena fuori da WebOperationContext, ma, dopo averlo smontato, capisco perché non lo fanno (specialmente quando si considerano richieste in streaming di dimensioni arbitrarie). – nitzmahone

+0

Grazie per la risposta. Ora capisco perché stai adottando questo approccio. È interessante notare che, per capire perché WCF funziona nel modo giusto, è necessario scavare nell'implementazione. In qualche modo sconfigge lo scopo di cercare di astrarre la complessità! –

9

Quindi, se si dichiara il contratto qualcosa come:

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

(è possibile utilizzare XML invece) Lo streamOfData dovrebbe essere il corpo di un HTTP POST. Puoi deserializzare usando qualcosa come:

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

Funziona così per noi, almeno. Potresti voler utilizzare un approccio diverso per ottenere la stringa in un XMLDocument o qualcosa del genere. Questo funziona per i nostri post JSON. Potrebbe non essere la soluzione più elegante, ma sta funzionando.

Spero che questo aiuti.

Glenn

+1

Glenn, grazie per la tua risposta. Al momento ho un contratto operativo che deserializza immediatamente l'xml pubblicato in un oggetto. Anche se ho accesso al nuovo oggetto per l'elaborazione, mi piacerebbe comunque che la richiesta grezza fosse disponibile. Solo una semplice rappresentazione a stringa del corpo. Grazie! – RossG

2

Prova questo,

OperationContext.Current.RequestContext.RequestMessage 
2

Ecco come si fa senza riflessione:

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

Se il lettore è una HttpStreamXmlDictionaryReader (come è stato nel mio caso), il l'implementazione della classe del metodo ReadContentAsBase64(byte[] buffer, int index, int count) passa semplicemente questi parametri al metodo d Stream.Rea d.

Una volta ottenuto lo byte[], converto i byte in una stringa tramite la codifica ASCII. Per una corretta implementazione, è possibile utilizzare il tipo di contenuto codifica & dalle intestazioni del messaggio per le specifiche HTTP.

+0

La tua soluzione non funziona se Message.State è già impostato su Read - ottieni una InvalidOperationException "Questo messaggio non supporta l'operazione perché è stata letta." – Dai

+0

Ecco come ci si prende cura di questo: http://stackoverflow.com/questions/2184806/read-wcf-message-body-twice-message-cannot-be-read –

0

È possibile arrestare lo HttpApplication.Request.InputStrea m in un HttpModule personalizzato del servizio WCF, leggere lo stream e impostare nuovamente la sua posizione su 0 nel gestore di eventi HttpModule personalizzato. Quindi memorizzarlo in sessione e accedervi ulteriormente nell'effettivo OperationContract.

Ad esempio:

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; 
    } 
}