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

