Olá pessoal,
Dando continuidade ao post anterior, onde criamos nossa falsa API externa (sem nenhum atributo do tipo Guid), neste iremos criar o provedor de dados customizado para consumi-la!
Provedor de Dados Customizado (código)
Vamos ao código do provedor de dados, mesmo padrão deste post.
O printscreen to projeto no Visual Studio para ajudar a se localizar:
Não realizei nenhuma mudança em todas as classes auxiliares (Helpers)… Assim, utilize o mesmo código em um post anterior.
A model/controller ExternalActivity.cs mudou um pouco, devido a chave primária precisar de ser um GUID, estou criando um em tempo de execução.
using Microsoft.Xrm.Sdk; using System; using System.Collections.Generic; namespace MyCustomDataProvider2 { public class ExternalActivity { #region Properties public int ExternalActivityKey { get; set; } public int ExternalContactKey { 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 FillEntityCollectionByContactId(ITracingService tracer, string json, Guid contactId) { 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; // First 24 characters of contact id (GUID). I.e.: C4BA7D70-347B-E811-A961-000D3AE05F14 = C4BA7D70-347B-E811-A961- string contactIdSubsGuid = contactId.ToString().Substring(0, 24); tracer.Trace($"contactIdSubsGuid: {contactIdSubsGuid}"); // Get External Activity Id and Format/Add 12 Left Zeros. I.e.: 15 = 000000000015 string formatedExternalActivityKey = entity["new_externalactivitykey"].ToString().PadLeft(12, '0'); tracer.Trace($"externalActivityKey: {formatedExternalActivityKey}"); // Create Guid object with first 24 characters of contact id + external activity Id Guid externalEntityId = new Guid(contactIdSubsGuid + formatedExternalActivityKey); tracer.Trace($"externalentityid: {externalEntityId}"); // Set External Entity Id entity["new_externalentityid"] = externalEntityId; results.Entities.Add(entity); } return results; } public static EntityCollection FillEntityCollectionByExternalEntityId(ITracingService tracer, string json, string externalEntityId) { 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; // Set External Entity Id entity["new_externalentityid"] = new Guid(externalEntityId); results.Entities.Add(entity); } return results; } private static Entity CreateExternalActivity(ExternalActivity externalActivity) { return new Entity("new_externalentity") { ["new_externalactivitykey"] = externalActivity.ExternalActivityKey, ["new_externalcontactkey"] = externalActivity.ExternalContactKey, ["new_subject"] = externalActivity.Subject, ["new_from"] = externalActivity.From, ["new_to"] = externalActivity.To, ["new_details"] = externalActivity.Details, }; } } }
Linhas Destacadas:
9 à 21 – Estrutura da classe, vejam quem não existe nenhuma propriedade do tipo Guid.
40 à 53 – Por não termos um Guid vindo do serviço externo, estou criando um, fazendo uma junção do id do contato (contactId) e a chave de negócios “new_externalentitykey”, assim é possível atribuir um valor para a chave primária “new_externalentityid”. Este método será chamado pelo plug-in de RetrieveMultiple (vou falar mais quando chegar no código do plug-in).
78 e 79 – Novamente, por não termos um atributo Guid como chave primária da entidade External Activity, estou atribuindo um valor em tempo de execução do plug-in Retrieve (vou falar mais quando chegar no código do plug-in).
87 à 98 – Criação do objeto Entity sem nenhum atributo do tipo Guid, mas veja que estou atribuindo valor à chave primária, nas linhas 40 à 53 e 78 à 79, como foi explicado anteriormente.
Agora vamos aos plug-ins!
Retrieve.cs
using Microsoft.Xrm.Sdk; using System; using System.Threading.Tasks; namespace MyCustomDataProvider2 { 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"]; // Check if thre is a target object if (target != null) { string externalActivityId = target.Id.ToString(); tracer.Trace($"Target Id: {externalActivityId}"); int externalActivityKey = Convert.ToInt32(externalActivityId.Substring(24, 12)); tracer.Trace($"externalActivityKey: {externalActivityKey}"); string url = $"http://HOST/DemoJSONService/JSONService.svc/ExternalActivityByExternalActivityKey/{externalActivityKey}"; 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.FillEntityCollectionByExternalEntityId(tracer, getAPIDataTask.Result, externalActivityId); 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:
34 à 41 – Recuperamos o ExternalActivityId do contexto (Target), depois pegamos apenas os últimos 12 caracteres, pois estes caracteres possuem a chave de negócio “ExternalActivityKey”. Algo similar irá ocorrer em tempo de execução: id recuperado “C4BA7D70-347B-E811-000000000015”, depois das conversões, teremos como valor “15”. Por fim, criamos a URL que será utilizada para chamar a API externa.
Nenhuma novidade nas demais linhas, são iguais ao exemplo anterior.
RetrieveMultiple.cs
using System; using System.Linq; using System.Threading.Tasks; using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Client; using Microsoft.Xrm.Sdk.Extensions; using Microsoft.Xrm.Sdk.Query; namespace MyCustomDataProvider2 { 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); OrganizationServiceContext orgContext = new OrganizationServiceContext(service); try { QueryExpression query = context.InputParameterOrDefault<QueryExpression>("Query"); EntityCollection results; SearchVisitor visitor = new SearchVisitor(); query.Accept(visitor); Guid contactId = new Guid(visitor.SearchKeyWord); tracer.Trace($"Keyword searched: {contactId}"); // Retrieve Contatc Custom Business Key value by Contact Id var contact = (from m in orgContext.CreateQuery("contact") where m.GetAttributeValue<Guid>("contactid") == contactId select new { id = m.GetAttributeValue<Guid>("contactid"), externalContactKey = m.GetAttributeValue<int>("new_externalcontactkey") } ).Single(); tracer.Trace($"External Contact Key: {contact.externalContactKey}"); string url = $"http://HOST/DemoJSONService/JSONService.svc/ExternalActivitiesByExternalContactKey/{contact.externalContactKey}"; 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.FillEntityCollectionByContactId(tracer, getAPIDataTask.Result, contactId); 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:
31 à 39 – Estas linhas são o “pulo do gato”, por não termos o Guid do contato (contactid) na API externa, precisamos recuperar o valor da chave de negócios (ExternalActivityKey). Assim, estou consultando o próprio Dynamics para recuperar o atributo “new_externalactivitykey”, pois com ele conseguimos consultar a API exterma!
Eu entendo que a performance será um pouco degradada, pois estamos realizando um consulta no Dynamics antes mesmo de chamar a API externa, porém, este overhead pode valer mais do que reescrever a API externa e talvez não impacte tanto assim na performance. Faça seus testes e confira!
Também não há nenhuma novidade nas demais linhas, são iguais ao exemplo anterior.
Bom, o código do provedor de dados é este, compile-o e registre-o, da mesma forma que eu descrevi aqui.
Pronto!!! Entidade virtual e provedor de dados customizado criados sem o uso de nenhuma chave do tipo GUID!
Espero ter ajudado!
[]’s,
Tiago