Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can a WCF OperationContract method's WebGet attribute have multiple ResponseFormat types?

Tags:

.net

wcf

I have a ServiceContract describing a method used in a WCF service. The method has a WebGet attribute which defines a UriTemplate and ResponseFormat.

I want to reuse a single method and have multiple WebGet attributes with different UriTemplates and different ResponseFormats. Basically I'm hoping to avoid having multiple methods just to differentiate things like return type being XML vs. JSON. In all of the examples I've seen so far I am required to create a different method for each WebGet attribute though. Here's a sample OperationContract

[ServiceContract]
public interface ICatalogService
{
    [OperationContract]
    [WebGet(UriTemplate = "product/{id}/details?format=xml", ResponseFormat = WebMessageFormat.Xml)]
    Product GetProduct(string id);

    [OperationContract]
    [WebGet(UriTemplate = "product/{id}/details?format=json", ResponseFormat = WebMessageFormat.Json)]
    Product GetJsonProduct(string id);
}

Using the example above I'd like to use the GetProduct method for both the xml and json return types like this:

[ServiceContract]
public interface ICatalogService
{
    [OperationContract]
    [WebGet(UriTemplate = "product/{id}/details?format=xml", ResponseFormat = WebMessageFormat.Xml)]
    [WebGet(UriTemplate = "product/{id}/details?format=json", ResponseFormat = WebMessageFormat.Json)]
    Product GetProduct(string id);
}

Is there a way to achieve this so I'm not stuck writing different methods just to return different ResponseFormats?

Thanks!

like image 843
Ian Suttle Avatar asked Nov 17 '09 05:11

Ian Suttle


2 Answers

You can do this

[ServiceContract]
public interface ICatalogService
{
    [OperationContract]
    [WebGet(UriTemplate = "product/{id}/details?format={format}")]
    Stream GetProduct(string id, string format);
}

And then in your code handle serialization based off the value specified on the parameter.

For XML write a helper method that handles your serialization.

public static Stream GetServiceStream(string format, string callback, DataTable dt, SyndicationFeed sf)
        {
            MemoryStream stream = new MemoryStream();
            StreamWriter writer = new StreamWriter(stream, Encoding.UTF8);
            if (format == "xml")
            {
                XmlSerializer xmls = new XmlSerializer(typeof(DataTable));
                xmls.Serialize(writer, dt);
                WebOperationContext.Current.OutgoingResponse.ContentType = "text/xml";
            }
            else if (format == "json")
            {
                var toJSON = new JavaScriptSerializer();
                toJSON.RegisterConverters(new JavaScriptConverter[] { new JavaScriptDataTableConverter() });
                writer.Write(toJSON.Serialize(dt));
                WebOperationContext.Current.OutgoingResponse.ContentType = "text/json";
            }
            else if (format == "jsonp")
            {
                var toJSON = new JavaScriptSerializer();
                toJSON.RegisterConverters(new JavaScriptConverter[] { new JavaScriptDataTableConverter() });
                writer.Write(callback + "( " + toJSON.Serialize(dt) + " );");
                WebOperationContext.Current.OutgoingResponse.ContentType = "text/json";
            }
            else if (format == "rss")
            {
                XmlWriter xmlw = new XmlTextWriter(writer);
                sf.SaveAsRss20(xmlw);
                WebOperationContext.Current.OutgoingResponse.ContentType = "text/xml";
            }
            else if (format == "atom")
            {
                XmlWriter xmlw = new XmlTextWriter(writer);
                sf.SaveAsAtom10(xmlw);
                WebOperationContext.Current.OutgoingResponse.ContentType = "text/xml";
            }
            else
            {
                writer.Write("Invalid formatting specified.");
                WebOperationContext.Current.OutgoingResponse.ContentType = "text/html";
            }

            writer.Flush();
            stream.Position = 0;
            return stream;
        }
}
like image 50
Danny G Avatar answered Oct 15 '22 19:10

Danny G


If I remember correctly, below method worked for me:

Contract for json service:

[ServiceContract]
public interface IServiceJson {
  [OperationContract()]
  [WebGet(UriTemplate = "Operation/?param={param}",
                         ResponseFormat = WebMessageFormat.Json)]
  ReturnType Operation(string param);
}

Contact for xml service:

[ServiceContract]
public interface IServiceXml {
  [OperationContract(Name = "OperationX")]
  [WebGet(UriTemplate = "Operation/?param={param}",
                         ResponseFormat = WebMessageFormat.Xml)]
  ReturnType Operation(string param);
}

Implementation for both:

public class ServiceImplementation : IServiceJson, IServiceXml {
  ReturnType Operation(string param) {
    // Implementation
  }
}

And web.config configuration (note endpoints for json and xml responses):

  <system.serviceModel>
    <behaviors>
      <endpointBehaviors>
        <behavior name="webHttp">
          <webHttp />
        </behavior>
      </endpointBehaviors>
      <serviceBehaviors>
        <behavior name="serviceBehaviour">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service behaviorConfiguration="serviceBehaviour" name="ServiceImplementation">
        <endpoint address="json/" behaviorConfiguration="webHttp" binding="webHttpBinding"
         bindingConfiguration="webHttpBindingSettings" contract="IServiceJson">
          <identity>
            <dns value="localhost" />
          </identity>
        </endpoint>
        <endpoint address="xml/" behaviorConfiguration="webHttp" binding="webHttpBinding"
         bindingConfiguration="webHttpBindingSettings" contract="IServiceXml">
          <identity>
            <dns value="localhost" />
          </identity>
        </endpoint>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
      </service>
    </services>
    <bindings>
      <webHttpBinding>
        <binding name="webHttpBindingSettings">
          <readerQuotas maxStringContentLength="5000000"/>
        </binding>
      </webHttpBinding>
    </bindings>
  </system.serviceModel>

Now you can call your service like this: json response: http://yourServer/json/Operation/?param=value xml response: http://yourServer/xml/Operation/?param=value

(Sorry if there are any bugs in code above, I didn't run it for verification).

like image 30
tomaszm Avatar answered Oct 15 '22 19:10

tomaszm