Dynamics NAV, Windows Azure & Windows Phone 7, Eric Wauters

Post on 13-Nov-2014

3.394 views 0 download

Tags:

description

 

Transcript of Dynamics NAV, Windows Azure & Windows Phone 7, Eric Wauters

Integrating Windows Phone 7 to Microsoft Dynamics NAV 2009R2

Eric Wauters

1

Development ManageriFacto Business Solutions NVWaldo’s blog (www.waldo.be)

2

Integrating Windows Phone 7 to Microsoft Dynamics NAV 2009R2

Eric WautersBackup for Freddy Kristiansen

Agenda

• Cloud? What is that all about? We’re all in .. but in what?

• Building a Windows Phone app that connects to NAV using the cloud.

Cloud computing?

Cloud computing is a pay-per-use model for enabling available, convenient, on-demand network access to a shared pool of configurable computing resources (e.g., networks, servers,

storage, applications, services) that can be rapidly provisioned and released with minimal management effort or service

provider interaction.

Cloud Computing?

Cloud Computing?

Rapid elasticity

Pay per use

Location independent resource pooling

Standard network access

On-demand self-service

Char

acte

ristic

s

Enterprise Cloud Triggers – DriversSpeed to Value1

Flexibility2

Cross Organizational Collaboration3

Cost Reduction 4

CapEx Avoidance5

Green IT6

CapEx Avoidance – Traditional IT

TIME

IT C

APAC

ITY

Actual Load

Allocated IT-capacities

“Waste“ of capacities

“Under-supply“ of capacities

Fixed cost of IT-capacities

Load Forecast

Barrier forinnovations

CapEx Avoidance – Cloud

Actual Load

Allocated IT capacities

Reduction of initial

investments

Reduction of “over-supply“

No “under-supply“

Possible reduction of IT-

capacities in case of reduced

load

Time

IT C

APAC

ITY

Load Forecast

Microsoft Online Services Platform

ON-PREMISES

BUSINESS APPSCOLLABORATION STORAGE PLATFORMIDENTITYCOMMUNICATIONSPRODUCTIVITY

CLOUD SERVICES

SERVICE ORDERS

Components in play

• NAV– Service Dispatchers RoleCenter– Customizations– Web Services, .net interop

• Proxy– C# Windows Service– Connecting to NAV Web Services– Exposing API on the Service Bus

• Windows Phone 7– Silverlight Application

• Cloud– Windows Azure Account

Windows AzureStorage

Service BusServices

Components - flow

NAVService

Tier

Firewall

Upload image

Register Notification

Channel

Window

s Auth

ProxySend Toast Notification

Services

Expose Proxy API on Azure AppFabric

Download image

Send Toast Notification

Get Service OrdersAccept Service Order

Change Order Status – In

ProcessChange Order Status –

FinishedAttach image URLAttach Note

Get Notification Channel Uri

Show map / Calculate route

SERVICE ORDERS – DEMO?

NAV customizations• Service Order card

– On Creating/Modifying Service Orders

• Send notifications (.net interop)

– Related Information – Images

• Download images from Azure storage

• Service Order Image table

• Exposes Web Services– For the Proxy

• Customer Card Page

• Service Order Card Page

• Special build Codeunit

NAVService

Tier

Windows Phone 7 Application• Connecting to NAV Web Services

– Via the Service Bus

• Get Service Orders

• Accept Service Order

• Change status on Service Order

• Attach images and notes to service orders

• Register notification channel (plumbing)

• Connecting to Azure Storage– To storage images

Windows Phone 7• Free Developer tools

– http://create.msdn.com/en-US/

– .net, C#, Silverlight

– Microsoft

• A TON of stuff on how to develop applications for Windows Phone already out there

Https Web Services Proxy - API

[ServiceContract]public interface IServiceOrderProxyClass{ [OperationContract] ServiceOrder[] GetServiceOrders(string username, string password);

[OperationContract] bool AcceptServiceOrder(string username, string password, string no);

[OperationContract] void ChangeServiceOrderStatus(string username, string password, string no, string status);

[OperationContract] void AttachServiceOrderImage(string username, string password, string no, string blobName);

[OperationContract] void AttachServiceOrderNote(string username, string password, string no, string note);

[OperationContract] void RegisterWp7ChannelUri(string username, string password, string channelUri);}

Https Web Services Proxy• Exposes endpoint on the Service Bus

• Connects to NAV Web Services

• Dedicated for Service Orders scenario

• Only expose necessary API– Isolating NAV from attacks

• Removes complexity

• Reduces roundtrips

• Loose coupling (device / NAV)– Possible to use other devices

Windows Azure Account• Windows Azure AppFabric (Service Bus)

– For communication between phone and on-premise NAV

• IssuerName, IssuerSecret

• Windows Azure Storage– For image storing / retrieving

• AccountName, AccessKey

STEP BY STEPWalkthrough

Exposing an API on Windows Azure AppFabric

NAVService

Tier

Firewall

Service Bus

Window

s Auth

ProxyExpose Proxy API on Azure AppFabric

Exposing an API on Windows Azure AppFabric

• Proxy– Create ServiceHost

– Add ServiceBus Endpoints

– Set Shared Secret credentials on endpoints

• IssuerName, IssuerSecret

– Start Servicehost

• Windows Azure AppFabric SDK– http://

www.microsoft.com/downloads/en/details.aspx?FamilyID=39856a03-1490-4283-908f-c8bf0bfad8a5

var serviceHost = new ServiceHost(new ServiceOrderProxyClass());

// sb:// bindingUri sbUri = ServiceBusEnvironment.CreateServiceUri("sb", "iFactoCloudDemo", instanceId);var sbBinding = new NetTcpRelayBinding(EndToEndSecurityMode.Transport, RelayClientAuthenticationType.None);serviceHost.AddServiceEndpoint(typeof(IServiceOrderProxyClass), sbBinding, sbUri);

// https:// binding (for Windows Phone etc.)Uri httpsUri = ServiceBusEnvironment.CreateServiceUri("https", "iFactoCloudDemo", "https/" + instanceId);var httpsBinding = new BasicHttpRelayBinding(EndToEndBasicHttpSecurityMode.Transport, RelayClientAuthenticationType.None);serviceHost.AddServiceEndpoint(typeof(IServiceOrderProxyClass), httpsBinding, httpsUri);

// Setup Shared Secret Credentials for hosting endpoints on the Service BusTransportClientEndpointBehavior sharedSecretServiceBusCredential = new TransportClientEndpointBehavior();sharedSecretServiceBusCredential.CredentialType = TransportClientCredentialType.SharedSecret;sharedSecretServiceBusCredential.Credentials.SharedSecret.IssuerName = issuerName;sharedSecretServiceBusCredential.Credentials.SharedSecret.IssuerSecret = issuerSecret;

// Set credentials on all endpoints on the Service Busforeach (ServiceEndpoint endpoint in serviceHost.Description.Endpoints) endpoint.Behaviors.Add(sharedSecretServiceBusCredential);

serviceHost.Open();

Register Notification Channel

NAVService

Tier

Firewall

Service Bus

Window

s Auth

ProxyExpose Proxy API on Azure AppFabric

Register Notification

Channel

Get Notification Channel Uri

• Phone– Creates a NotificationChannel using Windows Phone API

– Invokes RegisterWp7ChannelUri in Proxy through Web Services

• Proxy– Authenticates phone user

– Invokes RegisterWp7ChannelUri in Codeunit through Web Services

• NAV – Update/Insert record in Wp7Channel table

Register Notification Channelpublic void RegisterWp7ChannelUri(string username, string password, string channelUri){ var service = new ServiceOrderRef.ServiceOrder(); Authenticate(service, username, password); service.RegisterWp7ChannelUri("ServiceOrders", channelUri);}

RegisterWp7ChannelUri(Application : Text[40];ChannelUri : Text[250])Wp7Channel.SETRANGE(UserID, USERID);Wp7Channel.SETRANGE(Application, Application);IF Wp7Channel.FINDFIRST THENBEGIN Wp7Channel.ChannelUri := ChannelUri; Wp7Channel.MODIFY();END ELSEBEGIN CLEAR(Wp7Channel); Wp7Channel.INIT(); Wp7Channel.UserID := USERID; Wp7Channel.Application := Application; Wp7Channel.ChannelUri := ChannelUri; Wp7Channel.INSERT();END;

... HttpNotificationChannel.Find("waldo.ServiceOrders"); if (myChannel == null) { myChannel = new HttpNotificationChannel("waldo.ServiceOrders"); } SendURIToService(myChannel.ChannelUri); ...

private void SendURIToService(Uri uri){ var client = App.GetServiceOrderProxyClient(); client.RegisterWp7ChannelUriAsync(App.Settings.UserName, App.Settings.Password, uri.AbsoluteUri);}

Send Notification

NAVService

Tier

Firewall

Service Bus

Wndow

s Auth

ProxyExpose Proxy API on Azure AppFabricSend Toast Notification

Send Toast Notification

Send Toast Notification• NAV

– Loop through Wp7Channel table

– Using a WP7Helper class to send the notification (.Net Interop)

• Create XML Document

• Error handling

• Send request using HttpWebRequest / HttpWebResponse

BroadcastToastNotification(Application : Text[40];Header : Text[40];Text : Text[40])Wp7Channel.SETRANGE(Wp7Channel.Application, Application);IF Wp7Channel.FINDSET THEN BEGIN REPEAT Wp7Notifications.DoSendToastNotification(Wp7Channel.ChannelUri,2,Header,Text); UNTIL Wp7Channel.NEXT = 0;END;

DoSendToastNotification(Wp7Channel : Record Wp7Channel;Priority : Integer;Text1 : Text[40];Text2 : Text[40])IF Wp7Channel.NextAttempt > CURRENTDATETIME THEN EXIT;status := Wp7Helper.SendToastNotification(Wp7Channel.ChannelUri, Priority, Text1, Text2);//MESSAGE(FORMAT(status));CASE status OF1: BEGIN // Invalid Uri Wp7Channel.DELETE(); END;2: BEGIN // Queue Full - Wait one hour before reattempting this Uri Wp7Channel.NextAttempt := CURRENTDATETIME + 3600000; Wp7Channel.MODIFY; END;3: BEGIN // Service Unavailable - Wait one hour before reattempting this Uri Wp7Channel.NextAttempt := CURRENTDATETIME + 3600000; Wp7Channel.MODIFY; END;END;

Get Service Orders

NAVService

Tier

Firewall

Service Bus

Window

s Auth

ProxyExpose Proxy API on Azure AppFabric

Get Service Orders

GetServiceOrders• Phone

– Invoke GetServiceOrders in Proxy through Web Services

• Proxy– Reads Service Orders from card page

• Assigned to me

• Assigned to nobody

– Builds and returns ServiceOrderProxyClass collection

• With Customer info read from Customer Card

• Phone– Calculate distance from current location to each service location

– Update Collections (assigned, prioritized, closest)

private double CalculateDistance(double lat1, double lon1, double lat2, double lon2, char unit){ double theta = lon1 - lon2; double dist = Math.Sin(deg2rad(lat1)) * Math.Sin(deg2rad(lat2)) + Math.Cos(deg2rad(lat1)) * Math.Cos(deg2rad(lat2)) * Math.Cos(deg2rad(theta)); dist = rad2deg(Math.Acos(dist)) * 60 * 1.1515; if (unit == 'K') dist = dist * 1.609344; return (dist);}

private double deg2rad(double deg){ return (deg * Math.PI / 180.0);}

void LoadServiceOrders(){ var client = App.GetServiceOrderProxyClient(); client.GetServiceOrdersCompleted += new EventHandler<ServiceOrderProxy.GetServiceOrdersCompletedEventArgs>(client_GetServiceOrdersCompleted); client.GetServiceOrdersAsync(App.Settings.UserName, App.Settings.Password);}

void client_GetServiceOrdersCompleted(object sender, ServiceOrderProxy.GetServiceOrdersCompletedEventArgs e){ this.All.Clear(); foreach (ServiceOrderProxy.ServiceOrder serviceOrder in e.Result) { serviceOrder.Distance = CalculateDistance(CurrentLocation.Latitude, CurrentLocation.Longitude, serviceOrder.Latitude, serviceOrder.Longitude, unit); this.All.Add(serviceOrder); } UpdateCollections();}

public ServiceOrder[] GetServiceOrders(string username, string password){ ... var myFilter = new ServiceOrderCardRef.ServiceOrderCard_Filter(); myFilter.Field = ServiceOrderCardRef.ServiceOrderCard_Fields.Assigned_User_ID; myFilter.Criteria = "@" + service.GetMyUserID(); var myFilters = new ServiceOrderCardRef.ServiceOrderCard_Filter[] { myFilter }; var myOrders = serviceOrderService.ReadMultiple(myFilters, null, 0);

var unassignedFilter = new ServiceOrderCardRef.ServiceOrderCard_Filter(); unassignedFilter.Field = ServiceOrderCardRef.ServiceOrderCard_Fields.Assigned_User_ID; unassignedFilter.Criteria = "=''"; var unassignedFilters = new ServiceOrderCardRef.ServiceOrderCard_Filter[] { unassignedFilter }; var unassignedOrders = serviceOrderService.ReadMultiple(unassignedFilters, null, 0);

var serviceOrders = new ServiceOrder[myOrders.Length + unassignedOrders.Length]; for (int i = 0; i < myOrders.Length; i++) serviceOrders[i] = ServiceOrder.CreateServiceOrderClass(myOrders[i], customerCardService.Read(myOrders[i].Customer_No));

for (int i = 0; i < unassignedOrders.Length; i++) serviceOrders[i + myOrders.Length] = ServiceOrder.CreateServiceOrderClass(unassignedOrders[i], customerCardService.Read(unassignedOrders[i].Customer_No)); return serviceOrders;}

Accept Service Order

NAVService

Tier

Firewall

Service Bus

Window

s Auth

ProxyExpose Proxy API on Azure AppFabric

Accept Service Order

• Phone– Ask user if he wants to accept the Service Order

– Invoke AcceptServiceOrder in Proxy through Web Services

• Proxy– Read the Service Order from the Card Page through Web Services

– If the Service Order is assigned already – return false

– Set the Assigned_User_ID

– Write the Service Order again – if error – return false

• Phone– Display message and refresh data

Accept Service Order

void acceptAction_Click(object sender, EventArgs e){ if (MessageBox.Show("Do you want to accept this Service Order?", "Accept", MessageBoxButton.OKCancel) == MessageBoxResult.OK) { var client = App.GetServiceOrderProxyClient(); client.AcceptServiceOrderCompleted += new EventHandler<ServiceOrderProxy.AcceptServiceOrderCompletedEventArgs>(client_AcceptServiceOrderCompleted); client.AcceptServiceOrderAsync(App.Settings.UserName, App.Settings.Password, this.ServiceOrder.No); } }

public bool AcceptServiceOrder(string username, string password, string no){ var service = new ServiceOrderRef.ServiceOrder(); Authenticate(service, username, password);

var serviceOrderService = new ServiceOrderCardRef.ServiceOrderCard_Service(); Authenticate(serviceOrderService, username, password);

var serviceOrder = serviceOrderService.Read(no); if (!string.IsNullOrEmpty(serviceOrder.Assigned_User_ID)) return false; serviceOrder.Assigned_User_ID = service.GetMyUserID(); try { serviceOrderService.Update(ref serviceOrder); return true; } catch { return false; }}

void client_AcceptServiceOrderCompleted(object sender, ServiceOrderProxy.AcceptServiceOrderCompletedEventArgs e){ Deployment.Current.Dispatcher.BeginInvoke(() => { if (e.Result) { MessageBox.Show("ServiceOrder is now assigned to you!"); this.AssignedToMe = true; } else MessageBox.Show("ServiceOrder was already assigned to someone else"); App.ViewModel.LoadData(); UpdateApplicationBarButtons(); });}

Change Service Order Status

NAVService

Tier

Firewall

Service Bus

Window

s Auth

ProxyExpose Proxy API on Azure AppFabric

Change Service Order

Status

Change Service Order Status• Phone

– Change status on Service Order internally

– Invoke ChangeServiceOrderStatus in Proxy through Web Services

• Proxy– Read Service Order from the Card Page through Web Services

– Set the Status

– Update Starting Date/Time or Finishing Date/Time if necessary

– Write the Service Order again

private void ChangeServiceOrderStatus(ServiceOrderProxy.ServiceOrder serviceOrder, string status){ serviceOrder.Status = status; var client = App.GetServiceOrderProxyClient(); client.ChangeServiceOrderStatusAsync(App.Settings.UserName, App.Settings.Password, serviceOrder.No, status);}

public void ChangeServiceOrderStatus(string username, string password, string no, string statusStr){ var serviceOrderService = new ServiceOrderCardRef.ServiceOrderCard_Service(); Authenticate(serviceOrderService, username, password);

var status = (Status)Enum.Parse(typeof(Status), statusStr.Replace(' ', '_'), true); var serviceOrder = serviceOrderService.Read(no); serviceOrder.Status = status; serviceOrderService.Update(ref serviceOrder);}

Capture and Upload Image

NAVService

Tier

Firewall

Service Bus

Window

s Auth

ProxyExpose Proxy API on Azure AppFabric

Storage

Upload image

Capture and upload Image• Delay’s Blog

– http://blogs.msdn.com/b/delay/

– Blob API for Windows Phone 7

• Ms-PL (http://opensource.org/licenses/ms-pl.html)

• Phone– Capture Image

– Resize image

• to save time when uploading

– Upload image to Azure Storage

void cameraTask_Completed(object sender, PhotoResult e){ if (e.TaskResult == TaskResult.OK) { // Using WriteableBitmap's SaveJpeg to resize var bitmapImage = new BitmapImage(); bitmapImage.SetSource(e.ChosenPhoto); var bitmap = new WriteableBitmap(bitmapImage); var ms = new MemoryStream(); bitmap.SaveJpeg(ms, 1024, 768, 0, 90); var photo = ms.ToArray();

// Upload photo blobClient = new AzureBlobStoreClient(AzureStorageAccountName, AzureStoragePrimaryAccessKey, "images"); blobInfo = new BlobInfo(Guid.NewGuid().ToString()); blobClient.PutBlob(blobInfo, photo.Length, (s) => PutBlobAction(s, photo), PutBlobCompleted, (ex) => PutBlobFailed(ex)); }}

... CameraCaptureTask cameraTask; ... cameraTask = new CameraCaptureTask(); cameraTask.Completed += new EventHandler<PhotoResult>(cameraTask_Completed); ...

void cameraAction_Click(object sender, EventArgs e){ cameraTask.Show(); //shows camera to take the picture}

void PutBlobAction(Stream s, byte[] photo){ s.Write(photo, 0, photo.Length);}

void PutBlobCompleted(){ Deployment.Current.Dispatcher.BeginInvoke(() => AttachServiceOrderImage(this.ServiceOrder, blobInfo.Name));}

private void PutBlobFailed(Exception ex){ Deployment.Current.Dispatcher.BeginInvoke(() => MessageBox.Show("No connectivity.”));}

Attach Image / Note

NAVService

Tier

Firewall

Service Bus

Window

s Auth

ProxyExpose Proxy API on Azure AppFabric

Attach Image / Note

Storage

Attach Image / Note• Phone

– Invoke AttachServiceOrderImage/Note in Proxy through Web Services with Uri to Azure Storage blob or note

– Display message box when image uploaded

• Proxy– Invoke AttachServiceOrderImage/Note in NAV Codeunit through Web

Services with Uri or note

• NAV– Insert Image Uri in ServiceOrderImage Table or add note to RecordLink

table

private void AttachServiceOrderImage(ServiceOrderProxy.ServiceOrder serviceOrder, string blobName){ var client = App.GetServiceOrderProxyClient(); client.AttachServiceOrderImageCompleted += new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>(client_AttachServiceOrderImageCompleted); client.AttachServiceOrderImageAsync(App.Settings.UserName, App.Settings.Password, serviceOrder.No, blobName);}

void client_AttachServiceOrderImageCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e){ Deployment.Current.Dispatcher.BeginInvoke(() => MessageBox.Show("Image uploaded.")); }

private void Save_Click(object sender, EventArgs e){ var client = App.GetServiceOrderProxyClient(); client.AttachServiceOrderNoteAsync(App.Settings.UserName, App.Settings.Password, ID, this.textBox1.Text); NavigationService.GoBack();}

public void AttachServiceOrderImage(string username, string password, string no, string blobName){ var service = new ServiceOrderRef.ServiceOrder(); Authenticate(service, username, password); service.AttachServiceOrderImage(no, blobName);}

public void AttachServiceOrderNote(string username, string password, string no, string note){ var service = new ServiceOrderRef.ServiceOrder(); Authenticate(service, username, password); service.AttachServiceOrderNote(no, note);}

AttachServiceOrderImage(No : Code[20];Image : Text[40])ServiceOrderImage.INIT;ServiceOrderImage."Document No." := No;ServiceOrderImage."Image ID" := Image;ServiceOrderImage.INSERT(TRUE);

AttachServiceOrderNote(No : Code[20];Note : Text[250])ServiceHeader.GET(ServiceHeader."Document Type"::Order, No);RecRef.GETTABLE(ServiceHeader);RecordLink.INIT();RecordLink.Type := RecordLink.Type::Note;RecordLink.Created := CURRENTDATETIME;RecordLink."User ID" := USERID;RecordLink.URL1 := 'dynamicsnav://freddyk-appfabr:7047/DynamicsNAV/'+COMPANYNAME+'/runpage?page=5900&personalization=5900&'+ 'bookmark='+FORMAT(RecRef.RECORDID,0,10)+'&mode=edit';RecordLink.Description := 'Service Order - '+No;RecordLink.Company := COMPANYNAME;RecordLink."Record ID" := RecRef.RECORDID;CLEAR(RecordLink.Note);Note := ' '+Note;Note[1] := STRLEN(Note)-1;NoteText.ADDTEXT(Note);RecordLink.Note.CREATEOUTSTREAM(NoteStream);NoteText.WRITE(NoteStream);RecordLink.INSERT();

Download and display image

NAVService

Tier

Firewall

Service Bus

Window

s Auth

ProxyExpose Proxy API on Azure AppFabric

Storage

Download image

• NAV– Create .net class AzureStorage

• Using AccountName and AccessKey

• Wrapper for AzureBlobStoreClient (Delay’s Blog)

– Download all blobs for a specific Service Order to the Service Tier

– Use File.Download to show images on the Client

Download and display image

<Action1170000001> - OnAction()AzureStorage := AzureStorage.AzureStorage(AzureStorageAccountName, AzureStorageAccessKey);ServiceOrderImage.SETRANGE(ServiceOrderImage."Document No.", "No.");IF NOT ServiceOrderImage.FIND('-') THENBEGIN MESSAGE('No Images attached');END ELSEREPEAT AzureStorage.GetBlob('images', ServiceOrderImage."Image ID", TEMPORARYPATH+ServiceOrderImage."Image ID"+'.jpg'); toFile := ServiceOrderImage."Image ID"+'.jpg'; FILE.DOWNLOAD(TEMPORARYPATH+ServiceOrderImage."Image ID"+'.jpg', 'Service Order Image','', '', toFile);UNTIL ServiceOrderImage.NEXT = 0;

public class AzureStorage{ string AccountName; string AccessKey;

public AzureStorage(string AccountName, string AccessKey) { this.AccountName = AccountName; this.AccessKey = AccessKey; }

public void GetBlob(string container, string name, string filename) { EnsureContainerIsCreated(container); var blobClient = new AzureBlobStoreClient(AccountName, AccessKey, container); var blobInfo = new BlobInfo(name); blobClient.GetBlob(blobInfo, (s) => GetBlobAction(s, filename)); }

...

44

More info

• Will soon be published on:– http://blogs.msdn.com/b/freddyk

• Related info:– Vjeko’s blog (navigateintosuccess.com)

– Waldo’s blog (www.waldo.be)

– Mibuso

– Dynamicsuser.net

– ...

45

Questions?