23 February 2008

Calling a WCF service from Javascript

Note - I wrote this a long time ago. While the information in this article is still valid, nowadays I would use ASP.NET MVC 4 WebAPI on the server, and jQuery on the client to do something like this. I will blog about this soon.

In a previous post I pointed out how to call an ASP.NET ASMX web service from javascript in the browser. Although this still works fine, the world has moved on to .NET 3.5 and WCF services have now replaced ASMX services. Luckily, these are also callable from javascript although the procedure is now a little bit different. In this sample I presume you can use Visual Studio 2008 and know a little about WCF.

1. Create an empty solution, and add a WCF service library to it

We'll call the solution JSONWCFTest, and the library JSONServices.

2. Add a data contract

using System.Collections.Generic;
using System.Runtime.Serialization;

namespace JSONServices
{
 [DataContract]
 public class SampleDataObject
 {
   public SampleDataObject(int NewId, string NewName)
   {
     Id = NewId;
     Name = NewName;
   }
   [DataMember]
   public int Id { get; set; }

   [DataMember]
   public string Name { get; set; }
 }
}

3. Add service contract

Add an interface IJSONService with the following methods using System.Collections.Generic; using System.ServiceModel;
namespace JSONServices
{
 [ServiceContract(Namespace="http://DotNetByExample/JSONDemoService")]
 public interface IJSONService
 {
   [OperationContract]
   SampleDataObject GetSingleObject(int id, string name);

   [OperationContract]
   ICollection GetMultipleObjects(int number);
 }
}

4. Add an implementing class

Implement the service contract like this
System.Collections.Generic;

namespace JSONServices
{
 public class JSONService : IJSONService
 {
   public SampleDataObject GetSingleObject(int id, string name)
   {
     return new SampleDataObject(id, name);
   }

   public ICollection<SampleDataObject> GetMultipleObjects(int number)
   {
     List<SampleDataObject> list = new List<SampleDataObject>();
     for (int i = 0; i < number; i++)
     {
       list.Add(new SampleDataObject(i, "Name" + i));
     }
     return list;
   }
 }
}
So far, this is very basic WCF stuff. Nothing special here.

5. Add new web application

Add a new project of type "ASP.NET Web Application" to the solution. Call it JSONTestApp. Add a reference to the JSONWCFTest project.

6. Add a SVC file to the web application

This part is a bit awkward. Choose "Text File", and name it "AjaxService.svc". Then open the file, and enter the following code
<%@ServiceHost language="C#" debug="true"
Service="JSONServices.JSONService"
Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory"%>
It may be tempting to add an "Ajax-enabled WCF service" to the project but this creates its own operation contract, and in this example I want to simulate the situation in which I re-use an already existing operation contract implemented in a separate service library.

7. Check the javascript interface

Basically, you are done now. Your service is hosted callable from javascript. You can check right-clicking on AjaxService.svc, and you will now see a web page that will say something like "This is a Windows© Communication Foundation service. Metadata publishing for this service is currently disabled." and a lot things more. You can check the javascript interface by adding "/jsdebug" to the url displayed in your browser, in my case this is "http://localhost:1781/AjaxService.svc/jsdebug". This shows the dynamically generated javascript proxy that you can call in your client code:
Type.registerNamespace('dotnetbyexample.JSONDemoService');
dotnetbyexample.JSONDemoService.IJSONService=function() {
dotnetbyexample.JSONDemoService.IJSONService.initializeBase(this);
this._timeout = 0;
this._userContext = null;
this._succeeded = null;
this._failed = null;
}
dotnetbyexample.JSONDemoService.IJSONService.prototype={
_get_path:function() {
var p = this.get_path();
if (p) return p;
else return dotnetbyexample.JSONDemoService.IJSONService._staticInstance.get_path();},
GetSingleObject:function(id,name,succeededCallback, failedCallback, userContext) {
/// <param name="id" type="Number">System.Int32</param>
/// <param name="name" type="String">System.String</param>
/// <param name="succeededCallback" type="Function" optional="true" mayBeNull="true"></param>
/// <param name="failedCallback" type="Function" optional="true" mayBeNull="true"></param>
/// <param name="userContext" optional="true" mayBeNull="true"></param>
return this._invoke(this._get_path(), 'GetSingleObject',false,{id:id,name:name},succeededCallback,failedCallback,userContext); },
GetMultipleObjects:function(number,succeededCallback, failedCallback, userContext) {
/// <param name="number" type="Number">System.Int32</param>
/// <param name="succeededCallback" type="Function" optional="true" mayBeNull="true"></param>
/// <param name="failedCallback" type="Function" optional="true" TomayBeNull="true"></param>
/// <param name="userContext" optional="true" mayBeNull="true"></param>
Note: FireFox displays the javascript directly in the browser; for security reasons, Internet Explorer wants to download it to a file rather than display it, but after saving it and opening it in a text editor you will see the text above. As you can see this contains a lot of plumbing - and this is only the top part - but I emphasized the parts that are interesting right now: the name of the object that you need to call from javascript (dotnetbyexample.JSONDemoService.IJSONService) and the methods, which have - surprise, suprise - exactly the same name as their C# server-side counterparts. From here on, the procedure is pretty much the same as we were used to when calling asmx webservices from javascript:

8. Add an Web Form to the Web Application

Call it TestForm1.aspx

9. Add a script manager and a reference

Add a script manager to the form, right below the form tag, and make a reference to the AjaxService.svc file:

<form id="form1" runat="server">
<asp:scriptmanager runat="server">
<services>
<asp:ServiceReference Path="AjaxService.svc" />
</services>
</asp:scriptmanager>

10. Add some user interface elements to call the service

Right below the script reference, add the following code:
<div>
ID<asp:TextBox ID="tbId" runat="server">21</asp:TextBox>
Name<asp:TextBox ID="tbName" runat="server">John Doe</asp:TextBox><br />
<asp:Button ID="btnSelect" runat="server" Text="SingleSelect"
   UseSubmitBehavior="False"  OnClientClick="CallSingle(); return false;"/>
</div>
<div id="result1"></div>

11. Add Javascript code

Add some javascript code to call the WCF service and process the results. Place this inside the head-tag, below the title tag:
<script type ="text/javascript">
function CallSingle()
{
dotnetbyexample.JSONDemoService.IJSONService.GetSingleObject(
    document.getElementById("tbId").value,
    document.getElementById("tbName").value,
    CallBackSingle );
}
function CallBackSingle( WebServiceResult )
{
resultDiv = document.getElementById("result1");
resultDiv.innerHTML = "You entered: id = " +
 WebServiceResult.Id + " name = " +
 WebServiceResult.Name;
}
</script>
Notice again that calling a WCF method from javascript works the same as when using asmx services: one extra parameter at the end of the list is required, which is the callback. This is a function that is called upon receiving the result from the server - the server method result is put into the single parameter of the callback.

12. Run the test page

Run Test1Form.aspx. You should be able to input and numerical id and a alphanumeric name, which are sent to the server, and returned back to you as and SampleObject.

13. Deploying WCF services in IIS

This all works fine when running it in Cassini, but when you want to host it in IIS, you will need to take care two things. First, you will need to ensure that IIS and WCF are correctly installed and registered. If IIS does not seem to want to cough up your SVC files (you can check that by trying http://<your host here>/<your sitename here>/AjaxService.svc), try running the command ServiceModelReg.exe /i /x from %WINDIR%\Microsoft.NET\Framework\v3.0\Windows Communication Foundation. Restart IIS after running this command. Second, if you created a default ASP.NET 2.0 website, you may run into this error message:
IIS specified authentication schemes 'IntegratedWindowsAuthentication, Anonymous', but the binding only supports specification of exactly one authentication scheme. Valid authentication schemes are Digest, Negotiate, NTLM, Basic, or Anonymous. Change the IIS settings so that only a single authentication scheme is used.
To fix this, right-click the website in your IIS manager and choose "properties". Then click the "Directory" tab, click the "Edit" button in the "Anonymous access and authentication control" area. You will see that both Anonymous access and Integrated Windows Authentication are selected. Unselect one of them, then restart IIS. After that, the application should work smoothly.

Concluding remarks

Calling WCF services from javascript is even simpler than calling asmx services - you don't even have to mark a service as [ScriptService] anymore, you just put an .svc file into your web site with four lines of code, make a reference to an existing service library and you're done. The sample described here does not use the IJSONService.GetMultipleObjects method, but you can download a the complete source code which includes a second test page that does. Notice that although GetMultipleObjects returns a ICollection, in javascript this is translated into a simple array. Thus, you can also use data and operation contract that use generic types, as long as the can simply be translated into an array. Oh - and don't forget to remove "debug=true" from "AjaxService.svc" before publishing it on production. Complete code downloadable here. Author's note: this article is my most viewed posting by far: 20% the traffic of my blog goes to this page. Apparently it meets a growing need in knowlegde. Please spread the word, (and the link) and if you find any errors of have any additions, let me know. Compliments are appreciated as well.

66 comments:

The Small Fish said...

Don't you hvae to define the endpoints in the webconfig of your hosting Web App?

Anonymous said...

example doesn't work if you call service from another project

Andres said...

Great post!
Exactly what I was looking for.
Best regards

Joost van Schaik said...

mbs: apparently not ;-)

Joost van Schaik said...

Anonymous: no, you cannot call the service from another project. That is not the point of an JSON service: it is supposed to communicate data from browser to server within one application. Allowing this would open the door to all kinds of cross domain attacks

Anonymous said...

Great post, but I end up with one problem. It runs like a charm within Visual Studios own webserver, but if I move it into an IIS i get a 404 on the AjaxService.svc/jsdebug file. What am I missing in the IIS settings that I get from Visual Studios webserver?

Duckboy said...

Thank you, this helped greatly.

I'm a little concerned by the previous commenter saying it doesn't work when hosted under IIS - I'm still running under the debug web server (Cassini); do you know if I'm going to need to do anything special when I publish my service to an IIS site?

Joost van Schaik said...

@fredrik @duckboy: thanks for pointing this out, I added a section 13 with explanation about hosting the service in IIS. Have a look!

Anonymous said...

cool post thanks for that.

What about if I want to host the wcf service on the windows service not on the IIS?

Joost van Schaik said...

You mean hosting the WCF service in a managed service and THEN calling it from javascript? If that is the case, I do not think this is possible. As to hosting a WCF service in a managed service in general, you can look http://msdn.microsoft.com/en-us/library/ms733069.aspx or http://www.codeplex.com/CmdLineService for examples. You might want to combine that with my post about running a service in VS.NET

Ron Luo said...

I always get a save file dialog box if I request for the jsdebug proxy by inputting /jsdebug after the URL, say, like, http://localhost:1781/AjaxService.svc/jsdebug, why?

Joost van Schaik said...

@Roahn: that's common Internet Explorer behaviour, I guess a security measure preventing you from actually downloading a script in the browser. FireFox will simply show the javascript in a browser window if you enter a URL like you typed.

Pedro said...

Hi!
Thanks for the post, it explains all the stuff very well. There is however one more thing I still can't figure out: if modify service contract attribute on IJSONService to enforce WCF session (SessionMode=SessionMode.Required), then if I run AjaxService.svc in browser

Contract requires Session, but Binding 'WebHttpBinding' doesn't support it or isn't configured properly to support it. appears. It seems like the browser is using WebHttpBinding by default, while WsHttpBinding is required, right? How would you suggest to make it possible for WCF to run in SessionMode.Required in your example?

Regards,

Rich said...

Hi! This is a really great article. I found it very helpful.

Now what I need is to run this all on https. Unfortunately, webHttpBinding won't work.

Do you have any suggestions for running this scenario on https?

Thanks!

Joost van Schaik said...

@Richard: I have little experience with these things in a https scenario. Cannot help you much apart from searching and that is how you probably found me ;-)

Ron Luo said...

Thanks a lot! that's really a useful article for me!!

Jeroen said...

Thanx Joost! This is way better than using Json.Net :)

Tomoko said...

Thank you for the article!

I was wondering how you would put the javascript functions in the code behind...

Gopal Chauhan said...

Hi,
This is an nice article but i am getting some problem while executing this. I have problem on point number 6,7&9.
I have developed a wcf application as per your instruction,define its end point and when i browse its svc file, it is working fine.Suppose url of wcf service is http://localhost:1502/Service.svc.
Now i create new web application project to call this wcf service. Now my problem is
1- do i have to create another svc file in this web application project as per your instruction? If yes,and when i browse this svc file it gives me error of
"The type 'JSONServices.JSONService', provided as the Service attribute value in the ServiceHost directive could not be found" which is expected.

2- how to use /jsdebug. do i have to configure some thing to have this feature.

3- in point -9 path is like -"Path="AjaxService.svc". If wcf service is available on diff server then how web application come to know which server and port should application hit.

I hope answer of these problem will sort out my problems.

Joost van Schaik said...

@Gopal let's discuss this by e-mail ;-)

Joost van Schaik said...
This comment has been removed by the author.
Anonymous said...

Hi there, thanks for this great example. I am having a problem.

I have a simple example based on yours.

JS:

function callAjaxWFC()
{
tempuri.org.IRMService.justForJVS("John", callBack());
}

function callBack(r) {
resultDiv = document.getElementById("results");
resultDiv.innerHTML = r;
}

C#:

public string justForJVS(string i)
{
return "Hello " + i;
}

with all the plumbing as per your example.

I put breaks in the js callBack and in the C# wfc function.

what happens is the JS break is hit before the C# break - instead of the other way round.

any help please, thanks

Joost van Schaik said...

@thewandmaker Can you please e-mail me your solution

Gak Ada said...

How to call with javascript or JSON or ASP.NET AJAX if we are using Self Host WCF ?
I found nothing about that method in inet.
It's always call from same domain. :(
anyone has seen it ?

thx alot..

Unknown said...

/jsdebug does not work.

I modified my WCF service svc file to

<%@ ServiceHost Language="C#" Debug="true" Service="MyApp.Service.StockService"
Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory"
CodeBehind="StockService.cs" %>

typing

Tryingo to test it by

http://localhost:1839/MySvc.svc/jsdebug

returns

HTTP/1.1 400 Bad Request
Server: ASP.NET Development Server/10.0.0.0
Date: Tue, 02 Nov 2010 17:19:30 GMT
X-AspNet-Version: 2.0.50727
Cache-Control: private
Content-Length: 0
Connection: Close

How to get generated proxy ?

Joost van Schaik said...

@Andus, Can you please e-mail me your solution so I can have a look at it? It should work. Really ;-)

Unknown said...

Nice post thanks

Unknown said...

Very good article, point to point description.

Aran Mulholland said...

How would one configure the binding endpoints (for example to change the maxReceivedMessageSize or reader quotas)?

Anonymous said...

I followed the sample up to point 7 (viewing the service in browser) and I received the following error:
The type 'JSONServices.JSONService', provided as the Service attribute value in the ServiceHost directive, or provided in the configuration element system.serviceModel/serviceHostingEnvironment/serviceActivations could not be found.

Joost van Schaik said...

@octav wel... the only thing I can say check and double check you code. Or download the sample and do a side-by-side comparison

Anonymous said...

How to call a net.tcp enabled service from Ajax. My services returns data in chunks.

Joost van Schaik said...

@Sanjay I am afraid I have no idea

RajeshM said...

Great post..! Can u please tell how to call WCF service from javascript which is hosted in IIS7.0..

Regards,
Rajesh..

Joost van Schaik said...

@RajeshM well it works the same. The actual mouseclick are different, but you still have to disable anything but Anonymous authentication.

Anonymous said...

Great post! Everything works fine but I can't get a value / result set to call back in javascript. It comes up null. Should I be setting a callback parameter in [ServiceContract(CallbackContract)]. I can see the returned values when I'm in FF running debugger but not in IE. Otherwise it's great. I do a WCF call to add a record via LINQ and return two integers as part of a class but comes up null.. Any recommendations would be helpful..

far said...

How do you call a wcf service library hosted in a console application to be called by ajax client. I have modified your project and hosted the wcf service library in a console window and added one more web form to test this. I have added scriptmanager code as follows . but its not working it says dotnetbyexample is not defined or null when opened in browser.

Joost van Schaik said...

@far I am not sure this is possible. The AJAX client only wants to call back to services hosted on the server it comes from itself.

Joost van Schaik said...

@jimb hard to say what's going wrong without actually seeing your code. You did mark your return type [Serializable]? And have you tried using Fiddler?

Varun said...

A great post indeed!

We have implemented exactly the same approach in our web application.

This approach is working fine in the dev and test environment. When we move to the production server it starts causing issues - it becomes inaccessible.

In the production, the application is hosted on a load balanced system with 4 web servers. The application is accessed over HTTPS.

Event though the web application is accessed over HTTPS the client side request to the WCF service is still going over HTTP, which seems to be causing the issue and the service becomes inaccessible.

Please let me know, how do we ensure that the request to the internal WCF service is sent over HTTPS.

Joost van Schaik said...

@Varin does this help? http://www.craigwardman.com/blog/index.php/2008/11/scriptmanager-service-reference-and-https/ I got this tip from fellow MVP Maurice de Beijer

Scott E said...

Your example here is fantastic and we have managed to implement it successfully. Thank you for the work that went into the project. I have 2 things for you, one a suggestion, one a question.

1) While it is obvious after you realise your mistake, the stop of including a reference to the service project from the web project is skipped.

2) Is is possiblt to implement the svc through an htm page, or is it required to be an aspx? We are trying to build our front-end entirely html/js, so I was hoping to avoid it having to be an aspx page.

Joost van Schaik said...

@Scott e thank you very much for pointing out my little omission! Point 5 now sports a sentence mentioning adding a reference from the web project to the library project.

piyush bhatt said...

is it assumed here that the wcf-svc and the page with javascript are hosted on the same server/port?

In my sample the page is hosted at(http://localhost:1197/default.aspx) and the wcf is hosted at (http://localhost/wcfservice/wcfservice.cs)

The generated javascript tries to make service call to the same url where the page is hosted (http://localhost:1197/wcfservice/wcfservice.cs/GetData). And of course it does not find this - 404.

Joost van Schaik said...

@piyush that is correct yes, service and website are essentially hosted in once web application - i.e. on the same domain and port.

balaji said...

Everything works fine...but sometimes it rotates multiple times in wcf service and return the error to javascript function..
"the server method failed..status code 12152"

Gabo said...

Hello,
First of all, thanks for your work.

Second, I was wandering what would happen if my WCF service has transport and message security enabled. With this proxy, will I be opening a hole for hackers to steal important information?

Joost van Schaik said...

@gabo Let's be honest about it: I don't know ;-)

Adi said...

I had a problem calling the WCF, but this post saved me: http://stackoverflow.com/a/1016875/274589

Jui said...

Thanks for such a helpful tutorial. I would be glad if you can explian callback () function used in javascript

Joost van Schaik said...

@Jui would you please care to elaborate as to what exactly it is you want to have explained?

Lenny SIaalck said...

@point 5 do we add a web service reference to JSONTestApp? a little confused at that point.

Joost van Schaik said...

@Lenny You don't. You add a reference to the *project*. Right-click project, hit "Add reference", NOT "Add service reference".

Jui said...

Hello!! In Step 4 u have defined the following method:- public ICollection GetMultipleObjects(int number).
Can you please tell me how to call this method from JavaScript as it is returning a list

Joost van Schaik said...

@Jui if you download the demo solution, which you can find here you can find a TestForm2.aspx that does exactly show to do this. The link to the demo solution is also in the article text, near the bottom, under "Concluding remarks"

Unknown said...

This example has been a huge help for me transferring into consuming wcf service in JavaScript. I have searched high and low and cannot find a solution and I'm hoping returning to my origins will solve this :) I made a set of services for header and detail objects, and methods to retrieve and update them in the database. The header is always visible when checking the javascript interface but the detail is not. When looking closer, it shows and old method that I had deleted some weeks ago. Is there a known way to rebuild the interface? The code is nearly identical. The only difference in the classes is the name.

Joost van Schaik said...

@Nick - this is old old code, maybe things have changed a little. Dumb question - have you tried clearing the browser cache?

Unknown said...

Nice work
But I am unable to understand Step 7
Where should I write it and what to do with it?

Unknown said...

http://servicePath/jsdebug is empty

Unknown said...

what to do

Joost van Schaik said...

@ammar I'd suggest you download the sample solution and see if that works. If it does not, it's probably something on your setup

Munish Chauhan said...

great

Unknown said...

Thanks Mr. Joost van Schaik I found that markup of my svc file was missing Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory"

Joost van Schaik said...

@ammar great to hear that. It's quite tricky stuff indeed. You have to have all the steps right.

Pat said...

Nice job Joost, helped out a lot.

Hesham Zeid said...

Great post!
Exactly what I was looking for.
Perfect explanation , Thank you