WCF
Pradeep presented WCF and it's ability to, via configuration alone, communicate using different bindings using endpoints. He demonstrated self-hosted and IIS-hosted services. He also demonstrating calling the same method using multiple bindings from the WCF Test Client.The meeting got interesting when Russell asked Pradeep to create a method which accepts one int parameter and load it as a Web Service. Here is what we learned.
First, create a new "WCF Service Application" it will generate this code for you:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
namespace WcfService1
{
// NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in code, svc and config file together.
public class Service1 : IService1
{
public string GetData(int value)
{
return string.Format("You entered: {0}", value);
}
public CompositeType GetDataUsingDataContract(CompositeType composite)
{
if (composite == null)
{
throw new ArgumentNullException("composite");
}
if (composite.BoolValue)
{
composite.StringValue += "Suffix";
}
return composite;
}
}
}
Now add a new Console Project to this solution and add this service as a Web Reference and look at the implementation of the GetData proxy:
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/IService1/GetData", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
[return: System.Xml.Serialization.XmlElementAttribute(IsNullable=true)]
public string GetData(int value, [System.Xml.Serialization.XmlIgnoreAttribute()] bool valueSpecified) {
object[] results = this.Invoke("GetData", new object[] {
value,
valueSpecified});
return ((string)(results[0]));
}
And it only gets worse. Notice the return is decorated with IsNullable=true so let's modify the return type to be a non-nullable type:
public int GetData(int value)
{
return value + 1;
}
And regenerate the proxy. Now the implementation of GetData has moved the return value into two out parameters:
[System.Web.Services.Protocols.SoapDocumentMethodAttribute(
"http://tempuri.org/IService1/GetData",
RequestNamespace="http://tempuri.org/",
ResponseNamespace="http://tempuri.org/",
Use=Description.SoapBindingUse.Literal,
ParameterStyle=Protocols.SoapParameterStyle.Wrapped)]
public void GetData(
int value,
[XmlIgnoreAttribute()] bool valueSpecified,
out int GetDataResult,
[XmlIgnoreAttribute()] out bool GetDataResultSpecified) {
object[] results = this.Invoke("GetData", new object[] {
value,
valueSpecified});
GetDataResult = ((int)(results[0]));
GetDataResultSpecified = ((bool)(results[1]));
}
So now you are force to write code like this:
int result;
bool hasResult;
test.GetData(
value: 1,
valueSpecified: false,
GetDataResult: out result,
GetDataResultSpecified: out hasResult
);
I would have assumed that in the above code, where the value:1 is not actually provided (because valueSpecified:false) that this would throw an exception on the server. But instead of a fault it assumes a value of 0! So how do we make it default to a 10?
Obviously what is missing here is the ability to mark method parameters as *optional* to complement the feature of marking a field or property as required. And of course the C# language has syntax for that already:
public int GetData(int value = 10) // does not work
{
return value;
}
But don't count on this optional parameter defaulting to 10 because if valueSpecified is false it will get 0.
I went out in search of an explanation to this lunacy but only found this forum post dating back to August 24, 2006 with no solution in sight.
Best practices dictate the use of a composite type so all service methods accept one parameter and return one result. It's possible to decorate fields of a composite type as required using
[DataMember(IsRequired=true)] but the same is not true for method parameters. WCF and Microsoft are not perfect...I just wish I understood what they were thinking.
Back to Pradeep's presentation, here is is configuration which contains endpoints for the following bindings:
- wsHttpBinding
- mexHttpBinding
- basicHttpBinding
- netTcpBinding
- netNamedPipeBinding
He did not discuss nor am I familiar with the purpose of certificateReference .
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<compilation debug="true" />
</system.web>
<!-- When deploying the service library project, the content of the config file must be added to the host's
app.config file. System.Configuration does not support config files for libraries. -->
<system.serviceModel>
<services>
<service behaviorConfiguration="EvalServiceLibrary.Service1Behavior"
name="EvalServiceLibrary.EvalService">
<clear />
<endpoint address="ws" binding="wsHttpBinding" name="wsHttp"
contract="EvalServiceLibrary.IEvalService" listenUriMode="Explicit">
<identity>
<dns value="localhost" />
<certificateReference storeName="My" storeLocation="LocalMachine"
x509FindType="FindBySubjectDistinguishedName" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" name="Mex" contract="IMetadataExchange"
listenUriMode="Explicit">
<identity>
<dns value="localhost" />
<certificateReference storeName="My" storeLocation="LocalMachine"
x509FindType="FindBySubjectDistinguishedName" />
</identity>
</endpoint>
<endpoint address="basic" binding="basicHttpBinding" name="basicHttp"
contract="EvalServiceLibrary.IEvalService" listenUriMode="Explicit">
<identity>
<dns value="localhost" />
<certificateReference storeName="My" storeLocation="LocalMachine"
x509FindType="FindBySubjectDistinguishedName" />
</identity>
</endpoint>
<endpoint address="net.tcp://localhost:8888/EvalService" binding="netTcpBinding"
name="netTCP" contract="EvalServiceLibrary.IEvalService" listenUriMode="Explicit">
<identity>
<dns value="localhost" />
<certificateReference storeName="My" storeLocation="LocalMachine"
x509FindType="FindBySubjectDistinguishedName" />
</identity>
</endpoint>
<endpoint address="net.pipe://localhost/EvalService" binding="netNamedPipeBinding"
bindingConfiguration="" name="pipe" contract="EvalServiceLibrary.IEvalService" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8080/EvalService" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="EvalServiceLibrary.Service1Behavior">
<!-- To avoid disclosing metadata information,
set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="True"/>
<!-- To receive exception details in faults for debugging purposes,
set the value below to true. Set to false before deployment
to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
<configuration>
<system.web>
<compilation debug="true" />
</system.web>
<!-- When deploying the service library project, the content of the config file must be added to the host's
app.config file. System.Configuration does not support config files for libraries. -->
<system.serviceModel>
<services>
<service behaviorConfiguration="EvalServiceLibrary.Service1Behavior"
name="EvalServiceLibrary.EvalService">
<clear />
<endpoint address="ws" binding="wsHttpBinding" name="wsHttp"
contract="EvalServiceLibrary.IEvalService" listenUriMode="Explicit">
<identity>
<dns value="localhost" />
<certificateReference storeName="My" storeLocation="LocalMachine"
x509FindType="FindBySubjectDistinguishedName" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" name="Mex" contract="IMetadataExchange"
listenUriMode="Explicit">
<identity>
<dns value="localhost" />
<certificateReference storeName="My" storeLocation="LocalMachine"
x509FindType="FindBySubjectDistinguishedName" />
</identity>
</endpoint>
<endpoint address="basic" binding="basicHttpBinding" name="basicHttp"
contract="EvalServiceLibrary.IEvalService" listenUriMode="Explicit">
<identity>
<dns value="localhost" />
<certificateReference storeName="My" storeLocation="LocalMachine"
x509FindType="FindBySubjectDistinguishedName" />
</identity>
</endpoint>
<endpoint address="net.tcp://localhost:8888/EvalService" binding="netTcpBinding"
name="netTCP" contract="EvalServiceLibrary.IEvalService" listenUriMode="Explicit">
<identity>
<dns value="localhost" />
<certificateReference storeName="My" storeLocation="LocalMachine"
x509FindType="FindBySubjectDistinguishedName" />
</identity>
</endpoint>
<endpoint address="net.pipe://localhost/EvalService" binding="netNamedPipeBinding"
bindingConfiguration="" name="pipe" contract="EvalServiceLibrary.IEvalService" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8080/EvalService" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="EvalServiceLibrary.Service1Behavior">
<!-- To avoid disclosing metadata information,
set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="True"/>
<!-- To receive exception details in faults for debugging purposes,
set the value below to true. Set to false before deployment
to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>

