Getting Form Context from Sub-grid ribbon button in Dynamics V9.0 Gotchas!

After my last blog on getting the formContext and the client URL from a ribbon button on the form in Dynamics Version 9.0, I was simply taken back by the comments and was truly satisfied knowing that it had helped so many blog readers.

https://debajmecrm.com/2018/11/16/xrm-page-is-deprecated-with-version-9-0-how-do-i-get-the-clienturl-and-form-data-in-ribbon-actions/

However one of the questions was on how to get the formContext when a sub-grid button was clicked. So basically the requirement here is when the sub-grid button is clicked, the UI elements on the form needs to be accessed like the tabs, fields, sections, formSelector etc

And now with V9.0, since Xrm.Page has been deprecated, we need to use the formContext to access and any form-related values.

So I decided to give this a try.

I created a custom ribbon button on the account sub-grid and then on the click of the function, I added couple of CrmParameters

<Actions>
              < JavaScriptFunction FunctionName="subgridEvent" Library="$webresource:new_contactformload.js">
                <CrmParameter Value="PrimaryControl" />
                < CrmParameter Value="SelectedControl" />

              </JavaScriptFunction>

</Actions>

One is PrimaryControl and the other is SelectedControl. Well, PrimaryControl was very helpful last time for a form button, since it gave me the much desired FormContext with which I was able to play with the entire form.

Somehow I was reluctant to use the PrimaryControl still I included it.

SelectedControl is very much necessary because I know it gives me the Grid Context which would help me to play with the Grid. And I was pretty sure that it would give me something to access the FormContext.

All set and good. I clicked on the Test Button in the Account sub-grid.

image

While debugging, I got my two parameters – PrimaryControl and SelectedControl.

As I expected PrimaryControl was useless and SelectedControl gave me the Grid Context.

image

I kept on searching for something in the SelectedControl and to  my horror nothing was there to get the FormContext.

I stated thinking otherwise. How about storing the formContext in a global variable when the form loads? Well that does not work either. When you move to associated view, your form refreshes and unfortunately you won’t find that script global variable in there when the sub-grid button is clicked.

So is there no other way? Searched the heck out of google and could not find a permanent solution. Searched in docs. Still no luck.

So back to where I started. No supported way I realized or may be I could not find?

I started digging now into the parameter – PrimaryControl and found a method – get_crmFormControl(). And finally from this I was able to get the formContext.

Code below.

function subgridEvent(primaryControl, selectedControl) {
    var formContext = primaryControl.a.get_crmFormControl().getFormContext();
    var grid = selectedControl.getGrid();

    // interact with the form using formContext now
}

Please note that this is not a documented approach and hence unsupported. However I could not find anything else from this and no where this scenario is mentioned as well.

Would really like to hear if you get a solution working which is supported.

Hope this helps!

Debajit Dutta
(Dynamics MVP)
For consultation/ training visit
http://www.xrmforyou.com or reach out to us at info@xrmforyou.com

Advertisements

Firing on-load event every time when record is saved. Exploring the data onload event in Dynamics v9.0

Recently I was working with a customer and came across a requirement where they needed to fire an event on client side every time once the data is saved successfully to process some post-save operations.

As we all know from version CRM 2013 and onwards the formload event does not fire when save is completed. So what is the other way around?

Well there is way to do that. We need to use the formContext.data.addOnLoad to add an event which fires every time when the data is refreshed.

The below code adds the onLoad event of data refresh in the form load event of the account entity.

function formLoad(e) {
    var formContext = e.getFormContext();

    formContext.data.addOnLoad(dataOnLoad);

    console.log("form load called.");
}

function dataOnLoad(e) {
    // write your code here.
    console.log("data onload fired");
}

formLoad is the event which is registered through Form events. In the formload we are adding the event for data refresh using the formContext.data.addOnLoad method.

The function dataOnLaod fires on the initial page load as well as every time the user clicks on save. And now you have a way to perform your post-save operations in client side whenever the record is saved.

Also please note that the function also fires if formContext.data.refresh() function is called.

Debajit Dutta
(Dynamics MVP)
For consultation/ training visit
http://www.xrmforyou.com or reach out to us at info@xrmforyou.com

Getting logged in user roles in client side in Dynamics V9.0

You may be asking? Why this mundane post? After all we have been here close to 8 years since 2011 released and we have millions of time retrieved it using Xrm.Page.context.getUserRoles().

So what’s the fuss in it?

So if you are working on CRM version below 9.0, then it’s no fuss. But if you are working on V9.0 and above, may be this is an interesting read for you.

Xrm.Page is deprecated. So you should no longer be using Xrm.Page.context.getUserRoles()

So what’s the other way. Well you need to do it the V9.0 way. Below is the  code to do the same.

var userRoles = Xrm.Utility.getGlobalContext().userSettings.securityRoles;

And the userRoles variable here contains the list of GUID’s of the roles the user have. Wasn’t that easy?

Also now with the unified interface coming in, you should be very careful about every piece of Javascript you write. There are some properties which works only in UCI and some in Web

For e.g – not only you can get the security roles but also the privilege id’s for all the security roles associated with the user.

var userRolesPrivileges = Xrm.Utility.getGlobalContext().userSettings.securityRolePrivileges;

Unfortunately, the above line only works in UCI and not on the web version.

Debajit Dutta
(Dynamics MVP)
For consultation/ training visit http://www.xrmforyou.com or reach out to us at info@xrmforyou.com

{Solved} Call Web API from within a plugin in Dynamics 365 online

Well, this is one question I cam across countless number of times. And in a recently conducted training boot camp, i was asked the same question. To be honest after understanding OAuth and how various types of grants work in Azure Active Directory, I was thinking this should not be a very tough problem.

To my surprise, when I started searching the community I came across many links which suggested it can’t be done and many links which suggested ways but no complete guidance on how to do it.

So decided to challenge myself in it for my blog readers and came out pretty quick within couple of hours. So here we start out journey.

Before we go ahead and discuss on how to do this, let’s understand the problem faced.

The first thing that we need to connect to Web API instance is an access token. And getting an access token now is just piece of cake. You reference Active Directory Authentication Library (you can easily get this from Nuget with the keyword : ADAL) in your project and then use some 4-5 pre-defined lines to get the token.

So what’s the problem with that? The problem is when we try to run the same code in our plugins and try to access the token, we get a security exception. The problem is our online plugins runs in sandbox mode.

So the solution to this would be not to use ADAL. So what are the other options? Off-course we can use plain simple HttpRequest to get the token right? After all internally ADAL might be doing the same right? Not sure about ADAL but certainly this is harder than you think.

The first point to consider here is when the plugin is executing, we do not have the password for the user.

In my blog link – https://debajmecrm.com/2018/04/30/headless-authentication-with-dynamics-crm-online-and-external-web-app-which-requires-client-secret/ I have explained in detail on how to get the access token without using ADAL. The problem here is, I am  using the password here to get the token. In plugins I will not have that.

So how to tackle this.

If you observe carefully, there are various types of grants in Oauth and one of them “grant_type=password” I have used in the above link. Well since I can’t use that, there is another type called grant_type=client_credentials”. And this is what we are going to use here.

So let’s now jump to the solution.

Step 1:

Register an application of type WebApp/Api in your Azure active directory with proper permission to Dynamics Application and then create an application user in Dynamics with the Application ID of your registered App. Give access to your application user with proper security roles to gain necessary access to entities.

If you are not aware of registering App or the concept of Application User in Dynamics, it’s explained in great detail in the below link

https://debajmecrm.com/2018/08/16/step-by-step-guide-query-dynamics-crm-web-api-using-server-to-server-authentication-with-application-user/

Just set-up the APP and the application user. Don’t try to use the code since it will not work in plugins.

Step 2:

Plugin code

public class WebApiSample : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            // getting the pipeline context.
             var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            var ent = (Entity)context.InputParameters["Target"];

            var accessToken = GetTokenWithoutADAL().GetAwaiter().GetResult();
             var accountJson = RetrieveAccounts(accessToken).GetAwaiter().GetResult();

            ent["vvbv_description"] = accountJson;
        }

        private async Task<string> GetTokenWithoutADAL()
        {
            string azureAdDomain = "xrmforyou62.onmicrosoft.com";
            string loginUrl = $"
https://login.microsoftonline.com/{azureAdDomain}/oauth2/token";
             string resource = "https://xrmforyou62.crm.dynamics.com";
              string clientId = "9a3590d8-c89e-4de5-bb50-8dfa3d9796a9";
            string clientSecret = "<put your client secret here>";

            HttpClient client = new HttpClient();
            var postData = $"client_id={clientId}&client_secret={clientSecret}&resource={resource}&grant_type=client_credentials";

            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, loginUrl);
            request.Content = new StringContent(postData, Encoding.UTF8);
            request.Content.Headers.Remove("Content-Type");
            request.Content.Headers.TryAddWithoutValidation("Content-Type", $"application/x-www-form-urlencoded");

            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

            var responseMessage = await client.SendAsync(request);
            var jsonResponseString = await responseMessage.Content.ReadAsStringAsync();

           var jsonContent = JsonConvert.DeserializeObject<Dictionary<string, string>>(jsonResponseString);

            return jsonContent["access_token"];
        }

        private async Task<string> RetrieveAccounts(string token)
        {
             string webApiUrl = "
https://xrmforyou62.api.crm.dynamics.com/api/data/v9.1";
             string url = $"{webApiUrl}/accounts?$select=name";

            HttpClient client = new HttpClient();
            client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
           
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

            HttpResponseMessage response = await client.GetAsync(url);

            string jsonContent = await response.Content.ReadAsStringAsync();

            return jsonContent;
        }
    }

You can do a better job at handling the client_id and client_secret. But this is just sample code and hence I am using this directly.

Also to parse the JSON, I have used the Newtonsoft.json dll which you need to ilmerge with the plugin assembly before uploading to the plugin registration tool. Check for the highlighted code.

And I have registered this plugin on the Pre-create of a test entity. I set the description of the field in this plugin to the account JSON and voila it works!

image

Eureka! I can see the account JSON in the description field after I create the record.

Isn’t it great guys.

Hope this helps. And hope you just had something interesting to read in CRM world today.

Debajit Dutta

(Dynamics MVP)

For consultation/ training visit www.xrmforyou.com or reach out to us at info@xrmforyou.com

Xrm.Page is deprecated with Version 9.0. How do I get the Clienturl and form data in Ribbon Actions?

Simple isn’t it? After all it was so easy

All I needed to write to get the ClientUrl

var clientUrl = Xrm.Page.context.getClientUrl();

And to get value of the field on the form

var fieldValue = Xrm.Page.getAttribute(“<fieldname>”).getValue()

So what’s the fuss? Well, if your CRM version is 8.2 and below, no worries. It it perfect.

However with version 9.0 and above, the Xrm.Page has been deprecated. So it means you can use them no longer.

So how do I get the Client Url? The below code does it for you.

var globalContext = Xrm.Utility.getGlobalContext();

var clientUrl = globalContext.getClientUrl();

And how about getting the value of a field on the form?

With version 9.0 and above, you should try to access the form data using the formContext. But again the problem is, to get the formContext, you need to use ExecutionContext. So the next question lies on how to pass executionContext parameter to your Ribbon actions.

For that you need to pass the CrmParameter – Primary control to your ribbon action. Below is the code to get the data using formContext.

image

function ribbonHandler(e) {
    var formContext = e.getFormContext();

    var recordId = formContext.data.entity.getId();
    var fieldValue = formContext.getAttribute("<field_name>").getValue();
}

Just another day as a consultant and I hope this blog just adds a grain to your CRM knowledge heap.

Debajit Dutta
(Dynamics MVP)
For consultation/ corporate training visit http://www.xrmforyou.com or reach out to us at info@xrmforyou.com

Perform Retrieve using Alternate Key and SDK in Dynamics 365

Alternate keys have been implemented some time back and by now I think most of the consultants working with Microsoft Dynamics are familiar with the concept of alternate keys.

In case you are new to alternate key, you can follow the below documentation here.

https://docs.microsoft.com/en-us/dynamics365/customer-engagement/developer/define-alternate-keys-entity

Recently I got a request to retrieve using an alternate key. Strangely enough, there are many examples of Update/ Upsert using alternate key but not much with the retrieve using SDK.

Moreover developers coming in new gets confused whenever they try to retrieve using OrganzationService.Retrieve because the method does not have any parameter to take in an alternate key

Well the alternative to that is using the RetrieveRequest. Below is the sample code to retrieve a contact record with alternate key. The alternate key is set up on the Email (emailaddress1) field of the contact entity.

string entName = "contact";
            string alternateKeyfield = "emailaddress1";

            RetrieveRequest retrieveRequest = new RetrieveRequest();
            retrieveRequest.ColumnSet = new ColumnSet(new string[2] { "firstname", "lastname" });
            retrieveRequest.Target = new EntityReference(entName, alternateKeyfield, "test@123.com");

            RetrieveResponse resp = (RetrieveResponse)proxy.Execute(retrieveRequest);

            if(resp.Entity != null)
            {
                Console.WriteLine($"First Name: {resp.Entity.GetAttributeValue<string>("firstname")}");
                Console.WriteLine($"Last Name: {resp.Entity.GetAttributeValue<string>("lastname")}");
             }

Strangely enough the RetrieveRequest is hardly being used. The highlighted one is the line where we are trying to retrieve using Alternate key which is set on emailaddress1 field.

Please note if there is no record with the specified email address here (test@123.com), you will get an error. Your code should have appropriate try catch block to handle the same

Hope this helps next time when you try to retrieve a record using alternate key.

Debajit Dutta

(Dynamics MVP)

For consultation/ corporate training visit www.xrmforyou.com or reach out to us at info@xrmforyou.com

{Knowhow} Delete field from managed solution in Target environment when deleted from source environment–Dynamics 365

Dynamics CRM 2016 introduced the concept of solution segmentation, solution patching and cloning and indeed it has come a long way over the last 2-3 years. We are now exposed to CDM, flows, powerapps and what not. But CRM has some old tricks up it’s sleeve which makes you go back to the basics sometimes and I am going to pen down one such scenario here. Even experienced consultants in CRM are sometimes bamboozled by this problem and I hope this is going to benefit my blog readers.

So here is the problem

  • Source Environment (Env X) has a custom field of type Optionset – “new_FieldToBeDeleted” for Entity – Account (named it explicitly for this blog. Don’t follow such convention for stupid names Smile) . The field is Local Optionset.
  • The field is placed on the form of the account.
  • The solution from Env X is exported as managed and deployed to target environment (Env Y).
  • Because of change of requirement, the local optionset now deleted from the source and replace with a global optionset.

The question now arises. How to delete the field from the target environment? It is a managed solution. Hence if you try to delete the field, you will receive an error. Also importing the new managed solution from source won’t help in this case.

Fortunately there is one way to do so with the help of Clone Solution feature.

  • In the solution in the source environment, do all the necessary changes like removing dependencies, deleting the local optionset field, creating a global optionset and creating a new field in Account Entity to map to the global optionset.
  • Select the solution and click on Clone Solution.

image

  • Put a version number as asked for.
  • Export the solution as managed.
  • While importing the solution in the target environment, check the box – “Stage for Upgrade” and select the option – “Maintain customizations”.

image

  • Once the solution is imported click on Apply Solution Upgrade button. The field in the target would be deleted as well and you will see the new field in your target environment.

          image

Hope this helps next time you have trouble with your solution management.

Debajit Dutta

(Dynamics MVP)

For consultation/ corporate training visit www.xrmforyou.com or reach out to us at info@xrmforyou.com

Where is “APP” permission in security role in Dynamics V9.0

Recently one of our customers upgraded to version 9.0 and suddenly and once the upgrade completed, they reported to us that they are not able to find permission for Apps under their security roles, both system and custom.

Basically in the prior versions, the permission for App used to come under Customizations tab with the Name – “APP”

image

The App entity was missing in the security role screen in the customization tab, once they upgraded to V9.0.

Honestly, I did not explore much of the customization tab in Version 9.0 and when this was reported, I was kind of out of answers as to where it have gone.

Searched with the App word in the browser screen of the security role and found one called “Model-Driven App”. But was not sure about whether this referred to the same one or is it something different.

A bit of searching and finally came across this article which verified my understanding.

https://docs.microsoft.com/en-us/dynamics365/customer-engagement/outlook-app/deploy-dynamics-365-app-for-outlook#required-privileges

Verified with existing security roles and I can see the App permissions are rightly reflected in Model-driven App Entity

image

Hope this quick tip saves you some time

Debajit Dutta

(Dynamics MVP)

For consultation/ corporate training visit www.xrmforyou.com or reach out to us at info@xrmforyou.com

“Cannot read property ‘<entity name>’ of null”–Error while executing a bound action from from a WebResource in Dynamics V9.0 using Xrm.WebApi.execute

This one drove me crazy and believe these kind of things pop-up the most when you don’t expect them at all. A simple training going on and I was demoing them the wonderful Xrm.WebApi methods. Then came the turn of using Xrm.WebApi.execute to execute a bound action or entity action.

Since I worked a lot recently on Xrm.WebApi methods, was pretty confident of doing and just took this one example out of a participant wish. So here goes the requirement.

  • There is a custom entity (let’s call it Test Entity) which has N:1 relation with account. So account is a lookup on the Test Entity form.
  • There is a ribbon button on the Test Entity form which when clicked would open up a webresource which would show all the contacts related to the account in the form of HTML table.

So simple isn’t it. In the days when we moved to PowerApps and Flows, this just seems a walk in the park.

So here I started from scratch and wrote the below code onload of the HTML webresource.

var actionRequest = {};
     var crmContext = Xrm.Utility.getGlobalContext();
     var qString = crmContext.getQueryStringParameters();


    var accountId = qString.Data.replace(“}”, “”).replace(“{” ,””);


    actionRequest.StringParam = “Web Api Test”;
     actionRequest.DecimalParam = 30.43;
     actionRequest.entity = { entityType: “account”, id: accountId };


    actionRequest.getMetadata = function () {
         return {
             boundParameter: “entity”,
             operationName: “new_EntityAction”,
             operationType: 0,
             parameterTypes: {
                 “StringParam”: {
                     structuralProperty: 1,
                     typeName: “Edm.String”
                 },


                “DecimalParam”: {
                     structuralProperty: 1,
                     typeName: “Edm.Decimal”
                 },


                “entity”: {
                     structuralProperty: 5,
                     typeName: “mscrm.account”
                 }
             }
         }
     };


    Xrm.WebApi.execute(actionRequest).
         then(function (data) {
             // parsing your results here.


        },
         function (error) {
             debugger;
             console.log(error.message);
         });

Before we look at the code above, let’s find out the action.

It’s a bound action for the account entity which has two input parameters – String Parameter & Decimal Parameter

image

Button is in place and then just as i click the button and HTML webresource pops out boom! An error flashing – Cannot read property ‘account’ of null. The same would work if the webresource is embedded within the CRM Form.

For readers who are curious to know how I opened the WebResource, here is the code below.

var webResourceName = “new_/pages/spa.html”;
     var windowOptions = { height: 600, width: 800 };


    var parentAccount = Xrm.Page.getAttribute(“new_parentaccount”).getValue();


    var parentAccountId = ”;
     if (parentAccount != null) {
         parentAccountId = parentAccount[0].id;
     }


    Xrm.Navigation.openWebResource(webResourceName, windowOptions, parentAccountId);

Such an embarrassment in front of a big audience. Somehow I made this understand and they took it pretty well. If you are thinking that whether I included ClientGlobalContext.js.aspx, yeah I did. And did all whatever it takes to make it work. But none worked.

Searched the heck out of google, no luck as well.

Was not in a mood to leave. I started debugging, went inside all the system files stepping through each lines of the thousands of lines of system code.

And finally the Eureka moment. It was failing at the call of Xrm.Utility.getEntitySetName.

But let’s understand why? Well CRM relies on two arrays window.ENTITY_SET_NAMES  OR window.top.ENTITY_SET_NAMES  to get the entity set name from the logical name and window.ENTITY_PRIMARY_KEYS or window.top.ENTITY_PRIMARY_KEYS  to get the primary key property name of the entity.

Because the webresource is opening as a pop-up, both the arrays are coming as null and hence the error.

So before calling Xrm.WebApi.execute i just wrote the following lines.

var entNames = {};

entNames[“account”] = “accounts”;

window.ENTITY_SET_NAMES = JSON.stringify(entNames);


var primaryKeys = {};

primaryKeys[“account”] = “accountid”;

window.ENTITY_PRIMARY_KEYS = JSON.stringify(primaryKeys).

And this time when I run, what a relief. The code just ran fine and finally I could see my debugger being hit in the success block.

But please bear in mind this is unsupported and is never suggested. This post is more to highlight why it does not work in this scenario. May it start’s working in a future release.

However hope it saves some time or now you are aware before hand it will not work for this scenario

Debajit Dutta

(Dynamics MVP)

For consultation/ corporate training visit www.xrmforyou.com or reach out to us at info@xrmforyou.com




DateTime Attribute Onchange event in Dynamics Portals

Chandana's CRM Blog

Today’s blog title sounds to be very easy but, there lies something interesting as well. Well continued to work with Dynamics Portals, starting up with entity form and entity form attributes. I have a requirement like to calculate the age on input of date field.

So I am just trying to trigger an onchange event of date field in Entity Form. DateTime Field in entity form is usually a DateTimePicker which is evident to us. My datetime attribute schema name is “new_datefield”. So i tried simply to trigger on change event like

$(‘#new_datefield’).bind(“change”, function() {  alert(“hello i am change event.”);});

Which doesn’t fire the change event. 😦 Simple but not working. After couple of observations over the datetime attribute. DateTime field on click or expand opens up a datetimepicker where actually I need to attach my event handler to it.

To add a custom event handler to datetimepicker if I…

View original post 94 more words