Category: Development

Customizations.xml Advanced Hacks

Customizations.xml Advanced Hacks

For those who attended my session about customizations.xml advances hacks, here is everything I promessed you during the session.

The presentation

Here’s the PowerPoint with the relevant information.

Enable / Disable the Jumpbar on subgrids

First example was to enable or disable the jumpbar on subgrids. In order to do so, you need to add or set the <EnableJumpBar> property in the parameters of the subgrid as shown below.

<cell showlabel="false" locklevel="0" id="{a64dbfbd-4d31-4b01-99a8-d6019536c1a5}" rowspan="1" colspan="1">
  <labels>
    <label description="New SG control 1573914400892" languagecode="1033" />
  </labels>
  <control id="lbs_contacts" classid="{E7A81278-8635-4D9E-8D4D-59480B391C5B}" indicationOfSubgrid="true">
    <parameters>
      <TargetEntityType>contact</TargetEntityType>
      <ViewId>{00000000-0000-0000-00AA-000010001004}</ViewId>
      <ViewIds>{00000000-0000-0000-00AA-000010001003},{00000000-0000-0000-00AA-000010001004}</ViewIds>
      <EnableViewPicker>false</EnableViewPicker>
      <RelationshipName>lbs_Event_Contact_Contact</RelationshipName>
      <EnableJumpBar>false</EnableJumpBar>
    </parameters>
  </control>
</cell>

Hide columns on your view

To hide columns on your view, you can set the ‘ishidden’ attribute on the column of the view as shown below.

One of the attendees on the session in Brussels also marked the ‘disableSorting’ attribute that makes sure users cannot sort the view on that column.

<layoutxml>
  <grid name="opportunityproducts" jump="" select="1" icon="1" preview="1">
    <row name="opportunityproduct" id="opportunityproductid">
      <cell name="productid" width="200" />
      <cell name="productdescription" width="200" />
      <cell name="priceperunit" width="100" />
      <cell name="quantity" width="75" />
      <cell name="extendedamount" width="100" />
      <cell name="opportunityproductname" ishidden="1" imageproviderfunctionname="Sales.ProductTypeImage.getProductTypeImageUrl" imageproviderwebresource="$webresource:Sales/ClientCommon/Sales_ClientCommon.js" />
      <cell name="isproductoverridden" />
      <cell name="producttypecode" ishidden="1" disableSorting="1" />
      <cell name="baseamount" width="100" />
    </row>
  </grid>
</layoutxml>

Embed PowerBI Report in a Model Driven form

Microsoft has documented this feature well and you’re able to see all information on this page. Down here is the code that needs to be added to the section under the </labels> tag. To filter on the current record, please add the highlighted line. Also don’t forget to set the height of the section by adding rowspan=”10″ to the <cell> tag.

<control id="unfilteredreport" classid="{8C54228C-1B25-4909-A12A-F2B968BB0D62}">
  <parameters>
    <PowerBIGroupId>00000000-0000-0000-0000-000000000000</PowerBIGroupId>
    <PowerBIReportId>91649484-1601-4345-8c12-4dfea5d69399</PowerBIReportId>
    <TileUrl>https://app.powerbi.com/reportEmbed?reportId=91649484-1601-4345-8c12-4dfea5d69399</TileUrl>
    <PowerBIFilter>{"Filter": "[{\"$schema\":\"basic\",\"target\":{\"table\":\"Account\",\"column\":\"accountid\"},\"operator\":\"Eq\",\"values\":[$a],\"filterType\":1}]", "Alias": {"$a": "accountid"}}</PowerBIFilter>
  </parameters>
</control>

Creating custom dialogs

In the presentation I told you about custom dialogs in CRM. These are perfectly to build and it’s all explained very well by Bob Guidinger. You can find everything you need to know on these two posts: https://bguidinger.com/blog/category/dialog-boxes

Creating a custom Status for your (custom) entity

Creating a custom Status for your (custom) entity

It’s been always a mystery to me why we’re not able to change the status field on an entity. We just get to change the status reason field, but sometimes you just want some more. Microsoft knows it, cause they use multiple status fields on for instance the Lead and Opportunity entity.

Let face it: most of the time it’s just fine to have Activate and Inactive as statuses on your entity. (Now that we’re talking about statuses… Isn’t it just weird that Active has the value 0 and Inactive the value 1??!) But there are those situations that you just need more. For instance one of my customers log their medical consults in an entity. In this case it would have been great to have Active, Processed and Invoiced as statuses. And you know what: it’s possible!

Now before you get too excited about this I need to temper your enthusiasm a bit. Although it can be done, it’s unsupported customization. When you read my solution and you read this article, you’ll understand why it’s unsupported. Since it’s unsupported, I would strongly advice you to do this only for custom entities and not for oob entites. So far for the disclaimer 🙂

In order to add or change the statuses you need to change the customization xml file. Now let’s go through the steps. (I expect you to have some experience with customizing Dynamics 365 CE. If not, please contact your Microsoft partner to execute these steps.)

Create the solution and get the right file

Create a temporary solution with just what you need. In this case, just add the status and status reason fields from the entity you wish to change. Although you just want to change the status field, you’ll also need the status reason field. Later on it’ll come clear why that is needed.
Export the solution and open the zip file. Now get one file out – the customization.xml file – and store it somewhere to edit.

Edit the customization xml

Now comes the editing of the file. Please be careful here, cause you might break something here.
In the file, locate this part

<states>
  <state value="0" defaultstatus="1" invariantname="Active">
    <labels>
      <label description="Active" languagecode="1033" />
    </labels>
  </state>
  <state value="1" defaultstatus="2" invariantname="Inactive">
    <labels>
      <label description="Inactive" languagecode="1033" />
    </labels>
  </state>              
</states>

As you can see, both default statuses (Active and Inactive) are there. Notice the defaultstatus tag that is in there. This is important and indicates the default status reason for that status. Since we don’t have a status reason for it yet, we will just fill in a random number now that we’ll use later to create the status reason with.

To create the new status just copy everything between a <state> and </state> tag and change the value, defaultstatus, invariantname and desciption. It should look like this. In this example I use Invoiced with vaue 10000. I would always use a high number as you never know what Microsoft comes up with in the future.

<states>
  <state value="0" defaultstatus="1" invariantname="Active">
    <labels>
      <label description="Active" languagecode="1033" />
    </labels>
  </state>
  <state value="1" defaultstatus="2" invariantname="Inactive">
    <labels>
      <label description="Inactive" languagecode="1033" />
    </labels>
  </state>
  <state value="10000" defaultstatus="20000" invariantname="Invoiced">
    <labels>
      <label description="Invoiced" languagecode="1033" />
    </labels>
  </state>                  
</states>

Now we need to do the same for the status reason field. Please keep in mind the value for the status reason should be equal to the defaultstatus value that we’ve used before. The same goes for the state.

<statuses>
  <status value="1" state="0">
    <labels>
      <label description="Active" languagecode="1033" />
    </labels>
  </status>
  <status value="2" state="1">
    <labels>
      <label description="Inactive" languagecode="1033" />
    </labels>
  </status>                    
  <status value="20000" state="10000">
    <labels>
      <label description="Invoiced" languagecode="1033" />
    </labels>                    
  </status>
</statuses>

Import the file and you’re done! 🙂

Now that you’ve changed the cusomization xml file you should put the customization xml file back into the solution zip. Notice that it overwrites the existing customization xml file. Import the solution zip back into your environment and you should have the new status.

Things to take into consideration

Again, this customization is not supported. Always be careful with these unsupported customization and question yourself if you can’t do it any other way.

Since it’s unsupported, the user interface doesn’t take your new status into account. There is no out of the box option to set a record to your new status. You can use workflow or you’ll have to create a button on the command bar to set your status.

Last but not least: as for as I could see, when a record is in the new status, its forms are always in read-only state. The Active status is the only status where you can edit the data on the form.

WebApi batch request

WebApi batch request

The WebApi in Dynamics CRM has the option to send batch requests. This reduces the number of calls from client to server and when e.g. you need to delete a lot of records from JavaScript it saves time processing these deletes.

Unfortunately, creating a batch request is not as straight forward as doing a POST call for example. The MSDN documentation is a good starting point, but then I still had some trouble making it work. I’ve created a question on stackoverflow and the Dynamics Community. As I needed the functionality I kept trying different options and reading the documentation and I finally figured it out.

It’s very important that you build the content in the batch request as documented with MSDN. Also note that when you do a delete batch request you still need to send an empty object. With a single delete call to the api you don’t have to explicitly do this. Another things that’s a bit hard creating a batch request, the errors that are returned are not existent. You just get an empty result and you have to figure out yourself what you have done wrong.

This is the request if you’re using jQuery to do the call.

$.ajax(
{
    method: 'POST',
    url: 'http://crm/api/data/v8.0/$batch',
    headers: {
        'Content-Type': 'multipart/mixed;boundary=batch_' + batchId,
        'Accept': 'application/json'
    },
    data: payload
});

And here’s the way to build the payload:

var data = [];
data.push('--batch_123456');
data.push('Content-Type: multipart/mixed;boundary=changeset_BBB456');
data.push('');
data.push('--changeset_BBB456');
data.push('Content-Type:application/http');
data.push('Content-Transfer-Encoding:binary');
data.push('Content-ID:1');
data.push('');
data.push('POST http://tenanturl/api/data/v8.0/accounts HTTP/1.1');
data.push('Content-Type:application/json;type=entry');
data.push('');
data.push('{ "name": "batch acount 2"}');
data.push('--changeset_BBB456');
data.push('Content-Type:application/http');
data.push('Content-Transfer-Encoding:binary');
data.push('Content-ID:1');
data.push('');
data.push('DELETE http://tenanturl/api/data/v8.1/accounts(52eb1677-427b-e611-80bb-0050568a6c2d) HTTP/1.1');
data.push('Content-Type:application/json;type=entry');
data.push('');
data.push('{}');
data.push('--changeset_BBB456--');
data.push('--batch_123456--');

var payload = data.join('\r\n'); 
Creating an activity through the CRM WebApi

Creating an activity through the CRM WebApi

Recentely I had to create a phonecall activity through the WebApi in Dynamics 365 CRM. As most WebApi calls are pretty straight forward, I didn’t think much of this task, although I haven’t done this before. As I knew activities are special in CRM I looked around to see examples and couldn’t find any good ones. Therefore I’m writing this post, to make sure I have an example on hand when I need one.

Endpoint

To create an activity you use the relevant endpoint. These are /api/data/v9.0/phonecalls, /api/data/v9.0/emails, ect. Maybe there’s an option to use the generic endpoint, but I haven’t tested this.

Json message

For a phonecall the Json message could look like this

{
    "description" : "Lorem ipsem",
    "directioncode" : true,
    "leftvoicemail": false,
    "regardingobjectid_account@odata.bind" : "/accounts(EA82D93B-CFD9-E711-812D-E0071B6C2F31)",
    "subject" : "lorem ipsem", 
    "phonecall_activity_parties" : [
        {
            "partyid_systemuser@odata.bind" : "/systemusers(5549B1A7-A7CD-4047-84CC-64BA1FF4756F)",
            "participationtypemask" : 1
        },
        {
            "partyid_contact@odata.bind" : "/contacts(4F2D083E-D3D8-E711-812C-E0071B6C2F31)",
            "participationtypemask" : 2
        }]
}

Validate your IBAN bank numbers

Validate your IBAN bank numbers

During multiple implementations of Dynamics 365 for Customer Engagement or Dynamics 365 Business Central, we get the question weather it’s possible to validate the entered IBAN bank account numbers and generate the BIC / Swift code. Especially when companies use Direct Debit as a payment method, you want to make sure the IBAN is correct.

I’ve created a simple REST service based on Azure Functions to validate the entered IBAN bank account number and provide the name of the bank and the BIC / Swift code.

How it works

Let’s start with testing this in your browser. For now we want to check if an IBAN is valid. We can do that by combining the url below with and IBAN (for instance NL63TRIO0379272687).
URL: https://iban-validation.dynamics365blog.nl/api/IBANCheck?iban=NL63TRIO0379272687
You’ll get back all the information you need:

{
    &quot;valid&quot;: true,
    &quot;messages&quot;: null,
    &quot;iban&quot;: &quot;NL63TRIO0379272687&quot;,
    &quot;bankData&quot;: {
        &quot;bankName&quot;: &quot;TRIODOS BANK N.V&quot;,
        &quot;bic&quot;: &quot;TRIONL2U&quot;
    }
}
Using Dynamics 365 CRM lookups and alternate keys in Microsoft Flow

Using Dynamics 365 CRM lookups and alternate keys in Microsoft Flow

I’ve written a blog a while ago on using alternate keys with the Dynamics 365 / CRM WebApi. You can read the blog here. The post guides you in creating an alternate key and identifies some usage scenario’s.

Lately I’ve been working on an integration between Eventbrite and Dynamics 365 / CRM with Microsoft Flow. Currently I’m going back and forth between enthusiasm and disappointment. Sometimes it does more than you would expect and sometimes you’re missing the details you are looking for.

Lookup

Most often you would want to set a lookup value in a record you are creating and updating. With flow this might be a bit hard as looping is cumbersome, or even impossible, to implement (e.g. nested loops are not supported).

The first thing you could do it set hard coded values. E.g. when creating a product in CRM it’s sometimes feasible to hard code the unit of measure. Simply pasting in the Guids works fine in such case:

Alternate key

Other time you want want to set a lookup to a record you are also integrating. E.g. with Evenbrite you have the ‘entity’ Events that I integrate with a custom entity Events. Event tickets I’m integrating with a custom entity Event tickets. On that event ticket entity I need to set the lookup to the related event. One option would be query for the event in CRM using Flow and setting the value.

However, to simplify things, I’ve created an alternate key on the Event entity in CRM, using the EventId from Eventbrite. Now setting the lookup is fairly easy, and we don’t have to query CRM to fetch the record. This sample works perfectly:

Conclusion

For now, Microsoft Flow seems to be a perfect tool to do lightweight integration scenario’s. Using alternate keys might really help in making your life a little easier in extending the options you have with Microsoft Flow.

Using alternate keys with the WebApi

Using alternate keys with the WebApi

Alternate keys are very powerful feature in Dynamics 365 / CRM. Especially when you are integrating with other systems it’s very helpful to use alternate keys to efficiently retrieve and update records. In this blog post I’ll guide you through creating alternate keys and using them in retrieving, updating and upserting records.

Creating an alternate key

The first thing you need to do is to create a field that holds the alternate key. Please keep in mind that you can only use the field types: Decimal Number, Whole Number and Sinlge line of text for an alternate key. There’s also a maximum of five alternate keys per entity and a restriction in size. For a full reference visit the documentation.

When you created the field you need to go to the keys of the entity and create the alternate key. The key can be a combination of multiple fields. In this example I kept it simple by using one field:

For the key to work, a system job needs to run to create an index. Please check if this is successful. The key I created for this blog post had an error on the first try.

Creating an account

Setting the alternate key in the request is the same as setting the value of any other field:

url: /api/data/v8.2/accounts
method: POST
body: {
	"name": "Alternate key account",
	"blg_alternatekey" : "blg12345"
}

Retrieving a record

Retreiving a record is quite simple. Normally you would use a get request to /account(0000-000-000-000-000) to retreive a single record using the guid of the record. Using an alternate key the request is this:

url: /api/data/v8.2/accounts(blg_alternatekey='blg12345')
method: GET

Here you can see that instead of using the guid, you need to use the field name of the key (not the keyname) and the value to retrieve the account.

Updating a record

The sample to update the record:

url: /api/data/v8.2/accounts(blg_alternatekey='blg12345')
 method: PATCH
 body: {
     "name": "Alternate key account updated"
}

The nice thing with the PATCH request like this is, that by default it will update the account if it exists, or it will create the account if it doesn’t exist. This is a great feature for integration scenario’s with other systems.

Navigation properties

You can also use the alternate key for setting a lookup value. In this example we will create a contact for the account created above.

url: /api/data/v8.2/contacts
method: POST
body: {
	"firstname" : "john",
	"lastname" : "smith",
	"parentcustomerid_account@odata.bind" : "/accounts(blg_alternatekey='blg12345')"
}

Conclusion

Alternate keys are a great feature, especially when you’re integrating with other systems. It’s good to see that the WebApi has full support for this, even with upsert requests and using it in navigation properties. This saves writing a lot of logic and a lot of calls getting guids from Dynamics365 / CRM.

Authenticate to the Dynamics 365 oData API with AppId and Secret

Authenticate to the Dynamics 365 oData API with AppId and Secret

Authentication to the Dynamics 365 oData API (CRM) is something that all CRM Developers have been busy with. Previously, this was done by a normal user. The downside of this method is that the CRM web interface can be used with this user as well. Some versions ago, Microsoft introduced the concept of “Non-Interactive” users (see this article). This was already a huge step forward from security point of view. We could use a user now that didn’t have access to the web interface. There is still a downside to this: a username and password need to be stored somewhere. Since a username mostly has some kind of logic in it’s name, this can be predicted and may be available with an attack. Conclusion: still not so secure as you’d want to.

Introducing the AppId for Dynamics 365 oData API

Microsoft created the ability to authenticate to the Dynamics 365 oData API with the use of an AppId (in the December 2016 update)! Together with a secret, this replaces the username and password. Guess what: readability and logic are far more complex with an AppId and a secret, than usename and password. Again a great step forward in security. So to wrap up:

  • A username and password are readable and therefore less safe
    Username: api-user@domain.com
    Password: somePassword
  • An Appid and Secret are far more difficult to read and therefore safer
    AppId: 82068d67-54a7-4698-a4eb-876dcc90b9d1
    Secret: BD”m_hIy461-E!p&;u0l@7sPCvp?579MA`iU3ek|5rD]V

If you want this, please read on and I’ll describe what steps you should take to use this new feature.

Steps to take to implement the AppId

A quick overview of the steps for the quickies is here:
1. Create an Application with AppID and Secret in Azure Active Directory
2. Assign the Dynamics CRM Online API rights to the Application
3. Create an application user in CRM and attatch the AppId
4. Enjoy!

Now here we go for the deep-dive.

Create an Application with AppID and Secret in Azure Active Directory

  • Sign in to the Azure Portal and Azure AD tenant by selecting your account in the top right corner of the page.
  • On the left side of the page, go to Azure Active Directory and select App registrations.
  • Click New application registration and provide the name. The application type must be Web app / API. You can chose any valid URL as Sign-on URL.

Assign the Dynamics CRM Online API rights to the Application

  • Open the new App registration and select Required permissions. Click Add, Select an API and Chose Dynamics CRM Online. Select all permissions and click Select and Done. Now the permissions should look like this.
    Application registration Permissions for the Dynamics 365 oData API
  • Now select Keys and create a new Key. Save the Key for later, this is the Secret you need to authenticate. Together with your Application ID, you are now done creating the Application.

Create an application user in CRM and attatch the AppId

  • Go to the CRM users and open the Application Users view.
  • Click New and you’ll get a special form for Application Users. Here you’ll provide the Application ID of the registrated application, a full name that sounds logical to you and a primary email. This e-mail must contain an existing domain within the same tenant.
  • For the final step, assign a custom security role to this user. Please make sure it’s a custom security role. A default security role won’t do.

Sample Code and Libraries

If you want to test if authentication works properly, you can download and use this VS project. If you change the config-file and you run the project, you should see your 5 first Accounts.

Since not everyone uses C# for their projects, you should know Microsoft also has the ADAL Libraries in all kinds of programming languages. ADAL stands for Azure Active Directory Authentication, which you can use to authenticate to Dynamics 365 Online as well. You can find the ADAL Libraries here.

Enjoy!

Check if all required fields are populated

Check if all required fields are populated

In my previous post Workaround for double update on statecode change via WebAPI, I use a custom Action to make a status change. However, when this is done the status of the records gets changed regardless of the required fields that might not be populated by the user. So in this case, we need to check if all the reuired fields have been populated before we call the custom Action.

First, we need to have a function that checks if all required fields are populated. After the check, I want the function to return “true” when all required fields are populated and “false” if otherwise. This is the function:

function allRequiredFieldPolutated() {
    var populated = true;
    Xrm.Page.getAttribute(function (attribute, index) {
        if (attribute.getRequiredLevel() == "required") {
            if (attribute.getValue() === null) {
                populated = false;
            }
        }
    });
    return populated;
}

Now this function can be used in other functions, such as the function that has been used in my previous post, to check if the status can be changed.

Ignore the added https in a Text Field type URL

Ignore the added https in a Text Field type URL

Since Dynamics 365 version 2016 (8.0), text fields with type URL get automatically https added in front of the URL if the user has not typed that. In previous versions of Dynamics 365 / CRM this addition was http. Since a lot of websites still don’t have secure certificates and don’t do https, we want Dynamics 365 to use http instead of https.
With a simple JavaScript Web Resource, we can apply the old functionality as we used to have it. These 6 lines of code fire when an URL is typed in by the user, so the URL gets http as addition instead of https.

Make it work

In order to make this work, we first need to add the JavaScript Web Resource into Dynamics 365. So open your Dynamics 365 environment and go to your customization (either a solution or the default customizations). Now go to Web Resources and add a new Web Resource. Give the Web Resource any name you want to and upload the file that can be downloaded at the bottom of this page. Save and publish the Web Resource.

Next, go to the form on which you want this functionality and start editing this form. Open the Form Properties and add the Form Library you just created. After you clicked ok, you open the properties of the URL field and go to the Events tab. Add an Event Handler in which you select your library, set the Function field to ‘setHttp’ and check the ‘Pass execution contact as first parameter’. Now click ok, close all and publish your customizations.

That’s it! You’re good to go now. Please keep in mind that Dynamics 365 can sometimes experience some caching issues. When you open the form next time and you get a scripting error, please press Ctrl+F5 once. The scripting error should be gone now.

The code

UPDATE (2018): Since there have been some changes in javascript actions since Dynamics 365 v9.0, there is a new script for the v9.0 and above organisations.

V8:

function setHttp(context) {
    var webUrl = Xrm.Page.getAttribute(context.getEventSource().getName()).getValue();
    if (webUrl != null &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp; webUrl.substring(0,7) != &quot;http://&quot; &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;  webUrl.substring(0,7) != &quot;https:/&quot;) {
        Xrm.Page.getAttribute(context.getEventSource().getName()).setValue(&quot;http://&quot; + webUrl)
    }
}

V9:

function setHttp(executionContext) {
    var formContext = executionContext.getFormContext();
    var webUrl = formContext.getAttribute(executionContext.getEventSource().getName()).getValue();
    if (webUrl != null &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp; webUrl.substring(0, 7) != &quot;http://&quot; &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp; webUrl.substring(0, 7) != &quot;https:/&quot;) {
        formContext.getAttribute(executionContext.getEventSource().getName()).setValue(&quot;http://&quot; + webUrl)
    }
}

Download your file here

Download v8.0 and above

Download v9.0 and above