Posted on 14. March 2017

There is something rather different about Dynamics 365 Business Process Flows!

The new business process flow designer in Dynamics 365 is lovely! However, I'm not going to talk about that since it's rightly had lots of love by others already.

For me the biggest change in Dynamics 365 is the fact that running Business Process Flows (BPFs) are now stored as entity records. Instance details are no longer held as fields on the associated record. I first visited this topic back in the CRM2013 days with the introductions of Business Process Flows where I described how to programmatically change the process.

Previously when a BPF was started, all of the state about the position was held on the record it was run on was stored in fields on the record itself:

  • Process Id: The ID of the BPF running
  • Stage Id: The ID of the BPF step that was active
  • Traversed Path: A comma separated string listing the GUIDs of current path of steps taken through the BPF. This is to support BPFs with branching logic.

With the new Dynamics 365 BPFs, each process activated is automatically has an entity created that looks just like any other custom entity. The information about the processes running and any record is now stored as instances of this entity with a N:1 relationship to the parent record and any subsequent related entities. This BPF entity has similar attributes that were stored on the parent entity, but with the following additions:

  • Active Stage Id: The ID of the BPF step that is active – replaces the Stage Id attribute.
  • Activate Stage Started On: The Date Time that the current step was started on – this allows calculation of the amount of time it has been active for
  • State & Status: Each BPF Instance has its own state that allows finishing and abandoning before other BPF are run.

     

In addition to making migration of data with running BPFs a little easier - this approach has the following advantages:

  1. You can control access to BPFs using standard entity role privileges
  2. You can have multiple BPFs running on the same record
  3. You can see how long the current stage has been active for
  4. You can Abandon/Finish a BPF

BPF Privileges

Prior to Dynamics365, you would have controlled which roles could access your BPF using the Business Process Flow Role Check list.     In Dynamics 365 when you click the 'Enable Security Roles' button your BPF you are presented with a list of Roles that you can open up and define access in the 'Business Process Flow' tab:

Multiple BPFs on the same record

Switching BPFs no longer overwrites the previous active step – meaning that you can 'switch' back to a previously started BPF and it will carry on from the same place. This means that BPFs can run in parallel on the same record.

  • If a user does not have access to the running BPF they will see the next running BPF in the list (that they have access to).
  • If the user has no access to any BPF that is active – then no BPF is shown at all.
  • If user has read only access to the BPF that is running, then they can see it, but not change the active step.
  • When a new record is created, the first BPF that the user has create privileges on is automatically started.

When you use the Switch Process dialog, you can now see if the Business Process Flow is already running, who started it and when it was run.

NOTE: Because the roles reference the BPF entities – you must also include the system generated BPF entities in any solution you intend to export and import into another system.

Active Step timer

Now that we have the ability to store addition data on the running BPF instance, we have the time that the current step was started on. This also means that when switching between processes, we can see the time spent in each step in parallel running BPFs.

Abandon/Finish

Since each BPF has its own state fields, a business process can be marked as Finished – or Abandoned at which point it becomes greyed out and read only.

When you 'Abandon' or 'Finish' a BPF it is moved into the 'Archived' section of the 'Switch Process' dialog.

NOTE: You might think that this means that you could then run the BPF a second time, but in-fact it can only have a single instance per BPF – and you must 'Reactivate' it to use it again.

  • Reactivating an Abandoned BPF will start at the previously active step
  • Reactivating a Finished BPF will start it from the beginning again.

Example

Imagine your business has a sales process that requires an approval by a Sales Manager. At a specific step in that sales process you could run a workflow to start a parallel BPF that only the Sales Managers have access to. When they view the record, making the Approval BPF higher in the ordered list of BPFS will mean that they will see the Approval BPF instead of the main Sales Process. They can then advance the steps to 'Approved' and mark as Finished. This could then in turn start another Workflow that updates a field on the Opportunity. Using this technique in combination with Field Level Security gives a rather neat solution for custom approval processes.

When I first saw this change I admit I was rather nervous because it was such a big system change. I've now done a number of upgrades to Dynamics 365 and the issues I found have all been resolved.
I'm really starting to like the new possibilities that Parallel BPFs brings to Dynamics 365.

@ScottDurow

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 9. December 2016

Dynamic365 Data Export Service

If you've moved to Dynamics CRM/365 Online then the likelihood is that you've come up against the limitation of not being able to query the SQL database directly to perform more complex reporting or for custom integrations. Many on premises deployments rely on querying the backend databases and in the past this has been a blocker to moving to the cloud – or at least it has meant a complex and costly integration to copy the data from Dynamics 365 to a on prem SQL database.

The introduction of the Data Export Service is a real game changer with the possibility to replicate your data from Dynamics CRM/365 online to an Azure SQL database in your own Azure Subscription. Once you have your data in a SQL Database you can then using PowerBI, integrate with other systems and create a data warehouse. I've found that the speed of the replication is impressive, being minutes/seconds and not hours.

There are a number of perquisites to enabling this which you can read about in msdn: https://technet.microsoft.com/en-us/library/mt744592.aspx

  • Azure Active Directory linked to Office 365
  • Azure SQL Database and user with correct permissions
  • Azure KeyVault created (using PowerShell script provided)
  • Dynamics CRM Online 8.1 or later
  • Data Export Service solution installed from App Source
  • Change tracking enabled for custom entities you want to sync
  • You must be a System Administrator to create the export profiles

The PowerShell script requires that you install the Azure cmdlets – see https://docs.microsoft.com/en-gb/powershell/azureps-cmdlets-docs/

Here is a video that demonstrates this new service and how to set it up

Posted on 22. August 2016

Debugging JavaScript in the Interactive Service Hub (Part 1)

Those that read regularly my blog and follow my work with Sparkle XRM will know I'm a massive fan of using Fiddler to debug JavaScript. One of the most productive 'superpowers' that Fiddler gives us is the ability to change JavaScript on the disk and not have to upload/publish – we can simply refresh the form and the new script will be used.

The Interactive Service Hub (ISH) was first introduced in CRM2016 and has been improved with more support for customisations in CRM2016 Update 1.

I see the purpose of the ISH at this stage is not to replace the main User Interface but rather as a testing ground for the principle of bringing the MoCA mobile/tablet native client platform to the web client. I think of it similar to the introduction of the Polaris UI back in CRM2011 – there are many similarities in that they both only support a limited set of entities and have limited customisations features. The main difference is that the ISH is being incrementally improved with each release, where the Polaris UI was more of a throw away proof of concept. At this stage the ISH is only supporting 'case' oriented operations but I'm sure it'll eventually graduate to support all Sales, Service and Marketing features.

So why the new approach the UI?

Surely it would be better to improve the existing UI incrementally rather than replace it?

One of the key drivers for the Dynamics CRM Team over the last few releases has been 'configure once deploy everywhere'. This allows us to configure business rules that can be run on all devices/platforms reliably without having to perform separate testing and perhaps re-write to target different clients. The maintenance of having multiple user interface platforms is considerable so it's a natural step to try and achieve some degree of convergence between the mobile/tablet/web/outlook interfaces.

A little background on how the ISH loads metadata

I think we are all fairly comfortable with the normal Web 2.0 paradigm of loading resources. This is where with each operation the client requests an html page and then the browser requests all the additional resources (JavaScript, CSS etc. ) that are referenced by that page. JavaScript can then make additional XHR/Ajax requests to the server to display further dynamic content. The CRM2016 UI is very similar on this front as can be seen below. I documented the CRM2013 script loading sequence which hasn't significantly changed even in CRM2016.

  • Page Load Sequence Diagram
  • Each time you open the web client, the homepage.aspx or Main.aspx has to request the metadata for the specific resource (view or form) and then combine it with the requested data. Although there is browser and server side caching in place, this is still costly in terms of the requests and rendering overhead of the browser. The 'turbo forms' update in CRM2015 Update 1 has really helped with the speed of this since it minimises the resources that requested with each navigation however fundamentally it is still limited by the page per browser request architecture.

    ISH works very differently…

    The ISH is more what we would call a 'single page application'. The sequence is very different in that there is an initial download of metadata and then subsequently all user interactions only request the actual data using the Organization.svc and OrganizationData.svc.

  • New Page Load Sequence Diagram
  • This single page approach has the advantage that it makes navigation super slick but with the rather annoying drawback that there is an initial wait each time the ISH is opened where the metadata changes are checked. The first time you open the ISH all the metadata is downloaded but from then on only the differences from the last open are downloaded. If there haven't been any changes then it's super quick because all the metadata is stored in the browsers indexed Database but if you've done a publish then the next open can take a while. Furthermore, the new metadata won't be downloaded until you close and re-open the ISH - this is different to the Web 2.0 UI and can lead to the client working with stale metadata for a time. The Indexed Database is one of the significant differences between and HTML5 single page app and a more traditional Web 2.0 architecture.

    Note: For now the ISH mostly uses the SOAP/Xml based Organization.svc rather than the new JSON based Web API.

    The speed of the metadata sync can be helped further by using the 'Prepare Client Customizations' button on the solution since this will pre-prepare the download package rather than waiting for the first person to open the ISH to detect the changes in the metadata. The difference between the MoCA client and the ISH is that the MoCA asks if the user wants to download the updates – presumably because you may be on a low bandwidth connection.

    So where does that leave us with respect to JavaScript debugging?

    If you've been keeping up so far (you have right?) then you'll realise that because the metadata (this includes JavaScript) will be all stored in the browser Indexed DB and not relying on the browser cache. As a result, we can't simply prevent the files from being cached and download the latest version with each page load as we used to do with Fiddler. We're back with the uncomfortable debug cycle of having to make a change to a JavaScript web resource, upload it to CRM, publish, close and re-start the ISH - urgh!

    To preserve our collective sanity, I've created a little debug utility solution that you can use to clear the cache of specific web resources so that you can quickly make changes to JavaScript on your local disk and then reload it in the ISH without doing a full publish cycle. Here is how:

    1. Install the latest build of SparkleXRM
    2. Install the Interactive Service Hub Debug Helper Solution
    3. Setup Fiddler's Auto Responder to point to your local webresource file as per my instructions.
    4. Start the ISH to load your JavaScript
    5. Make a local change to your JavaScript
    6. Open the ISH Debug Utility Solution configuration page and enter the name of your script name, then click 'Refresh JavaScript Webresource'
      Note: You can enter only part of the webresource name and it will use a regular expression to match.
    7. Use Ctrl-F5 on your ISH Page and when re-loaded the Web Resource will use the new version since the debug utility has forced a new download and updated the Indexed DB storage.

    Sweet – but what about the MoCA client?

    Obviously this technique is not going to work for mobile client running on an iPad, iPhone etc. The good news is that you can run the MoCA client in the Chrome browser in the same way you can run the ISH – just navigate to:

    <crmserver>/nga/main.htm?org=<orgname>&server=<crmserver>

    Note: You must be pre-authenticated for this to work.

    OnPrem

    http://dev03/nga/main.htm?org=Contoso&server=http://dev03/Contoso

    OnPrem IFD

    https://myorg.contoso.com/nga/main.htm?org=myorg&server=https:// myorg.contoso.com

    Online

    https://myorg.crm4.dynamics.com/nga/main.htm?org=myorg&server=https:// myorg.crm4.dynamics.com  

    Since the ISH and the MoCA client are build using the same platform you can now use the ISH Debug Helper from the same browser session to perform the same script refresh! This is actually an excellent way of testing out your Scripts on the MoCA client! For more information, check out the comments in this tip of the day.

    Looking forwards to the future

    I'd really encourage you to check out the ISH and use the New CRM Suggestions site to record anything you find that you would like to see in subsequent releases. Whilst I suspect that the existing 'refreshed' UI will be available for some releases to come, it is likely at some point to become the new 'legacy' UI and with on-going investment being made in the ISH style UI.

    In part 2 we'll look at some limitation of the ISH and how to get around them.

    Any comments, just tweet me! @ScottDurow

    Posted on 25. April 2016

    Ribbon Workbench 2016 Beta

    A couple of weeks ago I had both the privilege and a most enjoyable hour on CRM Audio chatting with George, Joel and Shawn about the Ribbon Workbench and SparkleXRM. You'll have heard me mention that I'd be posting details on how to get involved with the Beta version of the new Ribbon Workbench 2016 that's written using HTML and JavaScript rather than Silverlight – so here it is!

    I've had a fruitful relationship with Silverlight over the years and it has been the enabler in many successful rich client Dynamics CRM customisations but things have moved on! In July 2015 the time had come to say goodbye in part because there was no Silverlight in Windows 10's Edge Browser. My main blocker for writing pure HTML and JavaScript Web Resources in the past had always been one a lack of productivity tooling, but that had moved on as well not least because of SparkleXRM, my framework for building rich user interface Dynamics CRM web resources. The Ribbon Workbench 2016 is written using SparkleXRM (although it comes pre-packaged in the solution) and if I'm honest I think one of my drivers originally for working so hard on that project was the inevitability of having to re-write the Ribbon Workbench in HTML one day. Without the framework it would have been a bridge-too-far, but as it happened I was pleasantly surprised as to how easy the conversion process went and I am really pleased with how it's turned out. Here are some highlights…

    Drag and Drop Flyout Editing

    Drag and Drop Flyout Editing

    Delete Undo

    Undo

    Drag and Drop Command Editing

    Drag Drop Command Editing

    Can you help with Beta Testing?

    You can download the beta version by signing up to beta test today!

    Please report issues and bugs via UserVoice! Thank you!

    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 6. November 2015

    New version of Network Visualisations for Dynamics CRM

    Many of you will have seen (and possibly used) my previous version of NetworkView. Thank you for all the feedback you've given. The top 5 points of feedback were:

    1. Can I show connections on the graph?
    2. Can I show custom entities on the graph?
    3. I have very large graphs which makes it hard to understand what is going on.
    4. I have records owned by Teams but I cannot see that on my graph.
    5. How do I know which node is the one that I've opened the graph from?

    With version 1.6 I've addressed these points as follows:

    Showing Connections

    You can now show connections between entities! The connection roles that are discovered (e.g. Partner, Influencer etc.) are listed so that you can then highlight all the connections of a particular role on the graph by selecting the role. Loading connections may be turned on or off on a per-entity basis in the configuration.

    Connections shown on graph

    Activity Overflow

    Since most of the time folks are only interested in the links between records via activities rather than the actual activities themselves, the graph now loads the first 10 records and then shows an overflow node that you can double click to load the next 5 activities. Under the covers, all that activities that match the FetchXml filter are loaded and the links worked out but the links are shown to and from the 'overflow' node rather than the individual activities until you expand the node.

    Expanding Activities

    Iteration Limit

    Network graphs can often grow to a large size if you have many connections, the graph will now pause loading after a set number of iterations and ask if you want to load more. The most frequent cause of graph growth is where there are connections that are also users – for this reason, the graph will supress connections that have the same email domain as any of your users.

    Loading Paused

    Team Ownership

    In addition to showing users who own records and participate on activities the graph now shows Teams in same list and allows highlighting records that are associated with that team.

    Team Ownership on Graph

    Root Node identification

    You can now see which record you started the graph from highlighted in green.

    Cycle Mode

    When you first load the Network view, the users and connection are cycled through so that the various cohorts on your graph are highlighted for your viewing pleasure! This can be turned off by clicking the cycle button and the configuration controls if it is on by default.

    Configurable

    The graph is now configured using a customisable web-resource named:

    dev1_/js/NetworkViewConfig_1033.js

    To add your custom configuration you simply need to edit this web resource and publish the changes. You can use the following options:

    iterationCountPerLoad

    The number of iterations of the load mechanism before prompting the 'Load More' option.

    Default:10

    trace

    Output trace to F12 console whilst loading. This is good for working out why your graph looks the way it does

    Default: false

    demoModeInitialState

    Turn on the demo cycle when the graph first loads.

    Default: false

    connectionFetchXml

    The query to fetch connections. The placeholder {0} must be inserted where the ID filter should be added.

    acitvityFetchXml

    The query to fetch activities. The placeholder {0} must be inserted where the ID filter should be added.

    entities

    Array of Entities to load (including custom entities)

    Each Entity configuration has the following fields:

    displayName

    The name of the entity used in messages.

    logicalName

    The logical name of the entity to load

    nameAttribute

    The logical name of the attribute that holds the display name of the record.

    idAttribute

    The logical name of the attribute that holds the unique ID of the record

    parentAttributeId

    The logical name of the parent Record ID when hierarchical loading is enabled.

    loadActivities

    True when the graph should show activities for this entity

    loadConnections

    True when the graph should show connections for this entity (connections must be supported by this entity)

    hierarchical

    True when the parent/child relationships should be traversed using the hierarchical operators.

    fetchXml

    The query to get the records. The placeholder {0} must be inserted where the filter conditions should be added.

    joins

    Holds an array of joins to other entities.

     

    Installation

    To install or upgrade from the previous version :

    1. Install SparkleXRM v7.2.8 or later
    2. Install the NetworkView managed solution

    The usual disclaimer & license applies.

    Special thanks goes to @dynamiccrmcat for help with testing this version.

    @ScottDurow

    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 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