tisdag 29 april 2008

Returning true JSON from WCF services


In a current project I desperately wanted to use jQuery in the frontend for doing some nice Ajax-stuff. I don't really care for Microsoft's implementation of ASP.NET AJAX, I've used it in some earlier projects. Anyway, this time my goal was to use the jQuery $.getJSON - function to pull data from the server.


At first, I just did an aspx-page and did an output with a ToJSON-extension (see Scott Guthrie's blogpost). No sin in that ofc, but not very elegant or practical, especially if you would like to send in some parameters. The elegant solution would be to use WCF services. With some nifty attributes and some additional parameters in web.config, it worked. This method was discovered using trail-and-error, and some Googling. I have not studied WCF in depth, I just barely learn what I need to, so there may very well be some better way to do it, I just haven't been able to find it yet.

This is how I did it using Visual Studio 2008:



  1. Create an ASP.NET Web Application (would surely work with ASP.NET Web Site too, but I prefer the Web Application)

  2. Add a new item, a WCF Service (not "Ajax-enabled WCF-service, we'll look at it in the next article), lets call it TrueJSON.

  3. You now have an WCF-service with .svc as extension, and that service implements an Interface. It's in the interface we'll set the attributes needed to return JSON.

  4. Create a class, MyClass as below. The attributes "DataContract" and "DataMember" must be defined for the class and the properties that would be returned through the WCF service. As you will see later on, so the WCF service will return everything marked as "DataMember", even if there is a private get.

    using System.Runtime.Serialization;
    namespace Alaz.DotNetDiscoveries.JSONWithWCF
    {
        [DataContract]
        public class MyClass
        {
            [DataMember]
            public int Id { get; set;}
            [DataMember]
            public string Name { get; set; }
     
            public string IHaveNoDataMemberAttribute { get; set;}
     
            [DataMember]
            public string NoPublicGet { private get; set; }
     
            [DataMember]
            internal ushort InternalProperty { get; set; }
        }
    }

  5. Now add a reference to System.ServiceModel.Web (this is where the WebGet-attribute is located).

  6. Change return type of the function DoWork() to MyClass, and add the WebGet-attribute as specified below. The BodyStyle Bare means "Both requests and responses are not wrapped" - we really want nothing more than a true JSON-response. The ResponseFormat should of course be "Json".
    using System.ServiceModel;
    using System.ServiceModel.Web;
     
    namespace Alaz.DotNetDiscoveries.JSONWithWCF
    {
        // NOTE: If you change the interface name "ITrueJSON" here, you must also update the reference to "ITrueJSON" in Web.config.
        [ServiceContract]
        public interface ITrueJSON
        {
            [OperationContract]
            [WebGet(ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Bare)]
            MyClass DoWork();
        }
    }


  7. Lets look at the class TrueJSON, change the return type of DoWork() to MyClass, and create something to return:
    namespace Alaz.DotNetDiscoveries.JSONWithWCF
    {
        // NOTE: If you change the class name "TrueJSON" here, you must also update the reference to "TrueJSON" in Web.config.
        public class TrueJSON : ITrueJSON
        {
            public MyClass DoWork()
            {
                var returnObject = new MyClass 
                    {
                        Id = 1, 
                        IHaveNoDataMemberAttribute = "Meaningless", 
                        Name = "Satchmo",
                        NoPublicGet = "Hello world!",
                        InternalProperty = 155
                    };
                return returnObject;
            }
        }
    }


  8. If you try to run the service and call it with url TrueJSON.svc/DoWork (we did actually with WebGet specified we wanted to use Get-calls) nothing happens, no response at all, not even "not found". We need to continue to web.config and make some adjustments.

  9. In web config, start with changing "wsHttpBinding" to "webHttpBinding" (we are not working with Web Services now), then add the whole <endpointBehaviors> section below with our self-named behavior "JsonBehavior". Now add the behaviorConfiguration="JsonBehavior" attribute to the service-tag.

      <system.serviceModel>
        <behaviors>
          <serviceBehaviors>
            <behavior name="Alaz.DotNetDiscoveries.JSONWithWCF.TrueJSONBehavior">
              <serviceMetadata httpGetEnabled="true"/>
              <serviceDebug includeExceptionDetailInFaults="false"/>
            </behavior>
          </serviceBehaviors>
          <endpointBehaviors>
            <behavior name="JsonBehavior">
              <webHttp />
            </behavior>
          </endpointBehaviors>
        </behaviors>
        <services>
          <service behaviorConfiguration="Alaz.DotNetDiscoveries.JSONWithWCF.TrueJSONBehavior" name="Alaz.DotNetDiscoveries.JSONWithWCF.TrueJSON">
            <endpoint address="" binding="webHttpBinding" contract="Alaz.DotNetDiscoveries.JSONWithWCF.ITrueJSON" behaviorConfiguration="JsonBehavior">
              <identity>
                <dns value="localhost"/>
              </identity>
            </endpoint>
            <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
          </service>
        </services>
      </system.serviceModel>


  10. Ok, time to try it out, start the project and point the url to TrueJSON.svc/DoWork. Save the file (add .txt for convenience) then open in notepad. The result reads: {"Id":1,"InternalProperty":155,"Name":"Satchmo","NoPublicGet":"Hello world!"} and all is well! If you examine the headers, you will see: Content-Type: application/json; charset=utf-8 and that is really good to.

  11. By default, WCF services lives outside the ASP.NET pipeline, so without more configuration, you have no HttpContext.Current, authorization, impersonation and so forth. To enable it, look at the article "Enabling HttpContext in WCF Services".


That was one method to do it, an alternative way to make WCF Service return true JSON is described in "Return JSON from Ajax-enabled WCF Service".