Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Problem Calling WCF Service Library from jQuery

I have a WCF Service Library exposed through my ASPX site as follows

[System.ServiceModel.OperationContract]
[System.ServiceModel.Web.WebInvoke(
Method= "POST",
RequestFormat=System.ServiceModel.Web. WebMessageFormat .Json,
ResponseFormat=System.ServiceModel.Web.WebMessageFormat .Json)]
LogonResponse Logon(LogonRequest logonRequest);


[System.Runtime.Serialization.DataContract]
[ Serializable()]
public class LogonRequest
{
[System.Runtime.Serialization.DataMember]
public string EMailAddress;
[System.Runtime.Serialization.DataMember]
public string Password;
}

In My test page I can call either through MS Ajax : -

<asp:ScriptManager ID ="ScriptManager1" runat="server">
<Services>
<asp:ServiceReference Path="~/testService.svc" />
</Services>
</asp:ScriptManager>
.
.
.
function clsLogonRequest(eMailAddress, password) {
this .EMailAddress = eMailAddress;
this .Password = password;
}

function login(eMailAddress, password) {
var LogonRequest = new clsLogonRequest(eMailAddress, password);
name.API.IAPI.Logon(LogonRequest, onSuccess, onFailure);
}

function onSuccess(result) {
$( "#txtSessionId").val(result.SessionId);
$( "#txtUserName").val(result.Name);
$( "#txtUserId").val(result.UserId);
}

which works fine, or through a jQuery $.ajax call: -

$(document).ready(function() {
$( "#Button1").click(function() {
var LogonRequest = new clsLogonRequest( '*************' , '***********');
$.ajax({
type: "POST",
url: "testService.svc/Logon",
data: "{'logonRequest':" + JSON.stringify(LogonRequest) + "}" ,
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(msg) {
alert(msg.d);
}
});
});
});

Which does not - under FireBug I can see the 500 Internal Server Error message and the exception starts

{"ExceptionDetail":{"HelpLink":null,"InnerException":null,"Message":"The token '\"' was expected but found '''.","StackTrace":"   at System.Xml.XmlExceptionHelper.ThrowXmlException(XmlDictionaryReader reader, String res, String arg1, String arg2, String arg3)\u000d\u000a   at System.Xml.XmlExceptionHelper .ThrowTokenExpected(XmlDictionaryReader reader, String expected, Char found)\u000d\u000a   at System.Runtime...

Why does it appear that the jQuery call is passing XML when I'm specifically telling it not to (and how can I stop it so that calls from all sources are handled as naturally as possible)?

Thanks in advance.

EDIT:

Thanks for the suggestions, I've looked at what you said and think that my explanation of the problem was not clear enough.

The problem is not that JSON is not being sent, it is being sent and is in the correct format, I can see that from firebug. The problem is that the WCF Service Library appears to be expecting XML and falls over when it receives JSON.

Just to be sure I haven't overlooked the suggested solution, here is the web.Config extract - I've tried removing from the behaviour and removing the behaviorConfiguration attribute from the services/service/endpoint tag and that just makes the whole thing fail without getting out of the page.

<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name ="MyServiceTypeBehaviors ">
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceMetadata httpGetEnabled="true" />
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name ="AspNetAjaxBehavior">
<enableWebScript/>
</behavior>
</endpointBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled= "true " />
<services>
<service name ="test.API.API" behaviorConfiguration = " MyServiceTypeBehaviors">
<endpoint behaviorConfiguration="AspNetAjaxBehavior" binding = " webHttpBinding" contract="test.API.IAPI" />
<endpoint contract ="IMetadataExchange" binding= " mexHttpBinding" address="mex" />
</service>
</services>
</system.serviceModel>

Thanks In Advance

like image 922
bean Avatar asked Oct 22 '09 14:10

bean


1 Answers

I'm guessing you have applied the WebScriptEnablingBehavior (enableWebScript element in .config)? This causes all requests to be wrapped in a JSON object with a single field named "d" which then contains an object with your named parameters. So you would need to change the jQuery data to be:

data: "{d:{logonRequest:" + JSON.stringify(LogonRequest) + "}}"

Either that or remove the enableWebScript behavior, but, if you do that, you must apply the webHttp behavior instead. Now, unfortunately, the default encoding is then XML and the behavior does not offer any switches to control the encoding for an entire service (bad design by MSFT). So, either you must marry yourself to a sepcific encoding on each method by setting the Request/ResponseFormat properties, or, how I've handled this in the past, is that I've created an "EnhancedWebHttpElement" which applies the same WebHttpBehavior, but gives config level control over it's various properties. The benefit of using this configuration is that you can now expose your same WCF service via different endpoints using ASP.NET AJAX encoding on one, plain JSON on the other and maybe even POX on another.

Here's the code:

public sealed class EnhancedWebHttpElement : BehaviorExtensionElement
{
    #region Type specific properties

    [ConfigurationProperty("defaultBodyStyle", DefaultValue=WebMessageBodyStyle.Bare)]
    public WebMessageBodyStyle DefaultBodyStyle
    {
        get
        {
            return (WebMessageBodyStyle)this["defaultBodyStyle"];
        }

        set
        {
            this["defaultBodyStyle"] = value;
        }
    }

    [ConfigurationProperty("defaultOutgoingRequestFormat", DefaultValue=WebMessageFormat.Xml)]
    public WebMessageFormat DefaultOutgoingRequestFormat
    {
        get
        {
            return (WebMessageFormat)this["defaultOutgoingRequestFormat"];
        }

        set
        {
            this["defaultOutgoingRequestFormat"] = value;
        }
    }

    [ConfigurationProperty("defaultOutgoingResponseFormat", DefaultValue=WebMessageFormat.Xml)]
    public WebMessageFormat DefaultOutgoingResponseFormat
    {
        get
        {
            return (WebMessageFormat)this["defaultOutgoingResponseFormat"];
        }

        set
        {
            this["defaultOutgoingResponseFormat"] = value;
        }
    }       

    #endregion

    #region Base class overrides

    protected override object CreateBehavior()
    {
        WebHttpBehavior result = new WebHttpBehavior();

        result.DefaultBodyStyle = this.DefaultBodyStyle;
        result.DefaultOutgoingRequestFormat = this.DefaultOutgoingRequestFormat;
        result.DefaultOutgoingResponseFormat = this.DefaultOutgoingResponseFormat;

        return result;
    }

    public override Type BehaviorType
    {
        get
        {
            return typeof(WebHttpBehavior);
        }
    }

    #endregion
}

And then you have to register it:

<system.serviceModel>
    <extensions>
        <behaviorExtensions>
            <add name="enhancedWebHttp" type="MyNamespace.EnhancedWebHttpElement, MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
            </behaviorExtensions>
    </extensions>

And then you use it like so:

<behavior name="MyBehavior">
    <enhancedWebHttp defaultOutgoingRequestFormat="JSON" defaultOutgoingResponseFormat="JSON" />
</behavior>

UPDATE:

This answer above applies strictly to .NET 3.x. If you're using .NET 4.x then you can now use the DefaultOutgoingResponseFormat property that is exposed on WebHttpElement to control this. Even better, you can expose a single endpoint and set the new AutomaticFormatSelectionEnabled property which will respond with the correct format based on what format the original request was in.

like image 104
Drew Marsh Avatar answered Oct 20 '22 01:10

Drew Marsh