关于C#:使用XSD验证XML时忽略提供的命名空间

Ignoring supplied namespaces when validating XML with XSD

背景:

我们正在构建一个应用程序,它允许我们的客户以预定义(即我们不控制)的XML格式提供数据。XSD由第三方提供给我们,我们希望在处理它之前收到一个通过模式验证的XML文件。

问题:

我们所提供的XSD包含默认和目标命名空间,这意味着如果客户提供的XML文件不包含命名空间,那么验证将通过。显然,我们不希望他们提供说他们通过了但不应该通过的东西,但更大的问题是,如果我找不到执行XML验证的解决方案,我们需要对每个元素进行大量的额外检查。

问题:

是否可以强制.NET执行验证并忽略所提供的XML和XSD上的命名空间。也就是说,在某种程度上"假定"名称空间是附加的。

  • 是否可以轻松、可靠地删除内存中的命名空间?
  • 在这种情况下,最佳实践是什么?
  • 到目前为止,我拥有的解决方案是:

  • 每次更新XSD时(不应该经常)从中删除名称空间。这并不能回避这样一个事实:如果他们提供一个名称空间,它仍然会通过验证。
  • 从XSD中移除名称空间,并找到每次从传入XML中剥离名称空间的方法。这似乎是执行一些简单操作的大量代码。
  • 在验证XML文件之前对其进行一些预限定,以确保其具有正确的命名空间。如果文件内容正确,则由于无效的命名空间而使它们失败似乎是错误的。
  • 创建一个没有名称空间的重复XSD,但是如果它们只是提供了错误的名称空间或不同的名称空间,那么它仍然会通过。
  • 实例XML:

    1
    2
    3
    4
    <?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>

    命名空间不同

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

    完全没有命名空间。

    1
    2
    3
    4
     <?xml version="1.0" encoding="UTF-8" ?>
    <root attr1="2001-03-03" attr2="google">
    ...
    </root>


    试图解决同样的问题。我想出了一个我认为是相当干净的解决方案。为了清晰起见,我对输入参数进行了一些验证。

    首先,场景:有一个WebService接收一个文件,该文件应该是"格式良好的"XML,并且对XSD有效。当然,我们不相信"好消息",也不相信"我们知道"是正确的,这对XSD是有效的。

    下面给出了这种WebService方法的代码,我认为这是不言而喻的。

    主要关注点是验证发生的顺序,加载前不检查名称空间,加载后检查,但要干净。

    我决定我可以接受一些异常处理,因为大多数文件都是"好的",因为这是处理的框架方式(所以我不会反对)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    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 });
    }

    现在,XSD将具有如下特性:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <?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>

    "好的"XML应该是这样的:

    1
    2
    3
    4
    5
    6
    <?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>
        Langgt 23</address>

    我测试了"错误的XML格式"、"根据XSD无效的输入"、"不正确的命名空间"。

    参考文献:

    从MemoryStream读取

    尝试避免异常处理检查是否正常

    根据XSD验证,捕获错误

    关于内联模式验证的有趣文章

    嗨,马丁,评论对我的答案来说太短了,所以我会在这里给出答案,这可能是一个完整的答案,让我们一起改进一下:)

    我做了以下测试:

    • 测试:xmlns="blaa"
    • 结果:由于名称空间错误,文件被拒绝。
    • 测试:xmlns="http://mydomain.com/ns/upload.xsd"和xmlns:a="blaa",元素有"a:someElement"
    • 结果:文件重新展开时出错,表示不需要"a:someElement"
    • test:xmlns="http://mydomain.com/ns/upload.xsd"和xmlns:a="blaa",元素有"someelement",缺少某些必需的属性
    • 结果:文件返回错误,说明属性丢失

    所遵循的策略(我更喜欢)是,如果文档不符合,那么就不接受,但提供一些关于原因的信息(例如"错误的名称空间")。

    这一策略似乎与你之前所说的相反:

    however, if a customer misses out the namespace declaration in their submitted XML then I would like to say that we can still validate it. I don't want to just say"You messed up, now fix it!"

    在这种情况下,您似乎可以忽略XML中定义的名称空间。为此,您将跳过正确命名空间的验证:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
        ...
        // 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.";                
        //}
        ...

    其他想法…

    在并行思路中,为了用自己的名称空间替换所提供的名称空间,也许可以设置doc.DocumentElement.NamespaceURI ="mySpecialNamespace",从而替换根元素的名称空间。

    参考文献:

    向根元素添加多个命名空间


    我使用xmlschemavalidationflags.reportvalidationwarnings标志。否则,具有未知命名空间(或不具有命名空间)的XML将自动通过验证。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    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));
    }


    XSD模式背后的关键是它使非类型化的XML成为强类型化的XML。

    XML类型可以定义为节点名和命名空间的组合。

    如果有人向您发送没有名称空间的XML,那么尽管有意图,XML并没有引用由XSD模式定义的类型。

    从XML验证的角度来看,只要

  • 它的形状很好
  • 它确认任何类型化的XML定义,如xmlns属性所指定的那样。