#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);
}
}
}