tags:

views:

2388

answers:

3

I am trying to obtain the data for JQGrid from a WCF Web service that is running from within my ASP.NET 2.0 WebForms application. The problem is that the WCF Web service expects the data to be formatted as a JSON string, and JQGrid is performing an HTTP Post and passing it as Content-Type: application/x-www-form-urlencoded.

Although there appears to be several options for the format of data returned to JQGrid (it accepts JSON, XML, and others), there doesn't seem to be a way to alter the way it passes inputs to the web service.

So I'm trying to figure out how to tweak the WCF service so that it will accept

Content-Type: application/x-www-form-urlencoded

rather than

Content-Type:"application/json; charset=utf-8"

When I did a test using JQuery to send an Ajax request using url encoding (shown here):

$.ajax({
    type: "POST",
    url: "../Services/DocLookups.svc/DoWork",
    data: 'FirstName=Howard&LastName=Pinsley',
    contentType: "Content-Type: application/x-www-form-urlencoded",
    dataType: "json",
    success: function(msg) {
        alert(msg.d);
    }
});

the call fails. Using Fiddler to inspect the traffic, I found the error as returned by the server:

{"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."...

Note that this code DOES work due to the difference in encoding

$.ajax({
    type: "POST",
    url: "../Services/DocLookups.svc/DoWork",
    data: '{"FirstName":"Howard", "LastName":"Pinsley"}',
    contentType: "application/json; charset=utf-8",
    dataType: "json",
    success: function(msg) {
        alert(msg.d);
    }
});

On the server, the service looks like:

[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class DocLookups {
    // Add [WebGet] attribute to use HTTP GET

    [OperationContract]
    public string DoWork(string FirstName, string LastName) {
        return "Your name is " + LastName + ", " + FirstName;
    }
}

and my web.config contains:

<system.serviceModel>
  <behaviors>
   <endpointBehaviors>
    <behavior name="DocLookupsAspNetAjaxBehavior">
     <enableWebScript />
    </behavior>
   </endpointBehaviors>
  </behaviors>
  <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
  <services>
   <service name="DocLookups">
    <endpoint address="" behaviorConfiguration="DocLookupsAspNetAjaxBehavior"
     binding="webHttpBinding" contract="DocLookups" />
   </service>
  </services>
</system.serviceModel>

Thanks for any help!

+3  A: 

If you don't have control over your ajax call I would recommend creating and Interceptor to override the Content-Type Header.

public class ContentTypeOverrideInterceptor : RequestInterceptor
{
    public string ContentTypeOverride { get; set; }

    public ContentTypeOverrideInterceptor(string contentTypeOverride) : base(true)
    {
        this.ContentTypeOverride = contentTypeOverride;
    }

    public override void ProcessRequest(ref RequestContext requestContext)
    {
        if (requestContext == null || requestContext.RequestMessage == null)
        {
            return;
        }
        Message message = requestContext.RequestMessage;
        HttpRequestMessageProperty reqProp = (HttpRequestMessageProperty)message.Properties[HttpRequestMessageProperty.Name];
        reqProp.Headers["Content-Type"] = ContentTypeOverride;
    }
}

Then if you view your .svc file you'll see and AppServiceHostFactory class change it to include the interceptor

class AppServiceHostFactory : ServiceHostFactory
{
    protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
    {
        var host = new WebServiceHost2(serviceType, true, baseAddresses);
        host.Interceptors.Add(new ContentTypeOverrideInterceptor("application/json; charset=utf-8"));
        return host;
    }
}

That should do it for you.

Update

As mentioned in the comments the method above is for use with the WCF REST Starter Kit. If you are using just a regular WCF Service you will have to create a IOperationBehavior and attached it to your service. here is the code for the behavior attribute

public class WebContentTypeAttribute : Attribute, IOperationBehavior, IDispatchMessageFormatter
{
    private IDispatchMessageFormatter innerFormatter;
    public string ContentTypeOverride { get; set; }

    public WebContentTypeAttribute(string contentTypeOverride)
    {
        this.ContentTypeOverride = contentTypeOverride;
    }


    // IOperationBehavior
    public void Validate(OperationDescription operationDescription)
    {

    }

    public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
    {
        innerFormatter = dispatchOperation.Formatter;
        dispatchOperation.Formatter = this;
    }

    public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
    {

    }

    public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
    {

    }

    // IDispatchMessageFormatter
    public void DeserializeRequest(Message message, object[] parameters)
    {
        if (message == null)
            return;

        if (string.IsNullOrEmpty(ContentTypeOverride))
            return;

        var httpRequest = (HttpRequestMessageProperty)message.Properties[HttpRequestMessageProperty.Name];
        httpRequest.Headers["Content-Type"] = ContentTypeOverride;
    }

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

And you would have to modify your Service contract to look like this one

[OperationContract]
[WebContentType("application/json; charset=utf-8")]
public string DoWork(string FirstName, string LastName)
{
    return "Your name is " + LastName + ", " + FirstName;
}

Links

As you requested here are some links describing these WCF Extensions

bendewey
BTW. this interceptor could be better and is loosely based on the XHttpMethodOverrideInterceptor from the WCF REST Starter Kit samples
bendewey
I don't see any code in the .svc file and the code behind has no factory class mentioned. Also, I'm unclear -- are you changing the content type of the incoming request on the fly (e.g. from url-encoding to JSon?) And if so, what is converting the passed parameters from one format to the other.
Decker
Are you using the WCF REST Starter Kit or just a WCF Service?
bendewey
What are doing here is Intercepting the message before it reaches the encoder and overriding the content type thats there.
bendewey
Thanks for all the help. No, I'm not using the WCF REST Starter Kit. Perhaps I should look into that since I'm unfamiliar with it and its capabilities. I'll look into your answer as well. Can you point me to where this technique is discussed more fully? Thanks again!
Decker
Ignore the REST Starter kit for now and focus on the update of my answer. This is what we call WCF Extensibility, I'm adding a couple links at the bottom that discuss IOperationBehavior. There not real sample out for this in particular, because I just wrote this from scratch.
bendewey
Thanks, that worked for me with the same problem. One addition, you need to make sure the WebInvoke attribute on the method is setup to expect the message type to be json. [WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.Wrapped, ResponseFormat = WebMessageFormat.Json)]
Col
A: 

I had the same problem and found the solution here: [http://techangle.blogspot.com/2009/03/mission-jqgrid-is-complete.html][1]

It works great.

A: 

If you have problems getting jqGrid to work with ASP.NET, please have a look here. This should save you a lot of time.

Ron Harlev