I have a RESTful call that can modify the state of an instance. My problem is that the JSON is different based on the type of action the user is taking, it could be a SetProperty
action or a SetState
action (among others).
For example, a POST /api/sample/
_would send the following JSON
{
instanceId: 12312,
actionId: 12345, // Maps to a SetState action
desiredState: 'CURATION'
}
But when calling the same URL for a SetProperty, the JSON would look like
{
instanceId: 12312,
actionId: 25354, // Maps to a SetProperty action
propName: "PropA",
propValue: "user-input"
}
I use the following data contract
[DataContract]
class PostInstanceRequest {
[DataMember]
int instanceId;
[DataMember]
int actionId;
[DataMember]
string desiredState; // Only used for SetState
[DataMember]
string propName; // Only used for SetProperty
[DataMember]
string propValue; // Only used for SetProperty
// Many more action specific properties here...
}
I don't really like this because it feels like I should have a base class for the common properties, and then I should define more base classes with the specific properties.
SetStatePostInstanceRequest
, that addsdesiredState
SetPropertyPostInstanceRequest
, that addspropName
andpropValue
However, if I do that, I don't know what class to deserialize it into. I would have to first deserialize as a PostInstanceRequest
, check the actionId
property, and then deserialize it to the correct subclass.
A possible compromise is to use composition
[DataContract]
class SetStateAction {
[DataMember]
string desiredState;
}
[DataContract]
class SetPropAction {
[DataMember]
string propName;
[DataMember]
string propValue;
}
[DataContract]
class PostInstanceRequest {
[DataMember]
int instanceId;
[DataMember]
int actionId;
[DataMember]
SetPropAction setPropParams;
[DataMember]
SetStateAction setActionParams;
// More references to action specific classes down here
}
Which of the options is going to cause me less trouble? Or is there an even better way that I didn't think of?
Best Answer
Did you author the service which you are posting to? If so, I would recommend changing it, so that you post SetState data to something like
/api/sample/State
, and post SetProperty data to/api/sample/Property
. Then you would have separate controller actions for each service method, with each receiving only the desired parameters. If the service isn't yours and you can't modify it however, then obviously you're a bit stuffed. Otherwise, read this: http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-and-action-selectionUpdate
If you definitely want to retain a single action, at the same address, there is something you can do. You can define your controller action to take an interface / base class-typed parameter, and have the json deserializer use type information which you provide in your json to determine which class to rehydrate with your data, for example:
Your json would become, for example:
The trick is to set up the serializer settings correctly (in your
WebApiConfig.cs
Register(HttpConfiguration config)
method for example):config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Objects;
I used this with an interface, but an abstract base class seems a better fit for your scenario.