Jquery – send array via jQuery to Ajax-enabled WCF

asp.netasp.net-ajaxjquerywcf

my js code:

var a = ["asdfa", "asdfa", "aaa"];
var data = [];
for (var i in a) data.push({ name: 'keys', value: a[i] });
$.post('<%=ResolveUrl("~/svc/aja.svc/GetMultiple") %>', $.param(data), function(d) {
//do stuff
});

my ajax enabled wcf

[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class Aja
{
    [WebInvoke(Method="POST")]
    [OperationContract]
    public IEnumerable<IdContent> GetMultiple(string[] keys)
    {
        return keys.Select(o => new IdContent { Id = o, Content = o + o });
    }

I tried debugging and the Method GetMultiple doesn't get hit,(I get error 500)

if I do this by sending a simple string not array, than it works

this the message that I get as a result in firebug :

{"ExceptionDetail":{"HelpLink":null,"InnerException":null,"Message":"The
incoming message has an unexpected message format 'Raw'. The expected
message formats for the operation are 'Xml'; 'Json'. This can be
because a WebContentTypeMapper has not been configured on the binding.
See the documentation of WebContentTypeMapper for more
details.","StackTrace":" at
System.ServiceModel.Dispatcher.DemultiplexingDispatchMessageFormatter.DeserializeRequest(Message
message, Object[] parameters)\u000d\u000a at
System.ServiceModel.Dispatcher.UriTemplateDispatchFormatter.DeserializeRequest(Message
message, Object[] parameters)\u000d\u000a at
System.ServiceModel.Dispatcher.CompositeDispatchFormatter.DeserializeRequest(Message
message, Object[] parameters)\u000d\u000a at
System.ServiceModel.Dispatcher.DispatchOperationRuntime.DeserializeInputs(MessageRpc&
rpc)\u000d\u000a at
System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc&
rpc)\u000d\u000a at
System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc&
rpc)\u000d\u000a at
System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage4(MessageRpc&
rpc)\u000d\u000a at
System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean
isOperationContextSet)","Type":"System.InvalidOperationException"},"ExceptionType":"System.InvalidOperationException","Message":"The
incoming message has an unexpected message format 'Raw'. The expected
message formats for the operation are 'Xml'; 'Json'. This can be
because a WebContentTypeMapper has not been configured on the binding.
See the documentation of WebContentTypeMapper for more
details.","StackTrace":" at
System.ServiceModel.Dispatcher.DemultiplexingDispatchMessageFormatter.DeserializeRequest(Message
message, Object[] parameters)\u000d\u000a at
System.ServiceModel.Dispatcher.UriTemplateDispatchFormatter.DeserializeRequest(Message
message, Object[] parameters)\u000d\u000a at
System.ServiceModel.Dispatcher.CompositeDispatchFormatter.DeserializeRequest(Message
message, Object[] parameters)\u000d\u000a at
System.ServiceModel.Dispatcher.DispatchOperationRuntime.DeserializeInputs(MessageRpc&
rpc)\u000d\u000a at
System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc&
rpc)\u000d\u000a at
System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc&
rpc)\u000d\u000a at
System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage4(MessageRpc&
rpc)\u000d\u000a at
System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean
isOperationContextSet)"}

Best Answer

The problem you're having: $.post sends data encoded in application/x-www-form-urlencoded, and this format is not supported natively by WCF. That's the issue.

Now for solutions: as often is the case with WCF, there are quite a few things you can do:

1) Change the client side to send some format which the service can understand. WCF understands JSON, so you can use a JSON stringifier (i.e., JSON.stringify from json2.js from Crockford's implementation). With this, you'd need to change the code to use $.ajax instead of the "shortcut" $.post:

$.ajax({
    type: "POST",
    url: '<%=ResolveUrl("~/svc/aja.svc/GetMultiple") %>',
    contentType: "application/json",
    data: JSON.stringify({ keys: a }),
    success: function (result) {
        alert(result);
    }
});

2) "Teach" WCF how to understand the native format for the $.params (or $.post) calls. There are two ways to do that:

2.1) Do it "by hand". It's not very trivial, but it can be done. You'll need an IDispatchMessageFormatter which can convert from the forms/encoded into the string[] parameter for your operation. You'd then need a behavior to hook this new format up and either a new service host factory (to hook the behavior via code) or a behavior config extension (if you want to do it in config). The following links are for posts explaining in more details the message formatters, the endpoint behaviors, the service host factory and the behavior config extensions. The code below shows one possible implementation for a dispatch formatter which knows how to deal with string arrays.

public class AjaServiceHostFactory : ServiceHostFactory
{
    protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
    {
        return new AjaServiceHost(serviceType, baseAddresses);
    }

    public class AjaServiceHost : ServiceHost
    {
        public AjaServiceHost(Type serviceType, Uri[] baseAddresses)
            : base(serviceType, baseAddresses) { }

        protected override void OnOpening()
        {
            base.OnOpening();
            ServiceEndpoint endpoint = this.AddServiceEndpoint(typeof(Aja), new WebHttpBinding(), "");
            endpoint.Behaviors.Add(new WebScriptEnablingBehavior());
            endpoint.Behaviors.Add(new MyFormsUrlEncodedAwareBehavior());
        }
    }
}
class MyFormsUrlEncodedAwareBehavior : IEndpointBehavior
{
    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
    }

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

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
        foreach (var operation in endpoint.Contract.Operations)
        {
            var dispatchOperation = endpointDispatcher.DispatchRuntime.Operations[operation.Name];
            dispatchOperation.Formatter = new MyFormsUrlEncodedAwareFormatter(operation, dispatchOperation.Formatter);
        }
    }

    public void Validate(ServiceEndpoint endpoint)
    {
    }
}
class MyFormsUrlEncodedAwareFormatter : IDispatchMessageFormatter
{
    private OperationDescription operationDescription;
    private IDispatchMessageFormatter originalFormatter;

    public MyFormsUrlEncodedAwareFormatter(OperationDescription operationDescription, IDispatchMessageFormatter originalFormatter)
    {
        this.operationDescription = operationDescription;
        this.originalFormatter = originalFormatter;
    }

    public void DeserializeRequest(Message message, object[] parameters)
    {
        if (message.Properties.ContainsKey(WebBodyFormatMessageProperty.Name))
        {
            var bodyFormat = (WebBodyFormatMessageProperty)message.Properties[WebBodyFormatMessageProperty.Name];
            if (bodyFormat.Format == WebContentFormat.Raw)
            {
                if (message.Properties.ContainsKey(HttpRequestMessageProperty.Name))
                {
                    var httpReq = (HttpRequestMessageProperty)message.Properties[HttpRequestMessageProperty.Name];
                    if (httpReq.Headers[HttpRequestHeader.ContentType] == "application/x-www-form-urlencoded")
                    {
                        var requestBodyParts = operationDescription.Messages[0].Body.Parts;
                        if (requestBodyParts.Count == 1 && requestBodyParts[0].Type == typeof(string[]))
                        {
                            string body = GetRawMessageBodyAsString(message);
                            NameValueCollection pairs = HttpUtility.ParseQueryString(body);
                            parameters[0] = pairs.GetValues(requestBodyParts[0].Name);
                            return;
                        }
                    }
                }
            }
        }

        this.originalFormatter.DeserializeRequest(message, parameters);
    }

    private string GetRawMessageBodyAsString(Message message)
    {
        XmlDictionaryReader reader = message.GetReaderAtBodyContents();
        reader.ReadStartElement("Binary");
        byte[] bytes = reader.ReadContentAsBase64();
        return Encoding.UTF8.GetString(bytes);
    }

    public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result)
    {
        return this.originalFormatter.SerializeReply(messageVersion, parameters, result);
    }
}

2.2) Use the new "jQuery support for WCF" which is published on http://wcf.codeplex.com. Those are some behaviors / formatters which add support for forms/urlencoded requests. But you'll need to change the operation to receive the data in an untyped way (i.e., as a JsonArray), and in the code you'd enumerate the items yourself.

Related Topic