0

I have a C# application that takes and returns information via RESTful services. The way I handled these operations before, was the methods simply returned JSON strings, and these strings were then wrapped in XML.

To prevent this, I changed the ResponseFormat to JSON and I am now returning DataContracts, which all derive from one abstract class:

using System.Runtime.Serialization;

/// <summary>
/// Abstract class for defining a standard definition of a command return result.
/// A command is not required to return a result, however it is recommended.
/// A return result must implement the properties defined in this abstract class.
/// A command may return an anonymous object instead of an instance of this abstract class.
/// </summary>
[DataContract(Namespace = "")]
public abstract class ICommandResult {
// Don't let the name disturb you, this used to be an interface.

    /// <summary>
    /// Gets or sets the message.
    /// </summary>

    [DataMember]
    public abstract string Message { get; set; }

}

To apply the DataContract attribute, I changed the above interface to a (now) abstract class.

Each derivative also applies the DataContract and DataMember attributes.

When I call a route of the service, the command gets executed, and I can see output in the console. When returning the return value, however, I see following in the console, and the web browser stays empty. Fiddler (on Windows) shows me a HTTP 504 error, whereas my Mac shows me a connection reset.

XmlException (Dropped Connection?): On JSON writer data type 'type' must be specified. Object string is 'object', server type string is '__type'.

The specific method I'm using to test this, is as follows:

IRestService:

[OperationContract]
[WebGet(UriTemplate = Routing.GetRoutesRoute, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Bare)]
ICommandResult GetAllRoutes();

RestService (implementation):

public ICommandResult GetAllRoutes() {
    var results = ExecuteCommand("print-routes", "--no-output");
    // ExecuteCommand returns an ICommandResult object
    return results;
}

Actual command implementation (so it can be called via the console, too):

ICommandResult Command_PrintRoutes(Command sender, params string[] args) {
        var fields = typeof(Routing).GetFields();
        var fieldValues = new Dictionary<string, string>();
        var outputToConsole = true;

        if (args.Count(x => x.ToLowerInvariant().Equals("--no-output")) == 1)
            outputToConsole = false;

        foreach (var field in fields)
            fieldValues.Add(field.Name, (string)field.GetRawConstantValue());

        var output = JsonConvert.SerializeObject(fieldValues, Formatting.Indented);

        if (outputToConsole)
            WriteLine(output);

        //return new CommandResult<Dictionary<string, string>>("Found following routes", fieldValues);
        return new CommandResult<string>("Found following routes (JSON-encoded)", output); // Trying out different return types
    }

How can this be fixed and prevented?

SimonC
  • 1,547
  • 1
  • 19
  • 43

1 Answers1

1

I think that this is a serialization problem, with your abstact DataContract.

Look this: KnownTypeAttribute - Serialization.

I think that you need specify all your subclasses on the abstract base class using KnownType Attribute.

In this way the DataContractSerializer can recognize all types when serialize and deserialize objects.

 [DataContract]
 [KnownType(typeof(SubClassName1))] <-
 [KnownType(typeof(SubClassName2))] <-
 [KnownType(typeof(SubClassName3))] <-
 public abstract class ClassName
 {
      ...
 }

Sorry for my english. I hope I was helpful,

Stefano

stfno.me
  • 898
  • 7
  • 24
  • Not a very scalable way to do things in that case. I'll give it a try and I'll get back to you. – SimonC Feb 09 '17 at 14:52
  • That didn't seem to solve the problem. Unless I'd have to define each and every single type that may or may not be used in a return value. It has to be easier than this. – SimonC Feb 09 '17 at 14:59
  • @Beatsleigher yes, you must define all of this attributes if you want use abstract class in wcf dto. I have tried to recreate your situation in vs and if I call a OperationContract that return an abstract class, I can debug it but when it return, the response do not arrive. (Like you saw). If I put the attribute [KnownType(typeof(SubClassName1))] I can see the wcf response. – stfno.me Feb 09 '17 at 18:40
  • If I return the subclass, the response is (Example): {"Message":"NoAbstract"} If I return the abstract class, the response is: {"__type":"TestResult","Message":"Test"} this '__type' is my subclass I suppose. If I remember, in your post I saw the word '__type' inside the XMLException. Maybe this two things are connected in some way? (I do not know, i suppose) – stfno.me Feb 09 '17 at 18:50
  • And the last thing, have you tried to modify your abstract class in 'normal' class and see if WCF return some data? If you try this you/we can know if the '504 error' is given by abstract class or by other things. – stfno.me Feb 09 '17 at 18:55
  • I've tried that now, but I'm still getting the same exception. I'll create a smaller test project, and I'll try some things there. – SimonC Feb 10 '17 at 08:30
  • Very strange, are you sure you have configured the web.config correctly? If you removed the abstract class and still does not work, maybe it's a problem of web.config. Anyway let me know how it goes with the new project – stfno.me Feb 10 '17 at 10:56
  • I don't have a web.config, this is a console application. – SimonC Feb 10 '17 at 11:40
  • It works fine in the smaller test application, I'm super confused by this.. – SimonC Feb 10 '17 at 11:47
  • It seems the problem occurs, when I add an object as a DataMember. This is a huge issue because I need to supply objects back over the REST API – SimonC Feb 10 '17 at 12:05
  • Yes, the problem is on WCF response. Before I was talking about to become the DataMember not abstract. I do not know if we had understood – stfno.me Feb 10 '17 at 12:11
  • Here is the code I created to troubleshoot this: http://pastebin.com/JBNbZSfR As you can see, I created one abstract class, and one class inheriting said class. As long as I don't add an object to the TestObject field, it works like a charm. As soon as I, for example, add an anonymous object to said field, I get an error and the application crashes. – SimonC Feb 10 '17 at 12:16
  • Why you try to return an object that have TestObject = new { Message = "" } an anonymous object inside? – stfno.me Feb 10 '17 at 12:47
  • Because one of the requirements of the API is that I need to be able to pass dynamic objects with dynamic data inside of them. – SimonC Feb 10 '17 at 13:05
  • And if you do something like this: [link](http://pastebin.com/SnwATkF3)? replace object TestObject with string TestObject. Inside TestResult Test() you convert anonymous object to json string and after append this json string to testobject – stfno.me Feb 10 '17 at 13:08
  • This is a solution, because you can not return an anonymous object from rest service or wcf. Response: `{"__type":"RestfulService.TestResult2:#WcfAbstractConsole","TestBool":false,"TestInt":1024,"TestObject":"{\"Message\":\"TestAnonymous\"}","TestString":"Test","TestString2":null}` – stfno.me Feb 10 '17 at 13:08