Calling WCF Web Services from JavaScript

This post was long due, so here it is. Prepare for a long post!

Whenever you need to consume a WCF web service from a web page, you have (at least) three options:

  • Have the ASP.NET ScriptManager generate a strongly-typed JavaScript proxy to the service that you can call directly (you even get Visual Studio intellisense!)
  • Use your own JavaScript, or some third party, library such as jQuery (which I use in my example) to invoke a service in REST style
  • Use your own JavaScript to invoke a service using SOAP

The first two require that you have control over the bindings specified in the Web.config file or, at least, the factory in the .svc file.

We want to be able to invoke a service looking like this:

public class Response
{
	public String A { get; set; }
	public String B { get; set; }
}

public Response PostTest(String a, String b);
public Response GetTest(String a, String b);

You probably know the difference between SOAP and REST, if not, check out this and this.

Let's start from the first option.

.NET 3.5 included a handy behavior, enableWebScript, which allows the ScriptManager to generate a proxy from the metadata published from the service. The service must look like this:

namespace WcfAjax.Services
{
	[DataContract]
	public class Response
	{
		[DataMember]
		public String A { get; set; }

		[DataMember]
		public String B { get; set; }
	}

	[ServiceContract(Namespace = "WcfAjaxServices")]
	[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
	public class TestService
	{
		[OperationContract]
		[WebInvoke]
		public Response PostTest(String a, String b)
		{
			return (new Response() { A = a, B = b });
		}

		[OperationContract]
		[WebGet]
		public Response GetTest(String a, String b)
		{
			return (new Response() { A = a, B = b });
		}
	}
}

Please note the following:

WebGetand WebInvoke allow us to specify wether we want to call the method using the HTTP GET method or the POST, respectively. This may be relevant if you want to send sensitive information, such as passwords, in which case you should use the POST method (the default if no attribute is specified); on the other hand, only GET requests can be cached.
By now you should probably know what the AspNetCompatibilityRequirements is, if not, go read the documentation.
As for the namespace, make sure you do not use something that looks like a URL, make it simple, you will see why.

You now need to register the service with the ScriptManager on the page (or master page) that you want to call the service in. Here's how we do it:

<asp:ScriptManager runat="server">
	<Services>
		<asp:ServiceReference Path="~/Services/TestService.svc"/>
	</Services>
</asp:ScriptManager>

Make sure the WCF bindings are configured this way in Web.config:

<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
<system.serviceModel>
    <behaviors>
      <endpointBehaviors>
        <behavior name="WcfAjax.Services.TestService">
          <enableWebScript />
        </behavior>
      </endpointBehaviors>
      <serviceBehaviors>
        <behavior name="">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service name="WcfAjax.Services.TestService">
        <endpoint address="" behaviorConfiguration="WcfAjax.Services.TestService" binding="webHttpBinding" contract="WcfAjax.Services.TestService" />
      </service>
    </services>
</system.serviceModel>

The .svc file contains:

<%@ ServiceHost Language="C#" Debug="true" Service="WcfAjax.Services.TestService" CodeBehind="TestService.svc.cs" %>

Technically, you can go without using enableWebScript, if you specify a special factory in the .svc file:

<%@ ServiceHost Language="C#" Debug="true" Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory" Service="WcfAjax.Services.TestService" CodeBehind="TestService.svc.cs" %>

And that's it. You can now call the service in JavaScript:

<script type="text/javascript">
	//<![CDATA[
	function test()
	{
		//the class name is named from the namespace specified in the OperationContract attribute
		//plus the contract class name
		var svc = new WcfAjaxServices.TestService();

		//the methods look like the ones defined in the contract, but they take 3 additional arguments:
		//- a function to call in case of success
		//- a function to call in case of error
		//- an optional context
		//result and error are JavaScript objects
		//methodName is the name of the function that started the request
		svc.GetTest('a', 'b', function(result, context, functionName)
		{
			window.alert('A: ' + result.A);
		}, function (error, context, methodName)
		{
			window.alert('error: ' + error);
		}, null);

		svc.PostTest('a', 'b', function(result, context, functionName)
		{
			window.alert('A: ' + result.A);
		}, function (error, context, methodName)
		{
			window.alert('error: ' + error);
		}, null);

	}
	//]]>
</script>

Moving on, the next option gives us total control over the way our parameters are sent to the service. Unfortunately, we cannot rely on automatically generated proxies, but it's easy anyway. This one relies on the webHttp behavior, which is also new on .NET 3.5. Specifically, this behavior allows REST-style calls.

Have your code look like this:

namespace WcfAjax.Services
{
	[ServiceContract(Namespace = "WcfAjaxServices")]
	[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
	public class RestTestService
	{
		[OperationContract]
		[WebInvoke(BodyStyle = WebMessageBodyStyle.Wrapped, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
		public Response PostTest(String a, String b)
		{
			return (new Response() { A = a, B = b });
		}

		[OperationContract]
		[WebGet(UriTemplate = "GetTest?a={a}&b={b}", ResponseFormat = WebMessageFormat.Json)]
		public Response GetTest(String a, String b)
		{
			return (new Response() { A = a, B = b });
		}
	}
}

The configuration should look like this:

<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
<system.serviceModel>
    <behaviors>
      <endpointBehaviors>
        <behavior name="WcfAjax.Services.RestTestService">
          <webHttp />
        </behavior>
      </endpointBehaviors>
      <serviceBehaviors>
        <behavior name="">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service name="WcfAjax.Services.RestTestService">
        <endpoint address="" behaviorConfiguration="WcfAjax.Services.RestTestService" binding="webHttpBinding" contract="WcfAjax.Services.TestService" />
      </service>
    </services>
</system.serviceModel>

As for the .svc file, nothing new:

<%@ ServiceHost Language="C#" Debug="true" Service="WcfAjax.Services.RestTestService" CodeBehind="RestTestService.svc.cs" %>

Now, the difference is in the way we invoke the service. Here's an example using jQuery AJAX:

<script type="text/javascript">
	//<![CDATA[
	function restGetTest()
	{
		$.ajax
		(
			{
				type: 'GET',
				url: 'Services/RestTestService.svc/GetTest',
				dataType: 'json',
				data: 'a=a&b=b',
				success: function (response, type, xhr)
				{
					window.alert('A: ' + response.A);
				},
				error: function (xhr)
				{
					window.alert('error: ' + xhr.statusText);
				}
			}
		);

		$.ajax
		(
			{
				type: 'POST',
				url: 'Services/RestTestService.svc/PostTest',
				dataType: 'json',
				contentType: 'application/json',
				data: '{ "a": "a", "b": "b" }',
				success: function (response, type, xhr)
				{
					window.alert('A: ' + response.PostTestResult.A);
				},
				error: function (xhr)
				{
					window.alert('error: ' + xhr.statusText);
				}
			}
		);
	}
	//]]>
</script>

Of note:

  • The data for the GET version must match the format specified in the WebGet attribute, by the UriTemplate property
  • For the POST version, the data object is a JSON-formatted string

Finally, some times we do not have possibility to change either the bindings or the factory that is used to create the services, and we have to deal with plain SOAP. Luckily, although SOAP can get quite complex, for simple scenarios it isn't too hard to handle.

The code:

namespace WcfAjax.Services
{
	[ServiceContract(Namespace = "WcfAjaxServices")]
	[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
	public class SoapTestService
	{
		[OperationContract]
		public Response PostTest(String a, String b)
		{
			return (new Response() { A = a, B = b });
		}

		[OperationContract]
		public Response GetTest(String a, String b)
		{
			return (new Response() { A = a, B = b });
		}
	}
}

Note this looks exactly like an ordinary WCF service, that doesn't even need ASP.NET compatibility mode. The configuration:

  <system.serviceModel>
    <behaviors>
      <endpointBehaviors>
        <behavior name="WcfAjax.Services.SoapTestService"/>
      </endpointBehaviors>
      <serviceBehaviors>
        <behavior name="">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
    <services>
      <service name="WcfAjax.Services.SoapTestService">
        <endpoint address="" behaviorConfiguration="WcfAjax.Services.SoapTestService" binding="basicHttpBinding" contract="WcfAjax.Services.SoapTestService" />
      </service>
    </services>
  </system.serviceModel>

Unlike the other examples, we must use the basicHttpBinding, not , for this one.

The .svc file:

<%@ ServiceHost Language="C#" Debug="true" Service="WcfAjax.Services.SoapTestService" CodeBehind="SoapTestService.svc.cs" %>

Finally, the invocation (SOAP only allows POST):

<script type="text/javascript">
	//<![CDATA[
	function test()
	{
		$.ajax
		(
			{
				type: 'POST',
				url: 'Services/SoapTestService.svc',
				dataType: 'xml',
				contentType: 'text/xml',
				data: '' +
				//uncomment this if you want to send a custom SOAP header
				//'' +
				//'' +
				//'MyName' +
				//'MyPassword' +
				//'' +
				//''
				'' +
				'' +
				'a' +
				'b' +
				'' +
				'' +
				'',
				beforeSend: function (xhr)
				{
					xhr.setRequestHeader('SOAPAction', 'WcfAjaxServices/SoapTestService/PostTest');
				},
				success: function (response, type, xhr)
				{
					var a = '';
					if (response.childNodes[0].childNodes[0].childNodes[0].childNodes[0].childNodes[0].textContent)
					{
						//Chrome and Firefox
						a = response.childNodes[0].childNodes[0].childNodes[0].childNodes[0].childNodes[0].textContent;
					}
					else if (response.childNodes[0].childNodes[0].childNodes[0].childNodes[0].childNodes[0].text)
					{
						//IE
						a = response.childNodes[0].childNodes[0].childNodes[0].childNodes[0].childNodes[0].text;
					}
					window.alert('A: ' + a);
				},
				error: function (xhr)
				{
					window.alert('error: ' + xhr.statusText);
				}
			}
		);
	}
	//]]>
</script>


And here you have it. Hope this turns out useful to anyone! If you need the code, drop me a line.

Bookmark and Share

                             

20 Comments

  • I tried your version #3, SoapTestService, exactly as you have it.

    But I keep getting the "unrecognized message version".

    I am using .NET 4 instead of 3.5.

    hmmmm.

  • John A:
    Did you include the SOAP header? It is there just to let people know how to use it, it is not required.
    Did you try it with .NET 3.5? You just have to change the project target framework version. I believe mine is running on .NET 4, though.

  • Hi Ricardo,

    So cool, you answered. Thank you.

    I lied when I said "exactly". I am placing the ServiceContract attribute on the Interface. Your example has it being placed on the implementation class.

    So, when I called the setRequestHeader method in the beforeSend method I needed to add the "I" to the "Name" portion of the "Namespace/Name/Method" string composition.

    Gosh, so subtle. I looked at it for hours and just didn't see it.

    Thanks for this amazing tutorial. I owe you a million dollars. When I have 2 million I will give you 1. No wait, when I have 3 million I will give you 1. I promised my girlfriend 1 too.

    Anyway, you saved my life. Thanks for sharing your great knowledge. I really needed it.

    Thanks again,

    John A








  • John:
    Save your millions! ;-)
    Glad to help!

  • Hi,
    Thanks for this brilliant article. It helps me to call WCF Web Services from JavaScript. Thanks you so much.

  • Hi,

    I'm new to javascript, and have been trying to cram it all in because I need to build an iphone app that calls an svc web service. Your example 3 comes close to what I believe I need. Is there any way we could work out an arrangement where you could help/mentor/contract/....whatever you want. I am stretched for time, and need assistance - and it sounds like you really know what you are doing.

    If you are available, please let me know how I can contact you.

    Thanks,
    Tom

  • I there any way to communicate with WCF with callback ?
    I want to implement Publish Subscribe on WCF and consume it with java script.

  • @Paul,
    Don't think so. You can try AJAX Comet, there are some implementations for .NET.

  • fantastic post, thank you

  • Hola Ricardo,
    I need to call a WCF service from a Classic ASP page. I need to work with a vendor who hosts a SAS system written in classic ASP; no .net framework.
    I need their the system to call a WCF service we have written in-house.
    The WCF uses wsHttpBinding and a HTTPS address (SSL) for its endpoint.
    I could adapt the WCF service, if needed, in order to suit their needs, but i should not give up security.

    Maybe your third option is closer to what I need?

    Would you please explain how to accomplish this?

    THANKS in advance,
    Marcelo
    -m(O.O)m-

  • @mfinky:

    Did you actually try any of these approaches?
    If the service is SOAP-based, yes, you should try the third option. You can call HTTPS addresses with it.
    If you have any problems, get back to me!
    RP

  • Very helpful tutorial.

  • I love you -- I spent so many hours today trying to get this to work, and your example (the 2nd one) was awesome. Thanks!

  • Rob:
    Glad to help! ;-)

  • After googling for hours without any progress I finally found this article and the first solution is a perfect fit to my case. Thanks for your effort.

    Small bug fix:
    element should be inside element.

  • xeondev:
    That's true, thanks!

  • Hi,
    Nice post ...
    I had one question
    1. My Web Application (java scripts & HTML) are hosted on IIS and the web service is not hosted on the same place, i just have the URL for communication ..
    IF i use option 2 .. will i end up in Cross domain issue ?
    or will it work without any issues?

  • Arvind:
    It won't work, unfortunately, due to cross browser.

  • here examples are given in JQuery can you please guide me in calling wcf in plain javascript. I am using HTML5 and javascript. Please do needful

  • Mallela:
    http://www.w3schools.com/ajax/ajax_xmlhttprequest_send.asp

Add a Comment

As it will appear on the website

Not displayed

Your website