Dynamics 365 CE/Model-Driven App – Solução Base para Visual Studio (Parte 2/3)


Olá pessoal,

Este é o segundo post desta série de como criar uma solução base para projetos D365/Model-Driven App no Visual Studio!

Para ver o que foi discutido anteriormente, clique aqui!

Para baixar a solução de Visual Studio contendo tudo que será discutido a solução completa, vá até o meu GitHub!

Neste post, irei detalhar como criar classes base para seus projetos de Plug-ins e Workflows customizados. Além disso, mostrarei como utilizá-las em exemplos práticos.

 

Classe Base para facilitar o trabalho com as interfaces dos Plugin e Workflows

Para trabalhar com Plugins e Workflows é necessário recuperar o contexto de execução para obter os objetos que queremos manipular, além disso podemos criar linhas de Trace, recuperar imagens (Pre e Post) e etc.

Na maioria das vezes nossos plugins e workflows começam da mesma forma, fazendo com que o código de inicialização seja repetido por várias vezes. Assim uma classe Base para Plugin e Workflows faz com que o trabalho seja minimizado neste posto de vista e cada classe contenha apenas códigos para executar única e exclusivamente sua regra de negócio.

Como criar uma classe Base?

Bom, vamos criar uma classe abstrata que implementa as interfaces dos Plugins (IPlugin) e Workflows (CodeActivity), pela classe ser abstrata, conseguimos novamente forçar a implementação do método Execute, que é primordial para a execução dos plugins e workflows.

Mas a grande vantagem é de recuperar tudo que precisamos do contexto e deixar público através das propriedades da classe, fazendo eles acessíveis por qualquer classe que implementar esta classe Base.

Com isso, só iremos uma vez codificar a obtenção de objetos do contexto e todos nossos plugins e workflows irão herdar a classe Base ao invés de suas interfaces como estamos acostumados.

Muito complexo? Dúvidas? Pois bem, vamos ao código!

Ambas classes Base ficarão fisicamente dentro do projeto Base, pois isso criamos as pastas Plugins e Workflows. Ao fazermos isso, todos os projetos que fazem referência ao projeto Base, receberão as classes Base dos Plugins e Workflows. Mas vamos aos códigos! Vamos à classe Base para…

Plugins:

using System;
using D365.BaseSolution.Base.Entities;
using Microsoft.Xrm.Sdk;

namespace D365.BaseSolution.Base.Plugins
{
    public abstract class BaseClass : IPlugin
    {
        private const string preImageName = "preImage";
        private const string postImageName = "postImage";

        public IOrganizationService service { get; private set; }
        public IPluginExecutionContext context { get; private set; }
        public IOrganizationServiceFactory serviceFactory { get; private set; }
        public CrmServiceContext svcContext { get; private set; }
        public ITracingService tracingService { get; private set; }
        public Entity entity { get; private set; }
        public Entity preEntityImage { get; private set; }
        public bool preEntityImageAvailable { get; private set; }
        public Entity postEntityImage { get; private set; }
        public bool postEntityImageAvailable { get; private set; }
        public string className { get; private set; }

        public void Execute(IServiceProvider serviceProvider)
        {
            className = GetType().Name;

            tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
            context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            service = serviceFactory.CreateOrganizationService(context.UserId);
            svcContext = new CrmServiceContext(service);

            preEntityImageAvailable = false;
            postEntityImageAvailable = false;

            if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
            {
                entity = (Entity)context.InputParameters["Target"];
            }

            if (context.PreEntityImages.Contains(preImageName) && context.PreEntityImages[preImageName] is Entity)
            {
                preEntityImage = context.PreEntityImages[preImageName];
                preEntityImageAvailable = true;
            }

            if (context.PostEntityImages.Contains(postImageName) && context.PostEntityImages[postImageName] is Entity)
            {
                postEntityImage = context.PostEntityImages[postImageName];
                postEntityImageAvailable = true;
            }

            ExecutePlugin();
        }

        public abstract void ExecutePlugin();
    }
}

Analisando o código…

Vejam que todas as propriedades só podem ser alteradas privadamente, assim, somente dentro desta classe. Posteriormente, por esta classe herdar de IPlugin, o Execute é implementado, só que ao inves de termos o código/regras de negócio do plugin, temos a inicialização das propriedades públicas da classe.

Desta forma, todo o contexto será recuperado e populado nas propriedades para a futura utilização.

Ao fim da classe, temos uma chamada para o método ExecutePlugin. Porém, trata-se de um método abstrato que ainda não sabe o que deverá fazer, mas isso garante que quem herdar desta classe irá inevitávelmente ter que fazer um override no método. É exatamente ai, onde as regras de negócios que o plugin irá ficar (já vou mostrar como implementar, ai ficará mais fácil para entender!).

Para implementar, ou seja, criar um plugin que consumirá nossa classe Base, vou criar um plugin na criação de uma Conta (Account). Visualmente algo como:


Como implementar a classe Base!

using System;
using D365.BaseSolution.Base.Entities;
using D365.BaseSolution.Base.Plugins;
using Microsoft.Xrm.Sdk;
using System.Linq;

namespace D365.BaseSolution.Plugins
{
    public class CreateAccount : BaseClass
    {
        public override void ExecutePlugin()
        {
            tracingService.Trace("Starting plugin {0}", className);

            try
            {
                if (entity != null)
                {
                    // Obtain the target entity from the input parameters.  
                    Account account = entity.ToEntity<Account>();

                    // *** Samples ***

                    // *** Create

                    Contact c = new Contact()
                    {
                        FirstName = "Test",
                        LastName = "123",
                        ParentCustomerId = entity.ToEntityReference()
                    };

                    service.Create(c);

                    //// *** Update

                    //Contact contact = new Contact()
                    //{
                    //    Id = new Guid("00000000-0000-0000-0000-000000000000"), // Inform a valid GUID
                    //    FirstName = "Test",
                    //    LastName = "123"
                    //};

                    //service.Update(contact);

                    //// *** Delete

                    //service.Delete(Contact.EntityLogicalName, new Guid("00000000-0000-0000-0000-000000000000")); // Inform a valid GUID

                    //// *** Retrieve

                    //var accounts = (from e in svcContext.AccountSet
                    //                where e.Id == new Guid("00000000-0000-0000-0000-000000000000") // Inform a valid GUID
                    //                select e);

                    // *** End Samples ***
                }
            }
            catch (Exception ex)
            {
                tracingService.Trace("Exception: {0}", ex.Message);

                throw new InvalidPluginExecutionException(ex.Message);
            }
            finally
            {
                tracingService.Trace("Finished plugin {0}", className);
            }
        }
    }
}

Não procurei fazer nada de extraordinário neste plugin, apenas inseri código para demonstrar como fazer um CRUD, isso ajudará você ao iniciar um projeto.

Analisando o código...

Como eu já havia dito no comentário da classe Base para Plugins, a classe que herdar de BaseClass obrigatoriamente tem que implementar ExecutePlugin por ser um método Abstract na classe mãe. Então faço isso, e ao acessar as propriedades públicas da classe mãe, obtenho todo o contexto que preciso para manipular meu plugin. Desta forma, nunca mais vou me preocupar em obter estas informações básicas para começar um plugin.

Agora o mesmo procedimento para:

Workflows
Assim como a classe Base para plugins, a classe Base para workflows ficará localizada dentro do projeto Base, mais precisamente dentro da pasta Workflow:


Vamos dar uma olhada no código…

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Workflow;
using System;
using System.Activities;
using D365.BaseSolution.Base.Entities;

namespace D365.BaseSolution.Base.Workflows
{
    public abstract class BaseClass : CodeActivity
    {
        public IOrganizationService service { get; private set; }
        public CodeActivityContext context { get; private set; }
        public IWorkflowContext workflowContext { get; private set; }
        public IOrganizationServiceFactory serviceFactory { get; private set; }
        public CrmServiceContext svcContext { get; private set; }
        public ITracingService tracingService { get; private set; }
        public string className { get; private set; }

        [MTAThread]
        protected override void Execute(CodeActivityContext codeActivityContext)
        {
            className = GetType().Name;

            context = codeActivityContext;
            workflowContext = context.GetExtension<IWorkflowContext>();
            serviceFactory = context.GetExtension<IOrganizationServiceFactory>();
            tracingService = context.GetExtension<ITracingService>();
            service = serviceFactory.CreateOrganizationService(workflowContext.UserId);
            svcContext = new CrmServiceContext(service);

            this.ExecuteWorkflow();
        }

        public abstract void ExecuteWorkflow();
    }
}

Analisando o código...

Vejam que é a mesma ideia anterior, propriedades que seriam inicializadas através do método principal e ao final um método abstrato que precisará ser reescrito (override) toda vez que alguma classe herdar a classe Base.

A única diferença aqui é a exata diferença do que é um plugin e um custom workflow, workflows precisam herdar de CodeActivity e ter o assembly de workflows. O restante é a mesma coisa!

Como implementar a classe Base!

Novamente criei um exemplo bem simples de como consumir a classe Base de workflows, irei fazer uma soma de dois números, por ser algo genérico, vou armazenar este custom workflow dentro da pasta Common do projeto Workflows:

using System;
using System.Activities;
using Microsoft.Xrm.Sdk.Workflow;
using D365.BaseSolution.Base.Workflows;

namespace D365.BaseSolution.Workflows
{
    public class SumNumbers : BaseClass
    {
        [Input("Number1")]
        [RequiredArgument]
        public InArgument<Decimal> Number1 { get; set; }

        [Input("Number2")]
        [RequiredArgument]
        public InArgument<Decimal> Number2 { get; set; }

        [Output("Result")]
        public OutArgument<Decimal> Result { get; set; }

        public override void ExecuteWorkflow()
        {
            tracingService.Trace("Starting custom workflow {0}", className);

            try
            {
                // TO DO: Add your business logic here

                // *** Sample ***

                var num1 = this.Number1.Get(context);
                var num2 = this.Number2.Get(context);

                Result.Set(context, (num1 + num2));

                // *** End Sample ***
            }
            catch (Exception ex)
            {
                tracingService.Trace("Exception: {0}", ex.Message);

                throw new InvalidWorkflowException(ex.Message);
            }

            tracingService.Trace("Finished custom workflow {0}", className);
        }

    }
}

Bom, neste post demonstrei como criar classes base para acelerar a criação de plugin e workflows, assim como, demonstrei como utilizá-las através de exemplos práticos.

No próximo (e final) post, falarei sobre os projetos de Web Resources e Testes, não deixe de voltar aqui para conferir!

[]’s,

Tiago

Deixe um comentário

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