2010-06-23 32 views
6

Tengo el siguiente código de análisis XML en mi solicitud:rompe XmlReader en UTF-8 BOM

public static XElement Parse(string xml, string xsdFilename) 
    { 
     var readerSettings = new XmlReaderSettings 
     { 
      ValidationType = ValidationType.Schema, 
      Schemas = new XmlSchemaSet() 
     }; 
     readerSettings.Schemas.Add(null, xsdFilename); 
     readerSettings.ValidationFlags |= XmlSchemaValidationFlags.ProcessInlineSchema; 
     readerSettings.ValidationFlags |= XmlSchemaValidationFlags.ProcessSchemaLocation; 
     readerSettings.ValidationFlags |= XmlSchemaValidationFlags.ReportValidationWarnings; 
     readerSettings.ValidationEventHandler += 
      (o, e) => { throw new Exception("The provided XML does not validate against the request's schema."); }; 

     var readerContext = new XmlParserContext(null, null, null, XmlSpace.Default, Encoding.UTF8); 

     return XElement.Load(XmlReader.Create(new StringReader(xml), readerSettings, readerContext)); 
    } 

lo estoy usando para analizar cadenas enviados a mi servicio WCF en documentos XML, para la deserialización personalizado.

Funciona bien cuando leo en archivos y los envío a través del cable (la solicitud); Verifiqué que la lista de materiales no se envió. En mi controlador de solicitudes estoy serializando un objeto de respuesta y enviándolo como una cadena. El proceso de serialización agrega una lista de materiales UTF-8 al frente de la cadena, lo que hace que se rompa el mismo código al analizar la respuesta.

System.Xml.XmlException : Data at the root level is invalid. Line 1, position 1. 

En la investigación que he hecho durante la última hora o así, parece que XmlReader debe respetar la lista de materiales. Si elimino manualmente la lista de materiales de la parte frontal de la cadena, la respuesta xml analiza bien.

¿Me falta algo obvio, o al menos algo insidioso?

EDIT: Aquí está el código de serialización que estoy usando para devolver la respuesta:

private static string SerializeResponse(Response response) 
{ 
    var output = new MemoryStream(); 
    var writer = XmlWriter.Create(output); 
    new XmlSerializer(typeof(Response)).Serialize(writer, response); 
    var bytes = output.ToArray(); 
    var responseXml = Encoding.UTF8.GetString(bytes); 
    return responseXml; 
} 

Si es sólo una cuestión de forma incorrecta el código XML que contiene la lista de materiales, a continuación, voy a cambiar a

var responseXml = new UTF8Encoding(false).GetString(bytes); 

pero no estaba claro en absoluto de mi investigación que la lista de materiales era ilegal en la cadena XML real; ver p. c# Detect xml encoding from Byte Array?

+0

Tuve ese problema aquí: http://stackoverflow.com/questions/291455/xml-data-at-root-level-is-invalid –

Respuesta

5

La cadena xml no debe contener (!) La lista de materiales, la lista de materiales solo está permitida en los datos de bytes (por ejemplo, flujos) que están codificados con UTF-8. Esto se debe a que la representación de cadena no está codificada, sino que ya es una secuencia de caracteres Unicode.

Por lo tanto, parece que carga la secuencia incorrecta, que está en el código que desafortunadamente no proporcionó.

Editar:

Gracias por publicar el código de serialización.

No debe escribir los datos en un MemoryStream, sino en un StringWriter que luego puede convertir en una cadena con ToString. Como esto evita pasar a través de una representación de bytes, no solo es más rápido sino que también evita dichos problemas.

Algo como esto:

private static string SerializeResponse(Response response) 
{ 
    var output = new StringWriter(); 
    var writer = XmlWriter.Create(output); 
    new XmlSerializer(typeof(Response)).Serialize(writer, response); 
    return output.ToString(); 
} 
+0

He hecho exactamente ese cambio, y funciona perfectamente. ¡Gracias! – arootbeer

+0

No hay restricciones para que la lista de materiales esté presente en XML de acuerdo con esto: http://www.w3.org/TR/REC-xml/#charencoding – knocte

+0

Esto funciona ... sin embargo, cuando cambia a ' StringWriter', el atributo 'encoding' en la declaración' 'parece aparecer siempre como UTF-16. Para, digamos, UTF-8, debe 'devolver output.ToString(). Replace (" utf-16 "," utf-8 ");'. – David

0

La lista de materiales no debe estar en la cadena en el primer lugar.
Las listas de materiales se utilizan para detectar la codificación de una matriz de bytes sin formato; no tienen nada que hacer en una cadena real.

¿De qué viene la cuerda?
Probablemente lo esté leyendo con la codificación incorrecta.

+0

Me aseguré de que al menos estaba usando la codificación correcta :) Agregué el código de serialización a mi pregunta. – arootbeer

+0

Esta es una respuesta interesante ... Tengo un caso donde estoy sacando de una API remota (que no controlo) y simplemente estoy cargando los datos a través de req.GetResponse(). GetResponseStream() y poniendo ese flujo directo a un XmlReader. ¿Hay una mejor manera de hacerlo (lo que evita este problema)? –

+0

@TomLianza: Eso depende. ¿Qué bytes está enviando? – SLaks

0

Las cadenas en C# se codifican como UTF-16, por lo que la lista de materiales sería incorrecta. Como regla general, codifique siempre matrices de bytes en bytes y decodifíquelas a partir de matrices de bytes.

+0

Esto no es exactamente cierto. Mientras que el formato de memoria es generalmente similar a UTF-16, las cadenas son una secuencia "abstracta" de caracteres con un número específico de caracteres. Tenga en cuenta que ha habido discusiones en el equipo de CLR para cambiar cadenas y tener otra representación en memoria para hacerlas más eficientes. De todos modos, dado que es una vista abstracta y no una secuencia de bytes, no debe existir una lista de materiales en la cadena. – Lucero

+0

He agregado el código de serialización. Ya estoy usando UTF-8 explícitamente. – arootbeer

+0

@Stephen, creo que el asunto con las representaciones alternativas de cadenas en memoria fue en el siguiente video de Channel 9: http://channel9.msdn.com/shows/Going+Deep/Vance-Morrison-CLR-Through-the-Years/ – Lucero

9

En mi controlador de solicitudes estoy serializando un objeto de respuesta y enviándolo de vuelta como una cadena. El proceso de serialización agrega una lista de materiales UTF-8 al frente de la cadena, lo que hace que se rompa el mismo código al analizar la respuesta.

Por lo que desea evitar que se agregue la lista de materiales como parte de su proceso de serialización. Lamentablemente, no proporciona cuál es su lógica de serialización.

Lo que debe hacer es proporcionar una instancia creada a través de la UTF8EncodingUTF8Encoding(bool) constructor para desactivar la generación de la lista de materiales, y pasar esta Encoding ejemplo a lo que los métodos que está utilizando, que están generando la cadena intermedia.

+0

¡Gracias! Encontré ese poco de sabiduría durante mi investigación, pero no pude encontrar ninguna instrucción explícita sobre incluir o excluir la lista de materiales. – arootbeer

+0

¡Ayudó mucho hoy, gran solución! –

+0

Esto es todo! ¡Gracias! – Grubl3r