Dynamics 365 – Entidade Virtual SEM Chave Primária do tipo GUID (Parte 2/2)


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

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s

Este site utiliza o Akismet para reduzir spam. Saiba como seus dados em comentários são processados.