1

I have the follow bit of Javascript:

 $.ajax({
    url: "/Web/WebServices/Operation.svc/SetScope",
    type: "POST",
    contentType: "application/json; charset=utf-8",
    beforeSend: function () {
    },
    dataType: "json",
    cache: false,
    data: JSON.stringify({
        Id: Id,
        RIds: RIds,
        rScope: rScope 
    }),
    success: function (data) { onSuccess(data); },
    error: function (data) { onError(data); }
    });

If you look at the data bit --

  • Id is an int
  • RIds is array of ints
  • rScope is an array of objects each containing an int and a string

The result of

data: JSON.stringify({
            Id: Id,
            RIds: RIds,
            rScope: rScope 
        })

is

'{"Id":1,"rIds":[1,2,3,4],"rScope":[{"id":3,"type":"barney"},{"id":2,"type":"ted"}]}'

Passing it into $.parseJSON returns the expected object.

The problem is that it returns 400 Bad Request.

The signature waiting for the call is in WCF hosted in IIS

public OperationResult SetScope(int rId, string rIds, string rScope)

If I remove rScope from the code, everything is fine, but it seems to be having trouble with an array populated with objects as opposed to just primitives.

What am I missing here?

I found similar questions but none that really explained my scenario.

1
  • 1
    I guess you already answered your own question, you are passing an array of objects when the expected type is string. Try converting your rScope parameter to a string before stringifying the data set. Commented Nov 26, 2012 at 23:54

2 Answers 2

1

If I'm not mistaken the result of your parsing implies that rScope is an object array with each object having an id and a type property and not just a simply string.

"rScope":[{"id":3,"type":"barney"},{"id":2,"nodetype":"ted"}]

If you have a class similar to this:

public class ScopeClass{
    public int Id {get; set;}
    public string Type {get; set;}
}

And use it in your method like this:

public OperationResult SetScope(int rId, string rIds, IList<ScopeClass> rScope)

Edit

Actually, I just noticed you also have the second object named nodetype instead of type.

If you use the above aproach you need to make sure in your data you pass up the nodetype is also named type, otherwise you can't match the values.

Also, using the property name Type might not be a good idea due to C#'s type of Type. If you have any influence you can update Type to something more meaningfull as in GroupType or what ever it represents and then name it the same in the data passed up. The model binder should then simply magic-match it.

Sign up to request clarification or add additional context in comments.

4 Comments

It is but it's stringified is it not? Let me give it a shot.
@Matt: I edited the answer as I only noticed now that the rScope implies an array. You do have an issue whereby the array of objects is not an array of same-type object. As the first one has id and type but the second one has id and ``notetype`. The HTML model binder would interpret this as an array as far as I know and not a simple string.
oh, they should both be 'type' I was trying to simplify the string and missed it.
I guess I just wanted the nested array serialized to a string so I could use JSON.NET to send it to an anonymous type. What you said makes sense though. I'll go down that route.
1

Your operation is expecting a string for the rIds parameter, but you're passing an array to it. There are some conversions which happen automatically, but only very simple ones (such as numbers to string). Also, rScope is expecting a string, but you're passing an object to it.

There are a few things you can do. The first would be to pass the data as a string instead of as their "normal" type - that means stringifying both the RIds and rScope parameters:

var data = JSON.stringify({
    Id: Id,
    rIds: JSON.stringify(RIds),
    rScope: JSON.stringify(rScope)
});
$.ajax({
   url: "/Web/WebServices/Operation.svc/SetScope",
   type: "POST",
   contentType: "application/json; charset=utf-8",
   beforeSend: function () { },
   dataType: "json",
   cache: false,
   data: data,
   success: function (data) { onSuccess(data); },
   error: function (data) { onError(data); }
});

Another alternative would be in line with what François Wahl mentioned, which would be make the types which would receive the data you're sending. You'll need to do that both for the rIds and for the rScope parameters:

public class StackOverflow_13575100
{
    [ServiceContract]
    public class Service
    {
        [WebInvoke(BodyStyle = WebMessageBodyStyle.Wrapped)]
        public string SetScope(int rId, string rIds, string rScope)
        {
            return string.Format("{0} - {1} - {2}", rId, rIds, rScope);
        }
        [WebInvoke(BodyStyle = WebMessageBodyStyle.Wrapped, ResponseFormat = WebMessageFormat.Json)]
        public string SetScopeTyped(int rId, int[] rIds, ScopeClass[] rScope)
        {
            return string.Format("{0} - {1} - {2}",
                rId,
                "[" + string.Join(", ", rIds) + "]",
                "[" + string.Join(", ", rScope.Select(s => s.ToString())) + "]");
        }
    }
    [DataContract]
    public class ScopeClass
    {
        [DataMember(Name = "id")]
        public int Id { get; set; }
        [DataMember(Name = "type")]
        public string Type { get; set; }

        public override string ToString()
        {
            return string.Format("Scope[Id={0},Type={1}]", Id, Type);
        }
    }
    public static void Test()
    {
        string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
        WebServiceHost host = new WebServiceHost(typeof(Service), new Uri(baseAddress));
        host.Open();
        Console.WriteLine("Host opened");

        WebClient c = new WebClient();
        c.Headers[HttpRequestHeader.ContentType] = "application/json";
        string data = @"{""Id"":1,""rIds"":[1,2,3,4],""rScope"":[{""id"":3,""type"":""barney""},{""id"":2,""type"":""ted""}]}";
        Console.WriteLine(data);
        try
        {
            Console.WriteLine(c.UploadString(baseAddress + "/SetScope", data));
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
        }

        c.Headers[HttpRequestHeader.ContentType] = "application/json";
        Console.WriteLine(c.UploadString(baseAddress + "/SetScopeTyped", data));

        Console.Write("Press ENTER to close the host");
        Console.ReadLine();
        host.Close();
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.