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.
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.
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.
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.
7. Then search “HttpClient” and select “Microsoft HTTP Client Libraries”, and click Install. Select CrmXForm.WinPhone project and click “OK”
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”
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.
13. Click iOS Application on the left pane, and select 8.1 for Deployment Target and remove Launch Storyborad. I selected 8.1 here.
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.
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 .
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).
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.
4. Open MainPage.xaml file and replace Label to Grid like below.
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.
4. When you prompted, enter user credential and click “Sign In”.
5. After you signed in wait for a while and the application displays login user’s fullname.
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.
7. When the application runs, you will see the button below. Click it.
8. You will be prompted. Enter user credential and click “Sign in”.
9.Fullname of login user will be displayed.
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