Olá pessoal,
Dando continuidade ao tema Provedor de Dados Customizado, utilizado para consumir APIs Externas do Dynamics 365, vamos a parte 2!
Para aqueles que ainda não viram a primeira parte, por favor, acessem a aqui!
Vamos lá…
Provedor de Dados Customizado (código)
Vamos ao código do provedor de dados, mesmo não sendo um grande desenvolvedor C#, tentei dividir as funcionalidades em algumas classes para facilitar o reuso. Então se assustem com a quantidade, as classes são pequenas.
O printscreen to projeto no Visual Studio para ajudar a se localizar:
Primeiro vamos as classes auxiliares(Helpers)…
O Helper.cs é a espinha dorsal, possui ométodo GetAPIData é o “cara”! Responsável pelas chamadas à API Externa e de recuperar a resposta.
using Microsoft.Xrm.Sdk; using System; using System.Collections.Generic; using System.IO; using System.Net.Http; using System.Runtime.Serialization.Json; using System.Threading.Tasks; namespace MyCustomDataProvider { public class Helper { public static async Task<string> GetAPIData(ITracingService tracer, string url) { using (HttpClient client = HttpHelper.GetHttpClient()) { tracer.Trace($"API URL: {url}"); HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, new Uri(url)); HttpResponseMessage response = await client.SendAsync(request); // If response status is NOT Sucess if (!response.IsSuccessStatusCode) throw new Exception("API has stopped this from happening"); string json = response.Content.ReadAsStringAsync().Result; tracer.Trace($"JSON response: {json}"); return json; } } public static List<T> DeserializeJSONObject<T>(string json) { return DeserializeObject<List<T>>(json); } public static T DeserializeObject<T>(string json) { using (MemoryStream stream = new MemoryStream()) { DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T)); StreamWriter writer = new StreamWriter(stream); writer.Write(json); writer.Flush(); stream.Position = 0; T responseObject = (T)serializer.ReadObject(stream); return responseObject; } } public static string SerializeObject<T>(object obj) { using (MemoryStream stream = new MemoryStream()) { DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T)); serializer.WriteObject(stream, obj); stream.Position = 0; StreamReader reader = new StreamReader(stream); string requestBody = reader.ReadToEnd(); return requestBody; } } } }
A classeHttpHelper.cs, apenas criar o objeto HttpClient:
using System.Net.Http; namespace MyCustomDataProvider { public class HttpHelper { public static HttpClient GetHttpClient() { HttpClient client = new HttpClient(); client.DefaultRequestHeaders.Add("Connection", "close"); return client; } } }
A classeSearchVisitor.cs, implementa a interface IQueryExpressionVisitor, obrigando o override do método Visit, com isso conseguimos recuperar o que está sendo consultado, ou seja, os critérios(criterias) da consulta:
using Microsoft.Xrm.Sdk.Query; namespace MyCustomDataProvider { public class SearchVisitor : IQueryExpressionVisitor { public string SearchKeyWord { get; private set; } public QueryExpression Visit(QueryExpression query) { // Returning null will get a random result if (query.Criteria.Conditions.Count == 0) return null; // Get the first filter value SearchKeyWord = query.Criteria.Conditions[0].Values[0].ToString(); return query; } } }
Criei a classe ExternalActivity.cs como uma espécie de Model e Controller ao mesmo tempo, poderia ter separado, mas deixei assim…
using Microsoft.Xrm.Sdk; using System; using System.Collections.Generic; namespace MyCustomDataProvider { public class ExternalActivity { #region Properties public string ExternalActivityId { get; set; } public int ExternalActivityKey { get; set; } public string ContactId { get; set; } public string Subject { get; set; } public string Details { get; set; } public string From { get; set; } public string To { get; set; } #endregion public static EntityCollection FillEntityCollection(ITracingService tracer, string json) { EntityCollection results = new EntityCollection(); // Deserialize JSON object to List of External Activities (List<ExternalActivity>) List<ExternalActivity> listExternalActivity = Helper.DeserializeJSONObject<ExternalActivity>(json); tracer.Trace($"Results Count: {listExternalActivity.Count.ToString()}"); // Loop API results foreach (ExternalActivity externalActivity in listExternalActivity) { Entity entity = CreateExternalActivity(externalActivity); if (entity == null) continue; results.Entities.Add(entity); } return results; } private static Entity CreateExternalActivity(ExternalActivity externalActivity) { // Check if primary field has informed if (string.IsNullOrEmpty(externalActivity.ExternalActivityId)) return null; return new Entity("new_externalentity") { ["new_externalentityid"] = new Guid(externalActivity.ExternalActivityId), ["new_contactid"] = new EntityReference("contact", new Guid(externalActivity.ContactId)), ["new_subject"] = externalActivity.Subject, ["new_from"] = externalActivity.From, ["new_to"] = externalActivity.To, ["new_details"] = externalActivity.Details, }; } } }
FillEntityCollection – é o método que realiza a deserialização do JSON retornado pela API, percorrendo o objeto e “montando” uma EntityCollection
CreateExternalActivity – recebe um objeto ExternalActivity e transforma em uma Entity, tenha cuidado com a conversão dos tipos de dados que o Dynamics está esperando recebemos, por exemplo, o atributo “new_contactid” é um lookup, assim temos que passar uma EntityReference.
Agora vamos aos plugins!
Retrieve.cs
using Microsoft.Xrm.Sdk; using System; using System.Threading.Tasks; namespace MyCustomDataProvider { public class Retrieve : IPlugin { #region Secure/Unsecure Configuration Setup private string _secureConfig = null; private string _unsecureConfig = null; public Retrieve(string unsecureConfig, string secureConfig) { _secureConfig = secureConfig; _unsecureConfig = unsecureConfig; } #endregion public void Execute(IServiceProvider serviceProvider) { ITracingService tracer = (ITracingService)serviceProvider.GetService(typeof(ITracingService)); IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext)); IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory)); IOrganizationService service = factory.CreateOrganizationService(context.UserId); try { EntityCollection results; EntityReference target = (EntityReference)context.InputParameters["Target"]; if (target != null) { string keyword = target.Id.ToString(); tracer.Trace($"Target: {keyword}"); string url = $"http://HOST/DemoJSONService/JSONService.svc/ExternalActivity/{keyword}"; tracer.Trace($"API URL : {url}"); tracer.Trace("Calling API"); // Call API through async method var getAPIDataTask = Task.Run(async() = > await Helper.GetAPIData(tracer, url)); Task.WaitAll(getAPIDataTask); tracer.Trace("API has been called"); // Parse API results to Entity Collection results = ExternalActivity.FillEntityCollection(tracer, getAPIDataTask.Result); tracer.Trace("Business Entity Collection has been filled up with API response"); if (results != null && results.Entities.Count > 0) { // Return Business Collection Object filled with API response context.OutputParameters["BusinessEntity"] = results[0]; } } } catch (Exception e) { tracer.Trace($"Message: {e.Message}"); tracer.Trace($"StackTrace: {e.StackTrace}"); if (e.InnerException != null) { tracer.Trace($"InnerException Message: {e.InnerException.Message}"); tracer.Trace($"InnerException StackTrace: {e.InnerException.StackTrace}"); } throw new InvalidPluginExecutionException(e.Message); } } } }
Linhas Destacadas:
33 – Recuperamos o ExternalActivityId do contexto(Target)
37 – URL da API Externa
43 e 44 – Invocamos o método assíncrono GetAPIData
49 – Resultado da API é transformado em uma EntityCollection
53 à 57 – OutputParameter do tipo BusinessEntity é populado com a primeira (e única linha) da EntityCollection
RetrieveMultiple.cs
using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Extensions; using Microsoft.Xrm.Sdk.Query; using System; using System.Threading.Tasks; namespace MyCustomDataProvider { public class RetrieveMultiple : IPlugin { public void Execute(IServiceProvider serviceProvider) { ITracingService tracer = (ITracingService)serviceProvider.GetService(typeof(ITracingService)); IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext)); IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory)); IOrganizationService service = factory.CreateOrganizationService(context.UserId); try { QueryExpression query = context.InputParameterOrDefault<QueryExpression>("Query"); EntityCollection results; SearchVisitor visitor = new SearchVisitor(); query.Accept(visitor); string keyword = visitor.SearchKeyWord; string url = $"http://HOST/DemoJSONService/JSONService.svc/ExternalActivities/{keyword}"; tracer.Trace($"Keyword searched: {keyword}"); tracer.Trace($"API URL : {url}"); tracer.Trace("Calling API"); // Call API through async method var getAPIDataTask = Task.Run(async() = > await Helper.GetAPIData(tracer, url)); Task.WaitAll(getAPIDataTask); tracer.Trace("API has been called"); // Parse API results to Entity Collection results = ExternalActivity.FillEntityCollection(tracer, getAPIDataTask.Result); tracer.Trace("Business Entity Collection has been filled up with API response"); // Return Business Collection Object filled with API response context.OutputParameters["BusinessEntityCollection"] = results; } catch (Exception e) { tracer.Trace($"Message: {e.Message}"); tracer.Trace($"StackTrace: {e.StackTrace}"); if (e.InnerException != null) { tracer.Trace($"InnerException Message: {e.InnerException.Message}"); tracer.Trace($"InnerException StackTrace: {e.InnerException.StackTrace}"); } throw new InvalidPluginExecutionException(e.Message); } } } }
Linhas Destacadas:
22-25 – Recuperamos o ContactId do contexto utilizando a classe SearchVisitor
26 – URL da API Externa
33 e 34 – Invocamos o método assíncrono GetAPIData
39 – Resultado da API é transformado em uma EntityCollection
44 – OutputParameter do tipo BusinessEntityCollection é populado
Chega de código, vamos registrar o provedor de dados! Este será meu próximo post, não percam!
Para maiores detalhes, seguem os documentos oficiais:
Custom virtual entity data providers
Sample: Generic virtual entity data provider plug-in
[]’s,
Tiago
Boa noite Parabéns pelo conteúdo.
Deixa eu falar, tem algum material relacionado a consulta de tabelas via API json, preciso consumir as tabelas em uma plataforma de BI que não tem integração como o Power BI.
CurtirCurtir
Ola Caio,
Obrigado! Toda a plataforma pode ser acessada atraves da url da sua organizacao + /api/9.1/ + nome da tabela no plural. Algo assim: https://SUAORG.crm.dynamics.com/api/data/v9.1/accounts. Voce pode navegar direto pelo browser onde vc ja esteja logado em sua organizacao.
[]’s,
Tiago
CurtirCurtir