TiagoM
TiagoM

Reputation: 3526

Deserializing JSON in REST Service

I am having a problem deserializing json in my rest self-hosted service.

I have a test page that invokes the self-hosted REST with JSON, here is the code:

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <script type="text/javascript">
        function doFunction() {

            xhr = new XMLHttpRequest();
            var url = "https://localhost:1234/business/test/testing2/endpoint";
            xhr.open("POST", url, true);
            xhr.setRequestHeader("Content-type", "application/json");
            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4 && xhr.status == 200) {
                    var json = JSON.parse(xhr.responseText);
                    alert(json);
                }
            }                            
            var data = JSON.stringify({ testing : "test" });
            xhr.send(data);
        }

    </script>
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <input id="clickMe" type="button" value="clickme" onclick="doFunction();" />
    </div>
    </form>

</body>
</html>

And here is the interface and contracts of my self-hosted service:

[DataContract]
    public class OperationInput
    {
        [DataMember]
        public string testing { get; set; }

    }

    [DataContract]
    public class OperationOutput
    {
        [DataMember]
        public int Status { get; set; }

        [DataMember]
        public string Message { get; set; }

        [DataMember]
        public string AddInfo { get; set; }

        [DataMember]
        public string PartnerID { get; set; }

        [DataMember]
        public string SessionID { get; set; }
    }

    [ServiceContract]
    interface IRegisterOperation
    {
        [OperationContract]
        [WebInvoke(UriTemplate = "/endpoint",
            RequestFormat = WebMessageFormat.Json,
            ResponseFormat = WebMessageFormat.Json, Method = "*")]
        OperationOutput Operation(OperationInput order);
    }

And here is the implementation of the interface:

public class RegisterOperation : IRegisterOperation
    {

        public OperationOutput Operation(OperationInput input)
        {
            System.IO.StreamWriter file = new System.IO.StreamWriter("c:\\testing.txt", false);
            file.WriteLine(input.testing);
            file.Close(); 

            OperationOutput output = new OperationOutput();
            output.Status = 200;
            output.Message = "The action has been successfully recorded on NAVe";
            output.AddInfo = "";


            return output;
        }
    }

I am creating the self-host using this code:

host = new ServiceHost(implementationType, baseAddress);

                ServiceEndpoint se = host.AddServiceEndpoint(endpointType, new WebHttpBinding(WebHttpSecurityMode.Transport), "");
                se.Behaviors.Add(new WebHttpBehavior());

                host.Open();

Using debug I notice that it hits the breakpoint inside my service, so the call to localhost is working but the parameter of input is null, as you can see in the image below:

debugging on visual studio

Here is 2 images capturing the POST Request with JSON on fiddler:

fiddler first image fiddler second image

Have you any idea why I am getting null ? instead of the string "test" as I do on the invocation in javascript?

Thank you very much in advance ;)

EDIT:

I activated HTTPS Decryption on Fiddler and now pressed "Yes" to install certificate on trusted ca instead of pressin "no", and the breakpoint is hitted, and fiddler now captured an options request as the following images represent

Options request Raw tab of Options request JSON tab of Options request

Shouldn't this be a post request instead of a options request?? maybe that's why I don't see the json?

Thanks a lot

Upvotes: 4

Views: 1024

Answers (3)

TiagoM
TiagoM

Reputation: 3526

I figured it out, the problem was the OPTIONS Request, I need to receive a POST Request so I get the JSON.

I added a behaviour attribute to my service host so that it responds to options request allowing the wcf service host to receive cross origin requests.

So I added the code from the answer of this (Cross Origin Resource Sharing for c# WCF Restful web service hosted as Windows service) question and now I get a POST Request after the first Options request:

If the link is no longer available, here is the solution of the question:

CODE:

Create 2 classes as follows:

  1. MessageInspector implementing IDispatchMessageInspector.
  2. BehaviorAttribute implementing Attribute, IEndpointBehavior and IOperationBehavior.

With the following details:

//MessageInspector Class
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Description;
namespace myCorsService
{
  public class MessageInspector  : IDispatchMessageInspector
  {
    private ServiceEndpoint _serviceEndpoint;

    public MessageInspector(ServiceEndpoint serviceEndpoint)
    {
      _serviceEndpoint = serviceEndpoint;
    }

    /// <summary>
    /// Called when an inbound message been received
    /// </summary>
    /// <param name="request">The request message.</param>
    /// <param name="channel">The incoming channel.</param>
    /// <param name="instanceContext">The current service instance.</param>
    /// <returns>
    /// The object used to correlate stateMsg. 
    /// This object is passed back in the method.
    /// </returns>
    public object AfterReceiveRequest(ref Message request, 
                                          IClientChannel channel, 
                                          InstanceContext instanceContext)
    {
      StateMessage stateMsg = null;
      HttpRequestMessageProperty requestProperty = null;
      if (request.Properties.ContainsKey(HttpRequestMessageProperty.Name))
      {
        requestProperty = request.Properties[HttpRequestMessageProperty.Name]
                          as HttpRequestMessageProperty;
      }

      if (requestProperty != null)
      {
        var origin = requestProperty.Headers["Origin"];
        if (!string.IsNullOrEmpty(origin))
        {
          stateMsg = new StateMessage();
          // if a cors options request (preflight) is detected, 
          // we create our own reply message and don't invoke any 
          // operation at all.
          if (requestProperty.Method == "OPTIONS")
          {
            stateMsg.Message = Message.CreateMessage(request.Version, null);
          }
          request.Properties.Add("CrossOriginResourceSharingState", stateMsg);
        }
      }

      return stateMsg;
    }

    /// <summary>
    /// Called after the operation has returned but before the reply message
    /// is sent.
    /// </summary>
    /// <param name="reply">The reply message. This value is null if the 
    /// operation is one way.</param>
    /// <param name="correlationState">The correlation object returned from
    ///  the method.</param>
    public void BeforeSendReply(ref  Message reply, object correlationState)
    {
      var stateMsg = correlationState as StateMessage;

      if (stateMsg != null)
      {
        if (stateMsg.Message != null)
        {
          reply = stateMsg.Message;
        }

        HttpResponseMessageProperty responseProperty = null;

        if (reply.Properties.ContainsKey(HttpResponseMessageProperty.Name))
        {
          responseProperty = reply.Properties[HttpResponseMessageProperty.Name]
                             as HttpResponseMessageProperty;
        }

        if (responseProperty == null)
        {
          responseProperty = new HttpResponseMessageProperty();
          reply.Properties.Add(HttpResponseMessageProperty.Name,
                               responseProperty);
        }

        // Access-Control-Allow-Origin should be added for all cors responses
        responseProperty.Headers.Set("Access-Control-Allow-Origin", "*");

        if (stateMsg.Message != null)
        {
          // the following headers should only be added for OPTIONS requests
          responseProperty.Headers.Set("Access-Control-Allow-Methods",
                                       "POST, OPTIONS, GET");
          responseProperty.Headers.Set("Access-Control-Allow-Headers",
                    "Content-Type, Accept, Authorization, x-requested-with");
        }
      }
    }
  }

  class StateMessage
  {
    public Message Message;
  }
}

//BehaviorAttribute Class
using System;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;

namespace OpenBetRetail.NFCReaderService
{
  public class BehaviorAttribute : Attribute, IEndpointBehavior,
                                 IOperationBehavior
  {        
    public void Validate(ServiceEndpoint endpoint) { }

    public void AddBindingParameters(ServiceEndpoint endpoint,
                             BindingParameterCollection bindingParameters) { }

    /// <summary>
    /// This service modify or extend the service across an endpoint.
    /// </summary>
    /// <param name="endpoint">The endpoint that exposes the contract.</param>
    /// <param name="endpointDispatcher">The endpoint dispatcher to be
    /// modified or extended.</param>
    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, 
                                      EndpointDispatcher endpointDispatcher)
    {
      // add inspector which detects cross origin requests
      endpointDispatcher.DispatchRuntime.MessageInspectors.Add(
                                             new MessageInspector(endpoint));
    }

   public void ApplyClientBehavior(ServiceEndpoint endpoint,
                                   ClientRuntime clientRuntime) { }

   public void Validate(OperationDescription operationDescription) { }

   public void ApplyDispatchBehavior(OperationDescription operationDescription,
                                     DispatchOperation dispatchOperation) { }

   public void ApplyClientBehavior(OperationDescription operationDescription,
                                   ClientOperation clientOperation) { }

   public void AddBindingParameters(OperationDescription operationDescription,
                             BindingParameterCollection bindingParameters) { }

  }
}

After this all you need to do is add this message inspector to service end point behavior.

ServiceHost host = new ServiceHost(typeof(myService), _baseAddress);
foreach (ServiceEndpoint EP in host.Description.Endpoints)
            EP.Behaviors.Add(new BehaviorAttribute());

Thanks for everyone help ;)

Upvotes: 2

user1075940
user1075940

Reputation: 1125

You actually pass primitive instead of expected object

var data = JSON.stringify({ ACTPRDX : "test" });

The above data would work for method :

   public XYZ Something(string ACTPRDX)

You should send object to your method like that

var obj= new Object();
obj.ACTPRDX = "test";
var data = JSON.stringify({ order: obj});

Also interface and implementation have different name for parameter OperationInput -> order and input.

Upvotes: 0

granadaCoder
granadaCoder

Reputation: 27904

You have Method = "*"

I would experiment with:

Method = "POST" ....

[ServiceContract]
interface IRegisterOperation
{
    OperationOutput Operation(OperationInput order);

like so:

[OperationContract]
[WebInvoke(UriTemplate = "/registeroperation",
       Method = "POST",
       ResponseFormat = WebMessageFormat.Json,
       BodyStyle = WebMessageBodyStyle.Bare)]
OperationOutput Operation(OperationInput order);

APPEND:

Your json does not look right (from the screen shots)

I would expect something simple like:

{
    "ACTPRDX": "test"
}

Can you do an "alert" right after you stringify the object? And show the results?

But (in general)...if your json is messed up, the "auto voodoo" of the Wcf Service Method won't work.

.....

This might be nit-picky, but try this:

Note the capital "T" after the hyphen.

xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");

I just found this in my code:

        var jsonObject = { ACTPRDX : "test" };
        var whatToSendOverTheWire = JSON.stringify(jsonObject);

Try that.

As mentioned, your json is wrong. the fix is to figure out how its being screwed up.

Upvotes: 1

Related Questions