#define WANT_TRACE_FILE using System; using System.ServiceModel; using System.ServiceModel.Description; // if your original .svc file was: // <%@ ServiceHost Language="C#" Debug="true" Service="CommunicationService" CodeBehind="~/App_Code/CommunicationService.cs" %> // // then use this, to get the custom service host: // <%@ ServiceHost Language="C#" Debug="true" Factory="dinoch.wcf.fixup.EnforceXmlTypeServiceHostFactory" // Service="CommunicationService" CodeBehind="~/App_Code/CommunicationService.cs" %> // namespace dinoch.wcf.fixup { /// /// This custom ServiceHost modifies the ServiceDescription to use the "preferred" /// namespaces on message parts. The ServiceDescription is used by the WCF service /// in generating metadata (eg WSDL) and also in driving the serialization behavior. /// /// We do this because the default behavior in WCF when using the XmlSerializer format /// (via the XmlSerializerFormat attribute) is to NOT use the namespace specified in the /// XmlTypeAttribute, if such an attribute is attached to the data type. This means that /// WCF services will not accept the same input messages as ASMX Services, and will not /// generate the same response messages as ASMX services - they will differ by XML namespace. /// /// The way this works - on opening the service, which happens before WSDL is generated and /// before any messages and dispatched to methods, this ServiceHost modifies the ServiceDescription /// of the service. It examines message parts, looks for XmlTypeAttributes attached to the /// types associated to those message parts, and then compares the xml namespace used in the message /// part to the xml namespace specified in the XmlTypeAttribute. If they namespaces differ, then /// this ServiceHost overrides the default namespace, and uses the namespace from the XmlTypeAttribute /// in its stead. /// /// Because this ServiceHost modifies the ServiceDescription before starting up the service, at runtime, /// this technique has the same effect as applying an (imaginary) attribute /// to the service implementation code; the metadata (WSDL) generated from this service /// will indicate the desired namespace for the message parts. /// /// This ServiceHost has no effect on service operations that do not use the XmlSerializer. /// /// There is one catch: running svcutil.exe over the assembly /// (as opposed to the service itself, hosted and running within IIS) will not /// detect the proper namespaces. /// /// To use this service host, use something like this in your .svc file: /// <%@ ServiceHost /// Language="C#" /// Debug="true" /// Factory="dinoch.wcf.fixup.EnforceXmlTypeServiceHostFactory" /// Service="CommunicationService" /// CodeBehind="~/App_Code/CommunicationService.cs" /// %> /// /// For some additional background, see http://msdn2.microsoft.com/en-us/library/Aa395224.aspx /// /// public class EnforceXmlTypeServiceHost : ServiceHost { public EnforceXmlTypeServiceHost(Type t, params Uri[] baseAddresses) : base(t, baseAddresses) { } public EnforceXmlTypeServiceHost(object singletonInstance, params Uri[] baseAddresses) : base(singletonInstance, baseAddresses) { } public EnforceXmlTypeServiceHost() : base() { } protected override void OnOpening() { TraceMe(""); TraceMe(""); ServiceEndpointCollection sec = this.Description.Endpoints; foreach (ServiceEndpoint se in sec) { if (se.Address.ToString().EndsWith(".svc")) { TraceMe("Endpoint: "); TraceMe(" Address: {0}", se.Address.ToString()); TraceMe(" Contract: {0}", se.Contract.ToString()); foreach (OperationDescription opDesc in se.Contract.Operations) { TraceMe(" operation name: {0}", opDesc.Name); // Are we using XmlSerializer? if (opDesc.Behaviors.Find() != null) { TraceMe(" Uses the XmlSerializer"); foreach (MessageDescription messDesc in opDesc.Messages) { TraceMe(" Message: {0}", messDesc.Body.WrapperName); TraceMe(" namespace: {0}", messDesc.Body.WrapperNamespace); for (int i = 0; i < messDesc.Body.Parts.Count; i++) { MessagePartDescription part = messDesc.Body.Parts[i]; TraceMe(" Part: {0}", part.Name); TraceMe(" namespace: {0}", part.Namespace); System.Reflection.MemberInfo info = (System.Reflection.MemberInfo)part.Type; System.Xml.Serialization.XmlTypeAttribute[] attributes = (System.Xml.Serialization.XmlTypeAttribute[])info.GetCustomAttributes(typeof(System.Xml.Serialization.XmlTypeAttribute), true); if ((attributes!= null) && (attributes.Length > 0)) // check to see if the xml namespace specified to the XmlSerializer via // the XmlTypeAttribute attribute, disagrees with the xml namespace used // in the WCF message description. if (attributes[0].Namespace != part.Namespace) { // the namespaces do not agree. we need to swap them. //messDesc.Body.Parts[i].Namespace = attributes[0].Namespace; // this won't work, the property is readonly TraceMe(" ==>Swapping namespaces"); TraceMe(" old: {0}", part.Namespace); TraceMe(" new: {0}", attributes[0].Namespace); // Duplicate the old part description, except for the XML namespace MessagePartDescription newPart = new MessagePartDescription(part.Name, attributes[0].Namespace); newPart.Index = part.Index; newPart.MemberInfo = part.MemberInfo; newPart.Multiple = part.Multiple; newPart.ProtectionLevel = part.ProtectionLevel; newPart.Type = part.Type; // Replace the old part with the new one messDesc.Body.Parts[i] = newPart; } } } } } } } base.OnOpening(); } private string TraceFilePath = String.Format("c:\\temp\\EnforceXmlTypeServiceHost.Log.txt"); private void TraceMe(string s) { #if WANT_TRACE_FILE using (System.IO.StreamWriter sw = System.IO.File.AppendText(TraceFilePath)) { if (s != "") sw.Write(DateTime.Now.ToString("dd MMM yyyy HH:mm:ss - ")); sw.WriteLine(s); } #endif } private void TraceMe(string format, params Object[] values) { #if WANT_TRACE_FILE String s = String.Format(format, values); TraceMe(s); #endif } } public class EnforceXmlTypeServiceHostFactory : System.ServiceModel.Activation.ServiceHostFactory { protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses) { return new EnforceXmlTypeServiceHost(serviceType, baseAddresses); } } }