Webservice, takes object as argument, but object is inherited


  • Trolleybus Mechanic

    VB.Net (though I can read C#), sending JSON.

    Objects are like this:

    Public MustInherit Class BaseThing
       Public BunchaProperties
       MustOverride Function ThisShouldNotMatterSinceItIsAFunction() As Boolean
    
    End Class
    
    Public Class ThingOne
       Inherits BaseThing
    
        Public SpecialPropertyForThingOne as String
    
    End Class
    
    Public Class ThingTwo
       Inherits BaseThing
    
        Public SpecialPropertyForThingTwo as String
    
    End Class
    

    The web service I want would be this:

    <WebMethod(EnableSession:=True)> _
    Public Function HandleAnyOldThing(ByVal Thing as BaseThing) as String
       Return  SendThingToGenericHandler(Thing)
    End Function
    

    Which means sending it this JSON:

    {BunchaProperties:"", SpecialPropertyForThingTwo : 9}
    

    But when I do, I get this error:

    Cannot deserialize object graph into type of 'BaseThing'.
    

    But it does work the moment I do this:

    Public Function HandleAnyOldThing(ByVal Thing as ThingTwo) as String
       Return  SendThingToGenericHandler(Thing)
    End Function
    

    Is there any way of getting the web service to recognize that I'm sending it a base object, and just let the Cast further down the line worry about the exact type?


  • Notification Spam Recipient

    @Lorne-Kates said in Webservice, takes object as argument, but object is inherited:

    Is there any way of getting the web service to recognize that I'm sending it a base object, and just let the Cast further down the line worry about the exact type?

    I'm not sure, but I don't think so. You're basically telling it to start with a generic object, then asking what specific-type object it was, but at that point it doesn't know.

    I think you might get away with stubbing out methods that take all the special objects and just call the original method, but other than that you might have to just accept Object and figure out what it is from there...

    Edit: NVM you already discovered my workaround idea.



  • Looks like you're trying to deserialize an object without knowing it's exact class.

    You could use a separated argument to identify it. Not sure if it's doing it wrong, but could work.

    Or different methods for different classes, because webservices aren't really as object oriented as the framework make them look.



  • I'm assuming this is a WCF service (from the WebMethod attribute).

    You usually have to attribute as DataContract any types that can be sent (BaseThing for example).

    You can also use the 'KnownType' attribute to associate other types that can also be sent. You should be able to attribute BaseThing with 'KnownType(typeof(ThingOne))'[C#] to get it to allow that as well.

    If the KnownType attribute doesn't give you enough freedom, you can always implement your own custom DataContractResolver that deals with 'BaseThing'. This may be necessary if ThingOne or ThingTwo are late bounded object types that you don't know about at compile time (and thusly can't attribute BaseThing with).



  • @Lorne-Kates What web service library are you using? WebAPI2?

    That's KIND OF critical to answering the question.



  • @lordofduct said in Webservice, takes object as argument, but object is inherited:

    You can also use the 'KnownType' attribute to associate other types that can also be sent. You should be able to attribute BaseThing with 'KnownType(typeof(ThingOne))'[C#] to get it to allow that as well.

    As long as your entire codebase is in one DLL. If you have multiple projects, sucks to be you.


  • Trolleybus Mechanic

    @blakeyrat said in Webservice, takes object as argument, but object is inherited:

    @Lorne-Kates What web service library are you using? WebAPI2?

    That's KIND OF critical to answering the question.

    It's an ASMX, but it's early in development so I'm not married to it, if something else gets me what I want.


  • Trolleybus Mechanic

    @atimson said in Webservice, takes object as argument, but object is inherited:

    @lordofduct said in Webservice, takes object as argument, but object is inherited:

    You can also use the 'KnownType' attribute to associate other types that can also be sent. You should be able to attribute BaseThing with 'KnownType(typeof(ThingOne))'[C#] to get it to allow that as well.

    As long as your entire codebase is in one DLL. If you have multiple projects, sucks to be you.

    Does it count if there's one project with all the classes, and another project that's the web project?



  • @Lorne-Kates I've never used ASMX so I can't help you.


  • Discourse touched me in a no-no place

    @atimson said in Webservice, takes object as argument, but object is inherited:

    As long as your entire codebase is in one DLL. If you have multiple projects, sucks to be you.

    You should be OK provided you're strict about what objects can actually be sent. The key is that the deserialization engine needs to figure out what it is going to do in all cases by the point that the service is instantiated (or you allow any old sub-document at some points, but then it's probably No Useful Classes For You, so don't do that) and that drives everything.

    If it was my project, I'd just not use inheritance (that I've told the deserialization engine about) at all for anything that goes over the wire.


  • Winner of the 2016 Presidential Election

    @Lorne-Kates said in Webservice, takes object as argument, but object is inherited:

    Is there any way of getting the web service to recognize that I'm sending it a base object, and just let the Cast further down the line worry about the exact type?

    That's simply impossible. Some kind of valid object has to be instantiated during deserialization, so the deserializer must know the concrete type.

    I don't know your environment very well, but this is how that kind of problem is usually solved in the Java world: http://wiki.fasterxml.com/JacksonPolymorphicDeserialization



  • @Lorne-Kates Consider:

    public class Derived1 : Base
    {
        public int Thing { get; set; } 
    } 
    
    public class Derived2 : Base
    {
        public int Thing { get; set; } 
    } 
    

    What now?


  • Trolleybus Mechanic

    @Maciejasjmj said in Webservice, takes object as argument, but object is inherited:

    @Lorne-Kates Consider:

    public class Derived1 : Base
    {
        public int Thing { get; set; } 
    } 
    
    public class Derived2 : Base
    {
        public int Thing { get; set; } 
    } 
    

    What now?

    Yeah. That's the exact issue I'm running into.

    I'm thinking of doing:

    Sub WebServiceThing(ByVal JustSomeThing as Object, ByVal TypeString as String)
    

    Then using TypeString to try cast JustSomeThing to the right object.

    Or I'll just say "fuck it" and create a different web service for everything.



  • @Lorne-Kates said in Webservice, takes object as argument, but object is inherited:

    I'm thinking of doing:
    Sub WebServiceThing(ByVal JustSomeThing as Object, ByVal TypeString as String)

    Then using TypeString to try cast JustSomeThing to the right object.

    Doubt that would work either. You'd have the exact same problem deserializing the object, except instead of BaseThing it would be unable to deserialize that into System.Object.

    The problem is, the deserializer needs to create an object of some type, and then stuff all those properties into it before handing it off to your controller. To create an object, it needs its type - you said it's System.Object, so it creates a new System.Object() and will subsequently shit itself because it can't put your data into it.

    I think this should work (translate to that blasphemous language as necessary):

    public string Post(JObject obj)
    {
        BaseThing target;
        if (obj["SpecialPropertyForThingOne"] != null)
        {
            target = JObject.ToObject<ThingOne>();
        }
        else
        {
            target = JObject.ToObject<ThingTwo>();
        }
        return SendThingToGenericHandler(target);
    }
    

  • Winner of the 2016 Presidential Election

    @Maciejasjmj said in Webservice, takes object as argument, but object is inherited:

    I think this should work

    Or you just encode the fully-qualified class name in a JSON attribute, as described in the link I posted above. That's what you're basically doing anyway.



  • @asdf said in Webservice, takes object as argument, but object is inherited:

    Or you just encode the fully-qualified class name in a JSON attribute, as described in the link I posted above.

    Yeah, the point is that the controller would need to take a JObject. I don't know whether WebAPI has any sort of attribute that would tell the deserializer "when you're trying to deserialize object of this type, instead of creating an object of this type, look into that property and you'll find the concrete type you need to instantiate" automatically.

    You'd have to either do it manually inside a controller and eat an untyped object as a parameter, or hook into WebAPI's deserialization process (which I'm pretty sure is possible, but I'm just as unwilling to do research on it) and write such an attribute yourself.



  • If you want to be able to specify base classes as well as derived classes, you can use the KnownTypeAttribute to get this working. You can only apply this to base classes that you create as the serialiser needs to know what you are trying to do. Then, you can use it like this:

    Sub WebServiceThing(ByVal JustSomeThing as Base)
    

    Sub WebServiceThing(ByVal JustSomeThing as Object, ByVal TypeString as String)
    

    You can't just put object there and expect thing to work, the serialiser won't know what to do.
    Also: how are callers of your web service ever going to figure out what the hell they have to supply to your web service? "JustSomeThing" can be anything (even an Int32), and the types that they have (in their proxy classes, or whatever) won't necessarily match what's in your codebase.

    Creating separate methods with a well-defined method signature is the most sane approach you can take.


  • Trolleybus Mechanic

    @AlexMedia said in Webservice, takes object as argument, but object is inherited:

    Also: how are callers of your web service ever going to figure out what the hell they have to supply to your web service? "JustSomeThing" can be anything (even an Int32), and the types that they have (in their proxy classes, or whatever) won't necessarily match what's in your codebase.

    It's meant to be a client/server for a game.

    Instead of "Thing", say it's "GameAction". The GameAction has a lot of data common to all types of actions. Player_id, target_id, that sort of stuff. Plus there's a game engine that takes GameAction, and spits out a GameResult.

    The client server can create these objects as JSON (it's a web game, so it's Javascript + JSON for simplicity sake). The client knows if it is sending "DrawCardGameAction" or "StartNewRoundGameAction". The server will, obviously, validate the action.

    So there were some approaches I was taking:

    1. Different web calls for everything. One for DrawCard, one for StartNewRound. But there's a ton of duplicate logic there, and the signatures were starting to get crazy for complex actions. And it all ended up creating a GameAction anyways to pass into the engine.
    2. A generic GameAction with shared properties, and derived classes from GameAction. But can't deserialize DrawCardGameAction...
    3. A combination of #1 and #2, with a different web call for each type of GameAction, which would just pass into a function that takes the now-instantiated GameAction.
    4. Get rid of derived classes, and just put in a GameActionType property, and a generic collection of values. But all the derive GameAction classes implement the "Validate()" and "DoAction()" functions that the game engine relies on, so the game engine doesn't need a huge "If type=a elseif type=b elseif type=c" block.

    @AlexMedia said in Webservice, takes object as argument, but object is inherited:

    Creating separate methods with a well-defined method signature is the most sane approach you can take.

    Yeah, it's looking like that.



  • @Lorne-Kates Here:

    GameState is a class with "Player_id, target_id, that sort of stuff"

    GameAction is a class with the thing you want to do

    Your controllers all look like:

    public GameResponse DoThing( GameState state, GameAction action ) {}
    

  • Trolleybus Mechanic

    @blakeyrat said in Webservice, takes object as argument, but object is inherited:

    @Lorne-Kates Here:

    GameState is a class with "Player_id, target_id, that sort of stuff"

    GameAction is a class with the thing you want to do

    Your controllers all look like:

    public GameResponse DoThing( GameState state, GameAction action ) {}
    

    Doesn't that run into the same problem, if I create classes that inherit from GameAction?


  • Trolleybus Mechanic

    Hacky (maybe) solution so far:

    client:

    function SpecificGameAction()
    {
       SpecificGameAction = {}
       SpecificGameAction.SpecificActionThing1 = "xxx";
       SpecificGameAction.SpecificActionThing2 = "xxx";
    
       SendActionToServer("SpecificGameAction", SpecificGameAction);
    }
    
    function SendActionToServer(GameActionTypeName, GameActionData)
    {
        // Do stuff to the GameActionData object, like adding in player id, authentication, whatever else
       StringOfData = JSON.stringify(GameActionData);
       // Send via Ajax using the same signature... 
    }
    
    

    Now the server does this:

    Function HandleGameAction(ByVal GameActionTypeName as String, ByVal GameActionData as String) as GameResponses
    
       // Get a concrete type
       Dim T as Type = GetObjectTypeFromString(GameActionTypeName)
    
       // Using Newtonsoft json.net library
       Dim GameAction as BaseGameAction = JSON.Deserialize(GameActionData, T)
    
       GameResponses = GameEngine.DoAction(GameAction)
    
       Return GameResponses
    
    End Function
    

    So it requires passing the type name (which I can do as TypeName = <%= GetType(SpecificGameAction).Name %>), but everything else is encapsulated in the GameAction.

    Obviously needs the standard error handling, and documentation, but this seems to work so far.



  • Why not embed the shared properties as an object in a field of each of the specialized types and then have multiple functions on the server that have the ability to call other functions?


  • Discourse touched me in a no-no place

    @Lorne-Kates said in Webservice, takes object as argument, but object is inherited:

    it requires passing the type name

    Some name for the action needs to be passed. There is literally no way to avoid that. But maybe there's a way to plug in a deserializer that can look at that name and give you a blank instance of the class; I know I can do this in Java, and I presume there must be some way to do it in the C# ecosystem.

    But don't just send the actual type name over the wire and use it blindly coming back. That'd be a security goatse…



  • Looks like you're trying to do a SOAP ws.

    I suggest you KILL IT WITH FIRE



  • @dkf said in Webservice, takes object as argument, but object is inherited:

    Some name for the action needs to be passed. There is literally no way to avoid that.

    If the derived classes' properties are non-optional, you don't have to - you can just check which type has the set of additional properties which matches the one in the JSON object. Or, in other words, take all the derived types and try to deserialize into each of them until one of them works.

    Which is what I think KnownTypeAttribute does, but I've never used it and the docs are a bit too obtuse for Sunday evening.


  • Winner of the 2016 Presidential Election

    @dkf said in Webservice, takes object as argument, but object is inherited:

    But don't just send the actual type name over the wire and use it blindly coming back.

    Yes, @Lorne-Kates should definitely check that the type extends the base class to prevent a malicious client from instantiating arbitrary classes on the server. That's what Jackson does as well.


  • Discourse touched me in a no-no place

    @clippy said in Webservice, takes object as argument, but object is inherited:

    Looks like you're trying to do a SOAP ws.

    He's reinventing it all in JSON. I really advise stopping thinking about classes, and starting thinking about messages and endpoints. Then the binding layer just is what it needs to be to efficiently get the stuff off the wire and into the code to process it. Starting from the classes is phenomenally harder.



  • @Lorne-Kates generally when you are sending objects that can be basically anything it's best to wrap them in an envelope, so basically

    TypeID = something (enum, reflected type, whatever is easiest to handle)
    Content = wrapped data object

    Then the wrapped data object can contain meta data (ie, timestamp for send, flags, etc) and a dynamic field content (or for older version, or languages without dynamic, use a serialized string)

    That way you can manage data contacts and processing either through straight deserialize (if you only need a few item types) or via reflection (true generic deserialize)

    Do yourself a favor, and get json.net through nuget, it has methods to

    JsonConvert.DeserializeObject<Type>(json)
    JObject.Parse(json)

    And supports methods that use

    Public void Deserialize<T>(string json) where T : class
    (

    JsonConvert.DeserializeObject<T)
    JObject.Parse(json)

    )



  • @dkf said in Webservice, takes object as argument, but object is inherited:

    He's reinventing it all in JSON. I really advise stopping thinking about classes, and starting thinking about messages and endpoints.

    This is really the best answer.

    Your models != your viewmodels (the stuff your views send to your controllers) necessarily.

    Yes it sometimes means you have a lot of shitty:

    this.y = that.y
    this.foo = that.foo
    this.bar = that.bar

    lines of code, but cope.


  • Discourse touched me in a no-no place

    @blakeyrat said in Webservice, takes object as argument, but object is inherited:

    This is really the best answer.

    We've tried the other ways. Stopping forcing object systems to go over the wire is the only way to not make everyone miserable. Well, miserabler. The advantage of working in messages is that you don't need the other side to be written in the same language to make sense of it. You can do that, sure, but forcing it is just a horrible thing to do over the web; it leads to shit like Java Applets and node.js…


  • Trolleybus Mechanic

    @Matches said in Webservice, takes object as argument, but object is inherited:

    get json.net

    Already done (mentioned above in comment).

    @dkf said in Webservice, takes object as argument, but object is inherited:

    I really advise stopping thinking about classes

    @blakeyrat said in Webservice, takes object as argument, but object is inherited:

    this.y = that.y

    @dkf said in Webservice, takes object as argument, but object is inherited:

    Stopping forcing object systems to go over the wire is the only way to not make everyone miserable.

    So what I'm hearing from this (though I may be wrong), is that rather than sending a GameAction object from the client to server, I should look at doing this:

    Client

    ActionCustomValues = {SomeVal: "Yo", Important: "I Really Don't Want To Inflict Node.js on People!"};
    SendGameAction("ActionType", ActionCustomValues);
    

    Server:

    json ReceiveGameAction(string ActionType, collection ActionCustomValues)
    {
       CustomAction action = CreateObjectFromTypeStringWithConstructor(ActionType, ActionCustomValues);
       GameEngine.DoAction(action);
       return result;
    }
    

    And then each derived class can have a constructor with custom values:

    Class SpecialActionType : BaseActionType
    {
       constructor(collection ActionCustomValues)
       {
          this.SpecialValue1 = ActionCustomValues["SpecialValue1"];
       }
    }
    
    

    (and yes, the class types will always be checked, and only instantiated from a derived class of the base GameAction class, so no one can instantiate "System.FuckYou.FormatHardDrive" class)



  • @Lorne-Kates I'd put it more generally as "don't feel your viewmodels (the things the API sends/receives) have to match your data models (the things your back-end actually does processing on)".



  • @Lorne-Kates said in Webservice, takes object as argument, but object is inherited:

    Does it count if there's one project with all the classes, and another project that's the web project?

    That's okay. You just can't split the classes across multiple projects, and still use a KnownTypeAttribute.

    @dkf said in Webservice, takes object as argument, but object is inherited:

    You should be OK provided you're strict about what objects can actually be sent.

    Not in my experience.

    I return Class X from a MVC Web API method, while working with Subclass Y internally. (The goal is to expose a subset of the data as JSON externally to consumers without needing to copy data to a new object before deserialization.) Class X and Subclass Y are in separate projects, so I can provide Class X in a DLL to consumers who are lazy & don't want to create their own model class.

    I forget which deserializer (built-in vs. JSON.Net) was which, but one choked entirely because of the type mismatch, while the other insists on sending all of the Subclass Y fields even though it's supposedly deserializing an object of Class X.

    (Which leaves coping with having to have a distinct viewmodel for my API, as @blakeyrat said. Which... actually isn't a bad philosophy when put that way. I had been offput by the thought of copying stuff around "needlessly", but I was thinking of the API as "external access to part of the data model", which it really isn't.)



  • @atimson said in Webservice, takes object as argument, but object is inherited:

    I had been offput by the thought of copying stuff around "needlessly"

    And that's where AutoMapper comes in handy.



  • @AlexMedia a tool that helps when you really want to :doing_it_wrong:


  • Discourse touched me in a no-no place

    @atimson said in Webservice, takes object as argument, but object is inherited:

    I was thinking of the API as "external access to part of the data model", which it really isn't.

    There will be some relation between the two — if there isn't, something very odd is going on — but it isn't necessarily a direct one. The external clients don't really care about your actual data model, as they can't see it. It helps a lot if you have to support third-party clients, as you can't pull shenanigans in that case. In virtually all cases where I've come across a shitty API, it's because third-party clients of that API have been a complete afterthought at best.



  • @clippy Shut up Clippy, nobody likes you. :D


  • Discourse touched me in a no-no place

    @AlexMedia said in Webservice, takes object as argument, but object is inherited:

    And that's where AutoMapper comes in handy.

    It only helps where you've got effectively a one-to-one mapping between database model and external API and that mapping is also trivial to tell the computer about (such as “property names are identical to column names in a table”). If the models don't have a simple map, you might as well maintain it by hand; it's not like writing a bunch of copying code for the simple bits is hard after all.



  • @AlexMedia it's not good to send complex objects trough the internet, they may get stuck in some junction in the tubes



  • @dkf said in Webservice, takes object as argument, but object is inherited:

    There will be some relation between the two — if there isn't, something very odd is going on — but it isn't necessarily a direct one. The external clients don't really care about your actual data model, as they can't see it.

    Which is what I meant, but expressed poorly. :p

    It helps a lot if you have to support third-party clients, as you can't pull shenanigans in that case. In virtually all cases where I've come across a shitty API, it's because third-party clients of that API have been a complete afterthought at best.

    In this case the API was created for third-party clients. However, it's the first time I've done so (prior ones were meant only for internal use), so learning that my thought process was the real :wtf: is not a big surprise.


  • Discourse touched me in a no-no place

    @atimson said in Webservice, takes object as argument, but object is inherited:

    In this case the API was created for third-party clients. However, it's the first time I've done so (prior ones were meant only for internal use), so learning that my thought process was the real :wtf: is not a big surprise.

    You get usually better by mindful practice. As long as you try to learn what works and what doesn't, you'll get better.


  • Trolleybus Mechanic

    @dkf said in Webservice, takes object as argument, but object is inherited:

    In virtually all cases where I've come across a shitty API, it's because third-party clients of that API have been a complete afterthought at best.

    That's actually very high up in my thoughts. I'm writing the game server and rules engine. I'm also writing a client. But I want it to be entirely dependent on the API, so that if someone wants to write their own client-- sure, go ahead.



  • @atimson - yeah, that does suck to be you.

    It's why I mentioned DataContractResolver as well.



  • @Lorne-Kates can you use reflection to get the actual wanted type from the method being called and then deserialize as that?


  • Trolleybus Mechanic

    @ben_lubar said in Webservice, takes object as argument, but object is inherited:

    @Lorne-Kates can you use reflection to get the actual wanted type from the method being called and then deserialize as that?

    I guess that's what's happening under the hood in the json.net deserializer (string, type)


Log in to reply