从.ASMX Web服务返回一个XElement

3

我最近在更新一个返回 XElement 的 .ASMX Web 服务时遇到了这个错误信息:

Cannot use wildcards at the top level of a schema

现在,这个错误是由以下代码产生的;

public class FooBarService : System.Web.Services.WebService
{            
    [WebMethod]
    public XElement Foo(string Bar)
    {
        return null;
    }    
}

然而,如果我将代码更改为接受 XElement 而不是 String

public class FooBarService : System.Web.Services.WebService
{        
    [WebMethod]
    public XElement Foo(XElement Bar)
    {
        return null;
    }
}

那么 Web 服务为什么不会抛出错误呢?

那么为什么接受 XElement 并返回 XElement 的方法可以运行,而另一种方法却不行呢?


这里有相关的问题;https://dev59.com/eUXRa4cB1Zd3GeqPv_SD - 看起来可能是个 bug? - Chris McAtackney
@ChrisMcAtackney 是的,我已经阅读了那个问题,其中一个答案的链接已经失效,其余的也没有解决问题。 - Tim B James
为什么不返回一个字符串呢? - João Bragança
因为我不想这样做 :P 我想找出为什么它不起作用。 - Tim B James
1个回答

3

首先需要查看堆栈跟踪(stack trace),它会指示异常发生的位置:

at System.Xml.Serialization.XmlSchemaExporter.ExportElement(ElementAccessor accessor)
at System.Xml.Serialization.XmlSchemaExporter.ExportTypeMapping(XmlTypeMapping xmlTypeMapping)
at System.Web.Services.Description.MimeXmlReflector.ReflectReturn()
at System.Web.Services.Description.HttpProtocolReflector.ReflectMimeReturn()
at System.Web.Services.Description.HttpPostProtocolReflector.ReflectMethod()

通过使用 ILSpy,我们可以观察引发异常的条件:

// System.Xml.Serialization.XmlSchemaExporter
private XmlSchemaElement ExportElement(ElementAccessor accessor)
{
    if (!accessor.Mapping.IncludeInSchema && !accessor.Mapping.TypeDesc.IsRoot)
    {
        return null;
    }
    if (accessor.Any && accessor.Name.Length == 0)
    {
        throw new InvalidOperationException(Res.GetString("XmlIllegalWildcard"));
    }
    // truncated method body
}

深入代码导航:
// System.Web.Services.Description.MimeXmlReflector
internal override bool ReflectReturn()

// System.Xml.Serialization.XmlReflectionImporter
private ElementAccessor 
    ImportElement(TypeModel model, 
        XmlRootAttribute root, 
        string defaultNamespace, 
        RecursionLimiter limiter)

等等,我们来到了这个方法:

// System.Xml.Serialization.XmlReflectionImporter
private static ElementAccessor 
    CreateElementAccessor(TypeMapping mapping, string ns)
{
    ElementAccessor elementAccessor = new ElementAccessor();
    bool flag = mapping.TypeDesc.Kind == TypeKind.Node;
    if (!flag && mapping is SerializableMapping)
    {
        flag = ((SerializableMapping)mapping).IsAny;
    }
    if (flag)
    {
        elementAccessor.Any = true;
    }
    else
    {
        elementAccessor.Name = mapping.DefaultElementName;
        elementAccessor.Namespace = ns;
    }
    // truncated
}

似乎 XElement 类型映射将 Any 属性值设置为 true,但未获得 DefaultElementName
问题的简单解决方法是创建一个派生类:
public class FooBarService : System.Web.Services.WebService
{
    [WebMethod]
    public MyXElement Foo(string bar)
    {
        return null;
    }
}
public class MyXElement : XElement
{
    public MyXElement()
        : base(XName.Get("default")) { }
}

哪个会在堆栈中调用:

System.Web.Services.Description.SoapProtocolReflector.ReflectMethod()

使用HttpPostProtocolReflector.ReflectMethod()方法的替代方法,并且名称正确地被分配:

messagePart.Name = members[0].MemberName;

回答您的问题,当您将 XElement 分配为参数时,方法调用起作用的原因是通过其他方法创建了类型映射,并且 name 成员不为空。因此,导致引发异常的条件不会发生。

这是一个非常好的答案,谢谢!同时也感谢您对这个问题进行的调查! - Tim B James
当我尝试使用VS2010中的Web引用消费接受和返回MyXElement的服务时,它会创建DataSet类型的参数和返回类型。你如何让它使用XElement? - xr280xr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接