segunda-feira, 16 de novembro de 2015

GT.Notifier for Universal Windows App

Folks!
Tenho o prazer de anunciar que o aplicativo GT PHONE NOTIFIER é agora GT NOTIFIER!...

O aplicativo agora se enquadra na categoria Universal Windows App, ou seja, roda em qualquer dispositivo windows (desktop, phone, tablet, etc).

Confira abaixo algumas imagens do app.

Get Adobe Flash player
Photo Gallery by QuickGallery.com


sábado, 29 de agosto de 2015

CRM IFD - Discover Expiration Token

No SDK do CRM existe um código bem extenso que é usado para conexão a qualquer tipo Dynamics CRM - ActiveDirectory, LiveId, Federation e OnlineFederation.

Com certeza este código é bem útil, porém, muito extenso se precisarmos de um recurso para uma versão de autenticação específica, como por exemplo a [Federation - IFD].

Por exemplo, como saber se o meu CRM IFD vai expirar? Neste sentido segue uma classe para nos ajudar a identificar quando o [Token] vai expirar.

Crie a classe [DiscoveryServiceHelper]

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.Description;
using System.Text;
 
public class DiscoveryServiceHelper
{
 public static string GetProxy(string url)
 {
  string httpAux = url.Substring(0, url.IndexOf("//") + 2);
  string urlAux = url.Replace(httpAux, "");
  string urlAux_part1 = urlAux.Substring(0, urlAux.IndexOf("/") + 1);
  string urlAux_part2 = url.Substring(url.IndexOf("XRMServices"));
  urlAux_part2 = urlAux_part2.Replace("Organization.svc", "Discovery.svc");
  string discoveryURL = httpAux + urlAux_part1 + urlAux_part2;
  return discoveryURL;
 }

 public static bool HasCRMProxyExpired(string url, ClientCredentials credentials)
 {
  string discoveryURL = GetProxy(url);
  IServiceManagement<IOrganizationService> serviceManagement;
  serviceManagement = ServiceConfigurationFactory.CreateManagement<IOrganizationService>(new Uri(discoveryURL));
  AuthenticationCredentials authCredentials = new AuthenticationCredentials();
  authCredentials.ClientCredentials = credentials;
  AuthenticationCredentials tokenCredentials = serviceManagement.Authenticate(authCredentials);

  var expired = (tokenCredentials.SecurityTokenResponse != null &&
   DateTime.Now.AddMinutes(15) >=
   tokenCredentials.SecurityTokenResponse.Response.Lifetime.Expires.Value.ToLocalTime());

  return expired;
 }
}


E utilize o método [HasCRMProxyExpired] para descobrir isto. Neste exemplo o código detecta 15 minutos antes se o token vai expirar.

P.S.: Não esqueça de adicionar as bibliotecas [System.ServiceModel.dll], [System.IdentityModel.dll] e [Microsoft.IdentityModel.dll] no seu projeto do Visual Studio. Uma maneira rápida de instalação do WIF (Windows Identity Foundation) é via NuGet do VS, usando o comando: Install-Package Microsoft.IdentityModel. Você pode também habilitá-lo via Painel de Controle -> Programas e Recursos -> Ativar ou desativar recursos do Windows -> Windows Identity Foundation 3.5 .

Ref: Helper code: ServerConnection class

Cheers!

quinta-feira, 16 de julho de 2015

Custom ShowModalDialog with JQuery!

Já é de conhecimento de todos (ou da maioria) que a API [window.showModalDialog] foi descontinuada pelo Browser Chrome, a partir da versão 37 - Disabling showModalDialog.

Com isto muitos WebSites simplesmente não executam mais este recurso neste browser.

Como ele é usado extensamente, o que fazer então em substituição ao depreciado showModalDialog? Vamos criar um próprio, usando a tecnologia JQuery!

A função ShowModalDialog


if (typeof (GT) == "undefined") { GT = {}; }

GT.Functions = {
    ShowModalDialog: function (content, parent, width, height, bkgImage, callbackFn) {
        var divTag = '<div id="divDialog" ';
        divTag += 'style="border: 6px solid gray; overflow:hidden; box-shadow: 1px 1px 5px #333; ">';
 
        if (bkgImage) {
            divTag += '<div id="divDialogLoading" ';
            divTag += 'style="width:' + width + 'px; height:' + height + 'px; ';
            divTag += 'background: white url(' + bkgImage + ') no-repeat center;" >';
            divTag += '</div>';
        }
 
        divTag += '<iframe id="frameDialog" frameBorder="0" scrolling="no" ';
        divTag += 'src="' + content + '" ';
        divTag += 'style="width:100%;" ';
 
        divTag += '>';
        divTag += '</iframe>';
 
        divTag += '</div>';
 
        var div = null;
        var $JQ = null;
 
        if (parent != null) {
            var $JQ = parent.jQuery.noConflict();
            parent.window.defer = $JQ.Deferred();
            div = $JQ(divTag).appendTo("body");
        }
        else {
            $JQ = jQuery.noConflict();
            window.defer = $JQ.Deferred();
            div = $JQ(divTag);
        }
 
        div.append('<style type="text/css"> \
        .ui-widget-overlay { \
            position: absolute; \
            top: 0; \
            left: 0; \
            width: 100%; \
            height: 100%; \
            background: #aaaaaa;\
            opacity: 0.3; \
            } \
        .ui-front { \
            z-index: 100; \
            } \
        </style>');
 
        div.dialog({
            position: {
                my: "center",
                at: "center",
                of: div,
                within: div
            },            
            autoOpen: false,
            closeOnEscape: false,
            height: height,
            width: width,
            modal: true,
            open: function () {
                $JQ('#frameDialog').on("load", function () {
                    $JQ("#divDialogLoading").hide();
                    this.height = div.height();
                });
            },
            close: function () {
                if (callbackFn) callbackFn();
            }
        });
 
        div.dialog('open');
 
        return (parent != null ? parent.window.defer.promise() : window.defer.promise());
 
    }
};


Algumas considerações

Em si a função é simples pois utiliza o componente JQuery UI Dialog para mostrar um diálogo para o usuário. O mais importante é o uso do método [$.Deferred()] e o retorno da função, que utiliza o método [promise].

É justamente isto que garante que o diálogo se comporte como a API window.showModalDialog, trabalhando de forma síncrona com o resto do seu código JavaScript.

Importante: Se você pretende usar as versões mais recentes (a partir da 1.7.x) do JQuery UI Dialog em uma página ASPX, não se esqueça de usar a seguinte declaração no documento de sua página

<!DOCTYPE html>

Isto garante que o diálogo vai ser posicionado corretamente (no caso da nossa função, no centro da página).

Como devo usar o método

Veja abaixo dois exemplos de uso do método. O primeiro uso é básico. O segundo é interessante porque permite ao desenvolvedor executar o método a partir de uma página dentro de um [iframe].


var url = "http://MeuSite/MinhaPagina.aspx?param=test";

GT.Functions.ShowModalDialog(
 url,
 null,
 470,
 470,
 null,
 function result() {
  window.defer.then(
   function (result) {
    if (result)
    {
     // TRABALHA COM O RETORNO DA PÁGINA (url) PASSADA POR PARÂMETRO
    }
   });
 });



var url = "http://MeuSite/MinhaPagina.aspx?param=test";
var _parentW = (document.parentWindow ? document.parentWindow.parent : document.defaultView.parent);
var $jParent = _parentW.jQuery.noConflict();

GT.Functions.ShowModalDialog(
 url,
 _parentW,
 470,
 470,
 null,
 function result() {
  _parentW.window.defer.then(
   function (result) {
    if (result)
    {
     // TRABALHA COM O RETORNO DA PÁGINA (url) PASSADA POR PARÂMETRO
    }
   });
 });


E por fim precisamos retornar um valor para o método através da nossa página de exemplo (MinhaPagina.aspx). Note novamente que temos um exemplo de retorno simples e outro na qual a página ASPX se encontra dentro de um [iframe]


function CloseDialog()
{
 window.defer.resolve("VALOR PARA RETORNAR");
 $("#divDialog").dialog("close");
 $("#divDialog").dialog("destroy");
 $("#divDialog").remove();
}



function CloseDialog()
{
 var _parentW = (document.parentWindow ? document.parentWindow.parent : document.defaultView.parent);
 _parentW.window.defer.resolve("VALOR PARA RETORNAR");
 var $jParent = _parentW.jQuery.noConflict();
 $jParent("#divDialog").dialog("close");
 $jParent("#divDialog").dialog("destroy");
 $jParent("#divDialog").remove();
}


Refs:
JQuery UI Dialog
JQuery Deferred API

Cheers!!

quinta-feira, 23 de abril de 2015

CRM Tips - Import Solution - RibbonCustomization

Por um acaso, ao tentar importar uma solução no CRM, já se deparou com o seguinte erro:

There should only be at most one instance of RibbonCustomization per solution per entity. Current entity: [entityName]. Current solution: [guid]

Pois bem, muito provavelmente o metadados de Ribbons do seu CRM está, digamos, corrompido! São vários os motivos para que isto possa ter ocorrido, como por exemplo, em uma migração de versão.

Como se corrige isto, então? Atue no metadados de Ribbons, eliminando as duplicidades.

A Procedure abaixo faz justamente isto, para todas as entidades do banco do CRM.


declare @tables table (id int identity, name nvarchar(100))

declare @count int, @currentTable nvarchar(100), @sql nvarchar(max)

declare @RibbonCustomizationId uniqueidentifier, 
  @RibbonCustomizationUniqueId uniqueidentifier,
  @Entity nvarchar(100),
  @SolutionId uniqueidentifier,
  @SupportingSolutionId uniqueidentifier,
  @ComponentState int,
  @OverwriteTime datetime,
  @OrganizationId uniqueidentifier,
  @PublishedOn datetime,
  @IsManaged bit

-- BUSCA TODAS AS TABELAS DO CRM
insert into @tables (name)
select name
from sysobjects
where type = 'v'
order by name

-- LOOP NAS TABELAS
select @count = count(*) from @tables
while (@count > 0)
begin
 select @currentTable = name from @tables where id = @count

 -- CRIA UMA TEMP TABLE PARA GUARDAR OS RIBBONS AGRUPADOS POR [SolutionId]
 declare @Unique_RibbonCustomization table (
  RibbonCustomizationId uniqueidentifier, 
  RibbonCustomizationUniqueId uniqueidentifier,
  Entity nvarchar(100),
  SolutionId uniqueidentifier,
  SupportingSolutionId uniqueidentifier,
  ComponentState int,
  OverwriteTime datetime,
  OrganizationId uniqueidentifier,
  PublishedOn datetime,
  IsManaged bit)

 insert into @Unique_RibbonCustomization
  select RibbonCustomizationId, RibbonCustomizationUniqueId, Entity, SolutionId, SupportingSolutionId, ComponentState, OverwriteTime, OrganizationId, PublishedOn, IsManaged
  from (
     select *,
      row_number() over (partition by SolutionId order by SolutionId) as row_number
     from RibbonCustomization
     where entity = @currentTable
     ) as r
  where row_number = 1

 -- GUARDA OS IDS (RibbonCustomizationId) QUE POSSUEM DUPLICIDADE DE RIBBON
 declare @Diff_RibbonCustomization table (RibbonCustomizationId uniqueidentifier)
 insert into @Diff_RibbonCustomization
  select RibbonCustomizationId
  from RibbonCustomization
  where entity = @currentTable and
     RibbonCustomizationId not in (select RibbonCustomizationId from @Unique_RibbonCustomization)

 if (exists(select * from @Diff_RibbonCustomization))
 begin

  print 'Correcting EntityRibbon for: ' + @currentTable + '...'

  -- EXCLUIR OS RIBBONS DUPLICADOS PARA A ENTIDADE DO LOOP
  delete from RibbonCustomization 
  where Entity = @currentTable and
     RibbonCustomizationId in (select RibbonCustomizationId from @Diff_RibbonCustomization)

  -- EXCLUI AS DEPENDÊNCIAS (SolutionComponentBase, dependencybase e DependencyNodeBase)
   delete from SolutionComponentBase where ObjectId in (select RibbonCustomizationId from @Diff_RibbonCustomization)

  delete db from dependencybase db 
      inner join dependencynodebase dnb 
     on db.dependentcomponentnodeid = dnb.dependencynodeid or
     db.RequiredComponentNodeId = dnb.dependencynodeid
  where dnb.ObjectId in (select RibbonCustomizationId from @Diff_RibbonCustomization)
 
  delete from DependencyNodeBase where ObjectId in (select RibbonCustomizationId from @Diff_RibbonCustomization)
 end

 select @count = @count - 1, @currentTable = null
end

print 'Done.'


Após rodar a proc você conseguirá importar sua solução sem problemas - espero! :)

[]s

terça-feira, 21 de abril de 2015

CRM Tips - Managed to UnManaged Solution

Se por um acaso - ou falta de conhecimento - você criou uma solução Gerenciada no CRM por engano e precisa convertê-la para não gerenciada, segue um Script que faz este trabalho por você, já que até o momento a MS não disponibilizou uma opção para isto na ferramenta.

Importante notar que esta ação é sim suportada porque inclusive é ensinada no TechEd (aos 43:08 min do vídeo referenciado no final deste artigo). Eu somente fiz algumas alterações na procedure para atender melhor nosso cenário.

No script abaixo substitua a palavra [SolutionName] pelo nome de sua solução gerenciada que deseja converter.


declare @solutionId uniqueidentifier, @systemSolutionId uniqueidentifier
select @solutionId = solutionid from solutionbase where uniquename = 'SolutionName'
select @systemSolutionId = solutionid from solutionbase where uniquename = 'active'

update publisherbase 
set isreadonly = 0 
where publisherid in
(
select publisherid from solutionbase where solutionid = @solutionId
)

declare @tables table (id int identity, name nvarchar(100), ismanaged bit, issolution bit)
declare @count int, @currentTable nvarchar(100), @currentM bit, @currentS bit, @sql nvarchar(max)

insert into @tables (name, ismanaged, issolution)
select name, 1, 0
from sysobjects
where id in
(
select id from syscolumns where name in ('ismanaged')
)
and type = 'u'
order by name

insert into @tables (name, ismanaged, issolution)
select name, 0, 1
from sysobjects
where id in
(
select id from syscolumns where name in ('solutionid')
)
and type = 'u'
and name not in ('solutioncomponentbase')
order by name

select @count = count(*) from @tables

while (@count > 0)
begin
 select @currentTable = name, @currentM = ismanaged, @currentS = issolution from @tables where id = @count

 if (@currentM = 1)
 begin
  select @sql = 'update ' + @currentTable + ' set isManaged = 0 where SolutionId = N''' + cast(@solutionId as nvarchar(100)) + ''''
  exec (@sql)
 end

 print 'updated IsManaged to 0 on: ' + @currentTable

 select @count = @count - 1, @currentTable = null
end


Basicamente este código busca todas as tabelas que possuem os atributos [ismanaged] e [solutionid] para poder transformar os componentes da solução em não gerenciáveis e redirecionar as dependências para a solução interna do CRM chamada [Active].

Ref: Advanced Bag of Tips & Tricks for Microsoft Dynamics CRM 2011 Developers

[]s