Category Archives: REST

Android Ax App – Part 2–The Android app


This is part 2 of the series to create an Andorid App to consume AIF services from Ax 2012.

Android Ax App – Part 1 – Set up the intermediate WCF service

This post is the equivalent of Joris’s blog:  10-Minute App – Windows Phone 7 App

For this app I will be using the Eclipse development environment, jammed with the Android SDK (i aimed it at using the 2.3.3 SDK, but i think you could use 2.1 or lower)

WARNING: There is a lot of code…..lots and lots of code (unlike the windows 7 app, and its code generation tools, there is not much here) I will try to provide the full source code for this project

UPDATE: The source code for the android application can be downloaded here
https://skydrive.live.com/embed?cid=39C5BF85D7859D05&resid=39C5BF85D7859D05%21301&authkey=AOma0LTxcyPetdU

For the requirements of this app, i will list some here to make sure we have all the libraries. I will assume that Eclipse and Android are setup based on the instructions

  1. Eclipse
  2. Android SDK(2.3.3) (We arent useing any OS specific feature. This app should be able to be written in much lower versions)
  3. Gson (Google Json library, this is a pretty lightweight and easy to use JSON library as compared to the one already included within the Java SDK)

So the first part of the process is to create the Android application. This should be some basic general settings CreateAndroidProject

Make sure to have the “Create activity” set to true. That creates a default activity to start with the application.

Because we are going to access our web service (i.e. use the internet) we need to give that access to the app (otherwise this will lead to frustration and excessive drinking and may also lead to baldness). To do so, add the following line to the AndroidManifest.xml file within the <manifest> tag

<uses-permission android:name="android.permission.INTERNET"/>

We will now create the UI for this. This will be split into 2 UI elements. One which is the main page, and the second which defines each row of the Items to be displayed. I Will try to make the UI look as close to the one in Joris’s Blog.

The main UI: When the application is created, a UI xml file is already created under /res/layout/main.xml

Main Layout

MainUILayout

Item row layoutItem_RowLayout

This UI starts with a TextView (User name label), a TextViewEdit (Username), Button (Get items), followed by another label (this will fetch the application name from the web service)

Follows that is a ListView placeholder. Later on the list view will be set to use the secondary view which contains the definition of a Row

The XML of the main view is (main.xml):

<!--?xml version="1.0" encoding="utf-8"?-->
<!-- Top section -->

	<button>

 <!-- Application name -->

 <!-- Listview placeholder -->

 

The row layout will be added in another UI file called “item_row.xml”

<!--?xml version="1.0" encoding="utf-8"?-->

Here we are only showing the Item Id and Item name below the other and with a different height for the item

We now have to link the Item_row UI to the LIstView. This is done using Adapters. There are lots of examples online how to attach a Listview to a array of strings, but very few with array of some class.

What i will do is First Show the Main Activity (this is where the item_row gets attached to the ListView too)

In the Activity class “LooneyDroidActivity” we will declare our 3 UI elements we would want to access. (In Android, the Activity can be linked to any UI, and hence we need to fetch the elements ourselves, unlike Silverlight Sad smile )

private ListView itemListView;
private EditText editTextUsername;
private TextView appNameTextView;

In the class, you will also see the onCreate method. This method is the entry point of the Activity. this is where we will initialize the private members that point to the UI elements.

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//Username button
editTextUsername = (EditText)this.findViewById(R.id.editTextUserName);

//Application name
appNameTextView = (TextView)this.findViewById(R.id.AppName);

this.itemListView = (ListView)this.findViewById(R.id.itemList);
}

The “setContentView” sets the UI to be used by the activity. This is how the app knows which UI layout to show. you also see “R.id.*”  R is a class auto generated and resides under /gen/package name/R. Every time UI element is created, the ID for it is generated and stored in the class.

in the Main.xml layout, you also see the button element has a onClick method. This needs to be manually defined inside the activity class.

public void LoginBtn_OnClick(View v){
    ItemAdapter adapter = (ItemAdapter)this.itemListView.getAdapter();
    //Clearing the ListView
    if(adapter != null) {
        this.itemListView.setAdapter(null);
    }
    //Fetch the AplicationName
    String username = this.editTextUsername.getText().toString();
    String appName = RESTClient.connect("http://192.168.1.100:8080/DevTest/WcfApi/Api1.svc/api1/appName", username);

    try {
        appName = (String)ProcessJson.ProcessResult(MessageType.GetAppName, appName);
        appNameTextView.setText("Logged in as: " + appName);
    } catch (MalformedJsonException e) {
        e.printStackTrace();
        appNameTextView.setText("Cannot Login");
    }
    catch (JsonSyntaxException e){
        e.printStackTrace();
        appNameTextView.setText("Cannot Login");
    }

    //Fetch the itemList
    String itemList = RESTClient.connect("http://192.168.1.100:8080/DevTest/WcfApi/Api1.svc/api1/items", username);
    try{
        Item[] items = (Item[])ProcessJson.ProcessResult(MessageType.GetItemsList, itemList);
        //Binding itemList to UI
        ItemAdapter itemAdapter = new ItemAdapter(this, R.layout.item_row, items);
        this.itemListView.setAdapter(itemAdapter);

    } catch (MalformedJsonException e) {
        e.printStackTrace();
        appNameTextView.setText("Cannot Login");
    }
    catch (JsonSyntaxException e){
        e.printStackTrace();
        appNameTextView.setText("Cannot Login");
    }
}

The button click is doing 2 calls to the WCF service, to get the application name and then the item list.

For each call it goes to the RESTClient class, which returns the JSON string. The ProcessJson class converts that JSON to an object using the GSON library. The first process returns a string, the second an array of item (the item class consists of just the ItemId, and name).

I think this is already well beyond the 10 minute mark (took me more than that too)

The REST Client: (To call the WCF services, and fetch the JSON result)

public class RESTClient {
    private static String convertStreamToString(InputStream is){
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        StringBuilder sb = new StringBuilder();

        String line = null;
        try{
            while((line = reader.readLine()) != null){
                sb.append(line + "\n");
            }
        } catch(IOException e){
            e.printStackTrace();
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return sb.toString();
    }

    public static String connect(String url, String username) {
        String result = "";
        HttpClient httpClient = new DefaultHttpClient();
        HttpGet httpGet = new HttpGet(url);
        HttpResponse response;
        try{
httpGet.addHeader("Authorization", username+":"+username);
            response = httpClient.execute(httpGet);
            Log.i("WCFAPI", response.getStatusLine().toString());

            HttpEntity entity = response.getEntity();
            if(entity != null){
                InputStream inStream = entity.getContent();
                result = convertStreamToString(inStream);
                Log.i("WCFAPI", result);
                inStream.close();
            }
        }catch(ClientProtocolException e){
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }
}

The ProcessJson Class(Convert JSON to custom objects)

public class ProcessJson {
    public enum MessageType{
        GetAppName,
        GetItemsList
    }
    public static Object ProcessResult(MessageType messageType, String result) throws MalformedJsonException{
        Gson gson = new Gson();
        Object returnValue = null;

        switch(messageType){
        case GetAppName :
            returnValue = gson.fromJson(result, String.class);
            return returnValue;
        case GetItemsList :
            returnValue = gson.fromJson(result, Item[].class);
            return returnValue;
        }
        return null;
    }
}

Item class: (data received from the WCF GetItems method)

public class Item {
    public String ItemId;
    public String Name;
}

And finally the ItemAdapter class (this takes the list of items, specifies how to map it to the Item_row.xml layout

OK……So “Finally” (finally,finally,finally)….our application is ready to deploy.

The start screen: (we have pre populated the username in the main.xml file)

AndroidAx Login Screen
Android Ax Items list
Android Ax Items Scrolled to the end
Android Ax Items Scrolled to the end

And if you enter an incorrect username, you get the error. (this is just error handling, and no connectivity to the web service will also give the same error)

Android Ax Incorrect Login result
Android Ax Incorrect Login result

This Android app is just a basic Single Activity based app, with no menu items or additional activities. The trusted intermediate Works just as expected with this too. What I haven’t covered on this yet is securing the WCF services by SSL and then consuming it from Android. I haven’t ventured into that jungle yet, but will try to post one on that later.
This concludes the Android app for Ax. I don’t think this would be anywhere near 10 minutes (especially with the ListView and the Custom Adapter we had to create), but that all depends with your Android skills. There was a lot of joy once I got this working, hopefully there will be more integration into Ax coming soon 🙂

Android Ax App – Part 1–Set up the intermediate WCF service


I came across a blog by Joris De Gruyter http://daxmusings.blogspot.com/2011/11/10-minute-app-windows-phone-7-app.html and decided to use that to connect to an Android device.

This is part 1 of 2 to explain the process of creating the Android application to connect to Ax via AIF services.

Unlike the blog mentioned above, I had to enable REST in the WCF service, and output it as JSON. This is because android has a better JSON support than SOAP (ksoap can be used to consume the WCF services, but I have found JSON to be a much easier approach)

To enable REST services, I installed the WCFRestContrib package. This is available from NuGet and can be installed using

PM> Install-Package wcfrestcontrib

We can create the WCF Service using the no configuration mode. This link defines how to create a WCF service with no configuration. Which means most of the web.config is deleted, and another identifier is attached to the SVC file. This however relies of transport security as there is no SOAP envelope around this. WCFRESTContrib allows to create a custom authentication system. The authentication data can be retrieved from the HTML headers and verified.

So the WCF service should look like this, it enables a GET method and has a certain URI, and outputs the data in JSON

namespace WcfService1
{
  [ServiceContract]
  [ServiceAuthentication] //WCFRESTContrib attribute - authentication per session (or at method level for per call) public interface IApi1 {
  [WebGet(UriTemplate = "/api1/appName", ResponseFormat = WebMessageFormat.Json)]
  [OperationContract]
  string ApplicationName();

  [WebGet(UriTemplate = "/api1/items", ResponseFormat = WebMessageFormat.Json)]
  [OperationContract]
  List GetAllItemIds();
}
}

The Service interface markup (this allows for a noconfiguration WCF Service)

<%@ ServiceHost 
    Language="C#" 
    Debug="true" 
    Service="WcfService1.Api1" 
    CodeBehind="Api1.svc.cs"
    Factory="System.ServiceModel.Activation.WebServiceHostFactory"
    %>

The Service file itself

namespace WcfService1
{
    [WebAuthenticationConfiguration(
    typeof(WcfService1.ApiAuthnetication),
    typeof(ApiAuthneticationValidator),
    true,
    "My Application")]
    public class Api1 : IApi1 {
    public string GetUsername()
    {
        object authValue;
        string authStr = "", username = "", password = "";
        try {
            var keys = OperationContext.Current.IncomingMessageProperties.Keys;
            OperationContext.Current.IncomingMessageProperties.TryGetValue("httpRequest", out authValue);
            if (authValue != null)
            {
                System.ServiceModel.Channels.HttpRequestMessageProperty p = (System.ServiceModel.Channels.HttpRequestMessageProperty)authValue;
                authStr = p.Headers.Get("Authorization");
                ApiAuthnetication.GetUsernamePwd(authStr, out username, out password);
            }
        }
        catch (Exception e)
        {
            return e.Message;
        }
        return username;
    }
    public string ApplicationName()
    {
        string username = this.GetUsername();

        return username;
    }
    public List GetAllItemIds()
    {
        return Item.GetItems(this.GetUsername());
    }
    }
}

Notice the header attributes. This needs to be defined and is based on the WCFRESTContrib package. The authentication classes created is. Notice that the Username and password are being sent in the header with the tag “Authorization” this can be renamed to anything you like

namespace WcfService1
{
    public class ApiAuthnetication : WcfRestContrib.ServiceModel.Dispatcher.IWebAuthenticationHandler {
    public static bool GetUsernamePwd(string authenticationStr, out string username, out string password)
    {
        bool result = true;
        username = "";
        password = "";
        try {
            var values = authenticationStr.Split(':');
            username = values[0];
            password = values[1];
        }
        catch {
            result = false;
        }
        return result;
    }

    public System.Security.Principal.IIdentity Authenticate(
            System.ServiceModel.Web.IncomingWebRequestContext request,
            System.ServiceModel.Web.OutgoingWebResponseContext response,
            object[] parameters,
            System.IdentityModel.Selectors.UserNamePasswordValidator validator,
            bool secure,
            bool requiresTransportLayerSecurity,
            string source)
   {
       string authHeader = "";
       string userName = "";
       string password = "";
       if (request.Headers.HasKeys() == true && request.Headers.AllKeys.Contains("Authorization") == true)
       {
           authHeader = request.Headers.Get("Authorization");
           //var values = authHeader.Split(':');
           //userName = values[0];
           //password = values[1];
           GetUsernamePwd(authHeader, out userName, out password);
       }
       validator.Validate(userName, password);
       return new GenericIdentity(userName, this.GetType().Name);
   }
}

   public class ApiAuthneticationValidator : System.IdentityModel.Selectors.UserNamePasswordValidator
   {
       public override void Validate(string userName, string password)
       {
          //Do your custom authentication here
          if (userName.Equals("claimguy@myapp.com", StringComparison.InvariantCultureIgnoreCase) == false
                  || password.Equals("claimguy@myapp.com", StringComparison.InvariantCultureIgnoreCase) == false)
              throw new Exception("Authentication Failed");
       }
   }
}

The code to get the InventTable data is pretty similar to Joris’s blog. Here is mine:

namespace WcfService1
{
    public class Item {
        public string ItemId { get; set; }
        public string Name { get; set; }

        public static List GetItems(string username)
        {
            List items = new List();
            string itemIdFrom = "1000"; //I cheated here 
            string itemIdTo = "1105";   //I cheated here again 
            //Fetch items from AX here 
            using (ItemService.ItemServiceClient client = new ItemService.ItemServiceClient())
            {
                ItemService.QueryCriteria qc = new ItemService.QueryCriteria();
                ItemService.CriteriaElement criteria = new ItemService.CriteriaElement();
                criteria.DataSourceName = "InventTable";
                criteria.FieldName = "ItemId";
                criteria.Operator = ItemService.Operator.Range;
                criteria.Value1 = itemIdFrom;
                criteria.Value2 = itemIdTo;
                qc.CriteriaElement = new ItemService.CriteriaElement[] { criteria };

                ItemService.CallContext context = new ItemService.CallContext();
                context.Company = "ceu";
                context.MessageId = Guid.NewGuid().ToString();
                context.LogonAsUser = string.Format("LoonAx\\{0}", username);

                var axItems = client.find(context, qc);
                if (axItems.InventTable != null)
                {
                    foreach (var inventTable in axItems.InventTable)
                    {
                        Item item = new Item() { ItemId = inventTable.ItemId, Name = inventTable.NameAlias };
                        items.Add(item);
                    }
                }
            }
            /* //Mocking for AIF
            for (int i = 0; i < 10; i++) { Item item = new Item() { ItemId = (1000 + i + 1).ToString(), Name = (1000 + i +          1).ToString() + " - name" }; items.Add(item); }*/ return items;
        }
    }
}

To make the connection secure (since we are sending the authentications via plain text in the HTML headers, this service should ideally be secure)

In the next blog I will go through the Android app to consume this service