Mostrando postagens com marcador ASP.NET MVC. Mostrar todas as postagens
Mostrando postagens com marcador ASP.NET MVC. Mostrar todas as postagens

terça-feira, 26 de setembro de 2017

CRM-GT - Consumo de Web Api usando Javascript

Este artigo tem como objetivo documentar o uso de um importante recurso do meu sistema de gerenciamento com o cliente, o CRM-GT. Este recurso se chama Web Api, e serve como um [conector] entre o sistema CRMGT e qualquer sistema legado.

Esta é a URL principal de consumo REST: /api/CRMGT/QueryXML

Com este conector, você pode efetuar todo CRUD de operações. Portanto, segue abaixo o uso prático deste recurso, usando Javascript/JQuery.

Pontos importantes:
- Não usamos a tecnologia SOAP
- Esta tecnologia exige o uso do recurso de Envelope XML para envio dos dados, e não fazemos desta forma

- Usamos REST (Desenvolvido na tecnologia ASP.NET webAPI da Microsoft, sempre com a versão mais atual do .NET)
- O que pode "confundir" alguns é o fato de passamos um [parametro no JSON] do tipo string XML, mas isto é somente mais um parâmetro da api, do tipo string, conforme veremos nos exemplos abaixo.
- Outro ponto que pode confundir é que, devido a forma de passagem dos parâmetros para nossa API, exista uma limitação do cliente para este padrão CRM-GT de envio, fazendo com que exista a necessidade de criação um pequeno desenvolvimento por parte do cliente antes do consumo da api.
- Este padrão CRM-GT de envio (ter um xml como parâmetro) torna nossa api mais segura para operações CRUD corporativas.


Para obter o Token de acesso (OAuth)
function getToken() {
	var param = {
		"orgName": "CRMGT1",
		"clientId": "+n8c++NABWZrkldVUaf0XYH2T2lG5xvUhZEVxFz46/4=__5@_",
		"clientSecret": "7RclDz1Ilbp3jyOuXJNnyfaK3bW+MIt5fU0BL1f46to=__0@_"
	};

	$.ajax({
		type: 'GET',
		url: 'https://crmgt.net/api/CRMGT/GetToken',
		data: param,
		dataType: 'json',
		async: false,
		beforeSend: function(xmlHttpRequest) {
			xmlHttpRequest.withCredentials = true;
		},
		success: function (data, textStatus, jqXHR) {
			token = data.token;
		},
		error: function (jqXHR, textStatus, errorThrown) {
			debugger;
		}
	});
}

Considerações importantes sobre o token.
- Ele expira a cada 3 horas.
- O [clientId] e o [clientSecret] são criados por Organização. O administrador da organização cria estas chaves e as disponibiliza para obtenção do Token.

Para Renovar o Token de acesso (OAuth)
function renewToken() {
    var param = {
        'orgName': 'CRMGT1',
        'clientId': '73ySJQsDnHDYugLRRYmtATjCxVmcNo2/Alz8RG9D+VQ=__5|@_11|4_13|._',
        'clientSecret': 'EPkMdqQcm0XgDxpn4lDcKKpEz0WPDQhgbXN6Nkl0JWw='
    };

    $.ajax({
        type: 'GET',
        url: 'https://crmgt.net/api/CRMGT/RenewToken',
        data: param,
        dataType: 'json',
        async: false,
        beforeSend: function (xmlHttpRequest) {
            xmlHttpRequest.withCredentials = true;
        },
        success: function (data, textStatus, jqXHR) {
            if (data == null) {
                return 0;
            } else {
                token = data.token;
            }
        },
        error: function (jqXHR, textStatus, errorThrown) {
            console.log(errorThrown);
        }
    });
}

Como saber se o token de acesso expirou? Ao executar o endpoint /GetToken, o sistema retorna um atributo informando que o tempo expirou ("expires_in" : "0").

Para os métodos de CREATE e UPDATE, no momento da configuração da tag [fields/field] atributo [type], informe os tipos correspondentes aos do [SQL SERVER].
Segue os principais:
bit
char
nchar
varchar
nvarchar
datetime
datetime2
bit
int
decimal
float
text
uniqueidentifier
Desta forma, se precisar adicionar um campo do tipo [string], informe [varchar].
Se precisar adicionar um campo do tipo [valor ou moeda], informe [decimal], e assim por diante.


Na tag [condition], atributo [operator], informe os tipos correspondentes aos do [SQL SERVER].
Segue os principais:
=
!=
<
<=
>
>=
like
like-removeformat (retira os caracteres "(", ")", "-", "espaço" e "." do campo no banco de dados)
between (coloque no atributo value o range, por ex "20200101T000000Z AND 20200131T235959Z")
in (coloque no atributo value os valores separados por vírgula, por ex "1,2,3,4")


Para efetuar um SELECT

function selectRecords() {
    var xmlSELECT = 
	"<select>" +
		"<organization name='CRMGT1' />" +
		"<table name='User' alias='tb1' />" +
		"<fields>" +
		"	<field name='tb1.UserId' label='ID' />" +
		"	<field name='tb1.Name' />" +
		"	<field name='tb1.Email' />" +
		"	<field name='tb2.Name' />" +
		"	<field name='tb3.Name' />" +
		"</fields>" +
		"<join isLeftJoin='false' name='Status' alias='tb2' fieldFrom='tb1.statuscode' fieldTo='tb2.statuscode'>" +
		"	<filter type='and'>" +
		"		<condition field='tb2.name' operator='=' value='Ativo' />" +
		"	</filter>" +
		"</join>" +
		"<join isLeftJoin='true' name='salesorder' alias='tb3' fieldFrom='tb1.userid' fieldTo='tb3.ownerid'>" +
		"	<filter type='and'>" +
		"		<condition field='tb3.name' operator='like' value='%ped%' />" +
		"	</filter>" +
		"</join>" +
		"<filter type='and'>" +
		"	<condition field='tb1.name' operator='like' value='%usr%' />" +
		"</filter>" +
		"<order>" +
		"	<field name='tb1.Name ASC'/>" +
		"	<field name='tb1.Email DESC'/>" +
		"</order>" +
	"</select>";

    var param = {
        "Type": "GET",
        "xmlContent": xmlSELECT
    };

    $.ajax({
        type: 'POST',
        url: "https://crmgt.net/api/CRMGT/QueryXML",
        data: param,
        dataType: 'json',
        contentType: "application/x-www-form-urlencoded; charset=utf-8",
        beforeSend: function (xhr) {
            xhr.setRequestHeader("Authorization", "Bearer " + token);
        },
        success: function (data, textStatus, jqXHR) {
            if (textStatus == "success") {
                // JSON que representa os [registros] retornados
                var json = JSON.parse(data.Result);
            }
        },
        error: function (jqXHR, textStatus, errorThrown) {
            debugger;
        }
    });
}

Se precisar retornar todos os campos, deixe a tag [fields] vazia **
<fields />
** Limitação: este recurso só vai funcionar se estiver buscando dados de uma única tabela.

Se precisar buscar dados NULOS (ou não nulos), utilize:
<condition field='tb1.name' operator='NULL' />
<condition field='tb1.name' operator='NOTNULL' />


Para efetuar um CREATE

function createRecord() {
    var xmlINSERT =
        "<insert>" +
        "<organization name='CRMGT1' />" +
        "<table name='account' isNN='false' setInternalFields='true' setWorkflowTrigger='false' workflowTriggerSyncMode='false' />" +
        "<records>" +
        "    <record>" +
        "        <fields>" +
        "            <field name='accountid' type='uniqueidentifier' value='NEWID' />" +
        "            <field name='name' type='varchar' value='Create sample 1...' />" +
        "            <field name='AccountNumber' type='varchar' value='1' />" +
        "            <field name='ownerid' type='uniqueidentifier' lookuptable='user' lookupsearchfield='name' value='system' />" +
        "        </fields>" +
        "    </record>" +
        "    <record>" +
        "        <fields>" +
        "            <field name='accountid' type='uniqueidentifier' value='NEWID' />" +
        "            <field name='name' type='varchar' value='Create sample 2...' />" +
        "            <field name='AccountNumber' type='varchar' value='2' />" +
        "        </fields>" +
        "    </record>" +
        "</records>" +
        "</insert>";

    var param = {
        "Type": "SET",
        "xmlContent": xmlINSERT
    };

    $.ajax({
        type: 'POST',
        url: "https://crmgt.net/api/CRMGT/QueryXML",        
        data: param,
        dataType: 'json',
        contentType: "application/x-www-form-urlencoded; charset=utf-8",
        beforeSend: function (xhr) {
            xhr.setRequestHeader("Authorization", "Bearer " + token);
        },
        success: function (data, textStatus, jqXHR) {
        },
        error: function (jqXHR, textStatus, errorThrown) {
        }
    });
}

[C.1] - Note a forma de configuração para inserir um valor no campo [ownerid].
Por ser um campo do tipo uniqueidentifier (ou GUID) no banco de dados, podemos passar como valor (para o atributo [value]) dois conteudos:

- o GUID diretamente
- Um texto para busca do GUID

Tomando o exemplo acima temos o atributo [lookuptable] preenchido como 'user', o atributo [lookupsearchfield] como 'name' e por fim o atributo [value] como 'system'.

Isto diz o seguinte para o sistema: faça um select na tabela 'user' onde o campo 'name' seja igual a 'system', depois retorne o GUID da 'user'.


Se precisar definir uma regra antes da criação dos registros, você pode adicionar a seguinte tag [rules] depois da tag [table]:
<rules>
	<rule action="insert_ignoreifexists" actionoperator="AND">
		<fields>
			<field name="protocol" type="VarChar" />
			<field name="incidentNumber" type="Int" />
			<field name="ownerid" type="UniqueIdentifier" lookuptable="user" lookupsearchfield="name" />
			<field name="modifiedon" type="datetime" hourformat="HH:mm" />
		</fields>
	</rule>
</rules>

Desta forma, através da regra [insert_ignoreifexists] você não permite a inclusão do registro se os campos especificados na tag [fields] existirem na tabela.
Mas se você quiser [atualizar] o registro se ele for encontrado, use a regra [insert_updateifexists]. Para esta regra, o update será realizado usando na cláusula [where] a [chave primária] da tabela.

Para campos do tipo [lookup] na regra você pode usar os atributos [lookuptable] e [lookupsearchfield] somente se o campo especificado na regra, no exemplo acima o [ownerid], tiver no XML de INSERT o conteúdo diferente de um GUID.
[lookuptable] - deve contér o nome da tabela que você deseja procurar o valor (no exemplo temos a tabela User)
[lookupsearchfield] - deve contér o nome do campo (da tabela [lookuptable]) para procurar o valor especificado no XML de INSERT.

Regras para campos do tipo [DateTime] vc pode especificar o formato da Hora para ser comparada.
Adicione o atributo [hourformat] com os seguintes conteúdos válidos:
HH:mm
HH:mm:ss
No exemplo acima temos uma regra para o campo [modifiedOn] com o atributo hourformat="HH:mm".

Formato de Campo Data
Sempre que passar um valor de data, passar no seguinte formato:


yyyyMMddTHHmmssZ


Exemplo: 20230616T095600Z


Se precisar [atualizar] o campo no CRM com valor Nulo:
<field name='dtFieldName' type='datetime' value='NULL' />


Se precisar [atualizar] o campo no CRM com a data [corrente]:
<field name='dtFieldName' type='datetime' value='GETDATE' />


Formato de Campo Decimal
Sempre que passar um valor decimal, usar o formato americano, somente com o separador de centavos:


Ex: 1234.56



Formato HTML
Sempre que passar uma string com conteúdo HTML, passe no formato ENCODE.

Exemplo: Preciso passar uma string html, onde em determinados pontos precisa ter uma quebra de linha.
No formato html, para quebras, usamos, por ex <br>.
Como passar esta tag br no formato Encode?
Desta forma: &lt;br&gt;
Onde:
&lt; -> representa o símbolo "<"
br -> comando de quebra
&gt; -> representa o símbolo ">"

Exemplo da tag <field> com html no [value]:
<field name='description' type='nvarchar' decodestring='true' value='123&lt;br&gt;456'/>

Não esqueça do atributo decodestring='true' para que a api efetue o tratamento de [decode] corretamente.



Para efetuar um UPDATE

function updateRecord() {
    var xmlUPDATE =
       "<update>" +
       " <organization name='CRMGT1' />" +
       " <table name='account' setWorkflowTrigger='false' />" +
       " <set>" +
       "  <field name='name' type='varchar' setWorkflowFieldTrigger='false' value='UPDATED - Create sample 1...' />" +
       "  <field name='modifiedOn' type='datetime' value='GETDATE' />" +
       " </set>" +
       " <filter type='and'>" +
       "  <condition field='name' operator='=' value='Create sample 1...' />" +
       " </filter>" +
       "</update>";

    var param = {
       "Type": "SET",
       "xmlContent": xmlUPDATE
    };

    $.ajax({
        type: 'POST',
        url: "https://crmgt.net/api/CRMGT/QueryXML",        
        data: param,
        dataType: 'json',
        contentType: "application/x-www-form-urlencoded; charset=utf-8",
        beforeSend: function (xhr) {
            xhr.setRequestHeader("Authorization", "Bearer " + token);
        },
        success: function (data, textStatus, jqXHR) {
        },
        error: function (jqXHR, textStatus, errorThrown) {
        }
    });
}

Para atualizar um campo do tipo [lookUp], vale a mesma regra especificada no item [C.1] deste documento.

Para efetuar um DELETE

function deleteRecord() {
    var xmlDELETE =
        "<delete>" +
        " <organization name='CRMGT1' />" +
        " <table name='account' setWorkflowTrigger='false' />" +
        " <filter type='and'>" +
        "  <condition field='name' operator='like' value='%Create sample%' />" +
        " </filter>" +
        "</delete>";

    var param = {
        "Type": "SET",
        "xmlContent": xmlDELETE
    };

    $.ajax({
        type: 'POST',
        url: "https://crmgt.net/api/CRMGT/QueryXML",        
        data: param,
        dataType: 'json',
        contentType: "application/x-www-form-urlencoded; charset=utf-8",
        beforeSend: function (xhr) {
            xhr.setRequestHeader("Authorization", "Bearer " + token);
        },
        success: function (data, textStatus, jqXHR) {
        },
        error: function (jqXHR, textStatus, errorThrown) {
        }
    });
}

Para executar uma [QUERY EXEC]

function executeQE() {
    var xmlQE =
	"<queryexec>" +
	" <organization name='CRMGT1' />" +
	" <queryexecid value='buscarocorrencia' />" +
	" <paramset>" +
	"	<param name='@_id_' type='uniqueidentifier' value='62a9a715-8574-4813-a69f-e732c76313e0' />" +
	" </paramset>" +
	"<filter type='and'>" +
	"	<condition field='inc.StatusCode' operator='=' value='1' />" +
	"</filter>" +
	"</queryexec>";

    var param = {
        "Type": "QueryExec",
        "xmlContent": xmlQE
    };

    $.ajax({
        type: 'POST',
        url: "https://crmgt.net/api/CRMGT/QueryXML",        
        data: param,
        dataType: 'json',
        contentType: "application/x-www-form-urlencoded; charset=utf-8",
        beforeSend: function (xhr) {
            xhr.setRequestHeader("Authorization", "Bearer " + token);
        },
        success: function (data, textStatus, jqXHR) {
        },
        error: function (jqXHR, textStatus, errorThrown) {
        }
    });
}

Obs: As tags <paramset> e <filter> são [opcionais]. Elas são úteis quando precisar passar mais parâmetros de filtro para a Query Exec criada no sistema ou para substituir o valor das macros do sistema (@_id_ por exemplo).

CRM-GT - Consumo de Web Api usando C#

Este artigo tem como objetivo documentar o uso de um importante recurso do meu sistema de gerenciamento com o cliente, o CRM-GT. Este recurso se chama Web Api, e serve como um [conector] entre o sistema CRMGT e qualquer sistema legado.

Esta é a URL principal de consumo REST: /api/CRMGT/QueryXML

Com este conector, você pode efetuar todo CRUD de operações. Portanto, segue abaixo o uso prático deste recurso, usando C#.

Pontos importantes:
- Não usamos a tecnologia SOAP
- Esta tecnologia exige o uso do recurso de Envelope XML para envio dos dados, e não fazemos desta forma

- Usamos REST (Desenvolvido na tecnologia ASP.NET webAPI da Microsoft, sempre com a versão mais atual do .NET)
- O que pode "confundir" alguns é o fato de passamos um [parametro no JSON] do tipo string XML, mas isto é somente mais um parâmetro da api, do tipo string, conforme veremos nos exemplos abaixo.
- Outro ponto que pode confundir é que, devido a forma de passagem dos parâmetros para nossa API, exista uma limitação do cliente para este padrão CRM-GT de envio, fazendo com que exista a necessidade de criação um pequeno desenvolvimento por parte do cliente antes do consumo da api.
- Este padrão CRM-GT de envio (ter um xml como parâmetro) torna nossa api mais segura para operações CRUD corporativas.

Busca do Token de acesso (OAuth)
var url = "https://crmgt.net/api/CRMGT/GetToken";
url += "?orgName=CRMGT1";
url += "&clientId=+n8c++NABWZrkldVUaf0XYH2T2lG5xvUhZEVxFz46/4=__5@_";
url += "&clientSecret=7RclDz1Ilbp3jyOuXJNnyfaK3bW+MIt5fU0BL1f46to=__0@_";
var jToken = GetString(url);
var token = JsonConvert.DeserializeObject<JObject>(jToken).SelectToken("token").ToString();

Considerações importantes sobre o token.
- Ele expira a cada 3 horas.
- O [clientId] e o [clientSecret] são criados por Organização. O administrador da organização cria estas chaves e as disponibiliza para obtenção do Token.

Para Renovar o Token de acesso (OAuth)
var url = "https://crmgt.net/api/CRMGT/RenewToken";
url += "?orgName=CRMGT1";
url += "&clientId=+n8c++NABWZrkldVUaf0XYH2T2lG5xvUhZEVxFz46/4=__5@_";
url += "&clientSecret=7RclDz1Ilbp3jyOuXJNnyfaK3bW+MIt5fU0BL1f46to=__0@_";
var jToken = GetString(url);
var token = JsonConvert.DeserializeObject<JObject>(jToken).SelectToken("token").ToString();

Como saber se o token de acesso expirou? Ao executar o endpoint /GetToken, o sistema retorna um atributo informando que o tempo expirou ("expires_in" : "0").


Para os métodos de CREATE e UPDATE, no momento da configuração da tag [fields/field] atributo [type], informe os tipos correspondentes aos do [SQL SERVER].
Segue os principais:
char
nchar
varchar
nvarchar
datetime
datetime2
bit
int
decimal
float
text
uniqueidentifier
Desta forma, se precisar adicionar um campo do tipo [string], informe [varchar].
Se precisar adicionar um campo do tipo [valor ou moeda], informe [decimal], e assim por diante.


Na tag [condition], atributo [operator], informe os tipos correspondentes aos do [SQL SERVER].
Segue os principais:
=
!=
<
<=
>
>=
like
like-removeformat (retira os caracteres "(", ")", "-", "espaço" e "." do campo no banco de dados)
between (coloque no atributo value o range, por ex "20200101T000000Z AND 20200131T235959Z")
in (coloque no atributo value os valores separados por vírgula, por ex "1,2,3,4")


Para efetuar um SELECT

1 - Monte o "SELECT" através de uma instrução XML:
string xmlSELECT = @"
<select>
	<organization name='{0}' />
	<table name='User' alias='tb1' />
	<fields>
		<field name='tb1.UserId' label='ID' />
		<field name='tb1.Name' />
		<field name='tb1.Email' />
		<field name='tb2.Name' />
		<field name='tb3.Name' />
	</fields>
	<join isLeftJoin='false' name='Status' alias='tb2' fieldFrom='tb1.statuscode' fieldTo='tb2.statuscode'>
		<filter type='and'>
			<condition field='tb2.name' operator='=' value='Ativo' />
		</filter>                     
	</join>
	<join isLeftJoin='true' name='salesorder' alias='tb3' fieldFrom='tb1.userid' fieldTo='tb3.ownerid'>
		<filter type='and'>
			<condition field='tb3.name' operator='like' value='%ped%' />
		</filter>                     
	</join>
	<filter type='and'>
		<condition field='tb1.name' operator='like' value='%usr%' />
	</filter>
	<order>
		<field name='tb1.Name ASC'/>
		<field name='tb1.Email DESC'/>
	</order>	
</select>";

Se precisar retornar todos os campos, deixe a tag [fields] vazia **
<fields />
** Limitação: este recurso só vai funcionar se estiver buscando dados de uma única tabela.

Se precisar buscar dados NULOS (ou não nulos), utilize:
<condition field='tb1.name' operator='NULL' />
<condition field='tb1.name' operator='NOTNULL' />


O XML acima é a representação do SELECT-SQL abaixo:
SELECT tb1.UserId, tb1.Name, tb1.Email, tb2.Name, tb3.Name
FROM [User] tb1 
JOIN [Status] tb2 ON tb1.statuscode = tb2.statuscode AND tb2.name = 'Ativo'
LEFT JOIN [salesorder] tb3 ON TB1.UserId = tb3.ownerid AND tb3.name like '%ped%'
WHERE tb1.Name like '%usr%'
ORDER BY tb1.Name ASC, tb1.Email DESC

2 - Passe como parâmetro a informação de [Organização], conforme a sequência abaixo.
var xmlF = string.Format(xmlSELECT, "CRMGT1");

3 - Execute a instrução, chamando a Web Api:
var header = new Dictionary<string, object>();
header.Add("Authorization", $"Bearer {token}");

var content = new Dictionary<string, object>();
content.Add("Type", "GET");
content.Add("Caller", "My Function Caller Name");
content.Add("xmlContent", xmlF);

var result = ExecutePostAsync("https://crmgt.net/api/CRMGT/QueryXML", content, header);

Segue abaixo métodos [GetString] e [ExecutePostAsync] que efetuam chamadas HTTP:
public static String GetString(String url)
{
   var urlContents = "";
   try
   {
      using (HttpClient client = new HttpClient())
      {
         urlContents = client.GetStringAsync(new Uri(url)).Result;
      }
   }
   catch (Exception ex)
   {
   }

   return urlContents;
}

public static string ExecutePostAsync(string url, Dictionary<string, object> content, Dictionary<string, object> headerList = null) {
	string result = "";

	try {
		using (HttpClient client = new HttpClient()) {
			if (headerList != null) {
				foreach (var item in headerList) {
					client.DefaultRequestHeaders.Add(item.Key, item.Value.ToString());
				}
			}

			if (content != null) {
				var multiPartContent = new MultipartFormDataContent();
				foreach (var item in content) {
					multiPartContent.Add(new StringContent(item.Value.ToString()), string.Format("\"{0}\"", item.Key));
				}

				var request = new HttpRequestMessage(HttpMethod.Post, url);
				request.Content = multiPartContent;
				result = client.SendAsync(request).Result.Content.ReadAsStringAsync().Result;
			}
		}
	}
	catch (Exception ex) {
	}

	return result;
}




Para efetuar um CREATE

1 - Monte o "CREATE" através de uma instrução XML:
string xmlINSERT = @"
    <insert>
        <organization name='{0}' />
        <table name='{1}' isNN='false' setInternalFields='true' setWorkflowTrigger='false' workflowTriggerSyncMode='false' />
        <records>
            <record>
                <fields>
                    <field name='statuscode' type='int' value='998' />
                    <field name='name' type='varchar' value='test1...' />
                    <field name='ownerid' type='uniqueidentifier' lookuptable='user' lookupsearchfield='name' value='system' />
                </fields>
            </record>
            <record>
                <fields>
                    <field name='statuscode' type='int' value='999' />
                    <field name='name' type='varchar' value='test2...' />
                    <field name='ownerid' type='uniqueidentifier' value='9EC0ABA8-2947-4857-9698-1C43BBBFAB5B' />
                </fields>
            </record>
        </records>
    </insert>";

2 - Passe como parâmetro as informações de [Organização] e [tabela], conforme a sequência abaixo.
var xmlF = string.Format(xmlINSERT, "CRMGT1", "lead");

3 - Execute a instrução, chamando a Web Api:
var header = new Dictionary<string, object>();
header.Add("Authorization", $"Bearer {token}");

var content = new Dictionary<string, object>();
content.Add("Type", "SET");
content.Add("Caller", "My Function Caller Name");
content.Add("xmlContent", xmlF);

var result = ExecutePostAsync("https://crmgt.net/api/CRMGT/QueryXML", content, header);

[C.4] - Note a forma de configuração para inserir um valor no campo [ownerid].
Por ser um campo do tipo uniqueidentifier (ou GUID) no banco de dados, podemos passar como valor (para o atributo [value]) dois conteudos:

- o GUID diretamente
- Um texto para busca do GUID

Tomando o exemplo acima temos o atributo [lookuptable] preenchido como 'user', o atributo [lookupsearchfield] como 'name' e por fim o atributo [value] como 'system'.

Isto diz o seguinte para o sistema: faça um select na tabela 'user' onde o campo 'name' seja igual a 'system', depois retorne o GUID da 'user'.


Para inserir dados em uma tabela N-N, adicione o atributo [isNN='true'] na tag xml [table], conforme exemplificado abaixo:
string xmlINSERT = @"
    <insert>
        <organization name='{0}' />
        <table name='{1}' isNN='true' />
        <records>
            <record>
                <fields>
                    <field name='pricelist_productid' type='uniqueidentifier' value='NEWID' />
                    <field name='pricelistid' type='uniqueidentifier' value='3BEF33F8-A159-4F77-964D-B820B8414E59' />
                    <field name='productid' type='uniqueidentifier' value='B091A317-4FF7-42BE-AD86-D77C22059E49' />
                </fields>
            </record>
            <record>
                <fields>
                    <field name='pricelist_productid' type='uniqueidentifier' value='NEWID' />
                    <field name='pricelistid' type='uniqueidentifier' value='3BEF33F8-A159-4F77-964D-B820B8414E59' />
                    <field name='productid' type='uniqueidentifier' value='6922B778-B4FF-4564-A992-E40A020C599F' />
                </fields>
            </record>
        </records>
    </insert>";

Se precisar definir uma regra antes da criação dos registros, você pode adicionar a seguinte tag [rules] depois da tag [table]:
<rules>
	<rule action="insert_ignoreifexists" actionoperator="AND">
		<fields>
			<field name="protocol" type="VarChar" />
			<field name="incidentNumber" type="Int" />
			<field name="ownerid" type="UniqueIdentifier" lookuptable="user" lookupsearchfield="name" />
			<field name="modifiedon" type="datetime" hourformat="HH:mm" />
		</fields>
	</rule>
</rules>

Desta forma, através da regra [insert_ignoreifexists] você não permite a inclusão do registro se os campos especificados na tag [fields] existirem na tabela.
Mas se você quiser [atualizar] o registro se ele for encontrado, use a regra [insert_updateifexists]. Para esta regra, o update será realizado usando na cláusula [where] a [chave primária] da tabela.

Para campos do tipo [lookup] na regra você pode usar os atributos [lookuptable] e [lookupsearchfield] somente se o campo especificado na regra, no exemplo acima o [ownerid], tiver no XML de INSERT o conteúdo diferente de um GUID.
[lookuptable] - deve contér o nome da tabela que você deseja procurar o valor (no exemplo temos a tabela User)
[lookupsearchfield] - deve contér o nome do campo (da tabela [lookuptable]) para procurar o valor especificado no XML de INSERT.

Regras para campos do tipo [DateTime] vc pode especificar o formato da Hora para ser comparada.
Adicione o atributo [hourformat] com os seguintes conteúdos válidos:
HH:mm
HH:mm:ss
No exemplo acima temos uma regra para o campo [modifiedOn] com o atributo hourformat="HH:mm".

Formato de Campo Data
Sempre que passar um valor de data, passar no seguinte formato:


yyyyMMddTHHmmssZ


Exemplo: 20230616T095600Z


Se precisar [atualizar] o campo no CRM com valor Nulo:
<field name='dtFieldName' type='datetime' value='NULL' />


Se precisar [atualizar] o campo no CRM com a data [corrente]:
<field name='dtFieldName' type='datetime' value='GETDATE' />


Formato de Campo Decimal
Sempre que passar um valor decimal, usar o formato americano, somente com o separador de centavos:


Ex: 1234.56



Formato HTML
Sempre que passar uma string com conteúdo HTML, passe no formato ENCODE.

Exemplo: Preciso passar uma string html, onde em determinados pontos precisa ter uma quebra de linha.
No formato html, para quebras, usamos, por ex <br>.
Como passar esta tag br no formato Encode?
Desta forma: &lt;br&gt;
Onde:
&lt; -> representa o símbolo "<"
br -> comando de quebra
&gt; -> representa o símbolo ">"

Exemplo da tag <field> com html no [value]:
<field name='description' type='nvarchar' decodestring='true' value='123&lt;br&gt;456'/>

Não esqueça do atributo decodestring='true' para que a api efetue o tratamento de [decode] corretamente.



Para efetuar um UPDATE

1 - Monte o "UPDATE" através de uma instrução XML:
string xmlUPDATE = @"
    <update>
        <organization name='{0}' />
        <table name='{1}' setWorkflowTrigger='false' />
        <set>
            <field name='name' type='varchar' setWorkflowFieldTrigger='false' value='TEST UPD...' />
            <field name='modifiedOn' type='datetime' value='GETDATE' />
        </set>
        <filter type='and'>
            <condition field='name' operator='=' value='TEST' />
        </filter>
    </update>";

2 - Passe como parâmetro as informações de [Organização] e [tabela], conforme a sequência abaixo.
var xmlF = string.Format(xmlUPDATE, "CRMGT1", "Status");

3 - Execute a instrução, chamando a Web Api:
var header = new Dictionary<string, object>();
header.Add("Authorization", $"Bearer {token}");

var content = new Dictionary<string, object>();
content.Add("Type", "SET");
content.Add("Caller", "My Function Caller Name");
content.Add("xmlContent", xmlF);

var result = ExecutePostAsync("https://crmgt.net/api/CRMGT/QueryXML", content, header);

Para atualizar um campo do tipo [lookUp], vale a mesma regra especificada no item [C.4] deste documento.

Para efetuar um DELETE

1 - Monte o "DELETE" através de uma instrução XML:
string xmlDELETE = @"
    <delete>
        <organization name='{0}' />
        <table name='{1}' setWorkflowTrigger='false' />
        <filter type='and'>
            <condition field='name' operator='=' value='TEST...' />
        </filter>
    </delete>";

2 - Passe como parâmetro as informações de [Organização] e [tabela], conforme a sequência abaixo.
var xmlF = string.Format(xmlDELETE, "CRMGT1", "Status");

3 - Execute a instrução, chamado a Web Api:
var header = new Dictionary<string, object>();
header.Add("Authorization", $"Bearer {token}");

var content = new Dictionary<string, object>();
content.Add("Type", "SET");
content.Add("Caller", "My Function Caller Name");
content.Add("xmlContent", xmlF);

var result = ExecutePostAsync("https://crmgt.net/api/CRMGT/QueryXML", content, header);


Para executar uma [QUERY EXEC]

1 - Monte a "QUERY EXEC" através de uma instrução XML:
string xmlQE = @"
	<queryexec>
	<organization name='{0}' />
	<queryexecid value='{1}' />
	<paramset>
		<param name='@_id_' type='uniqueidentifier' value='62a9a715-8574-4813-a69f-e732c76313e0' />
	</paramset>
	<filter type='and'>
		<condition field='inc.StatusCode' operator='=' value='1' />
	</filter>
	</queryexec>";

2 - Passe como parâmetro as informações de [Organização] e [ID da query exec], conforme a sequência abaixo.
var xmlF = string.Format(xmlQE, "CRMGT1", "buscarocorrencias");

3 - Execute a instrução, chamado a Web Api:
var header = new Dictionary<string, object>();
header.Add("Authorization", $"Bearer {token}");

var content = new Dictionary<string, object>();
content.Add("Type", "QueryExec");
content.Add("Caller", "My Function Caller Name");
content.Add("xmlContent", xmlF);

var result = ExecutePostAsync("https://crmgt.net/api/CRMGT/QueryXML", content, header);

Obs: As tags <paramset> e <filter> são [opcionais]. Elas são úteis quando precisar passar mais parâmetros de filtro para a Query Exec criada no sistema ou para substituir o valor das macros do sistema (@_id_ por exemplo).

segunda-feira, 2 de maio de 2016

CONHEÇA O CRM-GT

** Clique na imagem abaixo para mais detalhes.

quarta-feira, 23 de abril de 2014

Dependency Injection (DI) in ASP.NET MVC

Quem desenvolve para MVC percebe que um dos recursos mais importantes é o padrão que nos habilita a usar o [SoC] - Separation of Concerns.

Nós queremos que os componentes de nossa aplicação sejam os mais independentes possíveis uns dos outros (além de efetuarmos testes e manutenção na aplicação de forma mais fácil), sendo que numa situação ideal, um componente não deveria saber da existência de outro, apenas terem uma ligação fraca uns com os outros (loose coupling) através do uso de [Interfaces].


Por exemplo, se criarmos duas classes com a necessidade de ligação fraca entre elas, a criação de uma interface nos ajuda neste sentido:




public class PasswordResetHelper {
public void ResetPassword() {
   IEmailSender mySender = new MyEmailSender();
   //...call interface methods to configure e-mail details...
   mySender.SendEmail();
   }
}

Mas como você deve ter notado no código acima, ainda temos um problema!...não existe um recurso padrão do C# para implementarmos o uso da interface [IEmailSender] sem a criação de uma instância da classe [MyEmailSender].

Portanto, mesmo usando uma Interface, ainda temos uma ligação de qualquer maneira...



Existe uma solução para isto? Sim, e se chama DI - Dependency Injection!

Uma forma rápida para melhorar o código acima seria a criação de um [construtor] e nele passaríamos a implementação da interface somente para a classe [PasswordResetHelper]


public class PasswordResetHelper {
   private IEmailSender emailSender;

   public PasswordResetHelper(IEmailSender emailSenderParam) {
   emailSender = emailSenderParam;
   }

   public void ResetPassword() {
   // ...call interface methods to configure e-mail details...
   emailSender.SendEmail();
   }
}

Com isto conseguimos quebrar a dependência entre as classes [PasswordResetHelper] e [MyEmailSender]. O que acabamos de fazer se chama [Constructor Injection].

Agora, para melhorarmos ainda mais nosso projeto MVC, podemos e devemos utilizar ferramentas sólidas para o tratamento de [DI]. Para tanto, existem algumas no mercado, como a Unit da Microsoft e NInject, a qual será usada neste artigo.

Criando as Classes de Controle

Vamos criar um novo projeto no Visual Studio, clicando em Novo -> Projeto -> [ASP.NET MVC 4 Web Application] Template e selecione a opção [Empty] para criar o projeto sem conteúdo. Dê o nome para o projeto de [EssentialTools].

A seguir, adicione a seguinte classe na pasta Models do projeto, com o nome Product.cs:


namespace EssentialTools.Models {
   public class Product {
   public int ProductID { get; set; }
   public string Name { get; set; }
   public string Description { get; set; }
   public decimal Price { get; set; }
   public string Category { set; get; }
   }
}

Também adicione na pasta Models uma classe para efetuar um cálculo na coleção de produtos, com o nome [LinqValueCalculator]


using System.Collections.Generic;
using System.Linq;

namespace EssentialTools.Models {
      public class LinqValueCalculator {
      public decimal ValueProducts(IEnumerable<Product> products) {
      return products.Sum(p => p.Price);
      }
   }
}


Agora crie a outra classe (ShoppingCart) que se relaciona com a criada anteriormente


using System.Collections.Generic;

namespace EssentialTools.Models {
   public class ShoppingCart {
      private LinqValueCalculator calc;
      
      public ShoppingCart(LinqValueCalculator calcParam) {
         calc = calcParam;
      }

      public IEnumerable<Product> Products { get; set; }
      public decimal CalculateProductTotal() {
         return calc.ValueProducts(Products);
      }
   }
}


Adicionando um Controller

Adicione um controller na pasta Controllers chamado HomeController e altere o seu conteúdo para o proposto abaixo


using EssentialTools.Models;
using System.Web.Mvc;
using System.Linq;

namespace EssentialTools.Controllers {
public class HomeController : Controller {
   private Product[] products = {
   new Product {Name = "Kayak", Category = "Watersports", Price = 275M},
   new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},
   new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},
   new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}
   };

   public ActionResult Index() {
      LinqValueCalculator calc = new LinqValueCalculator();
      ShoppingCart cart = new ShoppingCart(calc) { Products = products };
      decimal totalValue = cart.CalculateProductTotal();
      return View(totalValue);
   }
}
}


Note que estamos usando este controller para testar nossas classes [LinqValueCalculator] e [ShoppingCart]

Adicionando uma View

Adicione uma View para visualizar o resultado do controler. Clique com o botão direito na ação Index da HomeController e selecione [Add View] e crie uma View chamada [Index], modificando o conteúdo conforme o proposto abaixo


@model decimal
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Value</title>
</head>
<body>
<div>
Total value is $@Model
</div>
</body>
</html>


Neste ponto, execute a aplicação e veja se tudo está rodando corretamente, apresentando a tela abaixo



Usando Ninject

Até o momento criamos duas classes com acoplamento forte (tightly-coupled), na qual ShoppingCart está ligada a classe LinqValueCalculator, e a HomeController ligada as duas. Pensando na manutenção disto em um projeto real podemos imaginar que será complicado, pois, por exemplo, se alterarmos o uso da classe [LinqValueCalculator] por outra, teremos que trocar a referência desta classe no projeto.

Vamos então aplicar primeiramente a Interface, que resolve parte do processo de acoplagem. Crie uma classe chamada [IValueCalculator] na pasta [Models] com o seguinte conteúdo:


using System.Collections.Generic;

namespace EssentialTools.Models {
   public interface IValueCalculator {
      decimal ValueProducts(IEnumerable<Product> products);
   }
}


Com isto podemos agora implementar esta interface na classe [LinqValueCalculator]


using System.Collections.Generic;
using System.Linq;

namespace EssentialTools.Models {
   public class LinqValueCalculator: IValueCalculator {
      public decimal ValueProducts(IEnumerable<Product> products) {
         return products.Sum(p => p.Price);
      }
   }
}


Com isto quebramos a ligação forte entre as classes ShoppingCart e LinqValueCalculator


using System.Collections.Generic;

namespace EssentialTools.Models {
 public class ShoppingCart {
  private IValueCalculator calc;
  
  public ShoppingCart(IValueCalculator calcParam) {
   calc = calcParam;
  }
  public IEnumerable<Product> Products { get; set; }
  
  public decimal CalculateProductTotal() {
   return calc.ValueProducts(Products);
  }
 }
}


Fizemos progresso, porém o C# ainda requer que implementemos a classe [LinqValueCalculator] na nossa HomeController, o que é ruim pois ainda estaremos ligando fortemente as classes


...
public ActionResult Index() {
 IValueCalculator calc = new LinqValueCalculator();
 ShoppingCart cart = new ShoppingCart(calc) { Products = products };
 decimal totalValue = cart.CalculateProductTotal();
 return View(totalValue);
}
...


Para desacoplar de uma vez este processo usaremos então Ninject mantendo também nossa HomeController somente com a implementação de uma Interface

Adicionando Ninject no Visual Studio

A forma mais fácil é o uso do recurso integrado ao VS, o NuGet, abrindo primeiro seu projeto, depois selecionando o menu Tools - > NuGet Package Manager -> Manage NuGet Packages for Solution



Na opção [Online] do Nuget, busque pela palavra [ninject]. Clique no botão install para adicionar as bibliotecas do Ninject no seu projeto. Podemos agora utilizar este recurso em nossa HomeController, conforme mostrado abaixo


using EssentialTools.Models;
using System.Web.Mvc;
using System.Linq;
using Ninject;

namespace EssentialTools.Controllers {
 public class HomeController : Controller {
  private Product[] products = {
  new Product {Name = "Kayak", Category = "Watersports", Price = 275M},
  new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},
  new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},
  new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}
  };

  public ActionResult Index() {
   IKernel ninjectKernel = new StandardKernel();
   ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
   IValueCalculator calc = ninjectKernel.Get<IValueCalculator>();
   
   ShoppingCart cart = new ShoppingCart(calc) { Products = products };
   decimal totalValue = cart.CalculateProductTotal();
   
   return View(totalValue);
  }
 }
}


O primeiro estágio é o preparo para uso do Ninject, através da instância da classe [StandardKernel]. Com isto conseguimos criar um relacionamento entre as interfaces em nossa aplicação e as classes de implementação que nós queremos trabalhar. Você notou isto quando foi executado o passo descrito abaixo


...
ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
...


E como passo final o uso do Ninject para obter a implementação da Interface que queremos


...
IValueCalculator calc = ninjectKernel.Get<IValueCalculator>();
...


a este ponto você pode se perguntar, eu ainda possuo uma ligação forte entre meu Controller e a classe [LinqValueCalculator]...e é verdade. Porém, fácil de resolver pois podemos atuar no [core] do MVC adicionando o Kernel do Ninject no processo interno do MVC, de forma a desacoplar de uma vez por todas nossas classes da HomeController.

Criando um Dependency Resolver

Este recurso é interessante pois vamos adicionar nossas resoluções de classes x interfaces, no [core] do MVC, criando uma classe [resolver] que será instanciada na inicialização do projeto, por configurá-la no arquivo [Global.asax.cs] do projeto. Como passo final, alteramos nossa HomeController para usar somente a interface que desejamos. Veja todos estes passos em sequência:

1 - Adicione uma nova pasta chamada [Infrastructure] no projeto e crie uma classe chamada [NinjectDependencyResolver] com o conteúdo abaixo:


using System;
using System.Collections.Generic;
using System.Web.Mvc;
using Ninject;
using Ninject.Parameters;
using Ninject.Syntax;
using System.Configuration;
using EssentialTools.Models;

namespace EssentialTools.Infrastructure {
 public class NinjectDependencyResolver : IDependencyResolver {
  private IKernel kernel;
  
  public NinjectDependencyResolver() {
   kernel = new StandardKernel();
   AddBindings();
  }
  
  public object GetService(Type serviceType) {
   return kernel.TryGet(serviceType);
  }
  
  public IEnumerable<object> GetServices(Type serviceType) {
   return kernel.GetAll(serviceType);
  }
  
  private void AddBindings() {
   kernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
  }
 }
}


2 - Registre a classe Dependency Resolver no Global.asax.cs (no método Application_Start)


using EssentialTools.Infrastructure;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;

namespace EssentialTools {
 public class MvcApplication : System.Web.HttpApplication {
  protected void Application_Start() {
   AreaRegistration.RegisterAllAreas();
   
   DependencyResolver.SetResolver(new NinjectDependencyResolver());
   
   WebApiConfig.Register(GlobalConfiguration.Configuration);
   FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
   RouteConfig.RegisterRoutes(RouteTable.Routes);
  }
 }
}


3 - Refatore sua HomeController


using EssentialTools.Models;
using System.Web.Mvc;
using System.Linq;

namespace EssentialTools.Controllers {
 public class HomeController : Controller {
  private Product[] products = {
  new Product {Name = "Kayak", Category = "Watersports", Price = 275M},
  new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},
  new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},
  new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}
  };

  private IValueCalculator calc;
  public HomeController(IValueCalculator calcParam) {
   calc = calcParam;
  }

  public ActionResult Index() {
   ShoppingCart cart = new ShoppingCart(calc) { Products = products };
   decimal totalValue = cart.CalculateProductTotal();
   return View(totalValue);
  }
 }
}


That's it!!

Ref: Pro ASP.NET MVC 4 - 4th Edition - Adam Freeman, aPress