I’m a self-admitted control-freak. And when I add a service reference to a project pointing to a WCF service that exposes metadata, I would like more control over the code-generation process. Not long ago I had a specific need to to this when creating a client-side T4 template for my Trackable DTO’s project with Entity Framework 4.0. Instead of relying solely on “Add Service Reference” in Visual Studio to generate client-side POCO classes, I wanted to generate the classes myself, hooking into a change-tracking mechanism and injecting data binding code.
The way that Self-Tracking Entities in EF4 accomplishes this is to add a class library project with a T4 template the inspects an entity data model (edmx file). The problem is that this class library assembly must then be referenced both by the service and the client, violating a tenet of service-oriented applications to share contract and schema but not class or assembly. That way, the client could be completely ignorant of the persistence stack used (in this case Entity Framework), and there could be a cleaner separation of concerns. The WCF service could use a data access layer (DAL) to abstract away persistence concerns, and the client could make use of a change-tracker that the service would have no interest in referencing.
And so I needed to generate client-side entities with data contracts using a T4 template that could read service metadata exposed as WSDL. The way to do this would be to create a WsdlImporter referencing data contracts from a service. The result would be a CodeCompileUnit, which I could reflect over using the CodeDom to generate class and member definitions.
private CodeCompileUnit GetMetadataCodeUnit(string mexAddress, long maxReceivedMessageSize, Type collectionType) { Binding mexBinding = GetMetadataBinding(mexAddress, maxReceivedMessageSize); var mexClient = new MetadataExchangeClient(mexBinding); mexClient.ResolveMetadataReferences = true; var metadata = mexClient.GetMetadata (new EndpointAddress(mexAddress)); var sections = metadata.MetadataSections .Where(s => s.Identifier.Contains("datacontract.org")); var metaDocs = new MetadataSet(sections); var wsdlImporter = new WsdlImporter(metaDocs); var schemaImporter = new XsdDataContractImporter(); schemaImporter.Options = new ImportOptions(); schemaImporter.Options.ReferencedCollectionTypes .Add(collectionType); schemaImporter.Import(wsdlImporter.XmlSchemas); return schemaImporter.CodeCompileUnit; }
The GetMetadataBinding method creates a custom binding with a transport element that has been configured with the specified max received message size.
private Binding GetMetadataBinding(string mexAddress, long maxReceivedMessageSize) { Uri uri = new Uri(mexAddress); BindingElement element = null; switch (uri.Scheme) { case "net.pipe": element = new NamedPipeTransportBindingElement(); ((NamedPipeTransportBindingElement)element) .MaxReceivedMessageSize = maxReceivedMessageSize; break; case "net.tcp": element = new TcpTransportBindingElement(); ((TcpTransportBindingElement)element) .MaxReceivedMessageSize = maxReceivedMessageSize; break; case "http": element = new HttpTransportBindingElement(); ((HttpTransportBindingElement)element) .MaxReceivedMessageSize = maxReceivedMessageSize; break; case "https": element = new HttpsTransportBindingElement(); ((HttpsTransportBindingElement)element) .MaxReceivedMessageSize = maxReceivedMessageSize; break; default: break; } if (element != null) { Binding binding = new CustomBinding(element); return binding; } else { return null; } }
Here is a link to a simple Console app that tests the WsdlImporter code. And here is a link to a project that implements it as a T4 template.
Enjoy.
Please let me know if you’re looking for a article author for your site. You have some really great articles and I think I would be a good asset. If you ever want to take some of the load off, I’d absolutely love to write some articles for your blog in exchange for a link back to mine.
Please blast me an email if interested. Thanks!
I was getting an XmlException “The maximum nametable character count quota (16384) has been exceeded while reading XML data.” when our service grew too big using this code. To fix, I had to update the MaxNameTableCharCount to int.MaxValue on the binding.
In the “GetMetadataBinding” function, the following code if statement block should read:
if (element != null)
{
var encoding = new BinaryMessageEncodingBindingElement();
encoding.ReaderQuotas.MaxNameTableCharCount = int.MaxValue;
Binding binding = new CustomBinding(element, encoding);
return binding;
}
You may have to adjust to your specific encoding type: TextMessageEncodingBindingElement, BinaryMessageEncodingBindingElement, or MtomMessageEncodingBindingElement.
I hope this helps anyone else who might waste the better part of a day debugging this code.
Ben