Posted on 11. March 2017

Simplified Connection Management & Thread Safety (Revisited)

There is one certainty in the world and that is that things don't stay the same! In the Dynamics 365 world, this is no exception, with new features and SDK features being released with a pleasing regularity. Writing 'revisited' posts has become somewhat of a regular thing these days.

In my previous post on this subject back in 2013 we looked at how you could use a connection dialog or connection strings to get a service reference from the Microsoft.Xrm.Client library and how it can be used in a thread safe way.

Microsoft.Xrm.Tooling

For a while now there has been a replacement for the Microsoft.Xrm.Client library – the Microsoft.Xrm.Tooling library. It can be installed from NuGet using:

Install-Package Microsoft.CrmSdk.XrmTooling.CoreAssembly

When you use the CrmServerLoginControl, the user interface should look very familiar because it's the same that is used in all the SDK tools such that Plugin Registration Tool.

The sample in the SDK shows how to use this WPF control.

The WPF control works slightly differently to the Xrm.Client ShowDialog() method – since it gives you much more flexibility over how the dialog should behave and allows embedding inside your WPF application rather than always having a popup dialog.

Connection Strings

Like the dialog, the Xrm.Tooling also has a new version of the connection string management – the new CrmServiceClient accepts a connection string in the constructor. You can see examples of these connection strings in the SDK.

CrmServiceClient crmSvc = new CrmServiceClient(ConfigurationManager.ConnectionStrings["Xrm"].ConnectionString);

For Dynamics 365 online, the connection would be:

<connectionStrings>
    <add name="Xrm" connectionString="AuthType=Office365;Username=jsmith@contoso.onmicrosoft.com; Password=passcode;Url=https://contoso.crm.dynamics.com" />
</connectionStrings>

Thread Safety

The key to understanding performance and thread safety of calling the Organization Service is the difference between the client proxy and the WCF channel. As described by the 'Improve service channel allocation performance' topic from the best practice entry in the SDK, the channel should be reused because creating it involves time consuming metadata download and user authentication.

The old Microsoft.Xrm.Client was thread safe and would automatically reuse the WCF channel that was already authenticated. The Xrm.Tooling CrmServiceClient is no exception. You can create a new instance of CrmServiceClient and existing service channels will be reused if one is available on that thread. Any calls the same service channel will be locked to prevent thread issues.

To demonstrate this, I first used the following code that ensures that a single CrmServiceClient is created per thread.

Parallel.For(1, numberOfRequests,
    new ParallelOptions() { MaxDegreeOfParallelism = maxDop },
    () =>
    {
        // This is run for each thread
        var client = new CrmServiceClient(username,
               CrmServiceClient.MakeSecureString(password),
               "EMEA",
               orgname,
               useUniqueInstance: false,
               useSsl: false,
               isOffice365: true);
        
        return client;
    },
    (index, loopState, client) =>
    {
        // Make a large request that takes a bit of time
        QueryExpression accounts = new QueryExpression("account")
        {
            ColumnSet = new ColumnSet(true)
        };
        client.RetrieveMultiple(accounts);
        return client;
    },
    (client) =>
    {
    });

With a Degree of Parallelism of 4 (the number of threads that can be executing in parallel) and a request count of 200, there will be a single CrmServiceClient created for each thread and the fiddler trace looks like this:

Now to prove that the CrmServiceClient handles thread concurrency automatically, I moved the instantiation into loop so that every request would create a new client:

Parallel.For(1, numberOfRequests,
    new ParallelOptions() { MaxDegreeOfParallelism = maxDop },
    (index) =>
    {
        // This is run for every request
        var client = new CrmServiceClient(username,
               CrmServiceClient.MakeSecureString(password),
               "EMEA",
               orgname,
               useUniqueInstance: false,
               useSsl: false,
               isOffice365: true);
        // Make a large request that takes a bit of time
        QueryExpression accounts = new QueryExpression("account")
        {
            ColumnSet = new ColumnSet(true)
        };
        client.RetrieveMultiple(accounts);
    });

Running this still shows a very similar trace in fiddler:

This proves that the CrmServiceClient is caching the service channel and returning a pre-authenticated version per thread.

In contrast to this, if we set the useUniqueInstance property to true on the CrmServiceClient constructor, we get the following trace in fiddler:

So now each request is re-running the channel authentication for each query – far from optimal!

The nice thing about the Xrm.Tooling library is that it is used exclusively throughout the SDK – where the old Xrm.Client was an satellite library that came from the legacy ADX portal libraries.

Thanks to my friend and fellow MVP Guido Preite for nudging me to write this post!

@ScottDurow

Posted on 20. February 2017

How to get assistance without the Relationship Assistant!

Dynamics 365 has brought with it a new and amazing feature called the 'Relationship Assistant'. It is part of a preview feature (unsupported and US only) called 'Relationship Insights' which promises to bring some amazing productivity tools to the Dynamics 365 platform.

Relationship Assistant shows actionable cards in both the web client and mobile client using a mix of both plain old filter conditions and machine learning.

Machine Learning Cards

One of the most exciting part of the Relationship Assistant is the use of machine learning to examine the contents of your emails and predict what you need to do next:

Customer Question Card

Issue Detected Card

'Plain old query' Cards

Whilst the machine learning aspects may be out of our reach to us mere mortals at this time, the cards that are based on simpler filter conditions such as 'Due Today' and 'Meeting Today' are items that can be easily shown in a dashboard without this preview feature. Here are some examples of information that can be gained from simple date queries:

Due Today Card

Meeting Today Card

Missed Close Date Card

(Images taken from the Relationship Assistant Card reference - https://www.microsoft.com/en-us/dynamics/crm-customer-center/preview-feature-action-cards-reference.aspx)

Create your own 'Relationship Assistant' Dashboard

The main challenge with producing information shown above is the date aspect to the query. We can easily show a single set of records that use the 'Next X days' type of operator, but you could not easily use 'todays' date in a dashboard chart – at least not until CRM2015 introduced calculated fields. Now it is rather easy to produce a dashboard similar to the following:

  • The key feature of dashboards is that they are can be tailored to show your own data which can be drilled into to show the underlying records. This is comparable to the 'actionable' aspect of the relationship assistant where you could drill into the tasks due to 'today' and open them to work upon.

    Notice the field 'Due' that can have the value 'Completed', 'Due Next 7 Days', 'Due Today', 'Overdue', or 'Scheduled'. This field isn't stored as a persistent field in the database, but instead it is a calculated field so there are no nightly jobs or workflows required to update a field based on the current date.

    Adding a 'Due Status' field to an Activity Entity

    1. Create a solution with the Activity Entity that you want to add the 'Due Status' field to
    2. Create a new field called 'Due Diff' – this will give us a field that shows the number of days before/after the activity due date.
    3. Click 'Edit' and type the expression
      DiffInDays(scheduledstart, Now())
      Note: This assumes that this is an Appointment and you want to use the scheduledstart date to control the due date.
    4. Add a new global Option Set that holds the possible values for the Due status
    5. Create a new Calculated Option Set field called 'Due' on the Activity record. Use the Existing Option Set created above.
    6. Click 'Edit' on the Calculated Field type and add the following logic:
    7. Create a chart something like:
    8. Publish and add the charts to a dashboard!

    Of course other more complex options exist but with all the excitement and awesomeness of Machine Learning it is important to remember that we can achieve great things with just the right kind of queries, charts and dashboards!

    Hope this helps!

    Posted on 19. February 2016

    SharePoint Integration Reloaded – Part 5 (belated)

    In previous articles in this series we've talked about the differences between Server Side Sync and the old List Component. Since I published the first articles, a new MSDN article on the topic has been posted which I thought would be good to signpost folks to => Important considerations for server-based SharePoint integration.

    One of the topics that has come up recently for people using Server Side Sync to SharePoint is the 5000 item limit of Document Libraries which has led to a bit of panic amongst some so I thought I'd dispel the rumours!    

    Here are the facts about the Throttling Limitation

    1) You can see how many items you have in a Document Library by opening the site in SharePoint and selecting 'Site Content' from the left hand navigation menu. The number of items will show below the Document Library name – this includes documents and folders.

    2) If you have more than 5000 items you can still use Server Side Integration with SharePoint provided that you only use the default sort order of the document view in CRM.

    The default sort is by Location Ascending. You can change this to sort Descending but if you change the sort to any other column you will receive the error "Throttling limit is exceeded by the operation"

    3) If you have a record with only 2 documents in the associated document locations folder you will still not be able to sort by any other column other than Location if the root Document Library has more than 5000 items overall.

    3) If the user clicks on 'Open SharePoint' then they will be able to do all the sorting they need since the limitation is not experienced by the native SharePoint interface – only the CRM document lists.

    I find that this sort limitation is not an issue because I encourage users to use SharePoint freely due to its rich interface. Don't try and hide SharePoint from them because it's important to understand the way in which documents are stored and the additional features that SharePoint has to offer. The documents view in CRM is simply to be used as a quick reference for documents associated with the CRM Record.

    Hope this helps!

    Posted on 21. November 2015

    Option-Set, Lookup or Autocomplete

    In the constant struggle to improve data quality it is common to avoid using free-text fields in favour of select fields. This approach has the advantage of ensuring that data is entered consistently such that it can easily be searched and reported upon.

    There are a number of choices of approaches to implementing select fields in Dynamics CRM. This post aims to provide all the information you need to make an informed choice of which to use on a field by field basis. The options are:

    • Option-Set Field - stored in the database as an integer value which is rendered in the user interface as a dropdown list. The labels can be translated into multiple languages depending on the user's language selection.
    • Lookup Field - a search field that is stored in the database as a GUID ID reference to another entity record. The Lookup field can only search a single entity type.
    • Auto Complete Field - a 'free text' field that has an auto complete registered using JavaScript to ensure that text is entered in a consistent form. The term 'autocomplete' might be a bit misleading since the field is not automatically completed but instead you must select the suggested item from the list. This is a new feature in CRM 2016 that you can read more about in the preview SDK.
    The following table provides an overview of the aspects that this post discusses for each option:
    • Number of items in list – The larger the list and the likelihood that it will grow, the more this becomes important.
    • Filtering based on user's business unit - This is especially important where you have different values that apply to different parts of the business and so the list of options must be trimmed to suit.
    • Adding new Items - Ease of adding values frequently by business users.
    • Removing values – Ease of removing values without affecting existing records that are linked to those values.
    • Multi-Language – Having options translated to the user's selected language
    • Dependant/Filtered options - This is important where you have one select field that is used to filter another such as country/city pairs.
    • Additional information stored against each option - This is important if you have information that you need to store about the selected item such as the ISO code of a country.
    • Mapping between entities - Is the option on multiple entity types? Often the same list of items is added as a field in multiple places and multiple entities. This can be important when using workflows/attribute maps to copy values between different entity types.
    • Number of select fields - The more select fields you have across all your entities, the more important this becomes.
    • Filters, Reports and Advanced Find - When creating advanced finds and views, a user should be able to select from a list of values rather than type free text.
    • Configure once, deploy everywhere – One key design goal should be that once a field is configured, it should be easily used across the web, outlook, tablet and phone clients.

    Option-Set Fields

    Option-Sets are the default starting point for select fields.

    Number of items in list (Option-sets)

    No more than ~100 for performance reasons. All items are downloaded into the user interface which will cause performance problems for large lists – especially where there are lots of option-sets on the same form.

    Filtering based on user's business unit (Option-sets)

    Requires JavaScript to filter items dynamically based on the user's role/business unit.

    Ease of adding values frequently by business users (Option-sets)

    Option-Sets require a metadata configuration change and a re-publish that would usually be done out of hours by an administrator. It is best practice to do this on a development environment and then import a solution into production. Adding new values to the list isn't something that can be done by business users.

    Removing values over time (Option-sets)

    Removing items causes data loss in old records. Items can be removed using JavaScript to preserve old records, but Advanced Find will still show the values.

    Multi-Language Options (Option-sets)

    Each option-set item can be translated into multiple languages.

    If you need to have the select field multi-language then an option-set is probably your best choice unless it is going to be a long list, in which case you'll need to make a compromise.

    Dependant/Filtered options (Option-sets)

    Requires JavaScript to filter options.

    Additional information stored against each option (Option-sets)

    It is not possible to store additional data other than the label and integer value of the option-set. You would need to store somewhere else in a lookup table format.

    Mapping between entities (Option-sets)

    Use a global option-set that can be defined once and used by multiple option-set fields.

    Number of select fields (Option-sets)

    You can have as many select fields as your entity forms will allow. The more fields you have the slower the form will load and save. 

    Search/Filtering (Option-sets)

    Option-sets are always presented as a drop down in advanced fine and view filters.

    Configure once, deploy everywhere (Option-sets)

    Works across all clients including phone and tablet native apps.

    Option-sets are the most 'native' choice for select fields and will work in all deployment types without much consideration.

     

    Lookup Fields with Custom Entity

    Lookup fields allow selecting a single reference to a custom entity using a free text search.

    Number of items in list (Lookup)

    Unlimited list items subject to database size. Since all list items are not downloaded to the user interface (unlike option-sets) the user can search quickly for the item they need.

    Filtering based on user's business unit (Lookup)

    Security Roles can be used in combination with a user owned lookup entity so that lookup records are visible to subset of users.

    Ease of adding values frequently by business users (Lookup)

    New records can easily be added by users using the 'Add New' link. Control over who can add new items can be done using Security Roles.

    If you need business users to add values regularly then a Lookup field is a good choice. The Configuration Migration tool in the SDK can be used to easily move values between environments.

     

    Removing values over time (Lookup)

    Old items can be easily deactivated and will no longer show in lookup fields (including in advanced finds) however existing records will retain their selected value (unlike when option-set items are removed).

    If you need to make changes constantly to the list and remove items without affecting previous records then a lookup field is most likely your best choice.

     

    Multi-Language Options (Lookup)

    Not possible without complex plugin server side code to dynamically return the name in the current user's language.

    Dependant/Filtered options (Lookup)

    Lookup filtering options can be added in the form field properties or can be added via JavaScript for more complex scenarios.

    Lookups are the easiest and quickest to setup dependant lists without writing code. This filtering will also work on tablet/mobile clients without further consideration.

    Additional information stored against each option (Lookup)

    Additional information can be stored as attributes on the lookup entity records. Lookup views can show up to 2 additional attributes within the inline lookup control.

    If you are integrating with another system that requires a special foreign key to be provided, lookup entities are good way of storing this key.

     

    Mapping between entities (Lookup)

    Lookups can easily be mapped between records using attribute maps/workflows or calculated fields.

    Number of select fields (Lookup)

    CRM Online is limited to 300 custom entities.

    This is an important consideration and it's unlikely to be a good idea to use Lookup entities for all of your select fields.
    If you are using CRM online you'll likely have to always use a combination of lookups and option-sets due to the limit of 300 custom entities. Don't take the decision to make all your select fields as lookups.

     

    Search/Filtering (Lookup)

    Lookups are presented as search fields in Advanced Find and Filters.

    Configure once, deploy everywhere (Lookup)

    Works across all clients including phone and tablet native apps. If working offline however, all lookup values may not be available.

    Text Field Auto Completes (CRM 2016)

    Autocompletes are a free text field with an on key press event added to show an autocomplete. The great thing about autocompletes is that they can show icons and additional action links.See below for an example of how to use autocompletes in Javascript.

    Number of items in list (Autocomplete)

    An autocomplete field can only show as many items as you return at a time but you'll want to put a limit for performance reasons.

    If you need the select field to be more like a combo-box where users can type their own values or select from predefined items then autocomplete text fields are a good choice.

     

    Filtering based on user's business unit (Autocomplete)

    You can add any search filtering you need using JavaScript.

     

    Ease of adding values frequently by business users (Autocomplete)

    If the autocomplete is using a lookup entity to store the data displayed then the same considerations would apply as for Lookup Entities. If the values are hard coded into the JavaScript then this would be more akin to the Option-Set solution import.

    Removing values over time (Autocomplete)

    Since the actual field is stored as a text field there is no issue with removing values. Existing data will still be preserved.

    Multi-Language Options (Autocomplete)

    You can detect the user interface language and return a specific language field to the user via JavaScript however it will be stored in the textbox and other users will not see it in their language (unlike an option-set). One solution to this would be to use the autocomplete for data entry and then use a web resource to present the field value in the local user's language.

    Dependant/Filtered options (Autocomplete)

    You can apply whatever filtering you need using JavaScript.

    Additional information stored against each option (Autocomplete)

    If you use the autocomplete to search a custom entity you can store additional data as additional attributes. The autocomplete flyout can display multiple values for each result row.

    Autocomplete fields have the advantage that they can show an icon that is specific to the record (e.g. The flag of the country). If you need this feature, then Auto completes are a good choice.

     

    Search/Filtering (Autocomplete)

    If you use a free text autocomplete it's advisable to additionally populate a backing lookup field to facilitate searching/filtering. This would also allow you to ensure that 'unresolved' values cannot be saved by using an OnSave event to check that the text field matches a hidden lookup field that is populated in the OnChange event.

    Configure once, deploy everywhere (Autocomplete)

    Autocomplete does not work on phone/tablet native apps yet.

    Show me the Code!

    I have added support for the Auto Complete SDK extensions in CRM2016 to SparkleXRM. To show a country autocomplete lookup, you'd add onload code similar to:

    public static void OnLoad()
    {
        Control control = Page.GetControl("dev1_countryautocomplete");
        control.AddOnKeyPress(OnCountrySearch);
    }
    
    public static void OnCountrySearch(ExecutionContext context)
    {
        string searchTerm = Page.GetControl("dev1_countryautocomplete").GetValue<string>();
        string fetchXml = String.Format(@"<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>
                                  <entity name='dev1_country'>
                                    <attribute name='dev1_countryid' />
                                    <attribute name='createdon' />
                                    <attribute name='dev1_name' />
                                    <attribute name='dev1_longname' />
                                    <attribute name='dev1_iso' />
                                    <attribute name='entityimage_url' />
                                    <order attribute='dev1_name' descending='false' />
                                    <filter type='and'>
                                      <condition attribute='dev1_name' operator='like' value='{0}%' />
                                    </filter>
                                  </entity>
                                </fetch>", searchTerm);
    
        OrganizationServiceProxy.BeginRetrieveMultiple(fetchXml, delegate(object state)
        {
            try
            {
                // We use an aysnc call so that the user interface isn't blocked whilst we are searching for results
                EntityCollection countries = OrganizationServiceProxy.EndRetrieveMultiple(state, typeof(Entity));
                AutocompleteResultSet results = new AutocompleteResultSet();
    
                // The Autocomplete can have an action button in the footer of the results flyout
                AutocompleteAction addNewAction = new AutocompleteAction();
                addNewAction.Id = "add_new";
                addNewAction.Icon = @"/_imgs/add_10.png";
                addNewAction.Label = "New";
                addNewAction.Action = delegate()
                {
                    OpenEntityFormOptions windowOptions = new OpenEntityFormOptions();
                    windowOptions.OpenInNewWindow = true;
                    Utility.OpenEntityForm2("dev1_country", null,null, windowOptions);
                };
                results.Commands = addNewAction;
                results.Results = new List<AutocompleteResult>();
                
                // Add the results to the autocomplete parameters object
                foreach (Entity country in countries.Entities)
                {
                    AutocompleteResult result = new AutocompleteResult();
                    result.Id = country.Id;
                    result.Icon = country.GetAttributeValueString("entityimage_url");
                    result.Fields = new string[] { country.GetAttributeValueString("dev1_name"),                           
                        country.GetAttributeValueString("dev1_iso"),
                         country.GetAttributeValueString("dev1_longname")
                    };
                    ArrayEx.Add(results.Results, result);                    
                }
                if (results.Results.Count > 0)
                {
                    // Only show the autocomplete if there are results
                    context.GetEventSource().ShowAutoComplete(results);
                }
                else
                {
                    // There are no results so hide the autocomplete
                    context.GetEventSource().HideAutoComplete();
                }
            }
            catch(Exception ex)
            {
                Utility.AlertDialog("Could not load countries: " + ex.Message, null);
            }   
        });
    }
    

    This would result in JavaScript:

    ClientHooks.Autocomplete = function ClientHooks_Autocomplete() {
    }
    ClientHooks.Autocomplete.onLoad = function ClientHooks_Autocomplete$onLoad() {
        var control = Xrm.Page.getControl('dev1_countryautocomplete');
        control.addOnKeyPress(ClientHooks.Autocomplete.onCountrySearch);
    }
    ClientHooks.Autocomplete.onCountrySearch = function ClientHooks_Autocomplete$onCountrySearch(context) {
        var searchTerm = Xrm.Page.getControl('dev1_countryautocomplete').getValue();
        var fetchXml = String.format("<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>\r\n                                      <entity name='dev1_country'>\r\n                                        <attribute name='dev1_countryid' />\r\n                                        <attribute name='createdon' />\r\n                                        <attribute name='dev1_name' />\r\n                                        <attribute name='dev1_longname' />\r\n                                        <attribute name='dev1_iso' />\r\n                                        <attribute name='entityimage_url' />\r\n                                        <order attribute='dev1_name' descending='false' />\r\n                                        <filter type='and'>\r\n                                          <condition attribute='dev1_name' operator='like' value='{0}%' />\r\n                                        </filter>\r\n                                      </entity>\r\n                                    </fetch>", searchTerm);
        Xrm.Sdk.OrganizationServiceProxy.beginRetrieveMultiple(fetchXml, function(state) {
            try {
                var countries = Xrm.Sdk.OrganizationServiceProxy.endRetrieveMultiple(state, Xrm.Sdk.Entity);
                var results = {};
                var addNewAction = {};
                addNewAction.id = 'add_new';
                addNewAction.icon = '/_imgs/add_10.png';
                addNewAction.label = 'New';
                addNewAction.action = function() {
                    var windowOptions = {};
                    windowOptions.openInNewWindow = true;
                    Xrm.Utility.openEntityForm('dev1_country', null, null, windowOptions);
                };
                results.commands = addNewAction;
                results.results = [];
                var $enum1 = ss.IEnumerator.getEnumerator(countries.get_entities());
                while ($enum1.moveNext()) {
                    var country = $enum1.current;
                    var result = {};
                    result.id = country.id;
                    result.icon = country.getAttributeValueString('entityimage_url');
                    result.fields = [ country.getAttributeValueString('dev1_name'), country.getAttributeValueString('dev1_iso'), country.getAttributeValueString('dev1_longname') ];
                    Xrm.ArrayEx.add(results.results, result);
                }
                if (results.results.length > 0) {
                    context.getEventSource().showAutoComplete(results);
                }
                else {
                    context.getEventSource().hideAutoComplete();
                }
            }
            catch (ex) {
                Xrm.Utility.alertDialog('Could not load countries: ' + ex.message, null);
            }
        });
    }
    
    ClientHooks.Autocomplete.registerClass('ClientHooks.Autocomplete');
    });
    

     

     

     

    Posted on 14. July 2015

    Refreshed Connections UI Solution

    Following on from my recent video series called 'Building a Refreshed Connections UI using SpakleXRM' this post shows you how to use the final solution to add a bit of sparkle to the otherwise slightly neglected connections feature in Dynamics CRM.

    Connections first saw an appearance in CRM2011 replacing the customer relationships entity. The big deal about connections was that they could connect any entity to any other entity using the same lookup field and could be viewed from either side of the relationship. This made them very powerful for creating complex relationships between contacts, contacts, cases and opportunities such that the relationships would be equally discoverable from both sides.

    • Two sided - If you add a link between John Smith and Contoso you can see that connection on the Contoso record as well as the John Smith record. Behind the scenes this is achieved by two connections being created with the duplicate having the Connect To and Connect From lookups reversed. The connection that you created is marked as the 'Master' and when opening the non-master connection you are actually shown the Master record.
    • Ubiquitous – A common ask from users is to see all relationships in one place. Since connections support multiple types of records you can have a single list that shows connections between many types of record. The Connection Role is used to determine the type of relationship and the Connection Role Category can be used to group the types of roles together (e.g. Business, Sales Team, Stakeholders etc.)
    • Customisable Intersect Entity – There are existing attributes such as Effective Start and Effective End that are useful when modelling relationships that change over time but you can also add you own fields. The downside of this of course is that those fields will be visible for all connections irrespective of the entities types that are being connected unless you do some hiding/showing dynamically.

    I've always loved the connection entity but it hasn't received the 'Refreshed UI' treatment in recent releases which is why I created the Refreshed Connection UI Solution.

    Current Experience (CRM2015)

    This is what the current experience is on the Opportunity Form:

    So, far so good, but if you add a custom connections sub grid you get the following. Perhaps the biggest drawback is that the Add New (+) button cannot be used on the form connections sub-grid.

    If you then use the associated sub grid you get the following. Adding connections uses the old style Ribbon Form.

    New Experience

    Using the new SparkleXRM Refreshed Connections UI this is the experience you'll get:

    Installing:

    The code is all in github if you want to take look - otherwise you can install the managed solution. 

    Configuring:

    1. You can add the component to any form that supports connections by using 'Add Web Resource'

    2. Select the Web Resource named con_/html/Connections.htm
    3. Within the Web Resources properties dialog on the Formatting tab set the height in Rows and un-check 'Display Boarder' and change the Scrolling to 'Never'
    4. Again, within the Web Resource properties dialog on the General tab you can pass the following properties:
      1. entities – (Optional) A Comma separated list of entity logical names that you want to support in the lookup. By default the entities will be account,contact,opportunity,systemuser
      2. pageSize – (Optional) The number of rows to display per page. By default a size of 10 will be used.
      3. view – (Optional) The name of the view to use (e.g. All Stakeholders). By default the Associated view will be used

    The usual disclaimer & license applies.

    Posted on 24. May 2015

    Turbo Forms: Get your JavaScript ready for CRM2015 Update 1

    With the introduction of 'Turbo Forms' in CRM2015 Update 1 I thought I'd give you a heads up on what you'll need to address in your JavaScript to support this new form rendering engine. The Dynamics CRM Team Blog has a very good article on the changes but there have been some misunderstandings of the statement 'we have parallelized as many operations as possible'. In actual fact the script loading of custom web resource scripts has not really changed since CRM2013 - It remains the same as I describe in my CRM2013 Script Loading Deep Dive. The operations that are parallelized with turbo form rendering are the internal form load operations rather than custom ones. Custom JavaScript loading has always been parallelized since CRM2013.

    Turbo Form Page Differences

    Before Turbo Forms both global scripts and your custom web resources would have been loaded into a content IFRAME within the main window each time you navigate between records.

    The biggest change with Turbo Forms is that the content IFRAME is kept in memory and re-used for performance reasons. This parent content IFRAME is even kept in memory between navigation between different entity types. Any custom JavaScript is then loaded into a child IFRAME (via the ClientApiWrapper.aspx page) so that when you navigate between records they are re-loaded.

    SparkleXRM solutions already have a load order mechanism that ensure that your custom scripts are loaded in order that they are needed.

    Impact on unsupported code

    If your JavaScript is using only supported SDK calls then there will be no impact from this new loading mechanism. Sometimes it is necessary to use unsupported functions or referencing parameters of the Content IFRAME (such as the query string). Since your custom JavaScript is now running in the context of the ClientApiWrapper (rather than the content IFRAME) any reference to window methods such as window.openStdWin or window.location.href will fail. In order to access these objects you will need to reference parent.window.

    The ribbon workbench 'how-to' article that shows starting a dialog from a ribbon button does infact use openStdWin to ensure consistency with the out of the box dialog experience. I have updated the code to use the parent window when required.

    Footnotes

    There are a couple of other notable aspects of Turbo Forms that I thought I'd point out:

    IFRAMES that are collapsed by default are not sized correctly.

    If you have an IFRAME or HTML Webresource inside a Tab that is collaposed by default you will find that they are not sized correctly when the tab is expanded. This will be fixed in an upcoming update but until then you will need to show the tab by default and collapse when the form is loaded.

    entityType vs typename

    Turbo Forms have dropped the typename attribute of Lookup values.

    In the past, the following code would return the same:

    Xrm.Page.getAttribute("parentcustomerid").getValue()[0].typename
    Xrm.Page.getAttribute("parentcustomerid").getValue()[0].entityType

    With Turbo Forms only the documented entityType is available. The typename attribute was left over from the CRM4 days and just had not been removed until now!

    @ScottDurow

     

    Posted on 12. May 2015

    Introducing SparkleXRM Metadata Server

    Metadata Server Logo

    Speed up your HTML web resources by caching metadata such as localised field labels and options sets.

    If you've developed an HTML web resource for Dynamics CRM that includes field labels, option sets or any other element that is stored in the Dynamics CRM metadata then you’ll know about the delay each time the your UI is rendered whilst information is downloaded from the server. You could of course hard code this information in your JavaScript but you'd suffer higher maintenance costs especially when supporting multiple languages.

    The SparkleXRM Metadata Server allows dynamic expressions to be included in JavaScript Web Resources that are evaluated on the server and then cached on the client side.

    /*metadata
    var fieldLabel = <@contact.fullname.DisplayName@>;
    metadata*/

    This will output and cache the following on the client (for an LCID of 1033):

    var fieldLabel = "Full Name";

    Learn more about the SparkleXRM Metadata Server!

    Posted on 2. May 2015

    Building a Refreshed Connection UI using SparkleXRM

    I've now published all 5 videos in this series. 

    If you find yourself needing to create HTML Webresource in Dynamics CRM then I'm sure you will find something here of interest. Part 5 shows how to use the new SparkleXRM Metadata Server that I'll be publishing a post on very soon.

    Part 1 - Creating your first SpakleXRM Project

    Part 2 - View Model Form Data Binding & Unit Testing

    Part 3 - Adding an Editable Grid

    Part 4 - Pulling it all together

    Part 5 - Adding multi-language support

    Have fun!

    @ScottDurow

    Posted on 10. April 2015

    Can I do that (in Dynamics CRM) ?

    I'm sure by now that you've seen the new hierarchical visualisations in Dynamics CRM 2015. By clicking on the little icon in lists or on a form you can see the records graphically laid out. In true Dynamics CRM style anything you can do through the user interface is accessible through the SDK and so we now have the ability perform fetchXml queries using the above, eq-or-above, under & eq-or-under conditions. These new search criteria will find records up to a maximum of 100 recursions for a single hierarchical relationship.

    Take a look at the excellent SDK topic Query hierarchical data on the subject.

    I have found that when some users see the cool looking Dynamics CRM Online logon page they will ask 'Can I do that?!' I thought I would answer that question with a new SparkleXRM sample solution. Take a look:

    Installing Network Visualisations (CRM 2015)

    1. Import SparkleXRM - SparkleXRM_7_2_3_managed.zip
    2. Import NetworkView - NetworkView_1_0_4_alpha_managed.zip
    Usual MIT license applies!

    Known Issues

    For a list of known issues and reporting new ones please use the GitHub repository.

    Thanks to CRMCAT and Jukka Niiranen for all the help and feedback so far.

    Have Fun!

    @ScottDurow

    Posted on 9. April 2015

    Control Async Workflow Retries

    The Dynamics CRM Async Server is a great mechanism to host integrations to external systems without affecting the responsiveness of the user interface. Once such example is calling SharePoint as I describe in my series – SharePoint Integration Reloaded.

    A draw back of this approach (compared to using an integration technology such as BizTalk) is that any exceptions thrown during the integration will simply fail the workflow job with no retries. You might already know that the Async Server does in fact have a retry mechanism built in as described by this excellent post from the archives - http://blogs.msdn.com/b/crm/archive/2009/03/25/when-do-asynchronous-jobs-fail-suspend-or-retry.aspx. As you'll see from this article there are some built in exceptions where CRM will automatically retry a number of times as defined by the 'AsyncMaximumRetries' deployment property. The interval between these retries is defined by the 'AsyncRetryBackoffRate' property.

    So how can we make use of this retry mechanism with our custom code?

    There is an undocumented and unsupported way of using this retry mechanism in your custom workflow activities. I first used this technique back in the CRM 4.0 days and I'm pleased to report that it still works with CRM 2015!

    Although unsupported, the worst that could happen is that the workflow would stop retrying in future version of Dynamics CRM and revert to simply reporting the exception. However it still wouldn't be a good idea to rely on this functionality for mission critical operations.

    So…plz show me the codez…

    catch (Exception ex)
    {
        // TODO: Rollback any non-transactional operations
    
        OrganizationServiceFault fault = new OrganizationServiceFault();
        fault.ErrorCode = -2147204346; // This will cause the Async Server to retry
        fault.Message = ex.ToString();
        var networkException = new FaultException(fault);
        throw networkException;
    }
    

    When an exception is thrown by your custom workflow activity you'll see the workflow enter into a 'Waiting' state and the 'Postpone until' attribute will be set to the time when the retry will take place. Once the maximum number of retries has been performed, it will enter the 'Failed' status.

    You can use this technique to ensure that any temporary integration failures such as service connectivity issues will be dealt with by a graceful retry loop. It only remains to ensure that you before throwing the exception you rollback any operations performed already by your code (such as creating records) so that your retries will not create duplicate records.

    Hope this helps!

    @ScottDurow