Telerik blogs

One of the questions I run into a lot is "how do I consume web services from a hybrid mobile app?" Especially since we released the Icenium Extension for Visual Studio, there are a number of developers seeking to access established Web API endpoints from the apps they're building with Icenium. The good news is - it's not hard at all. Let's take a look at how Burke Holland and I accessed a Web API backend (hosted on Azure) from the "Chuck Facts" example app (after all - Chuck Norris jokes never get old).

First - The EndPoints

I'm not going to lie - the C# end of this equation is not complicated. Burke and I set up a Web API project in Visual Studio, which wraps our use of Entity Framework to query a SQL Server Database. (We then deployed both the database and the Web API project to Azure.)

One of the endpoints exposed simply returns a random Chuck Norris joke. Here's the controller:

public class JokesController : ApiController
{
    readonly Repositories.JokesRepository _jokes = new Repositories.JokesRepository();

    public Models.Joke Get(string type)
    {
        return _jokes.Random(type);
    }
}

Incredibly simple, right? The Dashboard controller (used to populate our pie chart of Chuck Norris joke categories) is every bit as ridiculously straightforward:

public class DashboardController : ApiController
{
    readonly Repositories.DashboardRepository _repository = new Repositories.DashboardRepository();

    public object Get()
    {
        return _repository.Get();
    }
}

Thanks to work handled for us by the ASP.NET Web API libraries (and the fact that we're not doing any crazy custom routing), we know that hitting these endpoints is as simple as this:

I'm using the fabulously helpful "Postman" Chrome extension in the above screen shot to perform a GET against the random joke endpoint. I highly recommend installing Postman now.

You can see that our response can be parsed into a usable JavaScript object. That was easy. What about hitting the "Dashboard" endpoint? Let's see the result:

Again - we have an easily-JSON-parseable object. This, by the way, is because I'm sending a Content-Type header with my requests:

If you hit this endpoint directly, without specifying a content type of JSON, you might trigger latent agression issues and need counseling soon after, since you're going to have a bad time see XML:

OK - we've shown that our endpoints are indeed usable, and it will be super easy to parse the response into a JavaScript object. I intentionally showed that we can access those endpoints outside of the mobile UI we'll discuss in the next session to help illustrate that there's nothing special that needs to be done to these web services for our mobile app to utilize them. They just need to return a usable response.

We're half way there. Now - what do we have to do on the client?

Second - The Client

Burke and I used Kendo UI Mobile for this example - which means the annoying boilerplate of parsing the reponse into an object which is then used to update a view model's values is handled for us. Less boilerplate is a win.

Let's start with a random joke. Our app's "view" for a random "nerdy" joke looks like this:

<div data-role="view" id="nerdy" data-layout="main" data-model="app.nerdy.viewModel" 
    data-title="The Facts: Nerdy" data-init="app.nerdy.getRandomJoke">
    <!-- this header overrides the header defined in the layout -->
    <div data-role="header">
        <div data-role="navbar">
            <span data-role="view-title"></span>
            <button data-role="button" data-align="right" data-icon="refresh" data-bind="click: refresh"></button>
        </div>
    </div>
    <h3 data-bind="html: joke"></h3>
</div>

Even if you're not familiar with Kendo UI Mobile, the above markup probably makes sense.

  • The outer div has a data-model attribute set to app.nerdy.viewModel. This enables this chunk of DOM to declaratively bind to a view model that will be assigned to the viewModel property of our app.nerdy namespace (the app object gets attached to the window).
  • You can also see that we have a data-init attribute set to app.nerdy.getRandomJoke, which means when this view intializes, the app.nerdy.getRandomJoke() method will be invoked (which results in a call to the HTTP endpoint we discussed above to get a random joke).
  • The really important part, though, is the h3 element at the bottom, with data-bind="html: joke" This means that the view model to which this view is bound (app.nerdy.viewModel) has a joke property which will be used to set the content of our h3. If the joke value changes, our view will update to show the new value.

What about the JavaScript side of this - what does our view model look like?

// create a base class for the nerdy and funny view
var jokeModel = kendo.Class.extend({
    // the init method is the constructor and is called when the object 
    // is created using the 'new' keyword
    init: function (category) {
        // store a reference to the base class object
        var that = this;

        // attach a category field to this instance and set it's 
        // value to the parameter passed into the init method
        that.category = category;

        // create a view model
        that.viewModel = kendo.observable({
            joke: null,
            refresh: function () {
                getRandomJoke(that.category, that);
            }
        });

        // expose a random joke method
        that.getRandomJoke = function () {
            getRandomJoke(that.category, that.viewModel);
        }
    }
});

// and a bit later in the code (g is the window):

// create a new nerdy model from the base jokeModel
g.app.nerdy = new jokeModel("nerdy");

We're extending the base kendo.Class cosntructor with an init method. The init method handles setting everything else up once it's invoked. It adds a category, a viewModel and a getRandomJoke method to the instance. The part we're really interested in here is the getRandomJoke method, which delegates the actual HTTP request to this method below:

// get a random joke by category
var getRandomJoke = function (category, viewModel) {
    g.app.application.showLoading();
    $.get(api + "jokes?type=" + category, function (data) {
        // update the view model
        viewModel.set("joke", data.JokeText);
        g.app.application.hideLoading();
    });
};

When we make a request, we append the category type to the url and in our success callback we call viewModel.set("joke", data.JokeText). So - when this method is invoked, a request is made, the response parsed, the view model's joke property is updated – all behind the scenes for us – and the h3 element will automatically reflect the change thanks to Kendo UI's two-way binding behavior.

WAIT A SECOND, JIM! This looks exactly like what I do when I make HTTP requests from a normal web app.

I know, right?!

But there is something else you need to worry about.

The config.xml access element

While this looks and feels exactly like you'd do things in a normal straight-up web app, it's not a normal straight-up web app. It's a hybrid app, and you need to tell PhoneGap/Cordova what sites are OK for your app to communicate with. This is done in the config.xml file using access elements. From the docs:

"<access> elements define the set of external domains the app is allowed to communicate with.... See the Domain Whitelist Guide for details."

To edit config.xml, right-click your project, choose "Edit Configuration" and then select either "Android Config.xml" or "iOS Config.xml" (this is the same in Mist, Graphite and Visual Studio). For example - here's an Android config.xml file (or part of it):

<?xml version="1.0" encoding="utf-8"?>
<cordova>
    <access origin="http://127.0.0.1*"/> <!-- allow local pages -->
    <access origin=".*"/>

    <content src="index.html" />

    <!-- and more content here... -->
</cordova>

See the access elements at the top? These are the defaults given when you create an Icenium project. The first one allows for local calls, the second one allows any external site to be hit. Of course, it would be wise for us to limit our application's access to only those sites we explicitly allow. Since we know our Chuck Facts app only needs to hit our Azure endpoints, we could easily edit the config.xml to look as follows:

<?xml version="1.0" encoding="utf-8"?>
<cordova>
    <access origin="http://127.0.0.1*"/> <!-- allow local pages -->
    <access origin="http://facts.azurewebsites.net"/>

    <content src="index.html" />

    <!-- and more content here... -->
</cordova>

A Word On Testing in a Simulator

A word of caution when you are using a browser-based simulator to test your mobile app: Accessing an external site that is not providing an Access-Control-Allow-Origin header enabling the domain from which your simulator is running acccess to its endpoints will result in an error. If you're building apps with Icenium, you can get around the CORS restrictions in the Mist simulator by using the simulator provided with Graphite or the Icenium Extension for Visual Studio. You can also skip the simulator and test on a device. We support iOS remote debugging with Graphite, and you can remote debug Android devices quite easily using jsHybugger.

All in all, it's not difficult for your mobile app to consume external web services. Just be sure to lock down the sites you allow your app to communicate with before you deploy to the app store.

Jim Cowart

About the Author
is an architect, developer, open source author, and overall web/hybrid mobile development geek. He is an active speaker and writer, with a passion for elevating developer knowledge of patterns and helpful frameworks. Jim works for Telerik as a Developer Advocate and is @ifandelse on Twitter.


Comments

Comments are disabled in preview mode.