C# – Passing an array to WCF service via GET

cjquerywcf

I have an AJAX call that I want to run against a WCF GET service. Basically, the call to the service (via jquery) looks like this:

$.get(serviceEndpoint, {query : "some search text", statusTypes: [1, 2]}, function (result) { /* do something*/ }, 'text');

When this call gets run, I see the GET in firebug go through correctly, and I do hit the endpoint. However, the parameter statusTypes is always null.

The GET itself from jquery looks like it is encoded, but when I don't encode the brackets, the call won't enter the endpoint at all:

http://localhost/Services/SomeService.svc/Endpoint?statusTypes%5B%5D=1&statusTypes%5B%5D=2&query=some+search+text

And the WCF service itself:

[OperationContract]

[WebInvoke(Method= "GET", BodyStyle = WebMessageBodyStyle.WrappedRequest,
ResponseFormat =
WebMessageFormat.Json)]

public
ResultsViewModel
GetTags(string query, int[]
statusTypes)

Is it possible to pass an array via GET to a WCF service?

The permutations aren't numerous, so I could write an individual endpoint "per array", but I'd rather keep it in one.

Best Answer

It is possible, but not with the out-of-the-box WCF. With the "jQuery support" in the WCF codeplex page, you can receive all of the data sent by jQuery (including arrays, nested objects, etc) in an untyped variable, both on the body (for POST requests) and on the query string (for GET). The mapping between jQuery array variables (whose names contain '[' and ']') and operation parameters cannot be done in WCF 4.0 (at least not without writing a message formatter).

This should be simpler, however, on the new WCF Web APIs (also available on the codeplex site).

Update: this is an example of a formatter which works for your scenario:

public class StackOverflow_6445171
{
    [ServiceContract]
    public class Service
    {
        [WebGet(ResponseFormat = WebMessageFormat.Json)]
        public string GetLabelPacketTags(string query, int[] statusTypes)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("Query=" + query);
            sb.Append(", statusTypes=");
            if (statusTypes == null)
            {
                sb.Append("null");
            }
            else
            {
                sb.Append("[");
                for (int i = 0; i < statusTypes.Length; i++)
                {
                    if (i > 0) sb.Append(",");
                    sb.Append(statusTypes[i]);
                }
                sb.Append("]");
            }

            return sb.ToString();
        }
    }
    class MyWebHttpBehavior : WebHttpBehavior
    {
        protected override IDispatchMessageFormatter GetRequestDispatchFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint)
        {
            return new MyArrayAwareFormatter(operationDescription, this.GetQueryStringConverter(operationDescription));
        }

        class MyArrayAwareFormatter : IDispatchMessageFormatter
        {
            OperationDescription operation;
            QueryStringConverter queryStringConverter;
            public MyArrayAwareFormatter(OperationDescription operation, QueryStringConverter queryStringConverter)
            {
                this.operation = operation;
                this.queryStringConverter = queryStringConverter;
            }

            public void DeserializeRequest(Message message, object[] parameters)
            {
                if (message.Properties.ContainsKey("UriMatched") && (bool)message.Properties["UriMatched"])
                {
                    UriTemplateMatch match = message.Properties["UriTemplateMatchResults"] as UriTemplateMatch;
                    NameValueCollection queryValues = match.QueryParameters;
                    foreach (MessagePartDescription parameterDescr in this.operation.Messages[0].Body.Parts)
                    {
                        string parameterName = parameterDescr.Name;
                        int index = parameterDescr.Index;
                        if (parameterDescr.Type.IsArray)
                        {
                            Type elementType = parameterDescr.Type.GetElementType();
                            string[] values = queryValues.GetValues(parameterName + "[]");
                            Array array = Array.CreateInstance(elementType, values.Length);
                            for (int i = 0; i < values.Length; i++)
                            {
                                array.SetValue(this.queryStringConverter.ConvertStringToValue(values[i], elementType), i);
                            }
                            parameters[index] = array;
                        }
                        else
                        {
                            parameters[index] = this.queryStringConverter.ConvertStringToValue(queryValues.GetValues(parameterName)[0], parameterDescr.Type);
                        }
                    }
                }
            }

            public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result)
            {
                throw new NotSupportedException("This is a request-only formatter");
            }
        }
    }
    public static void Test()
    {
        string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
        ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
        host.AddServiceEndpoint(typeof(Service), new WebHttpBinding(), "").Behaviors.Add(new MyWebHttpBehavior());
        host.Open();
        Console.WriteLine("Host opened");

        WebClient c = new WebClient();
        Console.WriteLine(c.DownloadString(baseAddress + "/GetLabelPacketTags?query=some+text&statusTypes[]=1&statusTypes[]=2"));
        Console.WriteLine(c.DownloadString(baseAddress + "/GetLabelPacketTags?query=some+text&statusTypes%5B%5D=1&statusTypes%5B%5D=2"));

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