Trabalhando com DLLs externas em plugins ou atividades customizadas de workflow

by Pedro Azevedo 13. April 2016 08:38

Boas pessoal,

Hoje venho falar de um mecanismo que é o meu dia-a-dia no desenvolvimento de plugins ou atividades customizadas de workflow. Sempre que começamos um projeto vamos verificar que muito código pode ser reutilizado, ou seja, código que se repete em vários plugins por exemplo. Imaginem por exemplo um requisito que qualquer email temos que usar o mesmo utilizador, então em cada plugin teríamos que fazer o mesmo código.

A solução pode passar por colocar num projeto a parte e daí gerar uma DLL, assim podemos usar esse código nas atividades de workflow e em múltiplos projetos. O problema é que o CRM não deixa que coloquemos referências externas a DLLs, principalmente no CRM Online.

A solução passa por usarmos o ILMerge uma ferramenta gratuita e muito usada, onde o seu principal objetivo é fazer merge entre DLLs e gerar apenas uma. Assim a restrição de registar múltiplas DLLs fica ultrapassada, pois o plugin e a nossa biblioteca vão na mesma DLL. Antes a sua configuração ainda era complicada, mas a uns anos descobri este post que utiliza uma tarefa do MSBuild que permite otimizar muito o processo.

Presumindo que vocês já têm a vossa solution montada, vamos instalar o pacote NuGet chamado MSBuild.ILMerge.Task sobre o projeto do nosso plugin\atividade de workflow:

image

Depois de instalarmos este pacote NuGet, vamos adicionar a DLL da nossa biblioteca. Esta tarefa de MSBuild vai ter a função de fazer “merge”  de todas as DLLs que estejam na pasta bin do nosso plugin. Se forem a essa pasta vão visualizar mais DLLs e principalmente as quatro principais para desenvolvermos sobre o CRM:

image

Sobre estas referências temos que ir as propriedades dessas DLLs e mudar a configuração, “Copy Local” e colocamos a false. Assim essas DLLs não ficam no merge.

image

Antes de compilarmos podemos ver a estrutura atual da pasta bin:

image

Depois de um clean e se recompilarmos novamente o projeto passamos a ter apenas uma DLL:

image

Para confirmarmos que realmente as duas DLLs estão juntas, podemos recorrer a ferramenta ILSpy, que inspeciona a DLL:

image

Com isto podemos continuar com o registo da nossa DLL sem quaisquer problema.

 

Até a próxima.

Tags: , ,

Apresentação no TugaIT 2016

by Administrator 4. April 2016 18:56

Boas pessoal,

Fui a pouco tempo informado que a sessão que propus foi aceite no TugaIT 2016. O TugaIT é um evento internacional sobre Data Platform. Eu vou colaborar na track Sharepoint\Office365.

A sessão que submeti é para falar sobre a integração do Dynamics CRM com o Office 365 e o Azure, ou seja, como é que a Plataforma Cloud da Microsoft está ajudar a alavancar o Dynamics CRM e combater com os principais rivais.

É já no dia 21 de Maio, mas chamo atenção que este evento começa antes com vários workshops pagos, mas com instrutores muito bons e alguns internacionais, uma oportunidade a não perder.

Registem-se o quanto antes.

Até a próxima.

Tags: , , ,

Forçar o recalculo dos campos rollup

by Pedro Azevedo 1. March 2016 08:03

Boas pessoal,

Num post do fórum foi perguntado como colocar um campo obrigatório, quando a soma de um determinado tipo de produtos fosse maior a um determinado valor. A minha solução foi colocar num campo rollup a soma destes tipos de produtos e depois aplicar uma regra de negócio para colocar o campo obrigatório.  A resposta foi que o recalculo não é apenas de 12 em 12 horas. Hora esta resposta não está completamente correta como podemos ver aqui. Como eu menciono na resposta, mas mesmo de hora a hora este cálculo pode ser decisivo e quebrar o fluxo do processo.

Como tinha prometido e forçado devido a questão no fórum estou aqui escrever soluções para forçarmos o recalculo deste campo. A solução encontramos no SDK recorrendo a seguinte mensagem:

CalculateRollupFieldRequest

Neste caso um plugin ou uma atividade customizada, vou dar o exemplo num plugin e como vamos ver mais a frente já existe uma ferramenta que dá esta funcionalidade de graça. Para o plugin podemos correr o seguinte código:

public class PostCreateOpportunityProductPlugin : IPlugin
{
    public void Execute(IServiceProvider serviceProvider)
    {
        var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
 
        if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
        {
            var serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            var service = serviceFactory.CreateOrganizationService(context.UserId);
 
            var entity = (Entity)context.InputParameters["Target"];
            var oppId = (EntityReference)entity["opportunityid"];
 
            var crfr = new CalculateRollupFieldRequest
            {
                Target = new EntityReference("opportunity", oppId.Id),
                FieldName = "new_rollupsumprods"
            };
 
            var response = (CalculateRollupFieldResponse)service.Execute(crfr);
        }
    }
}

Para a configuração deste plugin que coloquei a executar na mensagem Create da entidade OpportunityProduct e forçar o recalculo do campo rollup da oportunidade.

Para facilitar foi criado uma atividade customizada de fluxos de trabalho e que está incluída numa ferramenta que espero em próximos posts falar que é Dynamics CRM 2016 Workflow Tools, que são ferramentas para utilizarmos dentro dos Fluxos de Trabalho

Até a próxima.

Tags: , ,

Como os campos rollup são recalculados

by Pedro Azevedo 24. February 2016 16:20

Boas pessoal,

Deixamos este post apenas para falar de como estes campos são recalculados e isto é uma questão importante porque poderemos a trabalhar com informações erradas se o campo não tiver sido recalculado.

Primeiramente vamos perceber como estes campo são calculados, como eu referi no post sobre os campos rollup na base dados são criados três campos por cada campo rollup:

  • <NomeCampo>
  • <NomeCampo>_date
  • <NomeCampo>_state

O campo state, pode ter um dos seguintes valores:

Valor Nome Descrição
0 NotCalculated Campo ainda não foi calculado
1 Calculated O valor do campo é da data presente no campo <NomeCampo>_date
2 OverflowError Erro de overflow.
3 OtherError O cálculo do valor de campo falhou devido a um erro interno..
4 RetryLimitExceeded Cálculo falhou porque o número máximo de tentativas para calcular o valor foi excedido devido ao número alto de conflitos de simultaneidade e bloqueio.
5 HierarchicalRecursionLimitReached O cálculo do valor de campo falhou porque o limite máximo de profundidade da hierarquia para o cálculo foi atingido.
6 LoopDetected Cálculo do campo falhou porque um loop recursivo foi detectado na hierarquia do registro.

Os campos são recalculados através das tarefas de sistema assincronamente, quando o campo é calculado é agendado o seu cálculo 12 horas depois, como podemos ver na mensagem que coloquei no post sobre estes campos. Depois da criação existe um agendamento de 1h em 1h como se pode verificar a seguir:

image

Podemos mudar estas configurações indo as tarefas de sistema e mudando a vista para “Tarefas de Sistema Periódicas”, vamos ver os campos criados:

image

image

Para modificar abrimos este trabalho sistema e dentro do formulários, temos as seguintes opções:

image

Podemos mudar a periodicidade neste formulário:

image

 

Até a próxima.

Tags: , ,

Campos rollup

by Pedro Azevedo 14. February 2016 14:57

Boas Pessoal,

No post anterior falamos sobre campos calculados, hoje vamos falar de campos rollup. Como referi no post anterior quando criamos um campo vai aparecer mais opções para além do campo simples, os campos calculados e rollup:

image

Os tipos de campos em que se pode aplicar são: Inteiro, Decimal, Data e Moeda. Ao editar vemos o seguinte formulário que passamos a explicar mais ao pormenor:

image

Antes de começarmos a configurar vamos tentar perceber quando utilizar este tipo de objeto. Basicamente quando queremos somar ou contar algo sobre a própria entidade ou entidades relacionadas, por exemplo saber o valor de oportunidades que temos para um determinado cliente, o número de atividades, saber qual foi a maior compra desse cliente ou a média do valor das oportunidades.

Começando no início do formulário, podemos verificar que a entidade de origem é a conta, ou seja, a entidade sobre a qual estamos a criar este campo rollup e este campo não pode ser modificado. O campo abaixo define se queremos navegar sobre a hierarquia, ou seja, se queremos que os cálculos percorram não só os registos diretamente relacionados com a entidade mas também sobre uma eventual hierarquia, por exemplo percorrer uma possível hierarquia dos clientes:

image

Depois temos que configurar sobre qual a entidade sobre a qual vamos fazer a agregação, aliado a isso podemos colocar filtros para segmentar dados:

image

Na Agregação, temos as seguintes hipóteses:

image

Pode parecer estranho mas para este caso apenas temos um campo disponível, mas isto tem uma razão é que este campo rollup é do tipo inteiro, então só podemos fazer contas sobre “coisas” inteiras, neste caso estamos a calcular a média de probabilidades das oportunidades de uma determinada conta:

image

Mas o mais natural é realizarmos contas sobre os valores, para isso basta criarmos um campo do tipo Moeda, e como podemos ver na imagem a baixo já aparecem outros campos do tipo Moeda:

image

Estes campos podem ser usados em formulários, gráficos, visualizações e relatórios. Para além destes objetos os campos calculados poderão usar estes campos e o vice-versa também é possível.

Vamos ver como estes campos são visualizados no formulário:

image

 

Como podemos ver na imagem acima temos os dois campos rollup que criamos. Na imagem vemos o campo “campo rollup” que não tem valor mas se recalcularmos ele vai colocar o valor correto. No aviso que aparece na configuração do campo rollup explica a razão deste comportamento:

image

Mas o mesmo acontece aquando da atualização de uma das oportunidades, por exemplo vou adicionar produtos a oportunidade e vamos ver que o valor não se atualiza, apenas se forçarmos o recalculo. Este recalculo não implica a alteração do campo “modificado em”. No próprio campo é referido qual foi a última atualização que aquele campo teve:

image

Mas esta questão da atualização e como podemos resolver esta questão será descortinada em futuros posts. É uma questão muito importante porque as campos calculados, as regras de negócio e fluxos de trabalho vão usar o último valor calculado e pode não ser o “verdadeiro”.

Tal como nos campos calculados também existem algumas considerações:

  • Poderá haver no máximo 100 campos numa organização e até 10 por entidade;
  • Workflows não podem ser lançados por estes campos e não podem ser utilizados numa condição de espera;
  • Não pode ser aplicado sobre um campo rollup;
  • Não podemos fazer “rollup” sobre um campo rollup;
  • Um rollup não pode ser efetuado sobre relações N:N ou relação 1:N se a entidade for a atividade;

Ao contrário dos campos calculados estes campos são físicos, ou seja, são gravados na base de dados através de três campos, que serão explicados num post futuro.

Até a próxima

Tags: , ,

Campos Calculados (Calculated Fields)

by Administrator 1. February 2016 22:55

Boas Pessoal,

Uma das operações mais efetuadas pelos programadores era o calcular valores recorrendo a Javascript ou C#(Plugins ou Workflows), para colocar noutro campo do formulário, por exemplo para aplicar um desconto se o valor de uma proposta for superior a um determinado valor, se esta regra mudar, por exemplo se o valor passar a ser outro, provavelmente teríamos que ir alterar o código, o que nem sempre é fácil e ágil.

Como podemos comprovar a plataforma Dynamics CRM tem vindo a tornar-se cada vez mais funcional, ou seja, que pode ser definida por um utilizador sem necessidade de recorrer a uma pessoa mais técnica. Como a vinda do Microsoft Dynamics CRM 2015 trouxe dois novos campos: os campos calculados e rollup. que vêm resolver esse problema, pois vamos passar a poder efetuar através de condições e ações, por exemplo o cálculo referido acima e sem necessidade de escrever uma única linha de código.

Para começar a utilizar temos um novo Tipo de Campo, que é o calculado, na segunda imagem aparece outra opção o Rollup que vou falar no próximo post, neste caso a diferença é que na primeira imagem o campo é do tipo string e na segunda imagem do tipo inteiro:

image

 

image

Os tipos disponíveis para criarmos os campos calculados são os seguintes:

  • Linha de texto
  • Conjunto de Opções
  • Duas Opções
  • Número Inteiro
  • Número Decimal
  • Moeda
  • Data e Hora

Quando escolhermos este tipo de campo, aparece-nos um botão para editar:

image

Quando editamos e caso não tenhamos gravado, ele vai gravar o campo e depois não poderemos modificar para um campo simples, como não podemos passar de um simples para um calculado. Depois de editarmos vai-nos aparecer o seguinte formulário muito parecido com as Business Rules:

image

Como podemos observar podemos colocar uma condição (não é obrigatório) ou criarmos logo uma ação. Podem ser complexas com várias condições ou ramos E e OU.

Neste caso vamos comparar o Preço Total se é maior que 500€ se for então aplicamos um desconto de 30%.

image

image

Como podem observar não temos apenas a entidade sobre a qual estamos a criar o atributo, temos acesso a todas as entidades relacionadas, por exemplo podíamos verificar qual o estado do cliente. Para comparar podemos comparar com outro campo da mesma entidade ou de outra entidade relacionada, como podemos observar esta configuração é bastante flexível. Podemos referenciar outro campo calculado.

Nas ações temos uma interface com intellisense e intuitiva para que “qualquer” pessoa.

image

As operações disponíveis são as seguintes, as operações terão que estar em letras maiúsculas como referenciado em baixo:

  • ADDHOURS, ADDDAYS, ADDWEEKS, ADDMONTHS, ADDYEARS, SUBTRACTHOURS, SUBTRACTDAYS,SUBTRACTWEEKS, SUBTRACTMONTHS, SUBTRACTYEARS, DIFFINDAYS, DIFFINHOURS, DIFFINMINUTES, DIFFINMONTHS, DIFFINWEEKS, DIFFINYEARS, NOW e DIFFINYEARS
  • CONCAT, TRIMLEFT e TRIMRIGHT.
  • Utilização dos campos das entidades
  • Utilização dos campos das entidades pai através do campo lookup para a entidade pai. Fazendo <NomeCampoLookup>.<NomeCampo>

image

  • Para além destes operadores poderemos usar os operadores aritméticos.

Se por exemplo usarmos um campo protegido nos cálculos, ele vai sugerir que protejamos também este campo calculado como podemos ver na imagem a seguir:

image

Estes campos podem ser usados nos formulários, vistas, gráficos e relatórios. Mas existem algumas limitações:

  • Em vistas de utilizador, gráficos e visualizações podem ter no máximo 10 campos;
  • Os valores dos campos calculados não são mostrados no CRM Outlook no modo offline;
  • O número máximo de campos calculados encadeados são 5;
  • A ordenação é desabilitada em campos calculados que referem uma entidade pai, que contenham um campo lógico (por exemplo o morada) ou que contenham outro campo calculado;

image

  • Um campo calculado pode apenas referenciar até duas entidades, a entidade onde o campo calculado está a ser criado e outra entidade pai.
  • Estes campos não lançam plugins nem fluxos de trabalho;
  • As regras de deteção de duplicados não pode ser ativada para estes campos.

Depois de tudo configurado a configuração é vista da seguinte forma:

image

Depois do campo criado, no CRM tem o seguinte aspeto:

image

Como podem verificar este campo é apenas de leitura. Os campos calculados são campos virtuais e não físicos o que isto significa é que estes campos não são gravados na BD, mesmo assim este campo está disponível nos vários objetos que referi e se realizarmos um select sobre a tabela este valor será retornado, pois ele grava a nossa configuração numa função no SQL Server.

Até a próxima

Tags: , ,

Notificador de notas

by Administrator 24. November 2015 18:50

Boas pessoal,

Recentemente um post nos fóruns perguntou se era possível haver notificações quando um utilizador referi-se outro nas notas. A minha primeira resposta foi referenciar o Yammer que servia para isso mesmo, mas a resposta não agradou ao utilizador e eu referi se a necessidade fosse muito básica e através de um plugin era possível resolvermos esta questão e prometi realizar este plugin, sei que já vou atrasado mas promessas são promessas.

O plugin é bastante básico mas tenciono depois melhorar com mais funcionalidades num futuro próximo o mais provável para o inicio de 2016. Mas para já vamos-nos focar em implementar este plugin que no futuro deverá evoluir para uma solução.

Este plugin será para ser registado na entidade Annotation e na fase Post-Create. O primeiro objetivo será obter as informações da Nota:

IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
 
Entity entity = (Entity)context.InputParameters["Target"];
 
var noteTitle = (string)entity["subject"];
var noteDescr = (string)entity["notetext"];
var objectid = (EntityReference)entity["objectid"];

Vamos a procura de referências a utilizadores recorrendo a @, para este caso eu vou assumir que vamos referir ao nome sem o domínio, por exemplo se tivermos pedro@psa201505.onmicrosoft.com que necessitamos de escrever apenas @pedro. A pesquisa por utilizadores fica da seguinte maneira:

List<string> usersToNotificate = new List<string>();
int indexAt = 0;
while (true)
{
    indexAt = noteDescr.IndexOf('@', indexAt);
    if (indexAt == -1)
        break;
 
    var indexSpace = noteDescr.IndexOf(' ', indexAt);
    if (indexSpace == -1)
        indexSpace = noteDescr.Length - (indexAt + 1);
 
    string userNotification = noteDescr.Substring(indexAt + 1, indexSpace - (indexAt + 1));
    usersToNotificate.Add(userNotification);
    indexAt = indexSpace;
}

Então neste momento já temos quais os utilizadores a notificar agora temos que verificar se realmente são utilizadores do CRM:

usersToNotificate.ForEach(x =>
{
    var queryUser = new QueryExpression("systemuser");
    var filter = new FilterExpression(LogicalOperator.And);
    filter.AddCondition("domainname", ConditionOperator.Like, x + "%");
    queryUser.Criteria = filter;
    queryUser.ColumnSet = new ColumnSet(true);
    EntityCollection retrievedUsers  = service.RetrieveMultiple(queryUser);
 
    if (retrievedUsers.Entities.Count > 0)
    {
        ...
    }
});

Caso seja utilizador vamos montar o nosso email com um link tanto para o objeto onde estamos a criar as notas, bem como para a anotação:

if (retrievedUsers.Entities.Count > 0)
{
    var systemuserid = (Guid)retrievedUsers.Entities[0]["systemuserid"];
 
    var fromParty = new ActivityParty { PartyId = new EntityReference(SystemUser.EntityLogicalName, context.UserId) };
    var toParty = new ActivityParty { PartyId = new EntityReference(SystemUser.EntityLogicalName, systemuserid) };
 
    var entObject = service.Retrieve(objectid.LogicalName, objectid.Id, new ColumnSet(new string[] { "name" }));
 
    var req = new RetrieveEntityRequest();
    req.RetrieveAsIfPublished = true;
    req.LogicalName = objectid.LogicalName;
    req.EntityFilters = EntityFilters.Entity;
    var resp = (RetrieveEntityResponse)service.Execute(req);
 
    var urlObj = String.Format("https://{0}/main.aspx?etc=" + resp.EntityMetadata.ObjectTypeCode + "&id=%7b{1}%7d&pagetype=entityrecord", ServerUrl, objectid.Id);
    var objectURL = "<a href=" + urlObj + ">" + entObject["name"] + "</a>";
    var urlNote = String.Format("https://{0}/main.aspx?etc=5&id=%7b{1}%7d&pagetype=entityrecord", ServerUrl, entity["annotationid"]);
    var noteURL = "<a href=" + urlObj + ">nota</a>";
 
    var email = new Email
    {
        To = new ActivityParty[] { toParty },
        From = new ActivityParty[] { fromParty },
        Subject = "Foi mencionado numa " + resp.EntityMetadata.DisplayName.UserLocalizedLabel.Label,
        Description = "Foi mencionado numa " + resp.EntityMetadata.DisplayName.UserLocalizedLabel.Label + " com o nome " + objectURL + ", veja a " + noteURL,
        DirectionCode = true
    };
    var emailGuid = service.Create(email);
 
    var sendEmailReq = new SendEmailRequest
    {
        EmailId = emailGuid,
        TrackingToken = "",
        IssueSend = true
    };
 
    var sendEmailResp = (SendEmailResponse)service.Execute(sendEmailReq);
}

Deste modo temos o nosso plugin finalizado, como vêm ficou um código bastante simples e com muitos pontos de melhoria que espero em breve complementar.

Até a próxima.

Tags: , ,

Slides do Evento comunidade Office 365

by Administrator 14. November 2015 17:35

Boas pessoal,

Como sempre deixo aqui os slides que me serviram de suporte para a apresentação na Comunidade Office 365, foi referente a 5ª reunião desta comunidade que se realizou ontem.

Espero que tenham gostado.

Até a próxima

Tags: , ,

Participação na Comunidade Office 365

by Administrator 4. November 2015 12:32

Boas pessoal,

É com grande orgulho que anuncio que vou participar como orador na 5ª reunião da Comunidade Office 365 com o tema “Dynamics CRM com Office 365”. Esta reunião ocorrerá no dia 14 de Novembro.

O objetivo será falar como é que o Dynamics CRM tira proveito do Office 365.

Apareçam, sei que custa mas vale a pena Smile

Até a próxima.

Tags: , , ,

Dynamics CRM 2015 - Update 1.1

by Pedro Azevedo 21. September 2015 13:59

Boas pessoal,

Foi publicado o Update 1.1 para o CRM 2015 online. Neste site podem ver as novidades bem como um detalhe dos bugs que foram resolvidos. Neste site podem fazer download. Entre várias correção de vários bugs e melhorias, ressalvamos o suporte ao:

  • Windows 10
  • Microsoft Edge
  • Android 5.0

Até a próxima

Tags: , ,

About

Muito bem casado, Pai babado e um gosto muito grande pela tecnologia.

Tenho um lema "Sharing is Learning"

Mais aqui -> http://www.psazevedo.com

Month List