*Before I get started with the topic, a quick note on the use cases of this particular post. While everything mentioned in the post would work just fine, this post is more of conceptual and feasibility study than a real life implementation use case. For working with CRM data in same environment, SDK is the best approach. Even for scenarios of consuming CRM data from a different environment from plugin, there are other ways to do the same. This won’t be a recommended approach for enterprise applications*
Follow Debajit’s Power Apps & Dynamics 365 Blog on WordPress.comNow coming to this topic, I came across this question countless number of times. And in a recently conducted training boot camp, I was asked the same question – “Can I invoke WebAPI calls from plugins?”. Also customers having multiple CRM instances in a single tenant and looking to consume data from one environment from plugins in another environment, are looking for an answer to this. 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.
{Solved} How to call Web API from a plugin or custom workflow in Dynamics 365
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/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
401. Unauthorized error while querying Dynamics 365 Web Api from external application. Follow these steps to avoid the error
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!
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
Discover more from Debajit's Power Apps & Dynamics 365 Blog
Subscribe to get the latest posts sent to your email.
Hello Debajit,
Nice article. I have a Restful API and it’s hosted on IIS server over https. I am trying to call it from Plugin but I am not able to do so. It has anonymous authentication.
Below is the sample code written in Plugin
ServicePointManager.ServerCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => true;
var webClient = new WebClient();
webClient.Headers[HttpRequestHeader.ContentType] = “application/json”;
var serviceUrl = “https://www.mysite1.com/api/Values/”;
string response = webClient.DownloadString(serviceUrl);
I want to run the plugin in sandbox mode. Can you put some light f I am doing something wrong.
Regards,
Sachin
Hi Sachin. Thanks for reading my blog. what is the error you are getting?
-Debajit