Wednesday, December 24, 2014

Consuming Microsoft web API from Angular JS application – Cross domain – Cross site: JSONP

In this post we are going to explore how to consume web API from angular JS application. This is a cross domain call where the web API will reside in a different application than the angular call. We will do this with JSONP.

In this example we will be using visual studio 2013, web API 2 and angular JS 1.3.8

Here I am using the Product web API sample from asp.net site. You can find the steps here. Follow the steps for creating the web API project.

Now if we check the URL(api/products), we will get listing of all products in the browser in xml format. In this example we will use JSON data as result, we can change this by changing formatter. We can add the following code to change the formatter in WebApiConfig at the end of Register(HttpConfiguration config) method.
config.Formatters.Clear();
config.Formatters.Add(new JsonMediaTypeFormatter());
Check the difference after changing the formatter.



Now we are ready for consuming it from angular JS.

We can add the angular JS reference using NuGet like below-



If you have issue with NuGet, you can download the latest version of angular JS from the angular site and refer it to the page.

Now let’s define a module(ProductModule) and a controller(ProductCtrl) for reading the products from the web API method.
var product = angular.module("ProductModule", []);
product.controller("ProductCtrl", ["$scope", "$http", function ($scope, $http) {
//to do    
}]);
Here we have added $http as we need to consume a service. We can do the call to API service with a callback and jsonp as calling method. We are using jsonp here are the angular site and the api are two different web site and hence cross domain.
var product = angular.module("ProductModule", []);

product.controller("ProductCtrl", ["$scope", "$http", function ($scope, $http) {
    $http({
        method: 'jsonp',
        url: 'http://localhost:51116/api/products?callback=JSON_CALLBACK'
    })
        .success(function (data, status, headers, config) {
            $scope.Products = data;
            
        })
        .error(function (data, status, headers, config) {
            
        });
}]);
As we are using jsonp, we need to append a callback to the calling URI, hence ?callback=JSON_CALLBACK'. Angular JS internally handles and returns the result with the data parameter in the success method.

Now let’s design a HTML page for consuming the result from the service. HTML goes like this-
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
</head>
<body ng-app="ProductModule">
    <table ng-controller="ProductCtrl">
        <thead>
            <tr>
                <th>Id</th>
                <th>Name</th>
                <th>Category</th>
                <th>Price</th>
            </tr>
        </thead>
        <tbody>
            <tr ng-repeat="product in Products">
                <td>{{product.Id}}</td>
                <td>{{product.Name}}</td>
                <td>{{product.Category}}</td>
                <td>{{product.Price}}</td>
            </tr>
        </tbody>
    </table>
    <script src="Scripts/angular.js"></script>
    <script src="Domain JS/ProductCtrl.js"></script>
</body>
</html>
Now before running the application please check the URL specially the port number. If this is correct, you should be getting some 404 error. Let’s check this in chrome developer tool. Screenshots are there in the image below. The Ajax request is 200 OK and there is proper data in the response tab-



Then what is the Issue. If you check the request URL, it is http://localhost:51116/api/products?callback=angular.callbacks._0 But we have provided the URL as http://localhost:51116/api/products?callback=JSON_CALLBACK. This is not an issue. Angular JS is internally changing JSON_CALLBACK to angular.callback._0. Now let’s check the response. It’s coming as-
[{"Id":1,"Name":"Tomato Soup","Category":"Groceries","Price":1.0},{"Id":2,"Name":"Yo-yo","Category":"Toys","Price":3.75},{"Id":3,"Name":"Hammer","Category":"Hardware","Price":16.99}]
Here is the problem. As it is a JSONP request the response should be like angular.callback._0(response) that is –
angular.callback._0( [{"Id":1,"Name":"Tomato Soup","Category":"Groceries","Price":1.0},{"Id":2,"Name":"Yo-yo","Category":"Toys","Price":3.75},{"Id":3,"Name":"Hammer","Category":"Hardware","Price":16.99}])
That is what Web API should be doing and the JSON formatter should support this. The formatter that we have used (JsonMediaTypeFormatter) is not having this feature. There is a nice work around for this. We can add JsonpFormatter for this. Please check the below link for detail-

https://github.com/WebApiContrib/WebApiContrib.Formatting.Jsonp

Let’s install JsonpFormatter formatter using NuGet. Search for WebApiContrib.Formatting.Jsonp in NuGet manager and install like below-



Next we need to add the callback capability and expose json data like below-
GlobalConfiguration.Configuration.AddJsonpFormatter();
GlobalConfiguration.Configuration.Formatters.XmlFormatter.SupportedMediaTypes.Clear();
Now the results is coming in correct format and successful. The complete source code is attached here.