In my post yesterday, I recommended that Web Service operations should only have a single input parameter rather than multiple for the sake of versioning. In the comments of that post, Todd makes some really good points why having multiple parameters isn't such a bad thing. Rather than responding in comment form, I thought [since I want to illustrate with code snippets] I'd just respond with a new entry. The fact that it also makes my new blog look more active isn't a bad thing either :-)
[WebService( Namespace="urn:many-param-service" )] [SoapDocumentService( SoapBindingUse.Literal, SoapParameterStyle.Bare )] public class Service : WebService { [WebMethod] public ResponseType GetTemp( string city, string state ) { return TemperatureLib.Calculate( city, state ); } }
The input type and input message are represented like this in the WSDL contract for the operation above:
<wsdl:definitions ...> <wsdl:types> <s:schema targetNamespace="urn:many-param-service"> <s:element name="city" type="s:string" /> <s:element name="state" type="s:string" /> ... </s:schema> </wsdl:types> <wsdl:message name="GetTempSoapIn"> <wsdl:part name="city" element="tns:city" /> <wsdl:part name="state" element="tns:state" /> </wsdl:message> ... </wsdl:definitions>
Notice how the city and state parameters are not contained in another element, but are direct children of the s:schema element. I'll tell you why this is a problem in a minute, but the reason it's like that is because the wsdl:part names in the wsdl:message are not "parameter" and I specified SoapParameterStyle.Bare in code (they each impact the other depending on whether you're developing code-first or wsdl-first). "Okay Don, so why not just use SoapParameterStyle.Wrapped?" Because then every node of that wrapper element becomes a parameter - even if you don't want it to be. Meaning, there's no way to send extra non-parameter metadata to the operation. Okay, I know I'm getting too vauge here so I'll use an example. Suppose we want to send version information to the operation. In the example above, there's nowhere for it to go. "Why would we want to send version information to the operation?" Because as we version the operation, we should include version information so it's obvious what changed in each version. We can't get that information by simply adding new parameters to the method signature. I'm not going to go into it here (I will in my article) but there is a approach Doug talks about here about how to version datatypes that contain this version metadata. By using a single uber-parameter, we can include the traditional parameters as well as any metadata we need to send like version infomation. Then, instead of having to check for nulls, we can switch on the version metadata.
Just for completeness, this is what the code for a single parameter operation might look like:
[WebService( Namespace="urn:one-param-service" )] [SoapDocumentService( SoapBindingUse.Literal, SoapParameterStyle.Bare )] public class Service : System.Web.Services.WebService { [WebMethod] public ResponseType GetTemp( RequestType req ) { ResponseType res = new ResponseType(); res.City = req.City; res.State = req.State; return res; } }
And the input type and input message in the WSDL contract for the operation will look like this:
<wsdl:definitions ...> <wsdl:types> <s:schema ... targetNamespace="urn:one-param-service"> <s:element name="req" type="tns:RequestType" /> <s:complexType name="RequestType"> <s:sequence> <s:element ... name="City" type="s:string" /> <s:element ... name="State" type="s:string" /> </s:sequence> </s:complexType> ... </s:schema> </wsdl:types> <wsdl:message name="GetTempSoapIn"> <wsdl:part name="req" element="tns:req" /> </wsdl:message> ... </wsdl:definitions>
A single parameter approach also has a bit to do with style. I don't feel the style aspect is as menial as how you use spaces in your method signatures because it adds a considerable level of consistency to them. And, as I mentioned above, it adds a level of elegancy to operations which consume metadata that doesn't work well as a traditional parameter.
Todd also states: "Sometimes it may make sense to put the new parameter on the TempRequest object, but not always. Following your example, if TempRequest really is Address, it makes sense to put a ZipCode field on the object rather than as another parameter to GetTemp. However, if ZipCode doesn't logically fit into TempRequest (ie, TempRequest is not an Address), then don't put it there."
That's the beauty of using a single parameter. It's not important whether TempRequest is an Address or not - or if it contains an Address or not. As far as the contract is concerned, we know it contains everything GetTemp needs to function. When we use a single parameter it will always make sense when something new is added. If you want to play with the differences between single and multiple parameters, I've posted some sample code here that might save you some time. Happy coding!