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
Hey you speak of enabling REST Services using WCFRestContrib, If I get this right, this should be installed in the MS AX server. Still, I don\’t see where I should put :
PM> Install-Package wcfrestcontrib
Can I install it without using that command? If not where should I input it?
Thanks in advance!
LikeLike
Hi Magnus,
WCFRestContrib is now discontinued, and has been replaced by Web API. this is now a standard part of .Net 4. A fair but has changed in the implementation, but the calls to the server remain the same
LikeLike
Hi Magnus,
WCFRestContrib is now discontinued, and has been replaced by Web API. this is now a standard part of .Net 4. A fair but has changed in the implementation, but the calls to the server remain the same
The Install-Package command is run the the Package manager console within Visual studio for that project. Its a command line for nuget
LikeLike
Thanks a lot for the quick answer!! I am trying to follow your steps and connect an android phone to Dynamics 2012 in order to recover user Alerts. (I have yet to find an other tutorial as complete as yours to do so). I guess I should look around for how to connect to web API from Android phones then.
Also, If I understood well what you did, you did not use a trusted intermediary like in DaxMusings windows app post, since you made your own “lweb authentification”. Will I have to make my own authentification with the web API?
Anyways thanks again for the answer and if you know of a website that could help me I would greatly appreciate if you could point me to it!
LikeLike
HI Magnus,
What i did use was a BasicAuthentication. If you search for it there are a few implementations on how to apply it to your Server project.
I did use the intermediate authentication like Joris’s example. I passed that value in the Basic Authentication of the REST call.
Since WCFRESTContrib there has been 2 releases (last one being the WEB API) and i had to change those authentication procedures for each of them.
I shall find an example and reply to you
LikeLike
HI Magnus,
This is one of the examples which should work.
http://remy.supertext.ch/2012/04/basic-http-authorization-for-web-api-in-mvc-4-beta/
LikeLike
Oh Oki, I’ll try to work with that then! Thanks for the link!
Also I wondered, at one point you say: “Put your custom authentication Here”. Since my aim is to get the Alerts associated to one user and not all of them, I can implement an authentication that recovers user and pwd values from a custom Table? (Table could be filled by a user with a form where he “allows” internet access to certain users).
Maybe there are much easier ways, i’m just a beginner in AX dev. Sorry to bug you with so many questions :s
LikeLike
Hi Magnus,
You may want to email me to discuss more regarding this. (look at my contact info for that, or twitter)
The place where I say “Custom authentication” is to tell you that if you are using the trusted intermediary, then Ax does not store any authentication information for that user. So you may either just allow anyone or have your own table to do the authentication, which will probably sit outside Ax.
LikeLike
Hi i would like to ask a step by step on how to create WCF and the use it to android i’m having trouble following the steps on creating WCF. hoping for your quick response thanks
LikeLike
Hi Louie, This blog is not aimed at how to create a WCF Service. There are other tutorials for this. At this stage, if you are still planning to create a WCF Service, you may want to look at using Web API instead.
LikeLike
is it possible for you to share the wcf project for this one ?
LikeLike
Hi Amir, you should be able to follow the part 2 of the blog maybe.
My code isint available anymore unfortunately.
LikeLike