2

Background:

Writing a client that consumes a web service, in C# using .NET 4.0. Proxy class includes an object of type record, which is populated from a SOAP response from the web service.

Situation:

The API includes two functions, findRecords and getRecords. Both functions download the actual record, with the difference being that findRecords is of type void and furnishes the record via an out argument, whereas getRecords is of type record and so returns the record.

Problem:

After executing a call to findRecords, I can access the members of the record object (like recordID, recordTitle, etc.) for use in other function arguments. However, if I try to pass the record object itself as an argument, I get an ArgumentNullException. The only way I can currently pass a record as an argument is if I use the record returned from the getRecords function. The drawback to this approach is that it doubles the number of API calls I need to make, which slows down both my client and the web service.

Question:

Why is it behaving this way, and is there something I can do to be able to pass the record object from findRecords as an argument?

Code:

This is the definition of the findRecords function:

    [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://localhost:8080/findRecords", RequestNamespace="http://www.<redacted>.com/ws/schemas", ResponseNamespace="http://www.<redacted>.com/ws/schemas", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
    public void findRecords([System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable=true)] string username, [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable=true)] filter filter, [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, DataType="integer")] string offset, [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, DataType="integer")] string count, [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable=true)] out System.Nullable<int> numResults, [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable=true)] [System.Xml.Serialization.XmlIgnoreAttribute()] out bool numResultsSpecified, [System.Xml.Serialization.XmlArrayAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable=true)] [System.Xml.Serialization.XmlArrayItemAttribute("list", Form=System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable=false)] out record[] results) {
        object[] results1 = this.Invoke("findRecords", new object[] {
                    username,
                    filter,
                    offset,
                    count});
        numResults = ((System.Nullable<int>)(results1[0]));
        numResultsSpecified = ((bool)(results1[1]));
        results = ((record[])(results1[2]));
    }

The definition of the getRecords function:

    [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://localhost:8080/getRecords", RequestNamespace="http://www.<redacted>.com/ws/schemas", ResponseNamespace="http://www.<redacted>.com/ws/schemas", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
    [return: System.Xml.Serialization.XmlArrayAttribute("records", Form=System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable=true)]
    [return: System.Xml.Serialization.XmlArrayItemAttribute("list", Form=System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable=false)]
    public record[] getRecords([System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable=true)] string username, [System.Xml.Serialization.XmlArrayAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable=true)] [System.Xml.Serialization.XmlArrayItemAttribute("list", Form=System.Xml.Schema.XmlSchemaForm.Unqualified, DataType="integer", IsNullable=false)] string[] stiIds) {
        object[] results = this.Invoke("getRecords", new object[] {
                    username,
                    recordIds});
        return ((record[])(results[0]));
    }

And what I'm trying to do with it:

        // Objects to use for the findRecords call
        int? numResults;
        bool numResultsSpecified;
        record[] records;

        // Object for handling and saving the XML
        XRecord r;

                try
                {
                    ws.findRecords(usernames[uname_Index], GetFilter(), offset.ToString(), count.ToString(),
                        out numResults, out numResultsSpecified, out returnRecords);

                    for (int i = 0; i < returnRecords.Length; i++)
                    {
                        count--;
                        r = GrabRecord(returnRecords[i]);
                        r.record.Save(@".\Records\" + r.id + "_" + r.date2 + ".xml");
                    }
                }

......

    private static XRecord GrabRecord(record _record)
    {
        XNamespace nameSpace = "http://www.<redacted>.com/ws/schemas";

        XDocument r =
            new XDocument(
                new XElement(nameSpace + "getRecordsResponse",
                    new XAttribute(XNamespace.Xmlns + "ns1", nameSpace),
                    new XElement("list",
                        new XElement("ID", _record.id),
                        new XElement("title", _record.title),
            ...............
                        new XElement("comments", _record.comments),
                        new XElement("category", _record.category),
                        _record.modifiedDateSpecified ? new XElement("modifiedDate", _record.modifiedDate) : null,
                        new XElement("attachments", from a in _record.attachments
                                                    select new XElement("list",
                                                        new XElement("id", a.id),
                                                        new XElement("filePath", a.filePath),
                                                        new XElement("type", a.type))));

        XRecord xr = new XRecord();
        xr.record = r;
        xr.id = _record.id;
        xr.date2 = ConvertToDateString(_record.modifiedDate);

        return xr;
    }

Here's the exception and stacktrace information. The line numbers referenced refer to the lines "XDocument r = " and "r = GrabRecord(...)" lines in the corresponding functions.

Unexpected error: Value cannot be null. Parameter name: source at System.Linq.Enumerable.Select[TSource,TResult](IEnumerable1 source, Func2 selector) at WsClient.WSAPI.GrabRecord(record _record) in C:\...WSAPI.cs:line 1235 at WsClient.WSAPI.PersistentUpdate(String[] usernames) in C:\...WSAPI.cs:line 354

As rsbarro suggested, I edited the code in the GrabRecord function to eliminate the possibility that ConvertToDateString() could have been causing the problem if modifiedDate was null. This did not eliminate the problem, and the exception message did not change.

Ant
  • 545
  • 1
  • 9
  • 26
  • 8
    Can you paste some code that would illustrate what you're talking about? – Floremin May 06 '13 at 20:03
  • 1
    Absolutely, and especially the unpassable *object* you're referring to. Also, be sure to include the code for the function that is throwing the exception. – Jesse May 06 '13 at 20:03
  • 1
    We need to see code. Do you use the `as` keyword in some (wrong) way somewhere to get a `null` reference instead of a reference to your instance? – Jeppe Stig Nielsen May 06 '13 at 20:05
  • Code added as requested. – Ant May 06 '13 at 20:32
  • In the time since I posted this, I've tried the following: adding a static member of type `record[]` to my class, then also initializing that member to the size of the `count` argument used in findRecords. Each time I get the same error. I've also used a foreach loop in the past instead of the iterator loop, also with no difference. – Ant May 06 '13 at 20:42

1 Answers1

2

From looking at the code, I'm guessing you are getting an ArgumentNullException in the call to GrabRecord. There is a potential problem in GrabRecord that could cause an ArgumentNullException. When you are building your XDocument to be stored in r, you check _record.modifiedDateSpecified before creating the modifiedDate XElement. However, towards the end of the method, the following statement is executed without any null check before it:

xr.date2 = ConvertToDateString(_record.modifiedDate);

It is possible that _record.modifiedDate is null, and that ConvertToDateString is kicking off an ArgumentNullException. Of course it is possible that ConvertToDateString handles null values without erroring, but without seeing that code it is tough to say.

If the issue is not what I've suggested, could you please update the question with some more details about the exception, and also add a stack trace?

Hope that helps.

rsbarro
  • 27,021
  • 9
  • 71
  • 75
  • I see what you're saying with regard to the null reference possibility there. Unfortunately, I've added code to work around that and am still getting the same error. I'll add the exception and stacktrace to the main post. – Ant May 07 '13 at 12:49
  • Hrm, is it possible that `_record.attachments` is null when you use `findRecords` to get the records? – rsbarro May 07 '13 at 12:54
  • I think you may have found it. I've used SoapUI to help model my API calls, so I executed a call to getRecords and another to findRecords using the same record ID. The former returned the list of attachments, while the latter excluded it. I should be able to work around that now. Thanks! – Ant May 07 '13 at 14:27