#define WANT_TRACE // // MagicContentTypeSelectingServiceHost.cs // // This service host automagically allows content-type selection by URL, for all WebGet // operations that do not have the ResponseFormat explicitly set (to WebMessageFormat.Xml or // WebMessageFormat.Json), and which are decorated with a special attribute // (ionic.samples.WcfRest.DynamicContentType). // // The host does this by cloning the OperationDescription for all operations in the service contract // that do not have an ResponseFormat explicitly specified, and then setting one of the clones // to WebMessageFormat.Xml and the other to WebMessageFormat.Json. Both clones point to the // same method in the service class, so there is no duplication of application code . // // Wed, 02 Apr 2008 15:51 // using System; using System.Collections.Generic; 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, to use this custom ServiceHost, specify this in your .svc file // <%@ ServiceHost Language="C#" Debug="true" // Factory="ionic.samples.WcfRest.MagicContentTypeSelectingServiceHostFactory" // Service="CommunicationService" // CodeBehind="~/App_Code/CommunicationService.cs" %> // namespace ionic.samples.WcfRest { public class MagicContentTypeSelectingServiceHost : ServiceHost { public MagicContentTypeSelectingServiceHost(Type t, params Uri[] baseAddrs) : base(t, baseAddrs) { } public MagicContentTypeSelectingServiceHost(object singletonInstance, params Uri[] baseAddresses) : base(singletonInstance, baseAddresses) { } public MagicContentTypeSelectingServiceHost() : base() { } protected override void OnOpening() { TraceMe(""); TraceMe(""); ServiceEndpointCollection sec = this.Description.Endpoints; foreach (ServiceEndpoint se in sec) { TraceMe("Endpoint: "); TraceMe(" Address: {0}", se.Address.ToString()); TraceMe(" Contract: {0}", se.Contract.ToString()); var opsToAdd= new List (); foreach (OperationDescription opDesc in se.Contract.Operations) { object[] attrs= opDesc.SyncMethod.GetCustomAttributes(typeof(ionic.samples.WcfRest.DynamicContentType), false); if ((attrs != null) && ( attrs.Length == 1)) { TraceMe(" operation: {0}", opDesc.Name); TraceMe(" Marked with {0} attribute", typeof(ionic.samples.WcfRest.DynamicContentType)); System.ServiceModel.Web.WebGetAttribute wga= opDesc.Behaviors.Find() ; if (wga != null) { if (wga.IsResponseFormatSetExplicitly) { throw new System.Exception ( String.Format("On method '{0}', there are conflicting attributes. When using the " + "custom service host {1}, on a method that is marked with {2}, the " + "ResponseFormat in the WebGet attribute must be omitted.", opDesc.Name, this.GetType().ToString(), typeof(ionic.samples.WcfRest.DynamicContentType))); } TraceMe(" Cloning this operation ..."); // Now, clone this OperationDescription. // We can copy references to all properties, except those we are changing. // The only thing that is changing is the WebGetAttribute, so // we must actually new up one of those. OperationDescription od= new OperationDescription(opDesc.Name+".clone", opDesc.DeclaringContract); string rootTemplate= wga.UriTemplate; System.ServiceModel.Web.WebGetAttribute wga2= null; foreach (System.ServiceModel.Description.IOperationBehavior b in opDesc.Behaviors) { if ((b as System.ServiceModel.Web.WebGetAttribute) != null) { wga2= new System.ServiceModel.Web.WebGetAttribute(); if (wga.IsBodyStyleSetExplicitly) wga2.BodyStyle = wga.BodyStyle; if (wga.IsRequestFormatSetExplicitly) wga2.RequestFormat = wga.RequestFormat; // Now, differentiate the two WebGetAttribute instances with the ResponseFormat. // The original OperationDescription gets XML, the clone gets JSON wga.ResponseFormat = System.ServiceModel.Web.WebMessageFormat.Xml; wga2.ResponseFormat = System.ServiceModel.Web.WebMessageFormat.Json; wga.UriTemplate = rootTemplate + "/xml"; wga2.UriTemplate = rootTemplate + "/json"; od.Behaviors.Add(wga2); } else od.Behaviors.Add(b); } foreach (System.ServiceModel.Description.MessageDescription md in opDesc.Messages) od.Messages.Add(md); foreach (System.ServiceModel.Description.FaultDescription fd in opDesc.Faults) od.Faults.Add(fd); od.SyncMethod= opDesc.SyncMethod; // remember to add this OperationDescription to the service contract opsToAdd.Add(od); } } } // add the cloned operation descriptions to the ServiceContract foreach (OperationDescription od in opsToAdd) se.Contract.Operations.Add(od); } base.OnOpening(); TraceMe(""); TraceMe(""); } #if WANT_TRACE // this is for tracing within IIS private string TraceFilePath = String.Format("c:\\temp\\MagicContentTypeSelectingServiceHost.Log.txt"); #endif private void TraceMe(string s) { #if WANT_TRACE // 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); // } // This is for tracing within a console app. if (s != "") Console.WriteLine("{0} - {1}", DateTime.Now.ToString("dd MMM yyyy HH:mm:ss"),s); else Console.WriteLine(s); #endif } private void TraceMe(string format, params Object[] values) { #if WANT_TRACE String s = String.Format(format, values); TraceMe(s); #endif } } public class MagicContentTypeSelectingServiceHostFactory : System.ServiceModel.Activation.ServiceHostFactory { protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses) { return new MagicContentTypeSelectingServiceHost(serviceType, baseAddresses); } } // the marker attribute [System.AttributeUsage( System.AttributeTargets.Method )] class DynamicContentType: System.Attribute { } }