WCF WebGet JSON DateTime.MinValue => “Connection Reset”

I created a WCF web service that implements a method that returns an object containing a DateTime value (not nullable, according to spec, don’t ask).

The web service worked fine, and I wanted to add GET support using the WebGet attribute and some web.config magic. Still worked fine.

Then I tried out some failure scenarios (if you have a status query that tries to connect to a database which happens to be offline, or misconfigured, you want to know how your status query behaves), and suddenly the only response I got from the browser was “connection reset”.

After switching on WCF tracing, the failing WCF call generated a 168kB XML file containing 160 lines of stack trace for a single call!

Despite its size, the information in the file is quite useful, as it pointed to an internal exception:

System.Runtime.Serialization.SerializationException

DateTime values that are greater than DateTime.MaxValue or smaller than DateTime.MinValue when converted to UTC cannot be serialized to JSON.

   at System.Runtime.Serialization.Json.JsonWriterDelegator.WriteDateTime(DateTime value)
   at WriteTimestampTOToJson(XmlWriterDelegator , Object , XmlObjectSerializerWriteContextComplexJson , ClassDataContract , XmlDictionaryString[] )
   at System.Runtime.Serialization.Json.JsonClassDataContract.WriteJsonValueCore(XmlWriterDelegator jsonWriter, Object obj, XmlObjectSerializerWriteContextComplexJson context, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.Json.XmlObjectSerializerWriteContextComplexJson.WriteDataContractValue(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithoutXsiType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.Json.DataContractJsonSerializer.InternalWriteObjectContent(XmlWriterDelegator writer, Object graph)
   at System.Runtime.Serialization.Json.DataContractJsonSerializer.InternalWriteObject(XmlWriterDelegator writer, Object graph)
   at System.Runtime.Serialization.XmlObjectSerializer.InternalWriteObject(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)
   at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)
   at System.Runtime.Serialization.Json.DataContractJsonSerializer.WriteObject(XmlDictionaryWriter writer, Object graph)
   at System.ServiceModel.Dispatcher.SingleBodyParameterMessageFormatter.SingleParameterBodyWriter.OnWriteBodyContents(XmlDictionaryWriter writer)
   at System.ServiceModel.Channels.BodyWriter.WriteBodyContents(XmlDictionaryWriter writer)
   at System.ServiceModel.Channels.BodyWriterMessage.OnWriteBodyContents(XmlDictionaryWriter writer)
   at System.ServiceModel.Channels.Message.OnWriteMessage(XmlDictionaryWriter writer)
   at System.ServiceModel.Channels.Message.WriteMessage(XmlDictionaryWriter writer)
   at System.ServiceModel.Channels.BufferedMessageWriter.WriteMessage(Message message, BufferManager bufferManager, Int32 initialOffset, Int32 maxSizeQuota)
   at System.ServiceModel.Channels.JsonMessageEncoderFactory.JsonMessageEncoder.WriteMessage(Message message, Int32 maxMessageSize, BufferManager bufferManager, Int32 messageOffset)
   at System.ServiceModel.Channels.WebMessageEncoderFactory.WebMessageEncoder.WriteMessage(Message message, Int32 maxMessageSize, BufferManager bufferManager, Int32 messageOffset)
   at System.ServiceModel.Channels.HttpOutput.SerializeBufferedMessage(Message message)
   at System.ServiceModel.Channels.HttpOutput.Send(TimeSpan timeout)
   at System.ServiceModel.Channels.HttpRequestContext.OnReply(Message message, TimeSpan timeout)
   at System.ServiceModel.Activation.HostedHttpContext.OnReply(Message message, TimeSpan timeout)
   at System.ServiceModel.Channels.RequestContextBase.Reply(Message message, TimeSpan timeout)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.Reply(MessageRpc& rpc)

with an internal exception of type System.ArgumentOutOfRangeException:

Specified argument was out of the range of valid values.
Parameter name: value

and many more stack traces.

Back to my code: If there is an error connecting to the database, I return an object that contains a field set to DateTime.MinValue.

It seems that MinValue causes problems when converted to or from UTC, or at least the subsequent JSON serialization does, even though the ECMA Date range is greater than the .Net DateTime range.

Whatever the causes of this boundary problem are, returning DateTime.MinValue.AddDays(1) solved the problem.

Reading Web Service Definition from Other Program’s app.config or web.config

The ConfigurationManager class allows you to open your own app.config (renamed to .exe.config) or web.config, but also the design-mode app.config of any other application by providing a ExeConfigurationFileMap parameter to the OpenMappedExeConfiguration method:

var config =
  ConfigurationManager.OpenMappedExeConfiguration(
    new ExeConfigurationFileMap { ExeConfigFilename = cfg },
    ConfigurationUserLevel.None);

You can then access the <host> configuration like this

ServiceModelSectionGroup serviceModel =
  ServiceModelSectionGroup.GetSectionGroup(config);
if (serviceModel != null
    && serviceModel.Services != null
    && serviceModel.Services.Services != null)
{
  foreach (ServiceElement service in serviceModel.Services.Services)
  {
    foreach (BaseAddressElement addr in service.Host.BaseAddresses)
      Console.WriteLine(addr.BaseAddress);
  }
}

This method works fine unless the services section has been extended by custom config sections such as a custom behavior

<extensions>
  <behaviorExtensions>
    <add name="myBehaviorExtension"
      type="My.BehaviorExtension, My.BehaviorExtension.Library" />
  </behaviorExtensions>
</extensions>

Such a custom extension without an accessible library implementing the type causes a ConfigurationErrorsException:

The type ‘x’ registered for extension ‘y’ could not be loaded

If this is the case, the only way I found was to load the .config file as an XmlDocument

XmlDocument xml = new XmlDocument();
xml.Load(cfg);

and process the results of the xml’s SelectNodes() method

foreach (XmlNode nd in xml.SelectNodes(
  "/configuration/system.serviceModel/" +
  "services/service/host/baseAddresses/add"))
{
  var url = nd.Attributes["baseAddress"];
  if (url != null)
    Console.WriteLine(url.Value);
}

Online Help and Computer-Based Training using DotNetNuke

A project that I am working on deals with DotNetNuke 6 as online help and/or Computer-based Training software (CBT) for an existing web application.

Both the application and DotNetNuke manage registered users and their privileges, and DNN handles the content management for the online help contents.

Note that a couple of years ago I wrote about Wikis for online help, but a Wiki (typically) does not allow for user-specific or role-specific content to be displayed, and was found as insufficient for this scenario.

The solution that we came up with was that both applications are synchronized via a custom Web Service that (mainly) matches the logins of both applications and logs application access.

Each application needed to be extended by a hyperlink mechanism that calculates the web address of the corresponding page in the other application (usually the landing page with some parameters), nicknamed “jumper” in the chart below, and the landing page itself which performs a login based on a session ID in the URL string, and redirects to the application or content page, also encoded in a URL parameter.

The DotNetNuke modules were developed using the DotNetNuke Module Development Template on CodePlex.

Checking VAT identification numbers using JavaScript

The EU Commission provides the checkVat web service to check the validity of EU VAT Identification Numbers (wsdl, redirected target).

To perform the web service query in the browser, I used the JavaScript SOAP Client on CodePlex which worked after a couple of tweaks.

First, IE9 seems to be unable to concatenate an XML document and an empty string, resulting in a Script Error. (see this post on the Discussion page). Edit the _loadWsdl function and modify the line checking the wsdl to:

if(typeof(wsdl) != "object" && wsdl + "" != "" && wsdl + "" != "undefined")

Next, the version of MSXML installed on my current work PC does not support the local-name() XPath function required in the function _getElementsByTagName. Since this function is used to retrieve the SOAP result only, and the result node is always on the same level in XML, I added to the function:

try
{
  var nd = document.selectNodes("//*/*/*/" + tagName );
  if (nd.length > 0) {
   return nd;
  }
}
catch (ex) {}
try
{
  return document.selectNodes("//*/*/urn:" + tagName );
}
catch (ex) {}

This handles both the SOAP *Result and *Response nodes (the checkVat service adds a urn: namespace to the response attributes).

The checkVat web service is somewhat “special” (at least according to my experience) in that it defines the parameters types in a sub-namespace (xsd:schema targetNamespace=”urn:ec.europa.eu:taxud:vies:services:checkVat:types”) rather than the web service’s namespace (wsdl:definitions targetNamespace=”urn:ec.europa.eu:taxud:vies:services:checkVat”). The parameter types namespace is also used in the Response (xmlns:urn=”urn:ec.europa.eu:taxud:vies:services:checkVat:types”).

I finally gave up trying to retrieve the correct namespace with XPath and simply added the line to _sendSoapRequest:

ns = ns + ":types";

The mismatch in namespaces causes Visual Studio to issue a comment in the generated Reference.cs file:

// CODEGEN: Generating message contract since the wrapper namespace (urn:ec.europa.eu:taxud:vies:services:checkVat:types) of message “checkVatRequest” does not match the default value (urn:ec.europa.eu:taxud:vies:services:checkVat).

Further, the checkVat service does not return a Result node, but only a Response message (probably the cause for the out parameters generated in C#).

So I had to fix the _onSendSoapRequest function to parse the Response node:

var nd = SOAPClient._getElementsByTagName(req.responseXML, method + "Result");
if(nd.length == 0)
  nd = SOAPClient._getElementsByTagName(req.responseXML, method + "Response");
if(nd.length == 0)
  nd = SOAPClient._getElementsByTagName(req.responseXML, "return"); // PHP web Service?

and remove the urn: namespace in the result elements (_node2object):

obj[node.childNodes[i].nodeName.replace("urn:", "")] = p;

Finally, my test page containing the web service call for both a valid and an invalid VATID was able to call the script:

var url = "http://ec.europa.eu/taxation_customs/vies/services/checkVatService";

function CheckVATOk()
{
  var pl = new SOAPClientParameters();
  pl.add("countryCode", "AT");
  pl.add("vatNumber", "U40600000");
  SOAPClient.invoke(url, "checkVat", pl, true, CheckVAT_callBack);
}
function CheckVATInvalid()
{
  var pl = new SOAPClientParameters();
  pl.add("countryCode", "XX");
  pl.add("vatNumber", "U40600000");
  SOAPClient.invoke(url, "checkVat", pl, true, CheckVAT_callBack);
}
function CheckVAT_callBack(r, rawXml)
{
  if (typeof(r.valid) != "undefined")
  {
    alert(r.countryCode + " " + r.vatNumber + "\r\n" + r.name + ": " +
      r.address + "\r\n" + r.valid);
  }
  else if (typeof(r.number) != "undefined")
  {
    alert(r.name + " " + r.number + ": " +
      r.message.replace(/[{}\s']/g, ""));
  }
  else
  {
    alert(r);
  }
}

In case of an error, the error message needs to be stripped of a couple of “special” characters to get a character-only error string.

The code worked from a local html page, but not when executed inside a web application. This is due to the Same Origin Policy and raises the message “This Page Accesses Data on Another Domain” in IE9 unless you make the browser “trust” the web service’s host.

To work around this problem, you’d need to implement a local webservice calling the remote checkVat web service.