Every so often, someone posts a question about handling Faults in the MSDN WCF Forums (http://forums.microsoft.com/MSDN/ShowForum.aspx?ForumID=118&SiteID=1). The question follows the lines of “I get the whole FaultException<T>/FaultContractAttribute ‘thing’, but sometimes I just like System.Exception-derived classes over custom Faults. What should I do?” The reason is frequently “because my business logic doesn’t know about Faults, and it shouldn’t”. This is perfectly correct and the right way to think. So, what should this person do? My favorite response for this is to use a behavior plus an IErrorHandler to make things ‘just work’. What we are going to do is enhance the set of faults that we understand. This complete technique ONLY works well with proxies generated via svcutil or similar methods. My technique here will fail when used when sharing the ServiceContract definition between the message sender and receiver.
The first thing I do is define an IContractBehavior. This behavior will have two jobs:
1. Modify every OperationDescription on a given ContractDescription to add a new Fault. I do this little bit of work in the behavior’s Validate method (your last chance to update any Contract information!). This information will then be published to any generated metadata. This method will only work on a running service—generating a proxy via assembly reflection won’t tell the complete story L
2. Add an IErrorHandler to the endpoint’s ChannelDispatcher.ErrorHandlers collection. Here, the custom IErrorHandler will get a chance to turn ArgumentNullExceptions and its brethren into SOAP Faults.
Step 1 is accomplished by the contract behavior:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]
class MyContractBehaviorAttribute : Attribute, IContractBehavior
{
public void Validate(ContractDescription contractDescription,
ServiceEndpoint endpoint)
{
foreach (OperationDescription od in contractDescription.Operations)
{
FaultDescription fd = new FaultDescription(
MyErrorHandler.ScottSeelyExceptionAction);
fd.DetailType = typeof (string);
fd.Name = MyErrorHandler.ScottSeelyExceptionName;
fd.Namespace = MyErrorHandler.ScottSeelyExceptionNamespace;
od.Faults.Add(fd);
}
}
public void ApplyDispatchBehavior(ContractDescription contractDescription,
ServiceEndpoint endpoint,
DispatchRuntime dispatchRuntime)
{
dispatchRuntime.ChannelDispatcher.ErrorHandlers.Add(
new MyErrorHandler());
}
public void ApplyClientBehavior(ContractDescription contractDescription,
ServiceEndpoint endpoint,
ClientRuntime clientRuntime)
{
}
public void AddBindingParameters(ContractDescription contractDescription,
ServiceEndpoint endpoint,
BindingParameterCollection bindingParameters)
{
}
}
Step 2 then looks like this:
class MyErrorHandler : IErrorHandler
{
internal const string ScottSeelyExceptionAction =
"http://scottseely.com/ScottSeelyFault";
internal const string ScottSeelyExceptionName =
"ScottSeelyFault";
internal const string ScottSeelyExceptionNamespace =
"http://scottseely.com/ScottSeelyFault/2007/08";
public bool HandleError(Exception error)
{
return typeof(ScottSeelyException).IsAssignableFrom(error.GetType());
}
public void ProvideFault(Exception error, MessageVersion version,
ref Message fault)
{
if (HandleError(error))
{
ScottSeelyException exception = (ScottSeelyException)error;
fault = Message.CreateMessage(version, FaultCode.
CreateReceiverFaultCode(ScottSeelyExceptionName,
ScottSeelyExceptionNamespace),
exception.Message, error.Message, ScottSeelyExceptionAction);
}
}
}
Of course, you can extend this to handle more types of exceptions and do other smart things, but I think you get the picture. And, since this is an implementation detail and not something the administrator will be deciding, I then decorate my ServiceContract like so:
[ServiceContract]
[MyContractBehaviorAttribute]
interface ISayHello
{
...
}
I was giving a demonstration today of versioning DataContracts. During the demo, I made a big mistake and couldn’t figure out where I had gone wrong. I wound up promising the folks in my class that I would diagnose the issue and explain to them the error I had made. In the end, I think this is an error many of us could make without realizing it.
What I did was start out with a first generation of a typical example DataContract. The DataContract supported unknown data coming in via IExtensibleDataObject and it knew how to handle itself and any derived type from its own assembly via the special usage of the KnownTypeAttribute. Note—the KnownTypeAttribute allows one to specify a static function that can return all the types that are known to work with the class in question. The implementation is simple and, dare I say, elegant:
[DataContract(Namespace =
"http://www.catalystss.com/Group/Product/2007/10/")]
[KnownType("Bob")]
class Person : IExtensibleDataObject
{
string _firstName;
string _middleInitial;
string _lastName;
ExtensionDataObject _extensionData;
[DataMember]
public string FirstName
{
get { return _firstName; }
set { _firstName = value; }
}
[DataMember(IsRequired=true)]
public string LastName
{
get { return _lastName; }
set { _lastName = value; }
}
[DataMember]
public string MiddleInitial
{
get { return _middleInitial; }
set { _middleInitial = value; }
}
public ExtensionDataObject ExtensionData
{
get
{
return _extensionData;
}
set
{
_extensionData = value;
}
}
public static Type[] Bob()
{
List<Type> retval = new List<Type>();
Type[] myTypes = Assembly.GetAssembly(typeof(Person)).GetTypes();
foreach (Type aType in myTypes)
{
if (typeof(Person).IsAssignableFrom(aType))
{
retval.Add(aType);
}
}
return retval.ToArray();
}
}
Great, right? I wrote some code to read and write this type from a file, and it all worked from the start. Feeling proud, I added an enum and then derived a type from Person that used that enum:
enum Gender
{
Male,
Female
}
[DataContract(Name="Person", Namespace =
"http://www.catalystss.com/Group/Product/2007/11/")]
class Personv2 : Person
{
Gender _gender;
[DataMember(IsRequired=true)]
public Gender Gender
{
get { return _gender; }
set { _gender = value; }
}
}
Nice, right? I even wrote some code to serialize this thing out. The code read as follows:
DataContractSerializer ser = new DataContractSerializer(typeof(Personv2));
FileStream fs = new FileStream("person.xml", FileMode.CreateNew);
Personv2 personv2 = new Personv2();
personv2.FirstName = "Scott";
personv2.LastName = "Seely";
personv2.MiddleInitial = "C";
personv2.Gender = Gender.Male;
ser.WriteObject(fs, personv2);
fs.Close();
The XML went to disk just fine too!
<Person xmlns="http://www.catalystss.com/Group/Product/2007/11/"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<FirstName
xmlns=http://www.catalystss.com/Group/Product/2007/10/
>Scott</FirstName>
<LastName
xmlns=http://www.catalystss.com/Group/Product/2007/10/
>Seely</LastName>
<MiddleInitial
xmlns=http://www.catalystss.com/Group/Product/2007/10/
>C</MiddleInitial>
<Gender>Male</Gender>
</Person>
And, this is where things fell apart on me. I tried to read the object back into a Person object like so:
DataContractSerializer ser = new DataContractSerializer(typeof(Person));
FileStream fs = new FileStream("person.xml", FileMode.Open);
Person person = (Person)ser.ReadObject(fs);
fs.Close();
XmlTextWriter xtw = new XmlTextWriter(Console.Out);
ser.WriteObject(xtw, person);
And I get this exception:
SerializationException: Error in line 2 position 61. Expecting element 'Person' from namespace 'http://www.catalystss.com/Group/Product/2007/10/'.. Encountered 'Element' with name 'Person', namespace 'http://www.catalystss.com/Group/Product/2007/11/'.
This code should have worked (or so I thought). Let’s look again at the XML and see what is wrong here. The XML states that the default XML namespace for the object is http://www.catalystss.com/Group/Product/2007/11/. That’s the v2 namespace! Crud… Why is this and what should it be to make things work? Well, the code wrote out the object using the v2 namespace because we told the DataContractSerializer in the first step to use Personv2 for serialization. It did, and wrote out things correctly. We then created a DataContractSerializer to read the file back in, but used Person as the base type. By switching the types, we switched the valid representation of the object. The correct way to write out the DataContract and then read in ANY type derived from person is this instead:
DataContractSerializer ser = new DataContractSerializer(typeof(Person));
FileStream fs = new FileStream("person.xml", FileMode.CreateNew);
Personv2 personv2 = new Personv2();
personv2.FirstName = "Scott";
personv2.LastName = "Seely";
personv2.MiddleInitial = "C";
personv2.Gender = Gender.Male;
ser.WriteObject(fs, personv2);
fs.Close();
In case you didn’t see it, the change is on the first line of code—I’m using typeof(Person) instead of typeof(Personv2). In this case, the XML is written as:
<Person i:type="a:Person"
xmlns="http://www.catalystss.com/Group/Product/2007/10/"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
xmlns:a="http://www.catalystss.com/Group/Product/2007/11/">
<FirstName>Scott</FirstName>
<LastName>Seely</LastName>
<MiddleInitial>C</MiddleInitial>
<a:Gender>Male</a:Gender>
</Person>
What a difference! Now, we have information about the base type. We have the default namespace as the base type, not the derived type. Finally, the enhancements in the derived type are show as belonging to a special namespace. And yes, the reader code starts to work as well. The lesson here: if you write out an object graph using a DataContractSerializer created with a derived type, you have to read it back in using DataContractSerializer at least as derived as the first one. It should be noted that the Personv2 written out with a Person-based DataContractSerializer can be read in using a Personv2-based DataContractSerializer too. That is, the following code ALSO works:
DataContractSerializer ser = new DataContractSerializer(typeof(Personv2));
FileStream fs = new FileStream("person.xml", FileMode.Open);
Person person = (Person)ser.ReadObject(fs);
fs.Close();
XmlTextWriter xtw = new XmlTextWriter(Console.Out);
ser.WriteObject(xtw, person);
xtw.Flush();
And people sometimes wonder why I can’t keep all these nuances in my head! The worlds of messaging and serialization are filled with these kinds of rules. The rules are always consistent, always predictable. My problem is that there are just so many rules! For those of you who spent the last three days with me on WCF, thank you for the experience!
A question I’ve seen in the forums and heard in person a few too many times over the past few weeks has been “how do I control the location of XML namespace declarations in my emitted XML?” In other words, folks want to control the Text encoding of a given bit of XML as it goes elsewhere. The same folks aren’t too concerned over the reading side. With that in mind, I decided to see what happens when one takes over for serialization. It’s surprisingly easy to control the ultimate format if you apply your efforts in the right places. My goal here was to simply emit XML namespaces into the root of the envelope and then have a Message serialized the right way. By doing this, I’m limiting myself to text message encoding only—no MTOM or Binary for me here! I’ve also locked down on UTF8 encoding for outgoing messages.
In this post, I’m assuming that you already know how to create a BindingElement and a MessageEncodingBindingElement. The secret sauce is in the MessageEncoder anyhow. What I did to control the serialization a bit more was to emit XML namespace declarations when writing out the SOAP Envelope, then let the Headers and Body do what they will. What I did was wrap a TextMessageEncodingBindingElement with my own type. I let TextMessageEncodingBindingElement handle the responsibilities with reading messages in. My encoder handles writing messages out.
Here’s the EncodingBindingElement:
class MyEncoderBindingElement : MessageEncodingBindingElement
{
private readonly MessageEncodingBindingElement _innerEncoding;
MyEncoderBindingElement(MyEncoderBindingElement clone)
{
_innerEncoding = (MessageEncodingBindingElement)clone._innerEncoding.Clone();
}
public MyEncoderBindingElement ()
{
_innerEncoding = new TextMessageEncodingBindingElement();
}
public override MessageEncoderFactory CreateMessageEncoderFactory()
{
return new MyEncoderFactory(_innerEncoding.CreateMessageEncoderFactory());
}
public override MessageVersion MessageVersion
{
get { return _innerEncoding.MessageVersion; }
set { _innerEncoding.MessageVersion = value; }
}
public override BindingElement Clone()
{
return new MyEncoderBindingElement(this);
}
public override bool CanBuildChannelFactory<TChannel>(BindingContext context)
{
return _innerEncoding.CanBuildChannelFactory<TChannel>(context);
}
public override bool CanBuildChannelListener<TChannel>(BindingContext context)
{
return _innerEncoding.CanBuildChannelListener<TChannel>(context);
}
public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
context.BindingParameters.Add(this);
return context.BuildInnerChannelFactory<TChannel>();
}
public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
context.BindingParameters.Add(this);
return context.BuildInnerChannelListener<TChannel>();
}
class MyEncoderFactory : MessageEncoderFactory
{
private readonly MessageEncoderFactory _innerFactory;
MessageEncoder _innerEncoder = null;
public MyEncoderFactory(MessageEncoderFactory innerFactory)
{
_innerFactory = innerFactory;
}
public override MessageEncoder Encoder
{
get
{
if (_innerEncoder == null)
{
_innerEncoder = new MyMessageEncoder(_innerFactory.Encoder);
}
return _innerEncoder;
}
}
public override MessageVersion MessageVersion
{
get { return _innerFactory.MessageVersion; }
}
}
private class MyMessageEncoder : MessageEncoder
{
readonly MessageEncoder _inner;
public MyMessageEncoder(MessageEncoder encoder)
{
_inner = encoder;
}
public override string ContentType
{
get { return _inner.ContentType; }
}
public override string MediaType
{
get { return _inner.MediaType; }
}
public override MessageVersion MessageVersion
{
get { return _inner.MessageVersion; }
}
public override Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentType)
{
return _inner.ReadMessage(buffer, bufferManager, contentType);
}
public override Message ReadMessage(Stream stream, int maxSizeOfHeaders, string contentType)
{
return _inner.ReadMessage(stream, maxSizeOfHeaders, contentType);
}
public override ArraySegment<byte> WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager, int messageOffset)
{
MemoryStream ms = new MemoryStream();
WriteMessage(message, ms);
ArraySegment<byte> retval = new ArraySegment<byte>(bufferManager.TakeBuffer((int)ms.Length));
for (long i = 0; i < ms.Length; ++i)
{
retval.Array
= ms.GetBuffer()
;
}
return retval;
}
string EnvelopeVersion(MessageVersion version)
{
string retval = string.Empty;
if (version.Envelope == System.ServiceModel.EnvelopeVersion.Soap11)
{
retval = "http://schemas.xmlsoap.org/soap/envelope/";
}
else if (version.Envelope== System.ServiceModel.EnvelopeVersion.Soap12)
{
retval = "http://www.w3.org/2003/05/soap-envelope";
}
return retval;
}
public override void WriteMessage(Message message, Stream stream)
{
XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter(stream, Encoding.UTF8);
writer.WriteStartElement("soap", "Envelope", EnvelopeVersion(message.Version));
writer.WriteAttributeString("xmlns", "scott", "http://www.w3.org/2000/xmlns/", "http://www.scottseely.com");
writer.WriteAttributeString("xmlns", "addressing", "http://www.w3.org/2000/xmlns/",
"http://www.w3.org/2005/08/addressing");
{
if (message.Headers.Count > 0)
{
writer.WriteStartElement("soap", "Header", EnvelopeVersion(message.Version));
foreach (MessageHeader header in message.Headers)
{
header.WriteHeader(writer, message.Version);
}
writer.WriteEndElement();
}
writer.WriteStartElement("soap", "Body", EnvelopeVersion(message.Version));
{
message.WriteBodyContents(writer);
}
writer.WriteEndElement();
}
writer.WriteEndElement();
writer.Flush();
}
}
}
And, here is some test code to exercise the binding:
MyEncoderBindingElement be = new MyEncoderBindingElement();
MessageEncoderFactory fact = be.CreateMessageEncoderFactory();
MemoryStream ms = new MemoryStream();
fact.Encoder.WriteMessage(Message.CreateMessage(
MessageVersion.Soap11WSAddressing10, "urn:someAction", new MyData()),
ms);
Console.WriteLine(Encoding.UTF8.GetString(ms.GetBuffer(), 0, (int)ms.Length));
Output is then:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:scott="http://www.scottseely.com"
xmlns:addressing="http://www.w3.org/2005/08/addressing">
<soap:Header>
<addressing:Action soap:mustUnderstand="1">urn:someAction</addressing:Action>
</soap:Header>
<soap:Body>
<scott:MyData xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<scott:MyName>Scott</scott:MyName>
</scott:MyData>
</soap:Body>
</soap:Envelope>
If you need more namespaces, you can either hard code or add some stuff to config or elsewhere.
I remember the joys of being a MFC/ATL developer in the 1990s. I could step through all the Microsoft code except for the system DLLs. Thanks to this visibility, I fixed my bugs faster, got an excellent view of how the framework underneath me worked, and got to see a lot of good, production quality code. This last bit-- "seeing production quality code" -- really helped me improve as a developer.
So, as many blogs are echoing, the source for the .NET 3.5, VS 2008 BCL will also be visible. See the announcement here:
http://weblogs.asp.net/scottgu/archive/2007/10/03/releasing-the-source-code-for-the-net-framework-libraries.aspx
What does this mean for Lutz Roeder's Reflector? To me, this means he proved his point-- the code is plainly visible and people love having access to it. Now, Lutz can put his prodigious skills to use elsewhere. (This is another 'good thing'! Lutz is really talented!)
To me, this is great news. It's about time developers who've only seen .NET get the last thing I miss from my C++ coding days-- full access to the code they rely upon. I hope their skills improve too!
Last week, David Pallmann posted this: http://davidpallmann.spaces.live.com/Blog/cns!E95EF9DC3FDB978E!260.entry. In it, he points out that the default values for Bindings are typically off and need to be altered. Tomas Restrepo chimed in with an 'I agree' + more opinion here: http://www.winterdom.com/weblog/2007/09/28/WCFTips.aspx
and here: http://www.winterdom.com/weblog/2007/09/28/SvcUtilGeneratedConfig.aspx
As I understood things, the main issue is that SvcUtil/VS 200x, when used in conjunction with the default settings for Bindings of all stripes, winds up giving sub-optimal settings. To fix things, people are taking the numeric settings-- buffer sizes, timeouts, parse depth, etc. -- and are setting things to maximum values just to make things work and not have to think so hard. This is not a good situation to be in and I don't like the idea that folks are starting to feel that this shortcoming means that SvcUtil and its infrastructure comprise a bad tool.
A little known/used extensibility point in WCF is the IWsdlImportExtension. This extensibility point allows one to get involved in the generation of a System.ServiceModel.Channels.Binding and influence what happens. The numeric timeouts aren't expressed in the standard set of WSDL/Policy importers, so this is a safe place to impose one's will. I created a IWsdlImportExtension that does its magic in the ImportEndpoint method. This method gives one access to the Binding that will be passed to config to create a configurable entity (see http://blogs.catalystss.com/blogs/scott_seely/archive/2007/09/28/204.aspx for how this all works).
The extension looks for an IBindingUpdater that can take the Binding that SvcUtil/VS200x created and sets defaults on that Binding. The defaults need to be visible to SvcUtil/VS 200x, so I have the defaults stored in machine.config. This isn't optimal for a production machine, but fine for most dev machines (a bigger machine.config slows down the start time of all .NET applications).
The code consists of two projects, CatSvcUtil and CatSvcUtilities. CatSvcUtil does setup of the machine.config and must be run under an Admin account. CatSvcUtilities is a strongly named assembly that contains the IWsdlImportExtension, related config, and a set of IBindingUpdaters for all standard bindings that ship in the System.ServiceModel assembly in .NET 3.5 (except MsmqIntegrationBinding). A batch file, InstallMe.bat, registers the CatSvcUtilities.dll in the GAC.
The way things work is pretty simple: the IWsdlImportExtension looks for a Binding of the same type as the other Binding and then loads a Binding from config where the name of the Binding is "default". Then, a select few of the settings are copied, typically some set that looks like this (settings missing as appropriate):
public class NetTcpBindingUpdater : IBindingUpdater
{
public void UpdateBinding(Binding binding)
{
if (binding == null)
{
throw new ArgumentNullException("binding");
}
NetTcpBinding bindingToUpdate = binding as NetTcpBinding;
if (bindingToUpdate == null)
{
throw new ArgumentException(
string.Format(CultureInfo.CurrentCulture, ExceptionResources.InvalidBindingType,
typeof (NetTcpBinding).AssemblyQualifiedName, binding.GetType().AssemblyQualifiedName),
"binding");
}
NetTcpBinding defaultBinding = new NetTcpBinding("default");
bindingToUpdate.CloseTimeout = defaultBinding.CloseTimeout;
bindingToUpdate.ListenBacklog = defaultBinding.ListenBacklog;
bindingToUpdate.MaxBufferPoolSize = defaultBinding.MaxBufferPoolSize;
bindingToUpdate.MaxBufferSize = defaultBinding.MaxBufferSize;
bindingToUpdate.MaxConnections = defaultBinding.MaxConnections;
bindingToUpdate.MaxReceivedMessageSize = defaultBinding.MaxReceivedMessageSize;
bindingToUpdate.OpenTimeout = defaultBinding.OpenTimeout;
bindingToUpdate.ReaderQuotas = defaultBinding.ReaderQuotas;
bindingToUpdate.ReceiveTimeout = defaultBinding.ReceiveTimeout;
bindingToUpdate.SendTimeout = defaultBinding.SendTimeout;
}
}
You can download and try this all out. I posted everything over at http://www.jsvap.com/ScottSeely/downloads/CatSvcUtilities.zip.
[update: you can also just grab the file from this post-- it's an attachment at the end!]
Right now, I'd love some feedback on good default settings for the various bindings. sseely@catalystss.(nospam).com. Or, leave your suggestions in comments. When I get a good set of recommendations, I'll take those suggestions and apply them in CatSvcUtil.exe (or a future setup.msi).
Again-- this changes the behavior of SvcUtil.exe and VS 2005/8 and 'fixes' those tools instead of rewriting them. I think this is what was asked for. I know it's definitely something that will come in handy for me.
I'm just posting this here so that I can refer to the post from the MSDN WCF Forum.
SvcTraceViewer is documented in this article on MSDN. The article doesn't have my favorite config for tracing. I much prefer this:
<system.diagnostics>
<sources>
<source name="System.ServiceModel.MessageLogging" switchValue="Warning, ActivityTracing">
<listeners>
<add name="ServiceModelTraceListener" />
</listeners>
</source>
<source name="System.ServiceModel" switchValue="Verbose, ActivityTracing"
propagateActivity="true">
<listeners>
<add name="ServiceModelTraceListener" />
</listeners>
</source>
<source name="System.Runtime.Serialization" switchValue="Verbose, ActivityTracing">
<listeners>
<add name="ServiceModelTraceListener" />
</listeners>
</source>
</sources>
<sharedListeners>
<add initializeData="App_tracelog.svclog"
type="System.Diagnostics.XmlWriterTraceListener, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
name="ServiceModelTraceListener" traceOutputOptions="Timestamp">
<filter type="" />
</add>
</sharedListeners>
</system.diagnostics>
<system.serviceModel>
<diagnostics>
<messageLogging logEntireMessage="true" logMessagesAtServiceLevel="true"
logMessagesAtTransportLevel="true" />
</diagnostics>
</system.serviceModel>
In your application, if you have an EXE, make this the first line in your Main method:
System.IO.File.WriteAllText("App_tracelog.svclog", String.Empty);
[clarification: I assert below that Thomas' post reminds me of the fact that many people don't know that SvcUtil is a tool that heavily leverages WCF assemblies. The point of this post is to explain where SvcUtil stops and System.ServiceModel/System.Web.Services begins)]
Thomas Restrepo has an interesting post here. In it, he reminds me of a common misconception about how svcutil works. I mean, Thomas knows WCF pretty well and he doesn't know this one.
When you ask SvcUtil to generate a proxy and config for a particular WSDL/MEX endpoint, SvcUtil has to write out all the assumptions it is making. There is no such thing as minimal config for a binding-- you get all values, including defaults, written out. A good question on this one might be 'why is it that way?'
Let's walk through how the SvcUtil goes from WS-Policy/WSDL to config (ignoring the most of the proxy part).
1. SvcUtil requests WS-Policy/WSDL
2. The infrastructure reads the WS-Policy/WSDL and goes through the set of known bindings. Each binding is asked if it can make a binding that matches the WSDL. Eventually, we'll get to CustomBinding and it'll ask each BindingElement if it can cover things.
3. With a Binding in hand, the SvcUtil infrastructure iterates over all BindingExtensions /system.serviceModel/extenensions/bindingExtensions in config and asks them if they know how to serialize the Binding in question by calling StandardBindingCollectionElement<TStandardBinding, TBindingConfiguration>.TryAdd. If the TryAdd returns 'True' we know the following happened:
- The StandardBinding config part understood the Binding
- All settings from the Binding were successfully copied to the config element
It's this last part, copying ALL settings from the Binding to the config element that causes every setting to appear in config. The System.Configuration.ConfigurationElement counts any change, even setting to the items default value, as a change that must be persisted. So, setting the config to all defaults, no matter the value's size, will cause these settings to be written out. This is NOT a svcutil issue. Instead, it is based on the set of default settings for each Binding.
Keep in mind that these settings are based on empirical evidence about what a normal client should set things as. Smaller settings make it harder to DOS a client. Bigger defaults would have made it easier to blow up your average WCF endpoint, and that wouldn't have been a good thing.
A little while ago, Jeff Prosise forwarded a question to me about implementing his single-threaded apartment (STA) workaround for ASMX (see MSDN’s Wicked Code, October 2006) in WCF. The reason one does this in the first place is to take an existing set of code and expose it to a wider audience: it's usually cheaper in terms of time and effort to take something that works well, put a facade on it, and expose the functionality to a wider audience. The problem works out something like this: when .NET code calls COM code, .NET sets up an appropriate thread to call to the COM object. In IIS hosted .NET applications, the .NET AppDomain runs in a multi-threaded apartment (MTA). If the COM object also uses an MTA, the call can use the .NET calling thread, allowing the COM interop calls to scale to the number of concurrent callers in the .NET AppDomain. Most Visual Basic 6 and earlier COM objects are created in a STA. The default mechanism for .NET to handle calls to STA COM objects is to create one STA for the AppDomain. Since only one thread hosts the STA, only one caller at a time can use the STA thread to call out to the COM objects. In the end, all callers into what is thought to be a scalable WCF endpoint wind up being serialized. Jeff goes into more detail in the previously mentioned article.
So, how would this work for WCF which has a different dispatch mechanism from ASMX? What we’d like to have happen is to only invoke the STA COM object on its own STA thread. We don’t need to do this for other operations. To me, this sounded like a great use case for replacing the default IOperationInvoker for a chosen operation with one of my own. Up until this point, I hadn’t really seen any great use cases for this capability that weren’t contrived.
In here, I’m only going to present how to handle the more common, Synchronous calling case. If you have written your OperationContract to support asynchronous calls, you have an ‘exercise for the reader.’ The first thing I did was create an IOperationBehavior, StaSyncOperationBehaviorAttribute. The behavior validates that the SyncMethod is set to make sure that the behavior is only used in tested scenarios. When asked to apply the dispatch behavior for the receive side, the behavior makes sure that the operation is still synchronous. If so, I wrap the existing Invoker with one of my own. In my IOperationInvoker, I just create an STA thread and invoke the call on there. The complete code for the behavior follows:
[AttributeUsage(AttributeTargets.Method)]
class StaSyncOperationBehaviorAttribute : Attribute, IOperationBehavior
{
public void Validate(OperationDescription operationDescription)
{
if (operationDescription.SyncMethod == null)
{
throw new InvalidOperationException(
"The StaSyncOperationBehaviorAttribute only works for Synchronous" +
" methods. OperationDescription.SyncMethod returned 'null'.");
}
}
public void ApplyDispatchBehavior(OperationDescription operationDescription,
DispatchOperation dispatchOperation)
{
if (dispatchOperation.Invoker.IsSynchronous == false)
{
throw new InvalidOperationException(
"The StaSyncOperationBehaviorAttribute only works for Synchronous methods." +
" DispatchOperatoin.Invoker.IsSynchronous returned 'false'.");
}
dispatchOperation.Invoker = new StaOperationSyncInvoker(dispatchOperation.Invoker);
}
public void AddBindingParameters(OperationDescription operationDescription,
BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(OperationDescription operationDescription,
ClientOperation clientOperation)
{
}
class StaOperationSyncInvoker : IOperationInvoker
{
private readonly IOperationInvoker _innerInvoker;
public StaOperationSyncInvoker(IOperationInvoker innerInvoker)
{
_innerInvoker = innerInvoker;
}
public object[] AllocateInputs()
{
return _innerInvoker.AllocateInputs();
}
class InvokeDelegateArgs
{
public IOperationInvoker Invoker
{
get;
set;
}
public Object Instance
{
get;
set;
}
public Object[] Inputs
{
get;
set;
}
public Object[] Outputs
{
get;
set;
}
public Object ReturnValue
{
get;
set;
}
public InvokeDelegateArgs(IOperationInvoker invoker, object instance, object[] inputs)
{
Invoker = invoker;
Instance = instance;
Inputs = inputs;
}
}
static void InvokeDelegate(object data)
{
InvokeDelegateArgs ida = (InvokeDelegateArgs) data;
Object[] outputs;
ida.ReturnValue = ida.Invoker.Invoke(ida.Instance, ida.Inputs, out outputs);
ida.Outputs = outputs;
}
public object Invoke(object instance, object[] inputs, out object[] outputs)
{
InvokeDelegateArgs ida = new InvokeDelegateArgs(_innerInvoker, instance, inputs);
Thread t = new Thread(InvokeDelegate);
t.SetApartmentState(ApartmentState.STA);
t.Start(ida);
t.Join();
outputs = ida.Outputs;
return ida.ReturnValue;
}
public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
{
throw new NotImplementedException();
}
public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
{
throw new NotImplementedException();
}
public bool IsSynchronous
{
get { return _innerInvoker.IsSynchronous; }
}
}
}
To apply the behavior, you would apply this to the implementation of the contract and not the contract itself. The interface doesn’t know or care if you have an STA COM object in use under the covers. On the service, I had it return the current ApartmentState for the thread the operation was running on.
[StaSyncOperationBehavior]
public System.Threading.ApartmentState GetData()
{
return System.Threading.Thread.CurrentThread.GetApartmentState();
}
This is one of those things that is making WCF seem really great to me. One person writes a whole bunch of code to make something happen. The end user experience is just ‘apply attribute, good stuff happens.’
Go here to RSVP: http://www.cnug.org/Default.aspx?tabid=40.
The summary is:
WCF is all about messaging. Sometimes, the application you are building calls for something simpler than WS-*. JSON, RSS, and ATOM are just a few of the things you can build with the WCF platform. In this talk, we take a look at how WCF allows these extensions and which ones are included in the .NET Fx 3.5 release.
Normally, I have 105 minutes to give this talk. Tomorrow, I have 60 minutes. I'll hit as much of the cool stuff as I can while trying to make sure that the next speaker can start promptly at 8.
To RSVP, go here: http://www.cnug.org/Default.aspx?tabid=40 (Choose the September 19, 2007 Downers Grove option).
Here's a little thing I was reminded of while going through the MSDN WCF Forums today:
If you have an IPv6 enabled machine and you ask it to listen on an address named 'localhost', what really happens is you listen on two addresses:
IPv4: 127.0.0.1
IPv6: [machine IP]
If you want to make sure that you don't open the port externally and incur the wrath of the 'Firewall God' that lives in your machine, you don't want to open that externally facing port. To do this, instead of localhost, type in '127.0.0.1' for the address you are opening. This informs the infrastructure that you only want an IPv4 address, which keeps the 'Firewall God' appeased. Example:
ServiceHost
serviceHost = new ServiceHost(typeof(SayHelloService), new Uri("net.tcp://127.0.0.1:9000/"))
This works for any IPv4/6 enabled machine for most scenarios. This isn't WCF specific, though WCF folks do seem to hit this the most often.
Courtesy of the ThinkTecture blogs, we have this post: http://blogs.thinktecture.com/buddhike/archive/2007/09/01/414926.aspx. Apparently, there is some confusion about how to get a single ChannelFactory instance to hit a single web service using many threads. You would want to share an instance in cases where the other side is expensive to setup (think WS-SecureConversation scenarios) but you want to multiplex the connection over many worker threads. The confusion comes in the form of how the client side code is setup. If you choose the normal, synchronous interface then all calls through a given ChannelFactory are serialized. This serialization happens even if you use one ChannelFactory across many threads. Why would this happen? It all has to do with decisions that one makes and the tradeoffs in what happens under the covers.
By default, SvcUtil creates synchronous ‘proxy’ classes. In the ServiceModel world, when I choose to connect to an endpoint using the synchronous pattern, I am also choosing to serialize all requests to an endpoint. This has a side benefit of making message correlation very easy on the client side. For normal, non-duplex contracts, if I send a request, the next data from the other endpoint will be the response. I don’t need to keep any data around to match requests and responses. This is the normal use case for contracts.
On the other hand, I may want to make many simultaneous requests to a single e