As part of my job, I'm to write a SOAP 1.2-based webservice that receives and processes XML requests. During testing with the remote client, however, I ran into a rather baffling and inconsistent problem.
This is the signature of one of the service's callable methods:
[WebMethod]
[ScriptMethod(UseHttpGet = false, ResponseFormat = ResponseFormat.Xml)]
public XmlDocument PostOrders(XmlDocument request)
The problem here is this:
request
and accordingly throws a NullReferenceException
down the line when the application tries to write its contents to file. It doesn't matter what the client actually sent, attaching Visual Studio to the IIS process reveals that the method is called with a null request
, with what the client actually sent having been lost somewhere on the .NET level.What I tried:
web.config
(verifying that both disappeared from WSDL, leaving only the 1.2) and had the client explicitly define version 1.2 on their side. No dice.XmlDocument
to Linq XElement
. Suspecting a server-side deserialization problem due to discovering that WSDL abstracts the expected data types for multiplatform compatibility, I changed the method's parameter from XmlDocument
to XElement
. No dice.Contents of web.config
:
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.5.2" />
<httpRuntime />
<webServices>
<protocols>
<remove name="HttpGet" />
<remove name="HttpPost" />
<remove name="HttpSoap"/> <!-- disables SOAP 1.1 -->
</protocols>
<conformanceWarnings>
<remove name='BasicProfile1_1'/>
</conformanceWarnings>
</webServices>
<globalization uiCulture="en-US" />
<customErrors mode="Off" />
<pages controlRenderingCompatibilityVersion="3.5" clientIDMode="AutoID" />
<httpModules>
<add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web" />
</httpModules>
</system.web>
<system.codedom>
<compilers>
<compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<providerOption name="CompilerVersion" value="v4.0" />
</compiler>
<compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" type="Microsoft.VisualBasic.VBCodeProvider, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" warningLevel="4" compilerOptions="/langversion:14 /nowarn:41008 /define:_MYTYPE=\"Web\" /optionInfer+" />
</compilers>
</system.codedom>
<system.webServer>
<modules>
<remove name="ApplicationInsightsWebTracking" />
<add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web" preCondition="managedHandler" />
</modules>
<handlers>
<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
<remove name="OPTIONSVerbHandler" />
<remove name="TRACEVerbHandler" />
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
<validation validateIntegratedModeConfiguration="false" />
</system.webServer>
<system.serviceModel>
<bindings />
<client />
</system.serviceModel>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
Considering that I was able to successfully send request from two separate computers in two separate applications without any manual configuration, but not from another two, is this a server-side problem or a client-side misconfiguration?
Found the solution and am posting it just in case someone else runs into this issue.
It was a server-side error. If you look at the signature of the method I posted above:
public XmlDocument PostOrders(XmlDocument request)
The problem was the parameter being an XmlDocument
. As the payload of a SOAP request is already encased inside an XML, it cannot be deserialized as an XmlDocument
due to the payload's root element not being a root element where it's coming from, only as an XmlElement
or Linq XElement
. Yet even changing the parameter type didn't work - and that's when I looked at the WSDL.
As it turned out, neither .NET XML types worked because even when I picked a serializable class (XmlElement
/XElement
) whatever generates the WSDL didn't recognize them and couldn't figure out what kind of data the method is expecting, so the WSDL didn't declare the parameter's data type. With no data type declared in the WSDL, the client-side application didn't know what to send either, so it cut out the payload and sent only the SOAP header. I don't know why php's SOAP classes work like that, but they apparently do.
I didn't notice it during testing because my testing application is also .NET and thus automatically knows what to send, while SoapUI's requests are raw strings without any serialization before sending so it neither needs nor cares about the data type.
The solution was to change the parameter type to string
and disable basic HTTP. While basic HTTP was enabled, the deserialization always threw an exception when the method was called with a string parameter, hence why I didn't find this solution sooner.
Bottom line: if you're writing a .NET webservice not exclusively for .NET clients, don't use .NET classes as input parameters regardless of whether they're serializable or not. Use strings. You'll save yourself a lot of headache.