Fixed –Lookup value plugintypeexportkey [Guid] is not resolvable – Solution Import error (Dynamics 365 / Dataverse)


Recently, we faced an interesting import failure while moving a solution containing a Custom API.

Solution “Temp Plugin Step Custom API Transfer” failed to import: Lookup value 8f3269b7-a24d-43e4-9319-0c5e7ddf2b53 is not resolvable.

A screen shot of a computer

AI-generated content may be incorrect.

This clearly pointed to a lookup resolution issue — the solution import process was trying to bind the Custom API to a Plugin Type (class), but couldn’t find the referenced plugin in the target environment.

Each Custom API will have its own folder inside the Solution.

A screenshot of a computer

AI-generated content may be incorrect.

Looking into the solution files (specifically the customapi.xml) of that particular Custom API, we found this section:

A screenshot of a computer
AI-generated content may be incorrect.

Notice the <plugintypeexportkey> tag. This is where the Custom API references the Plugin Type (the actual C# class implementing the logic).

A computer screen with text and arrows pointing to it

AI-generated content may be incorrect.

When a Plugin class is created in Dynamics 365, it gets assigned a unique Plugin Type Id (GUID).

In the source environment, the Custom API was tied to a plugin with ID 420c7261-7461-4b37-87f0-1afcec427a46. However, in the destination environment, which was another development environment, a different plugin class was already created for that custom api. So during solution import, Dataverse tried to match the GUID 420c7261… but couldn’t find it in the target environment. Hence, the lookup resolution failed, and the solution import was blocked.

To resolve this, we manually updated the GUID in the customapi.xml to match the Plugin Type Id of the destination environment. Below, we are getting the ID from the Plugin Registration tool. The other option to fix would have been to remove the Plugin reference from the source, export, and then import.

A screenshot of a computer

AI-generated content may be incorrect.

After making this change, we re-imported the solution, and it worked successfully.

Also check – https://technicalcoe.com/2024/07/04/troubleshooting-power-platform-solution-import-errors/

Hope it helps..

Advertisements

Dataverse Custom API – Global and Entity (Binding Type) example


We can define the Custom API’s binding type as Global, Entity, or Entity Collection. In this post, we can see how a Global and Entity binding type Custom API can be defined, write a corresponding plugin, and then invoke/test through Postman.

We can create Custom API through Plugin Registration Tool, Power Apps, Code, Solution files, and or can use XrmToolBox Plugin – Custom API Manager.

Below we have defined a Custom API name custom_GlobalAPI, with binding type as Global and one Request (input) parameter and Response (output) Property of type string.

Below is how we define the plugin type for it and can access the input and output parameters through context.

To test it we can use the XrmToolBox Custom API Tester plugin as shown below

And from Postman, once we have the access token, we can call the Custom API as shown below.

Now for a Bound Custom API, we have the following definition. It is bound to the Contact table and has one input parameter and one output parameter similar to our Global Custom API.

Below is how we define the plugin type for it, and can access the input and output parameters similar to Global Custom API.

However, in the case of binding type Entity, we will have the Request parameter named Target of type Entity Reference for the bound entity added automatically.

We can test it using the Custom API Tester plugin, however as it is bound type, we need to select/specify the contact record (the table it is bound to), before we can execute it.

To call it from Postman, we need to use the fully qualified name i.e. Microsoft.Dynamics.CRM.[unique name of the Custom API] unlike Global one.

 public class APIPluginGlobal : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService orgService = factory.CreateOrganizationService(context.UserId);

            try
            {
                tracingService.Trace("start plugin execution: {0}", this.GetType().FullName);

                // check for the message name i.e. Unique Name of the Custom API
                if (context.MessageName.Equals("custom_GlobalAPI", StringComparison.OrdinalIgnoreCase))
                {
                    // check for the request parameter in the inputparamters of the context 
                    if (context.InputParameters.Contains("inputParam"))
                    {
                        // get the value of the input parameter
                        string inputValue = context.InputParameters["inputParam"].ToString();
                        // set the value of response property through outputparameters 
                        context.OutputParameters["outputParam"] = "Got following value as Input : " + inputValue;
                    }
                }

                tracingService.Trace("end plugin execution: {0}", this.GetType().FullName);
            }
            catch (System.ServiceModel.FaultException<OrganizationServiceFault> ex)
            {
                tracingService.Trace(ex.Detail.Message);
                throw;
            }
            catch (Exception ex)
            {
                tracingService.Trace(ex.ToString());
                throw;
            }
        }
    }
public class APIPluginBound : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService orgService = factory.CreateOrganizationService(context.UserId);

            try
            {
                tracingService.Trace("start plugin execution: {0}", this.GetType().FullName);

                // unique name of the custom api
                if (context.MessageName.Equals("custom_BoundAPI", StringComparison.OrdinalIgnoreCase))
                {
                    // Target property of type Entity Reference
                    if (context.InputParameters.Contains("Target") &&
                        context.InputParameters["Target"] is EntityReference)
                    {
                        var contact = (EntityReference)context.InputParameters["Target"];
                        // access the input request parameter
                        if (context.InputParameters.Contains("inputParam"))
                        {
                            string inputValue = context.InputParameters["inputParam"].ToString();
                            // set the output parameter value
                            context.OutputParameters["outputParam"] = "Got following as Input Parameter : " + inputValue
                                + " for record : " + contact.Id.ToString();
                        }
                    }
                }

                tracingService.Trace("end plugin execution: {0}", this.GetType().FullName);
            }
            catch (System.ServiceModel.FaultException<OrganizationServiceFault> ex)
            {
                tracingService.Trace(ex.Detail.Message);
                throw;
            }
            catch (Exception ex)
            {
                tracingService.Trace(ex.ToString());
                throw;
            }
        }
    }

Hope it helps..

Advertisements

Dataverse Custom API – Key Articles / Tool


Thanks to David Rivard for the wonderful articles and XrmToolBox Plugin Custom API Manager on Custom API

Custom API Test (XrmToolBox plugin) by Jonas Rapp

https://www.xrmtoolbox.com/plugins/Rappen.XrmToolBox.CustomAPITester/

Also check the other interesting articles on Custom API –

https://xrmdynamicscrm.wordpress.com/tag/dynamics-crm-custom-api/

https://www.pragmatic-development.io/blog/implement-business-logic-with-dataverse-custom-api/

Advertisements

How to – Create a custom API of type function in Dynamics 365 / Microsoft Dataverse


Continuing our previous posts,

Create Custom API Message
https://nishantrana.me/2021/01/13/use-custom-api-to-create-custom-messages-in-dynamics-365/

Allowed Custom Processing Step Type Property –

https://nishantrana.me/2021/01/20/allowed-custom-processing-step-type-allowedcustomprocessingsteptype-property-of-custom-api-in-dynamics-365-microsoft-dataverse/

Here we will create a custom API of type function by setting Is Function property as true.

  • The function requires HTTP GET method.
  • The function must include at least one Custom API Response Property defined.
  • The function cannot use Entity or Entity Collection Request Parameter, i.e. Binding Type can only be Global in case of Function.

Let us define a Custom API Response Property and associate it with the custom function defined.

Here we have defined a custom API response property of type Boolean.

The other data type supported are

Below is the plug-in that we will associate with the Custom API message, here we are setting the value of the response parameter.

Associated the plugin to the custom API message

Let us trigger our function using the Postman.

Invoking Custom APIs

https://docs.microsoft.com/en-us/powerapps/developer/data-platform/custom-api#invoking-custom-apis

Plugin trace log –

More information on Custom API

Hope it helps..

Advertisements

Custom API Request Parameter entity in Dynamics 365 / Microsoft Dataverse


Continuing our previous posts on Custom API

Here we will look at the CustomAPIRequestParameter entity.

  • This entity can be used to define input parameters to the Custom API.
  • It is not a mandatory entity for Custom API i.e. we can define a custom API without any input/request or output/response parameter.
  • In the case of multiple parameters, ordering is not important as they are identified using the name.
  • One parameter can be associated with only one Custom API.
  • We can have multiple parameters with the same Unique Name as long as they are associated with different Custom API.

Say e.g. below is our Custom API

And this is our Custom API Request Parameter associated with the above API of type String

The different data type for the request parameter

Below is our plugin registered for the custom message.

We are checking for the input parameter and writing it in the trace log.

Let us call our custom API

And let us check the plugin trace logs.

We can see the message being called successfully.

Get more details –

Create and use Custom APIs

Hope it helps..

Export key attribute uniquename for component CustomAPI must start with a valid customization prefix exception while creating Custom Action in Dynamics 365


We might get below error while creating a custom action from the maker portal without specifying the prefix.

Unhandled exception:

Exception type: System.ServiceModel.FaultException`1[Microsoft.Xrm.Sdk.OrganizationServiceFault]

Message: Export key attribute uniquename for component CustomAPI must start with a valid customization prefix

As the message suggests, we need to add prefix to the Unique Name of the custom API.

Check the prefix of the solution publisher and specify the same.

Adding the prefix allows us to save and create the Custom action record.

Microsoft docs suggest that is should match the prefix specified for the solution publisher

but it seems we can specify any prefix there and it allows us to save the record.

More on Custom API –

Create Custom API Message –
https://nishantrana.me/2021/01/13/use-custom-api-to-create-custom-messages-in-dynamics-365/

Execute Privilege Name Property –

https://nishantrana.me/2021/01/14/execute-privilege-name-executeprivilegename-property-of-custom-api-in-dynamics-365-microsoft-dataverse/

Allowed Custom Processing Step Type Property –

https://nishantrana.me/2021/01/20/allowed-custom-processing-step-type-allowedcustomprocessingsteptype-property-of-custom-api-in-dynamics-365-microsoft-dataverse/

Hope it helps..