WSDL-world vs CLR-world – some differences
A change in mindset is required when switching between a typical CLR application and a web service application. There are some things in a CLR environment that just don’t add-up in a WSDL arena (and vice-versa). I’m listing some of them here. When I say WSDL-world, I’m mostly talking with respect to a WCF Service and / or a Web Service.
No (direct) Method Overloading: You definitely can have overloaded methods in a, say, Console application, but when it comes to a WCF / Web Services application, you need to adorn these overloaded methods with a special attribute so the service knows which specific method to invoke.
When you’re working with WCF, use the Name property of the OperationContract attribute to provide unique names.
1: [OperationContract(Name = "AddInt")]
2: int Add(int arg1, int arg2);
3:
4: [OperationContract(Name = "AddDouble")]
5: double Add(double arg1, double arg2);
By default, the proxy generates the code for this as:
1: [System.ServiceModel.OperationContractAttribute(
2: Action="http://tempuri.org/ILearnWcfService/AddInt",
3: ReplyAction="http://tempuri.org/ILearnWcfService/AddIntResponse")]
4: int AddInt(int arg1, int arg2);
5:
6: [System.ServiceModel.OperationContractAttribute(
7: Action="http://tempuri.org/ILearnWcfServiceExtend/AddDouble",
8: ReplyAction="http://tempuri.org/ILearnWcfServiceExtend/AddDoubleResponse")]
9: double AddDouble(double arg1, double arg2);
With Web Services though the story is slightly different. Even after setting the MessageName property of the WebMethod attribute, the proxy does not change the name of the method, but only the underlying soap message changes.
1: [WebMethod]
2: public string HelloGalaxy()
3: {
4: return "Hello Milky Way!";
5: }
6:
7: [WebMethod(MessageName = "HelloAnyGalaxy")]
8: public string HelloGalaxy(string galaxyName)
9: {
10: return string.Format("Hello {0}!", galaxyName);
11: }
The one thing you need to remember is to set the WebServiceBinding accordingly.
1: [WebServiceBinding(ConformsTo = WsiProfiles.None)]
The proxy is:
1: [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/HelloGalaxy",
2: RequestNamespace="http://tempuri.org/",
3: ResponseNamespace="http://tempuri.org/",
4: Use=System.Web.Services.Description.SoapBindingUse.Literal,
5: ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
6: public string HelloGalaxy()
7:
8: [System.Web.Services.WebMethodAttribute(MessageName="HelloGalaxy1")]
9: [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/HelloAnyGalaxy",
10: RequestElementName="HelloAnyGalaxy",
11: RequestNamespace="http://tempuri.org/",
12: ResponseElementName="HelloAnyGalaxyResponse",
13: ResponseNamespace="http://tempuri.org/",
14: Use=System.Web.Services.Description.SoapBindingUse.Literal,
15: ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
16: [return: System.Xml.Serialization.XmlElementAttribute("HelloAnyGalaxyResult")]
17: public string HelloGalaxy(string galaxyName)
18:
You see the calling method name is the same in the proxy, however the soap message that gets generated is different.
Using interchangeable data types: See details on this here.
Type visibility: In a CLR-based application, if you mark a field as private, well we all know, it’s ‘private’. Coming to a WSDL side of things, in a Web Service, private fields and web methods will not get generated in the proxy.
In WCF however, all your operation contracts will be public as they get implemented from an interface. Even in case your ServiceContract interface is declared internal/private, you will see it as a public interface in the proxy. This is because type visibility is a CLR concept and has no bearing on WCF.
Also if a private field has the [DataMember] attribute in a data contract, it will get emitted in the proxy class as a public property for the very same reason.
1: [DataContract]
2: public struct Person
3: {
4: [DataMember]
5: private int _x;
6:
7: [DataMember]
8: public int Id { get; set; }
9:
10: [DataMember]
11: public string FirstName { get; set; }
12:
13: [DataMember]
14: public string Header { get; set; }
15: }
16: }
See the ‘_x’ field is a private member with the [DataMember] attribute, but the proxy class shows as below:
1: [System.Runtime.Serialization.DataMemberAttribute()]
2: public int _x {
3: get {
4: return this._xField;
5: }
6: set {
7: if ((this._xField.Equals(value) != true)) {
8: this._xField = value;
9: this.RaisePropertyChanged("_x");
10: }
11: }
12: }
Passing derived types to web methods / operation contracts:
Once again, in a CLR application, I can have a derived class be passed as a parameter where a base class is expected.
I have the following set up for my WCF service.
1: [DataContract]
2: public class Employee
3: {
4: [DataMember(Name = "Id")]
5: public int EmployeeId { get; set; }
6:
7: [DataMember(Name="FirstName")]
8: public string FName { get; set; }
9:
10: [DataMember]
11: public string Header { get; set; }
12: }
13:
14: [DataContract]
15: public class Manager : Employee
16: {
17: [DataMember]
18: private int _x;
19: }
20:
21: // service contract
22: [OperationContract]
23: Manager SaveManager(Employee employee);
24:
25: // in my calling code
26: Manager manager = new Manager {_x = 1, FirstName = "abc"};
27: manager = LearnWcfServiceClient.SaveManager(manager);
The above will throw an exception saying:
In short, this is saying, that a Manager type was found where an Employee type was expected!
Hierarchy flattening of interfaces in WCF:
See details on this here. In CLR world, you’ll see the entire hierarchy as is. That’s another difference.
Using ref parameters:
This one kind of stumped me. Not sure why I tried this, but you can pass parameters prefixed with ref keyword* (* terms and conditions apply).
The main issue is this, how would we know the changes that were made to a ‘ref’ input parameter are returned back from the service and updated to the local variable? Turns out both Web Services and WCF make this tracking happen by passing the input parameter in the response soap. This way when the deserializer does its magic, it maps all the elements of the response xml thereby updating our local variable. Here’s what I’m talking about.
1: [WebMethod(MessageName = "HelloAnyGalaxy")]
2: public string HelloGalaxy(ref string galaxyName)
3: {
4: string output = string.Format("Hello {0}", galaxyName);
5: if (galaxyName == "Andromeda")
6: {
7: galaxyName = string.Format("{0} (2.5 million light-years away)", galaxyName);
8: }
9: return output;
10: }
This is how the request and response look like in soapUI.
As I said above, the behavior is quite similar for WCF as well. But the catch comes when you have a one-way web methods / operation contracts. If you have an operation contract whose return type is void, is marked one-way and that has ref parameters then you’ll get an error message when you try to reference such a service.
1: [OperationContract(Name = "Sum", IsOneWay = true)]
2: void Sum(ref double arg1, ref double arg2);
3:
4: public void Sum(ref double arg1, ref double arg2)
5: {
6: arg1 += arg2;
7: }
This is what I got when I did an update to my service reference:
Makes sense, because a OneWay operation is… one-way – there’s no returning from this operation. You can also have a one-way web method:
1: [SoapDocumentMethod(OneWay = true)]
2: [WebMethod(MessageName = "HelloAnyGalaxy")]
3: public void HelloGalaxy(ref string galaxyName)
This will throw an exception message similar to the one above when you try to update your web service reference.
In the CLR space, there’s no such concept of a ‘one-way’ street! Yes, there’s void, but you very well can have ref parameters returned through such a method.
Just a point here; although the ref/out concept sounds cool, it’s generally is a code-smell. The better approach is to always return an object that is composed of everything you need returned from a method.
These are some of the differences that we need to bear when dealing with services that are different from our daily ‘CLR’ life.