Dynamics CRM Developers: Build Your Own Mobile Apps for Windows, iOS, and Android Part 5


In the last article, I said I will explain how to use Web API developer preview. However, I got several questions regarding Xamarin.Forms, so I will introduce how you can build Xamarin.Forms app for Dynamics CRM first in this article.

I assume you already know what is Xamarin, how to register your application to Azure AD, what is OAuth 2.0, overview of Active Directory Authentication Library (ADAL) etc. Please read previous articles for more detail.

Part 1 | Part 2 | Part 3 | Part4

Please note, that Xamarin and ADAL are evolving technologies and released new update often, so the technical details in this article may be incorrect when you read it in the near future. I am using Xamarin 3.11.666 and Visual Studio 2013 Update 4 when I write this article.

Xamarin.Forms

Xamarin.Forms is one of Xamarin’s product, which lets you write not only business logic but also UI with XAML like technology once and share across multi-platform. Please refer to following link for more detail.
http://xamarin.com/forms
http://developer.xamarin.com/guides/cross-platform/xamarin-forms/

Prepare a solution

1. Open Visual Studio, and create new project. and select Mobile Apps | Blank App (Xamarin.Forms Shared).
The reason I do not use Portable Class Library (PCL) is that ADAL doesn’t support Xamarin.Forms yet, thus need to reference ADAL for each project.

image

2. Enter name and click “OK”. I name it CrmXForm.

3. Windows Phone project is targeted to Windows Phone 8.0 by default. Right Click CrmXForm.WinPhone (Windows Phone 8.0) project and click Properties.
Select Windows Phone 8.1 from Target. Save the changes.

image

4. Right click the project in solution explorer and click “Manage NuGet Packages..” to launch the NuGet Package Explorer. Click Updates in the left pane and update Xamarin.Forms to the latest version.

5. In the NuGet Package explorer, make sure you select nuget.org on the left for the search source, then Search for: Json.NET and install it.

image

6. Next, search “ADAL” and select “Active Directory Authentication Library”. Confirm the version is 2.16 or higher which support Silverlight.
Click Install and it only shows WinPhone project. Click “OK” to install it.

image

image

7. Then search “HttpClient” and select “Microsoft HTTP Client Libraries”, and click Install. Select CrmXForm.WinPhone project and click “OK”

image

image

8. Next, change release category to “Include Prerelease” and search ADAL. Select “Active Directory Authentication Library”, and confirm the version is version 3.3 or higher which supports Xamarin.iOS, and Xamarin.Android. Click Install and select Droid and iOS projects, then click “OK”

image

image

9. Expand CrmXForm.Droid project and right click and click Add References. Add following references.

System.Net
System.Net.Http
System.Runtime.Serialization

10. Do the same for CrmXForm.iOS project and add following references.

System.Net
System.Net.Http
System.Runtime.Serialization
System.Xrm.Linq

11. Delete unnecessary files from iOS projects. Delete iTunesArtwork files and all files under Resources folder of iOS Project.

12. Right click CrmXForm.iOS project and click Properties. Change SDK version as desired on iOS Build | General. I selected 8.1 here.

image

13. Click iOS Application on the left pane, and select 8.1 for Deployment Target and remove Launch Storyborad. I selected 8.1 here.

image

14. Build the solution, and run each project to confirm it works fine at this point.

Add CRM SDK and Help Files

Next, add CRM and ADAL related codes.  There are many ways to achieve the same, so please feel free to change code or folder structure if you want to manage them in a different way.

1. Firstly, add CRMSDK files to the shared project. Right click the CrmXForms Shared project and click Add | New Folder. Name it as CRMSDK.

2. Open browser and go to https://code.msdn.microsoft.com/Mobile-Development-Helper-3213e2e6. Download the sample and extract it all to a known location.

image

3. In the solution explorer right click the the CRMSDK Folder in the Visual Studio solution explorer under the project, select Add, Existing Item, select all the .cs files you extracted above except CRMHelper.cs .

image

4. Once completed, double click Microsoft.Xrm.Sdk,Samples.cs to open the cs file.

5. Comment out line 35, and 666-682. There lines are for Windows Store/Phone application and Xamarin does not understand it.

6. Next, add CRMSDK wrapper which handles Authentication and error handling when calling SDK methods. Right Click the CrmXForms Shared project and add another folder, and name it as Helper.

7. Right click the Helper folder and add a class file, name it as OrganizationServiceProxy.cs, which will be wrapper of CRMSDK.

8. Replace the folder with following code. Use“http://crmxform.local” as redirect Uri for Android and iOS and register the application to Azure AD get obtain ClientId.

using Microsoft.Crm.Sdk.Messages.Samples;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.Xrm.Sdk.Query.Samples;
using Microsoft.Xrm.Sdk.Samples;
using System;
using System.Threading.Tasks;

namespace CrmXForm.Helper
{
    // Inherit from OrganizationDataWebServiceProxy.
    public class OrganizationServiceProxy : OrganizationDataWebServiceProxy
    {
        #region Method     

        public OrganizationServiceProxy()
        {
            ServiceUrl = ServerUri;
        }

        // Wrap SDK methods. This example uses Execute and Retrieve method only.
        public async Task<OrganizationResponse> Execute(OrganizationRequest request)
        {
            // Aquire Token before every call.
            await Authenticate();
            // I omit try/catch error handling to make sample simple.
            return await base.Execute(request);
        }

        public async Task<Entity> Retrieve(string entityName, Guid id, ColumnSet columnSet)
        {
            await Authenticate();
            return await base.Retrieve(entityName, id, columnSet);
        }

        private async Task Authenticate()
        {
            // Make sure AccessToken is valid.
            await GetTokenSilent();

            // Wait until AccessToken assigned
            while (String.IsNullOrEmpty(AccessToken))
            {
                await System.Threading.Tasks.Task.Delay(10);
            }
        }
       
        #endregion

        #region ADAL

        #region Property

        public AuthenticationContext authContext = null;
       
#if __ANDROID__
        public Android.App.Activity activity;
#elif __IOS__
        public UIKit.UIViewController uiViewController;
#endif

        private string OAuthUrl = “https://login.windows.net/common/oauth2/authorize”;
        private string ServerUri = “https://<org>.crm.dynamics.com”;
       
        // Register the application to get ClientId.
        private string ClientId = “<your ClientId>”;

#if __ANDROID__ || __IOS__
        private string RedirectUri = “http://crmxform.local”;
#else
        private string RedirectUri = Windows.Security.Authentication.Web.WebAuthenticationBroker.GetCurrentApplicationCallbackUri().ToString();
#endif             

        #endregion

        #region Method

        private async Task GetTokenSilent()
        {
            // If no authContext, then create it.
            if (authContext == null)
            {
#if __ANDROID__ || __IOS__
                authContext = new AuthenticationContext(OAuthUrl);
#else
                authContext = AuthenticationContext.CreateAsync(OAuthUrl).GetResults();
#endif
            }

            AuthenticationResult result = null;

#if __ANDROID__
            IPlatformParameters parameters = new PlatformParameters(activity);
#elif __IOS__
            IPlatformParameters parameters = new PlatformParameters(uiViewController);
#endif

#if __ANDROID__ || __IOS__
            try
            {
                result = await authContext.AcquireTokenAsync(ServerUri, ClientId, new Uri(RedirectUri), parameters);
                StoreToken(result);
            }
            catch (Exception ex)
            {               
            }
#else
            result = await authContext.AcquireTokenSilentAsync(ServerUri, ClientId);
            if (result.Status == AuthenticationStatus.Success)
                StoreToken(result);
            else
                authContext.AcquireTokenAndContinue(ServerUri, ClientId, new Uri(RedirectUri), StoreToken);
            return;
#endif
        }

        /// <summary>
        /// This mothod called when ADAL obtained AccessToken
        /// </summary>
        /// <param name=”result”></param>
        private void StoreToken(AuthenticationResult result)
        {           
            AccessToken = result.AccessToken;
        }

        #endregion

        #endregion
    }
}

9. Save the change and add another class file, and name it as CrmXFormHelper.cs. Overwrite the code with following. This is the helper class which application uses.

using Microsoft.Crm.Sdk.Messages.Samples;
using Microsoft.Xrm.Sdk.Query.Samples;
using Microsoft.Xrm.Sdk.Samples;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace CrmXForm.Helper
{
    static public class CrmXFormHelper
    {
#if __ANDROID__
        static public Android.App.Activity activity
        {
            set { Proxy.activity = value; }
        }
#elif __IOS__
        static public UIKit.UIViewController uiViewController
        {
            set { Proxy.uiViewController = value; }
        }
#endif
        static private OrganizationServiceProxy proxy;
        static public OrganizationServiceProxy Proxy
        {
            get
            {
                if (proxy == null)
                    proxy = new OrganizationServiceProxy();
                return proxy;
            }
        }

        static public async Task<string> GetLoginUser()
        {
            WhoAmIResponse result = (WhoAmIResponse)await Proxy.Execute(new WhoAmIRequest());
            Entity user = await Proxy.Retrieve(“systemuser”, result.UserId, new ColumnSet(“fullname”));

            return user[“fullname”].ToString();
        }
    }
}

Update Project startup files

1. Now its time to update each Project startup page to pass information for ADAL to Helper code. Expand CrmXForm.Droid project and open MainActivity,cs. Add below using statement.

using CrmXForm.Helper;
using Android.Content;
using Microsoft.IdentityModel.Clients.ActiveDirectory;

2. Add following line in OnCreate method.

CrmXFormHelper.activity = this;

3. Add following method in MainActivity class which will be called after authentication.

protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
    base.OnActivityResult(requestCode, resultCode, data);
    // Pass the authentication result to ADAL.
    CrmXFormHelper.Proxy.authContext.SetAuthenticationAgentContinuationEventArgs(requestCode, resultCode, data);
}

4. Expand CrmXForm.iOS project and open AppDelegate.cs file. Add below using statement.

using CrmXForm.Helper;
using Xamarin.Forms;

5. Replace inside FinishedLaunching method with following code.

public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{           
    global::Xamarin.Forms.Forms.Init();

    var application = new CrmXForm.App();
    window = new UIWindow(UIScreen.MainScreen.Bounds);
    window.RootViewController = application.MainPage.CreateViewController();
    window.MakeKeyAndVisible();
           
    CrmXFormHelper.uiViewController = window.RootViewController;

    LoadApplication(application);
    return true;
}

6. Add following code in AppDelegate class.

UIWindow window;

7. Expand CrmXForm.WinPhone and expand App.xaml, then open App.xaml.cs file. Add below using statements.

using Windows.ApplicationModel.Activation;
using CrmXForm.Helper;

8. Add following method in App class which will be called after Authentication.

private async void Application_ContractActivated(object sender, Windows.ApplicationModel.Activation.IActivatedEventArgs e)
{
    var webAuthenticationBrokerContinuationEventArgs = e as WebAuthenticationBrokerContinuationEventArgs;
    if (webAuthenticationBrokerContinuationEventArgs != null)
    {
        await CrmXFormHelper.Proxy.authContext.ContinueAcquireTokenAsync(webAuthenticationBrokerContinuationEventArgs);
    }
}

9. Add following code in InitializePhoneApplication method, which relates above code to ContractActivated event.

PhoneApplicationService.Current.ContractActivated += Application_ContractActivated;

10. Double click Package.appxmanifest file and click Capabilities tab. Check Internet (Client & Server).

image

Add MainPage

Finally, lets add a page to the project. As Xamarin.Forms support data binding natively, I am using MVVM model. I am including INotificationPropertyChanged and Relay command in the same file to simply the code, but I recommend to separate those into single files in real application.

1. Right click the CrmXForms Shared project and click Add | New Folder. Name it as View. Add ViewModel folder too.

2. Right click ViewModel folder and add a class file, name it as “MainPageViewModel.cs. Replace code with following. This ViewModel will be used by MainPage.xaml which you will add next.

using CrmXForm.Helper;
using System;
using System.ComponentModel;
using System.Windows.Input;

namespace CrmXForm.ViewModel
{
    public class MainPageViewModel : INotifyPropertyChanged
    {
        private string userName;
        public string UserName
        {
            get { return userName; }
            set
            {
                userName = value;
                NotifyPropertyChanged();
            }
        }

        public RelayCommand GetUserName
        {
            get
            {
                return new RelayCommand(async () =>
                {
                    UserName = await CrmXFormHelper.GetLoginUser();
                });
            }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        // Used to notify Silverlight that a property has changed.
        internal void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberNameAttribute] string propertyName = “”)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion

        #region RelayCommand

        public class RelayCommand : ICommand
        {
            private readonly Action _execute;
            private readonly Func<bool> _canExecute;

            public event EventHandler CanExecuteChanged;

            public RelayCommand(Action execute)
                : this(execute, null)
            {
            }

            public RelayCommand(Action execute, Func<bool> canExecute)
            {
                if (execute == null)
                    throw new ArgumentNullException(“execute”);
                _execute = execute;
                _canExecute = canExecute;
            }

            public bool CanExecute(object parameter)
            {
                return _canExecute == null ? true : _canExecute();
            }

            public void Execute(object parameter)
            {
                _execute();
            }

            public void RaiseCanExecuteChanged()
            {
                var handler = CanExecuteChanged;
                if (handler != null)
                {
                    handler(this, EventArgs.Empty);
                }
            }
        }

        #endregion       
    }
}

3. Right click View folder and add “Form Xaml Page”. Name it as MainPage, which will add MainPage.xaml and MainPage.xaml.cs files.

image

4. Open MainPage.xaml file and replace Label to Grid like below.

image

5. Open MainPage.xaml.cs. Replace the code with following.

using CrmXForm.ViewModel;
using Xamarin.Forms;

namespace CrmXForm.View
{
    public partial class MainPage : ContentPage
    {
        MainPageViewModel vm = new MainPageViewModel();
        public MainPage ()
        {
            InitializeComponent ();
            this.BindingContext = vm;
        }
    }
}

6. Now set the added page to initial page. Open App.cs. Replace the constructor code as following.

public App ()
{
    // The root page of your application
    MainPage = new MainPage();
}

7. Compile the solution.

Compile the solution.

Run the application

Let’s try Windows Phone first.

1. Right click CrmXForm.WinPhone project and click “Set as Startup Project”.

2. Select target and presss F5. I selected “Emulator 8.1 WVGA 4 inch 512MB”.

3. When the application launched, click the button.

image

4. When you prompted, enter user credential and click “Sign In”.

image

5. After you signed in wait for a while and the application displays login user’s fullname.

image

6. Let’s try iOS next. Right click CrmXForm.iOS project and click “Set as Startup Project”. Select target and press F5. I selected “iPhone 6 iOS 8.1” simulator.

image

7. When the application runs, you will see the button below. Click it.

image

8. You will be prompted. Enter user credential and click “Sign in”.

image

9.Fullname of login user will be displayed.

image

10. Do the same for Android, too.

Xamarin.Forms PCL and WinRT application?

The reason I used Xamarin.Forms shared project is because ADAL does not support it yet as I explained. In the future, ADAL may support Xamarin.Forms PCL, and it also supports WinRT application in addition to Silverlight. So what is the best choice? It is up to you, as there are several pros and cons to use Shared vs. PCL, and I do not have any recommendation myself.

What’s Next?

As I promised in previous article, I will show you how to use Web API developer preview.

Ken
Premier Mission Critical/Premier Field Engineer 
Microsoft Japan


Comments (11)

  1. tom says:

    Hi Ken,

    why I can't use Early bound?

    kind regards

    tom

  2. Tom says:

    Hi ken,

    I have used the CRM Service Utility for Mobile Development and generated the Model.cs.

    After that I have edit the EnableProxyTypes()….but it doesn't working

    Do you have any ideas?

    Tom

  3. tom says:

    Fixed!

    I forgot "await proxy.EnableProxyTypes();

    Sorry

  4. Tom,

    So is it working now?

    Ken

  5. tom says:

    Hi Ken,

    It is working. (early bound)

    Tom

  6. TonyHe2016 says:

    Hi,

    Now I still can not make it work…. I followed this article step by step but still can not get it. Is there anything wrong about the codes? Sorry I am really confused.

  7. TonyHe2016 says:

    Hi,
    Now I still can not make it work…. I followed this article step by step but still can not get it. Is there anything wrong about the codes? Sorry I am really confused.

    Tony

    1. What error do you see? Could you explain where you stuck a bit more detail please?

  8. Ahmmad says:

    Hi Ken,

    CrmXFormHelper.Proxy.authContext.SetAuthenticationAgentContinuationEventArgs(requestCode, resultCode, data);

    give me this error :
    ‘Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext’ does not contain a definition for ‘SetAuthenticationAgentContinuationEventArgs’ and no extension method ‘SetAuthenticationAgentContinuationEventArgs’ accepting a first argument of type ‘Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext’ could be found (are you missing a using directive or an assembly reference?)”

    How to solve this?

    kind regards
    ahmmad

    1. which version of ADAL are you using? and win platform? If you use Win10 and ADAL 3x, then how ADAL works has been changed. If you still on win81, then make sure you use ADAL x2

Skip to main content