2012-01-04 18 views
12

Antecedentes: formato XMLsuministrados al validar XML con XSD

Estamos construyendo una aplicación que permite a nuestros clientes para el suministro de datos en una predefinida (es decir, que no controlamos.). Un tercero proporciona el XSD y esperamos recibir un archivo XML que pase la validación del esquema antes de procesarlo.

El problema:

El XSD que se suministran con un espacio de nombres incluye por defecto y el objetivo, lo que significa que si un cliente suministra un archivo XML que no incluye el espacio de nombres, entonces la validación pasará . Obviamente no queremos que suministren cosas que digan que aprueben, pero no deberían hacerlo, pero la mayor preocupación es la cantidad de controles adicionales que tendremos que hacer en cada elemento si no puedo encontrar una solución para hacerlo. la validación XML.

las preguntas:

¿Es posible forzar .NET para realizar la validación e ignorar el espacio de nombres en el XML y XSD suministrado. es decir, de alguna manera "asumir" que el espacio de nombre estaba adjunto.

  1. ¿Es posible eliminar los espacios de nombres en la memoria de manera fácil y confiable?
  2. ¿Cuál es la mejor práctica en estas situaciones?

Soluciones que tengo hasta ahora:

  1. Retire el espacio de nombres de la XSD cada vez que se pone al día (no deben ser muy a menudo Esto no quiere moverse por el hecho de que si. suministrar un espacio de nombres, será validación de pase.
  2. Eliminar el espacio de nombres del XSD, Y encontrar una manera de quitar el espacio de nombres del XML entrante cada vez. Esto parece mucho código para realizar algo simple.
  3. Doe s alguna precalificación en el archivo XML antes de validarlo para garantizar que tenga el espacio de nombre correcto. Parece incorrecto que falle debido a un espacio de nombre no válido si el contenido del archivo es correcto.
  4. Cree un XSD duplicado que no tenga un espacio de nombre, sin embargo, si solo proporcionan el espacio de nombre incorrecto o un espacio de nombre diferente, entonces aún pasará.

Ejemplo XML:

<?xml version="1.0"?> 
<xsd:schema version='3.09' elementFormDefault='qualified' attributeFormDefault='unqualified' id='blah' targetNamespace='urn:schemas-blah.com:blahExample' xmlns='urn:blah:blahExample' xmlns:xsd='http://www.w3.org/2001/XMLSchema'> 
... 
</xsd:schema> 

con espacio de nombres que es diferente

<?xml version="1.0" encoding="UTF-8" ?> 
<root xmlns="urn:myCompany.com:blahExample1" attr1="2001-03-03" attr2="google" > 
... 
</root> 

sin espacio de nombres en absoluto.

<?xml version="1.0" encoding="UTF-8" ?> 
<root attr1="2001-03-03" attr2="google" > 
... 
</root> 
+0

espacios de nombres XML son una buena cosa, ¿por qué pelear? –

+1

es algo que no podemos controlar, quiero asegurarme de que los clientes envíen el XML correcto; sin embargo, si un cliente no detecta la declaración del espacio de nombres en su XML enviado, me gustaría decir que aún podemos validarlo. No quiero simplemente decir "¡Te equivocaste, ahora arréglalo!" (y sí, usaría mejores palabras, pero entiendes la idea). – Martin

Respuesta

6

Tratando para resolver el mismo problema Se me ocurrió lo que creo que es una solución bastante limpia. Para mayor claridad, he omitido alguna validación sobre los parámetros de entrada.

Primero, el escenario: Hay un servicio web que recibe un archivo, que se supone que es "bien formado" xml y válido contra un XSD. Por supuesto, no confiamos en el "bien fomrmness" ni que es válido contra el XSD que "sabemos" es el correcto.

El código para tal método de servicio web se presenta a continuación, creo que se explica por sí mismo.

El principal punto de interés es el orden en el que se realizan las validaciones, no se comprueba el espacio de nombres antes de la carga, se comprueba después, pero limpiamente.

Decidí que podría vivir con un manejo de excepciones, ya que se espera que la mayoría de los archivos sean "buenos" y porque esa es la forma de trabajo marco (así que no voy a luchar contra eso).

private DataTable xmlErrors; 
[WebMethod] 
public string Upload(byte[] f, string fileName) { 
    string ret = "This will have the response"; 

    // this is the namespace that we want to use 
    string xmlNs = "http://mydomain.com/ns/upload.xsd"; 

    // you could put a public url of xsd instead of a local file 
    string xsdFileName = Server.MapPath("~") + "//" +"shiporder.xsd"; 

    // a simple table to store the eventual errors 
    // (more advanced ways possibly exist) 
    xmlErrors = new DataTable("XmlErrors"); 
    xmlErrors.Columns.Add("Type"); 
    xmlErrors.Columns.Add("Message"); 

    try { 
     XmlDocument doc = new XmlDocument(); // create a document 

     // bind the document, namespace and xsd 
     doc.Schemas.Add(xmlNs, xsdFileName); 

     // if we wanted to validate if the XSD has itself XML errors 
     // doc.Schemas.ValidationEventHandler += 
     // new ValidationEventHandler(Schemas_ValidationEventHandler); 

     // Declare the handler that will run on each error found 
     ValidationEventHandler xmlValidator = 
      new ValidationEventHandler(Xml_ValidationEventHandler); 

     // load the document 
     // will trhow XML.Exception if document is not "well formed" 
     doc.Load(new MemoryStream(f)); 

     // Check if the required namespace is present 
     if (doc.DocumentElement.NamespaceURI == xmlNs) { 

      // Validate against xsd 
      // will call Xml_ValidationEventHandler on each error found 
      doc.Validate(xmlValidator); 

      if (xmlErrors.Rows.Count == 0) { 
       ret = "OK"; 
      } else { 
       // return the complete error list, this is just to proove it works 
       ret = "File has " + xmlErrors.Rows.Count + " xml errors "; 
       ret += "when validated against our XSD."; 
      } 
     } else { 
      ret = "The xml document has incorrect or no namespace.";     
     } 
    } catch (XmlException ex) { 
     ret = "XML Exception: probably xml not well formed... "; 
     ret += "Message = " + ex.Message.ToString(); 
    } catch (Exception ex) { 
     ret = "Exception: probably not XML related... " 
     ret += "Message = " + ex.Message.ToString(); 
    } 
    return ret; 
} 

private void Xml_ValidationEventHandler(object sender, ValidationEventArgs e) { 
    xmlErrors.Rows.Add(new object[] { e.Severity, e.Message }); 
} 

Ahora, el xsd tendría algo como:

<?xml version="1.0" encoding="utf-8"?> 
<xs:schema id="shiporder" 
    targetNamespace="http://mydomain.com/ns/upload.xsd" 
    elementFormDefault="qualified" 
    xmlns="http://mydomain.com/ns/upload.xsd" 
    xmlns:mstns="http://mydomain.com/ns/upload.xsd" 
    xmlns:xs="http://www.w3.org/2001/XMLSchema" 
> 
    <xs:simpleType name="stringtype"> 
     <xs:restriction base="xs:string"/> 
    </xs:simpleType> 
    ... 
    </xs:schema> 

Y la "buena" XML sería algo así como:

<?xml version="1.0" encoding="utf-8" ?> 
<shiporder orderid="889923" xmlns="http://mydomain.com/ns/upload.xsd"> 
    <orderperson>John Smith</orderperson> 
    <shipto> 
    <names>Ola Nordmann</names> 
    <address>Langgt 23</address> 

probé, "malo formato XML", "entrada no válida según XSD", "espacio de nombre incorrecto".

referencias:

Read from memorystream

Trying avoid exception handling checking for wellformness

Validating against XSD, catch the errors

Interesting post about inline schema validation


Hola Martin, el comentario sction es demasiado corto para mi respuesta, así que lo voy a dar aquí, puede o no ser sea una respuesta completa, vamos a mejorar juntos :)

hice las siguientes pruebas :

  • prueba: xmlns = "blaa"
  • Resultado: el archivo es rechazado, a causa de espacio de nombre equivocado.
  • prueba: xmlns = "http://mydomain.com/ns/upload.xsd" y xmlns: a = "blaa" y los elementos habían "a: someElement"
  • Resultado: El error diciendo que es retunrs archivos no esperaba "a: someElement"
  • Prueba: xmlns = "http://midominio.com/ns/upload.xsd" y xmlns: a = "blaa" y los elementos tenían "algún elemento" con algún atributo requerido que faltaba
  • Resultado: el error diciendo presentar declaraciones que el atributo no se encuentra

el estrategia seguida (wich yo prefiero) era, si el d el documento no cumple, entonces no lo acepta, pero brinde información sobre el motivo (ej. "espacio de nombres incorrecto").

Esta estrategia parece contrario a lo que ha dicho anteriormente: sin embargo

, si un cliente pierde la declaración de espacio de nombres en su XML presentado a continuación, me gustaría decir que todavía podemos validarlo. No quiero simplemente decir "¡Te equivocaste, ahora arréglalo!"

En este caso, parece que puede ignorar el espacio de nombre definido en el XML. Para hacer que usted no tome la validación de espacio de nombres correcta:

... 
    // Don't Check if the required namespace is present 
    //if (doc.DocumentElement.NamespaceURI == xmlNs) { 

     // Validate against xsd 
     // will call Xml_ValidationEventHandler on each error found 
     doc.Validate(xmlValidator); 

     if (xmlErrors.Rows.Count == 0) { 
      ret = "OK - is valid against our XSD"; 
     } else { 
      // return the complete error list, this is just to proove it works 
      ret = "File has " + xmlErrors.Rows.Count + " xml errors "; 
      ret += "when validated against our XSD."; 
     } 
    //} else { 
    // ret = "The xml document has incorrect or no namespace.";     
    //} 
    ... 


Otras ideas ...

En una línea paralela de pensamiento, para reemplazar el espacio de nombres suministrada por su cuenta, tal vez usted podría establecer doc.DocumentElement.NamespaceURI = "mySpecialNamespace" reemplazando así la pieza del nombre del elemento raíz.

Referencia:

add-multiple-namespaces-to-the-root-element

+0

¿Has probado esto con ellos proporcionando un espacio de nombres que no es el que agregas? y también con el que agregas. Además, el problema que tuvimos fue que si proporcionaban un espacio de nombre con un prefijo (por ejemplo, xmlns: a = "blah"), no podríamos eliminarlo correctamente y agregar el nuestro. – Martin

+0

@Martin Edité mi respuesta para responder a su comentario. –

+0

El problema con la eliminación de la verificación del espacio de nombres es que el validador no encontrará nada para validar. Si agrega un espacio de nombres y los nodos tienen un prefijo de espacio de nombres, entonces no serán validados (que yo sepa). Tendré que pensar en cómo iterar los elementos y eliminar sus prefijos ... debe ser posible ... – Martin

0

El punto detrás de un esquema XSD es que convierte el XML sin tipo en un XML fuertemente tipado.

Un tipo XML se puede definir como la combinación de nombre de nodo y espacio de nombres.

Si alguien le envía XML sin espacio de nombres, a pesar de las intenciones, el XML no se refiere a los tipos definidos por el esquema XSD.

Desde una perspectiva de validación de XML XML es válido mientras

  1. Es bien formada
  2. Confirma a cualquier definición XML con tipo, según lo especificado por el atributo xmlns
+0

Por lo tanto, la mejor práctica dice rechazar el XML que no tiene la definición de espacio de nombres correcto (o cualquiera). ¿Cómo verificaría que el XML que se ha recibido esté fuertemente tipado entonces? – Martin

+0

Puede verificar la combinación de nombre de nodo y nombre de nodo raíz para ver si el xml que se le envió era del tipo correcto. –

+0

¿hay una forma elegante de hacerlo en C#? – Martin

0

utilizo bandera XmlSchemaValidationFlags.ReportValidationWarnings. De lo contrario, xml con espacio de nombre desconocido (o sin espacio de nombres) pasará la validación en silencio.

public static void Validate(string xml, string schemaPath) 
{ 
    //oops: no ValidationFlag property, cant use linq 
    //var d = XDocument.Parse(xml); 
    //var sc = new XmlSchemaSet(); 
    //sc.Add(null, schemaPath); 
    //sc.CompilationSettings.EnableUpaCheck = false; 
    //d.Validate(sc, null); 

    XmlReaderSettings Xsettings = new XmlReaderSettings(); 
    Xsettings.Schemas.Add(null, schemaPath); 
    Xsettings.ValidationType = ValidationType.Schema; 
    Xsettings.ValidationFlags |= XmlSchemaValidationFlags.ReportValidationWarnings; 
    Xsettings.Schemas.CompilationSettings.EnableUpaCheck = false; 
    Xsettings.ValidationEventHandler += new ValidationEventHandler(ValidationCallBack); 

    XmlReader reader = XmlReader.Create(new StringReader(xml), Xsettings); 
    while (reader.Read()) 
    { 
    } 
} 

private static void ValidationCallBack(object sender, ValidationEventArgs e) 
{ 
    if (e.Severity == XmlSeverityType.Warning) 
     throw new Exception(string.Format("No validation occurred. {0}", e.Message)); 
    else 
     throw new Exception(string.Format("Validation error: {0}", e.Message)); 
}