Featured Post

Web API Requests Series

Web API Series:  Create and Retrieve , Update and Delete , Retrieve Multiple , Associate/Disassociate , Impersonation , Run Workflows

30 May 2011

Silverlight Bing Maps in Crm 2011 with REST endpoint

In this example I have used the CRM REST endpoint, see this post to learn how to use the CRM endpoint with a Silverlight project in Visual Stuido 2010.

The objective of this example is to create a Silverlight Web resource that can be included in a Microsoft Dynamics CRM solution and installed in any organization and any kind of deployment. Authentication is not an issue because the Silverlight Web resource will be included in the application.


...recently I happened to play a bit with the Bing Maps API for Silverlight (you can play as well from here) and thought it was a good idea to use it within the contact or account form entity to show a map based on the post code field of the entity. You can download the Bing Maps SDK from here.
So basically what I have done is to create a simple Silverlight application, to host the Bing Map, created a Silverlight web resource in CRM, added it to an entity form, remember to tick the "Pass record object-type code and unique identifier as parameter" checkbox when you create the web resource.
In this way you can then retrieve the unique GUID of the entity and pass it to the Silverlight application and use it to retrieve the post code on the form to pass it as input parameter to the Bing Map.
The snippet below shows how to access the parameters you passed from the CRM form to your Silverlight app:


            #region get the data passed from the CRM form context
            var queryStrings = HtmlPage.Document.QueryString;
            string recordGuid = string.Empty;
            if (queryStrings.ContainsKey("id"))
            {
                // it is also possible to get the GUID of the curent record from the query string
                string  entityId = queryStrings["id"].ToString();
                //get the GUID of the current record
                recordGuid = App.Current.Host.InitParams["id"];
                //get the current user language code i.e.1033 for English
                string userLcid = App.Current.Host.InitParams["userlcid"];
            }
            else
            {
                recordGuid = "87C203HB-C07F-E011-B487-000C29FD9425";
            }
            #endregion


For  a list of parameters that you may access from a Silverlight application embedded in an entity form see this post.


Now that we have our WCF data service in place, we need to create a Silverlight Web resource; we are actually going to create two web resources: one is the Silverlight xap web resource and the other is the HTML page that was created from Visual Studio if we have chosen to host our Silverlight application within a web application. We need to create the HTML web resource because we need to have access to the CRM client global context from our code behind in order to be able to retrieve information about our organization like: the server URL and Xrm.Page object e.g.
Ok, to do this we need to change few things in our HTML page


  1.  Remove or comment out this line: <script type="text/javascript" src="Silverlight.js"></script>
  2.  and add this line instead <script src="../ClientGlobalContext.js.aspx" type="text/javascript"></script> 

Now we can add our web resources. We need to pay a little attention at the way we create the web resources: I personally always follow these guidelines:

  1. HTML Web resource name = /MyApplicationName.html, this will result,after saving, in    customizationprefix_/MyApplicationName.html.
  2. HTML Web resource display name = MyApplicationName.html.
  3. XAP Web resource name = /ClientBin/MyApplicationName.xap, this will result,after saving, in                customizationprefix_/ClientBin/MyApplicationName.xap.
  4. XAP Web resource display name = MyApplicationName.xap.
Save and don't forget to publish the web resources.
All is left to do at this point is to create the code! :()

First of all let's create a helper class that will be used to retrieve our organization server URL:

public static class ServerUtility
    {
        /// <summary>
        /// Returns the ServerUrl from Microsoft Dynamics CRM
        /// </summary>
        /// <returns>String representing the ServerUrl or String.Empty if not found.</returns>
        public static String GetServerUrl()
        {
            String serverUrl = String.Empty;

            //Try to get the ServerUrl from the Xrm.Page object
            serverUrl = GetServerUrlFromContext();

            return serverUrl;
        }

        /// <summary>
        /// Attempts to retrieve the ServerUrl from the Xrm.Page object
        /// </summary>
        /// <returns></returns>
        private static String GetServerUrlFromContext()
        {
            try
            {
                // If the Silverlight is in a form, this will get the server url
                ScriptObject xrm = (ScriptObject)HtmlPage.Window.GetProperty("Xrm");
                ScriptObject page = (ScriptObject)xrm.GetProperty("Page");
                ScriptObject pageContext = (ScriptObject)page.GetProperty("context");

                String serverUrl = (String)pageContext.Invoke("getServerUrl");

                return serverUrl;
            }
            catch
            {
                return String.Empty;
            }
        }
    }

In our MainPage.xaml.cs add the following code:

public partial class MainPage : UserControl
{
     private SynchronizationContext _syncContext;

     private YourOrganizationNameContext _context;
     private string _serverUrl;
     private string _entityId = string.Empty;


public MainPage()
        {
            InitializeComponent();


            #region get the data passed from the CRM form context
            var queryStrings = HtmlPage.Document.QueryString;
            string recordGuid = string.Empty;
            if (queryStrings.Count > 1)
            {
                // it is also possible to get the GUID of the curent record from the query string
                string  entityId = queryStrings["id"].ToString();
                //get the GUID of the current record
                recordGuid = App.Current.Host.InitParams["id"];
                //get the current user language code i.e.1033 for English
                string userLcid = App.Current.Host.InitParams["userlcid"];
            }
            else
            {
                recordGuid = "87C203HB-C07F-E011-B487-000C29FD9425";
            }
            #endregion
            #region REST ENDPOINT

            //Keeps a reference to the UI thread
            _syncContext = SynchronizationContext.Current;

            //Get the ServerUrl (ServerUrl is formatted differently OnPremise than OnLine)
            _serverUrl = ServerUtility.GetServerUrl();

            if (!String.IsNullOrEmpty(_serverUrl))
            {

                //Setup Context
                _context = new ImpartaDevContext(
                    new Uri(String.Format("{0}/xrmservices/2011/organizationdata.svc/",
                        _serverUrl), UriKind.Absolute));

                //This is important because if the entity has new attributes added the code will fail.
                _context.IgnoreMissingProperties = true;

                //Retrieve the contact with its Guid
                BeginRetrieveContact(new Guid(recordGuid));
            }
            else
            {
                //No ServerUrl was found. Display message.
                MessagePanel.Children.Add(new TextBlock()
                {
                    Text =
                        "Unable to access server url. Launch this Silverlight " +
                        "Web Resource from a CRM Form OR host it in a valid " +
                        "HTML Web Resource with a " +
                        "<script src='../ClientGlobalContext.js.aspx' " +
                        "type='text/javascript'></script>"
                });
            }

        }
        /// <summary>
        /// Creates a DataServiceQuery to retrieve the current Contact
        /// </summary>
        /// <param name="Id"></param>
        private void BeginRetrieveContact(Guid Id)
        {
            try
            {
                DataServiceQuery<Contact> query = (DataServiceQuery<Contact>)_context
                    .ContactSet.Where<Contact>(c => c.ContactId == Id);

                query.BeginExecute(OnRetrieveContactComplete, query);
            }
            catch (DataServiceQueryException dsqe)
            {
                _syncContext.Send(new SendOrPostCallback(showErrorDetails), dsqe);
            }
        }
        ///<summary>
        /// Extracts the retrieved Contact from the query result.
        /// </summary>
        /// <param name="result"></param>
        private void OnRetrieveContactComplete(IAsyncResult result)
        {
            try
            {
                DataServiceQuery<Contact> results = result.AsyncState as DataServiceQuery<Contact>;

                Contact retrievedContact = new DataServiceCollection<Contact>         (results.EndExecute(result)).First<Contact>();
                
                _postCode = retrievedContact.Address1_PostalCode;

                GeocodeInput(_postCode);

                MessagePanel.Children.Add(new TextBlock() { Text = 
                    String.Format("Retrieved the contact named \"{0}\".",
                        retrievedContact.Address1_PostalCode)
                });                
            }
            catch (SystemException se)
            {
                _syncContext.Send(new SendOrPostCallback(showErrorDetails), se);
            }
        }
        /// <summary>
        /// Will display exception details if an exception is caught.
        /// </summary>
        /// <param name="ex">An System.Exception object</param>
        private void showErrorDetails(object ex)
        {
            //Assure the control is visible
            MessagePanel.Visibility = System.Windows.Visibility.Visible;

            Exception exception = (Exception)ex;
            String type = exception.GetType().ToString();

            MessagePanel.Children.Add(new TextBlock()
            {
                Text =
                    String.Format("{0} Message: {1}", type, exception.Message)
            });

            MessagePanel.Children.Add(new TextBlock()
            {
                Text = String.Format("Stack: {0}", exception.StackTrace)
            });

            if (exception.InnerException != null)
            {
                String exceptType = exception.InnerException.GetType().ToString();
                MessagePanel.Children.Add(new TextBlock()
                {
                    Text =
                        String.Format("InnerException: {0} : {1}", exceptType,
                        exception.InnerException.Message)
                });
            }
        }




#endregion

        #region Bing Map
        private int geocodesInProgress;
        private PlatformServices.GeocodeServiceClient geocodeClient;
        private PlatformServices.GeocodeServiceClient GeocodeClient
        {
            get
            {
                if (null == geocodeClient)
                {
                    //Handle http/https; OutOfBrowser is currently supported on the MapControl only for http pages
                    bool httpsUriScheme = !Application.Current.IsRunningOutOfBrowser && HtmlPage.Document.DocumentUri.Scheme.Equals(Uri.UriSchemeHttps);
                    BasicHttpBinding binding = httpsUriScheme ? new BasicHttpBinding(BasicHttpSecurityMode.Transport) : new BasicHttpBinding(BasicHttpSecurityMode.None);
                    UriBuilder serviceUri = new UriBuilder("http://dev.virtualearth.net/webservices/v1/GeocodeService/GeocodeService.svc");
                    if (httpsUriScheme)
                    {
                        //For https, change the UriSceheme to https and change it to use the default https port.
                        serviceUri.Scheme = Uri.UriSchemeHttps;
                        serviceUri.Port = -1;
                    }

                    //Create the Service Client
                    geocodeClient = new PlatformServices.GeocodeServiceClient(binding, new EndpointAddress(serviceUri.Uri));
                    geocodeClient.GeocodeCompleted += new EventHandler<PlatformServices.GeocodeCompletedEventArgs>(client_GeocodeCompleted);
                }
                return geocodeClient;
            }
        }

        private GeocodeLayer geocodeLayer;
        private GeocodeLayer GeocodeLayer
        {
            get
            {
                if (null == geocodeLayer)
                {
                    geocodeLayer = new GeocodeLayer(MyMap);
                }
                return geocodeLayer;
            }
        }

        private void GeocodeAddress(string address)
        {
            PlatformServices.GeocodeRequest request = new PlatformServices.GeocodeRequest();
            request.Culture = MyMap.Culture;
            request.Query = address;
            // Don't raise exceptions.
            request.ExecutionOptions = new PlatformServices.ExecutionOptions();
            request.ExecutionOptions.SuppressFaults = true;

            // Only accept results with high confidence.
            request.Options = new PlatformServices.GeocodeOptions();
            // Using ObservableCollection since this is the default for Silverlight proxy generation.
            request.Options.Filters = new ObservableCollection<PlatformServices.FilterBase>();
            PlatformServices.ConfidenceFilter filter = new PlatformServices.ConfidenceFilter();
            filter.MinimumConfidence = PlatformServices.Confidence.High;
            request.Options.Filters.Add(filter);

            Output.Text = "< geocoding " + address + " >";
            geocodesInProgress++;

            MyMap.CredentialsProvider.GetCredentials(
                (Credentials credentials) =>
                {
                    //Pass in credentials for web services call.
                    //Replace with your own Credentials.
                    request.Credentials = credentials;

                    // Make asynchronous call to fetch the data ... pass state object.
                    GeocodeClient.GeocodeAsync(request, address);
                });
        }

        private void client_GeocodeCompleted(object sender, PlatformServices.GeocodeCompletedEventArgs e)
        {
            // Callback when the geocode is finished.
            string outString;
            geocodesInProgress--;
            try
            {
                if (e.Result.ResponseSummary.StatusCode != PlatformServices.ResponseStatusCode.Success)
                {
                    outString = "error geocoding ... status <" + e.Result.ResponseSummary.StatusCode.ToString() + ">";
                }
                else if (0 == e.Result.Results.Count)
                {
                    outString = "No results";
                }
                else
                {
                    // Only report on first result.
                    outString = e.Result.Results[0].DisplayName;
                    Location loc = GeocodeLayer.AddResult(e.Result.Results[0]);
                    // Zoom the map to the location of the item.
                    MyMap.SetView(loc, 18);
                }
            }
            catch
            {
                outString = "Exception raised";
            }

            Output.Text = outString;
        }


        private void GeocodeInput(string postCode)
        {
            // Geocode whatever is in the textbox.
            string address = postCode;
            if (address.Length > 0)
            {
                GeocodeAddress(address);
            }
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            //GeocodeInput();
        }

        private void Input_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                //GeocodeInput();
            }
        }

        private void Input_GotFocus(object sender, RoutedEventArgs e)
        {
            Input.SelectAll();
        }
        #endregion


}

And this is the XAML:


<Grid x:Name="LayoutRoot" Background="White" HorizontalAlignment="Left" Width="600" Height="400">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="150"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="40"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
            <StackPanel Grid.ColumnSpan="2" Orientation="Horizontal" Grid.Column="0" Background="LightGray">
                <TextBox x:Name="Input" KeyDown="Input_KeyDown" GotFocus="Input_GotFocus" Height="25" Width="400" Margin="10,0,10,0" Text="type address or post code here"
                         FontSize="13" />
                <Button Content="Search" Height="25" Click="Button_Click"/>
                <TextBlock x:Name="Output" FontSize="10" Foreground="Red" Margin="14,0,0,0" VerticalAlignment="Center"/>
            </StackPanel>
     
        <m:Map CredentialsProvider="yourBingMapKey" x:Name="MyMap"
               Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Mode="Aerial" Center="51.216101,1.0"
               ZoomLevel="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
        <StackPanel x:Name="MessagePanel" Grid.Row="2" Grid.ColumnSpan="2" />
    </Grid>


The code shown in this example is taken from CRM SDK to retrieve a Contact and from the Bing maps interactive SDK.

Summary



  1. We have created a Silverlight application hosted in a web site
  2. Modified the html page that hosts the Silverlight object
  3. created two web resources, one for the xap and one for the html page
  4. Downloaded the CSDL file from CRM
  5. Created a WCF data service based on the CSDL file
  6. Passed parameters from CRM to Silverlight
  7. Used the Guid of the current Contact record to retrieve the postal code through the REST endpoint with a Data Service Query
  8. Passed the postal code as input to the Bing map to show the location on the map

This is the result:













Hope you found this article useful, and stay tuned for the next post. ;)

How to use the CSDL file generated by the REST endpoint with Silverlight.

In this walk through, I will walk you through the steps needed to download the CSDL file that the REST endpoint generates based on the entities available to your development organization. Later we will see how to create a WCF data service based on the CSDL file.


N.B. Only those entities that are present when you generate the classes will be available for the Silverlight application.
Steps:

  1. In CRM 2011 navigate to Settings. Choose Customizations and then Developer Resources.
  2. Under Service Endpoints, click the Download CSDL link and save the OrganizationData.csdl file to your computer.


Now that we have our file generated and downloaded to our computer, we can use it to create a WCF data service, so from Visual Studio:
  1. In your Visual Studio Silverlight application project, right-click References, and then click Add Service Reference.
  2. Type the path of the location where you saved the OrganizationData.csdl file, and then click Go.
  3. Enter an appropriate Namespace and then click OK.
The name of the System.Data.Services.Client context will be "YourOrganizationNameContext".

Well done! Now we have our Silverlight application all set up ready to access the entities in our CRM organization. In another post I will show how to use the REST endpoint with Silverlight.
Stay tuned!

26 May 2011

ALFAPEOPLE ITSM SUITE 2011

IT Infrastructure Library Service Management
The purpose of IT Service Management (ITSM) is to integrate IT strategy and the delivery of IT services with the goals of the business, with an emphasis on providing benefit to customers. The ITSM journey demands a shift in focus and culture, from managing IT within separate technology silos, to managing the end to end delivery of services using guidance from best practice frameworks such as the IT Infrastructure Library (ITIL)
ITIL (IT Infrastructure Library) provides the world’s most recognized best practices to manage IT services. AlfaPeople's solution is designed to help organizations optimize their existing IT service management practices. ITIL addresses methods for IT organizations to generate strategic business value and deliver quality IT services.

03 May 2011

Add Silverlight to an entity form Navigation Menu.

...another way to get advantage of the power of Silverlight within Crm 2011 is to use the whole entity form real estate. We can do this by adding a navigation item to an existing group. The navigation area is divided into five areas: Common, Sales, Service, Marketing and for entities that support them, Processes, We can either use the form editor using drag and drop to change their vertical position, add new items, move existing items to another area or use the "FormXml" element (I will talk about how to customise the FormXml element in another post) to achieve the same result.

N.B. "In Microsoft Dynamics CRM 2011 , the ability to add navigation items to the entity form has been moved from ISV.Config to the <FormXml> (FormXML) element for each entity form. During upgrade from Microsoft Dynamics CRM 4.0, any entity form navigation customizations in the ISV.Config are moved to the definition of the main form for that entity."

So, let's see how we can customise an entity navigation form; first of all we are going to add a new navigation item to an existing area (it is not possible to add a new area). Open an entity record select the Customize tab and click on "Form",







this will open the form editor, at this point click on "Navigation" select the area on the navigation menu that you want to customise,














select the "Insert" tab,









and click the Navigation Link button,
















Select a name, look up for an icon and the HTML web resource that hosts your Silverlight application that you have previously added.

And here is the final result.















Hope you enjoyed. :)