fredag 2 maj 2008

Return JSON from Ajax-enabled WCF Service


This is another way to return JSON from WCF than I described in the previous article "Returning true JSON from WCF services".

As in that article, 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.



  1. In a web project, "Add new item.." and choose "Ajax-enabled WCF Service", let's call it "AjaxEnabled.svc"

  2. This service is made without an interface to specify the methods (i.e. no "IAjaxEnabled.cs" was created as it would have done if you choose to add a new WCF Service like in the previous article), and consists of the single method DoWork().

  3. Let's create something to return, add the "MyClass" from the other article and replace the method DoWork() from there too.

    Add the [WebGet] attribute too, since there doesn't seem to be a $.postJSON-function in jQuery.

    You should now have an AjaxEnabled.svc.cs that looks like:



    using System.ServiceModel;
    using System.ServiceModel.Activation;
    using System.ServiceModel.Web;
     
    namespace Alaz.DotNetDiscoveries.JSONWithWCF
    {
        [ServiceContract(Namespace = "")]
        [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
        public class AjaxEnabled
        {
            // Add [WebGet] attribute to use HTTP GET
            [OperationContract]
            [WebGet]
            public MyClass DoWork()
            {
                var returnObject = new MyClass
                {
                    Id = 1,
                    IHaveNoDataMemberAttribute = "Meaningless",
                    Name = "Satchmo",
                    NoPublicGet = "Hello world!",
                    InternalProperty = 155
                };
                return returnObject;
            }
     
            // Add more operations here and mark them with [OperationContract]
        }
    }

  4. Let's try it out, run the project and call the service with adding /DoWork to the AjaxEnabled.svc-url. You should've got:



    <MyClass xmlns="http://schemas.datacontract.org/2004/07/Alaz.DotNetDiscoveries.JSONWithWCF" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
      <Id>1</Id>
      <InternalProperty>155</InternalProperty>
      <Name>Satchmo</Name>
      <NoPublicGet>Hello world!</NoPublicGet>
    </MyClass>


    Not really what we've wanted, apparently MS Ajax implementation use xml to communicate. Time to fiddle with some settings then:

  5. Add the ResponseFormat-property to WebGet, and set it to Json (default seems to be xml):

            [WebGet(ResponseFormat=WebMessageFormat.Json)]


  6. Run again, inspect the response in notepad, and now we got some Json, but not really in the format we would like to:

    {"d":{"__type":"MyClass:#Alaz.DotNetDiscoveries.JSONWithWCF","Id":1,"InternalProperty":155,"Name":"Satchmo","NoPublicGet":"Hello world!"}}

    There seem's to be some envelope around it. Let's remove it:

  7. Set the BodyFormat-property to Bare, we do not wan't to have Wrapped messages, only bare Json:

            [WebGet(ResponseFormat=WebMessageFormat.Json,BodyStyle = WebMessageBodyStyle.Bare)]


  8. Run again, and you will get an error:

    Server Error in '/' Application.

    --------------------------------------------------------------------------------

    The body style 'Bare' is not supported by 'WebScriptEnablingBehavior'. Change the body style to be 'WrappedRequest'.

  9. We must change something in the web.config, look at this tag:



        <behavior name="Alaz.DotNetDiscoveries.JSONWithWCF.AjaxEnabledAspNetAjaxBehavior">
         <enableWebScript />

        </behavior>   




  10. Change "enableWebScript" to "webHttp" like this:
        <behavior name="Alaz.DotNetDiscoveries.JSONWithWCF.AjaxEnabledAspNetAjaxBehavior">
         <webHttp />
        </behavior>

    The behavior is connected to:
       <service name="Alaz.DotNetDiscoveries.JSONWithWCF.AjaxEnabled">
        <endpoint address="" behaviorConfiguration="Alaz.DotNetDiscoveries.JSONWithWCF.AjaxEnabledAspNetAjaxBehavior"
         binding="webHttpBinding" contract="Alaz.DotNetDiscoveries.JSONWithWCF.AjaxEnabled" />
       </service>


  11. Run again, and now you got the correct Json-string {"Id":1,"InternalProperty":155,"Name":"Satchmo","NoPublicGet":"Hello world!"}.

  12. One more thing worth mention is the AspNetCompatibilityRequirements-attribute to the service-class:

        [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]

    If you want to access the current HttpContext, using ASP.NET's file/url authorization and that normal ASP.NET-stuff you must use either AspNetCompatibilityRequirementsMode.Allowed as in this example, or AspNetCompatibilityRequirementsMode.Required. My advice is to use Required if you need any of the ASP.NET-features, then the service throws an error if it is not enabled. You must enable this in the web.config (however, the "Add new Ajax-enabled WCF service" seems to add it automagically):



      <system.serviceModel>
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
    If "Allowed" you can run it even if web.config says "false", and you could have a hard time finding out why HttpContext.Current is null.


3 kommentarer:

Anonym sa...

Thanks! I been looking all over and it turns out all i was missing was the setting.
With WCF configuration will kill you...

Anonym sa...

Thank you, this solved the issue for me :)

hue sa...

Satchmo, thanks for the post.

One thing to mention: calling the service from javascript ($.ajax, $.getJSON) you may need to add ("Access-Control-Allow-Origin", "*") on the server side:

[OperationContract]
[WebGet(ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Bare)]
public Echo echo(...)
{
WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Origin", "*");

Has taken me a while to figure it out... may be helpful for others.

Cheers,
hue