2009-12-04 11 views
9

Actualmente estoy desarrollando un servicio WCF RESTful. Dentro de la validación de los datos POST, estoy lanzando excepciones si el XML de la solicitud no se ajusta a nuestras reglas comerciales.WCF + REST: ¿Dónde están los datos de solicitud?

El objetivo es enviar un correo electrónico al personal adecuado si se presenta una solicitud que se considera inválida. Pero, junto con los encabezados de las solicitudes entrantes, el método y el URI, me gustaría enviar también el XML que se publicó.

No he podido encontrar la manera de acceder a esta información. ¿WCF realmente está destruyendo el cuerpo/datos de la solicitud antes de que tenga la oportunidad de acceder o me falta algo?

Su ayuda es apreciada ya que no estoy seguro de por qué no puedo acceder a los datos de solicitud.

Respuesta

9

Desafortunadamente no es compatible, teníamos una necesidad similar, y lo hicimos llamando a miembros internos con reflexión. Simplemente lo usamos en un controlador de errores (para que podamos volcar la solicitud sin procesar), pero funciona bien. Sin embargo, no lo recomendaría para un sistema que no es de su propiedad y para el que opere (por ejemplo, no envíe este código a un cliente), ya que puede cambiar en cualquier momento con un paquete de servicio o lo que sea.

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!Eso es aún peor que mi solución :-) –

+0

acordados el beneficio es que no tenemos que pasar de contrabando una segunda copia de la capa de transporte con un inspector de mensaje en cada petición. De esta forma, podemos acceder al buffer original directamente desde el código de servicio, y solo cuando hay un problema. De ahí mi precaución original. :) Me gustaría que acababan de exponer fuera de la WebOperationContext, pero después de haber desmontado, no veo por qué no lo hacen (sobre todo si tenemos en cuenta las solicitudes de streaming de tamaño arbitrario). – nitzmahone

+0

Gracias por responder. Ahora entiendo por qué estás tomando este enfoque. Es interesante que para comprender por qué WCF funciona de la manera en que lo hace, debe profundizar en la implementación. ¡De alguna manera se derrota el propósito de tratar de abstraer la complejidad! –

9

Por lo tanto, si se declara su contrato algo como:

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

(se puede utilizar XML en lugar) El streamOfData debe ser el cuerpo de un HTTP POST. Puede deserializarlo usando algo como:

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

Funciona así para nosotros, al menos. Es posible que desee utilizar un enfoque diferente para obtener la cadena en un XMLDocument o algo así. Esto funciona para nuestras publicaciones JSON. Puede que no sea la solución más elegante, pero está funcionando.

Espero que esto ayude.

Glenn

+1

Glenn, gracias por su respuesta. Actualmente tengo un contrato de operación que deserializa inmediatamente el xml publicado en un objeto. Aunque tengo acceso al nuevo objeto para procesar, me gustaría que la solicitud en bruto esté disponible. Solo una simple representación de cuerdas del cuerpo. ¡Gracias! – RossG

2

Prueba de esto,

OperationContext.Current.RequestContext.RequestMessage 
2

Así es como lo haces sin reflexión:

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

Si el lector es un HttpStreamXmlDictionaryReader (como lo fue en mi caso), la la implementación de la clase del método ReadContentAsBase64(byte[] buffer, int index, int count) simplemente pasa estos parámetros al método Stream.Rea d.

Una vez que tengo el byte[], convierto los bytes en una cadena mediante codificación ASCII. Para una implementación adecuada, puede usar la codificación de tipo de contenido & de los encabezados del mensaje para hacer por especificación HTTP.

+0

Su solución no funciona si el Message.State ya está configurado para leer - se obtiene una InvalidOperationException "Este mensaje no puede soportar la operación, ya que se ha leído." – Dai

+0

Así es como cuidar de que: http://stackoverflow.com/questions/2184806/read-wcf-message-body-twice-message-cannot-be-read –

0

Puede detener el HttpApplication.Request.InputStrea m en un HttpModule personalizado del Servicio WCF, leer la secuencia y establecer nuevamente su posición en 0 en el controlador de eventos HttpModule personalizado. Luego guárdelo en sesión y acceda a él más adelante en el OperationContract.

Por ejemplo:

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