Blog

Artículos y noticias relacionadas con el mundo AX3 group

Autor del archivo

Reiniciar servicios de AOS mediante PowerShell en AX2012

Se puede dar el caso de realizar un reinicio controlado de todos los AOS de AX2012 de una implantación de forma secuencial y en ocasiones, remota, para lo cual podemos hacer uso de un comando PowerShell que podemos automatizar a posteriori.

Para ello, podemos utilizar los siguientes comandos:

Reinicio del servicio en el mismo equipo:

1
  Get-Service -Name AOS60`$01 Restart-Service

Reinicio del servicio desde un equipo remoto:

1
  Get-Service -Name AOS60`$02 -ComputerName REMOTE_MACHINE_NAME | Restart-Service

Los números finales del parámetro -Name indican el número de instancia de AOS dentro del equipo, en el caso de disponer de más de un servicio de AOS por máquina.

Para automatizar el reinicio de todos los servicios sería posible salvar todos los comantos en un fichero ps1 para automatizar su ejecución mediante el task scheduler de Windows.

Aunque normalmente el servicio se para y se inicia sin problemas, sería también posible ejecutar un cierre forzado del/los proceso/s AX32Serv.exe si por alguna circunstancia el servicio no se ha detenido / iniciado en un tiempo determinado.

Espero haya sido de utilidad.

Expresiones de SQL en rangos de Query sobre campos de tipo datetime (AX2012)

Siguiendo como base este artículo de Axaptapedia Expresiones en rangos de Query vemos que puede ser algo tedioso el utilizar esta funcionalidad sobre campos de tipo datetime en Ax2012. Básicamente hay que utilizar la función DateTimeUtil::toStr para convertir el valor del tipo utcdatetime a un tipo str que sea entendido por la expresión.

A continuación muestro un ejemplo sencillo de uso para disponer de esta estupenda funcionalidad con este tipo de campos:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//Expresión en query range para campos de tipo datetime
static void AX3QueryRangeExp_DateTime(Args _args)
{
    utcDateTime             _utcDateTime = DateTimeUtil::newDateTime(mkDate(1,4,2017),0);
    Query                   query = new Query();
    QueryBuildDataSource    qst = query.addDataSource(tableNum(CustTable));
    QueryRun                queryRun;
    str                     qstr;
    int                     i;
 
    qstr = strFmt('((%1.CreatedDateTime > %2) || (%1.ModifiedDateTime > %2))', qst.name(), DateTimeUtil::toStr(_utcDateTime));
 
    qst.addRange(fieldNum(CustTable,DataAreaId)).value(qstr);
 
    query.literals(true);
 
    queryRun = new queryRun(query);
    while(queryRun.next())
    {
        i++;
    }
    print i;
    pause;
 
}

Espero os haya sido de utilidad.

Soluciones para exponer servicios RESTFul en Ax2012

RESTFul

Como muchos que estéis al tanto de las novedades del mundo Ax, ya sabréis que el estándar de AX7 (O Dynamics 365 for operations) ya trae la posibilidad de exponer al mundo servicios en este formato y mejorar la compatibilidad de éstos con cualquier plataforma y cliente. Más info en este link

Pero en la versión que más se está trabajando en la actualidad, es decir, AX2012, no existen soluciones estándar medianamente avanzadas para permitir lo que se requiere para cualquier instalación media, como por ejemplo el consumo de servicios de AX2012 desde aplicaciones no Windows, sin Active Directory de por medio.

La solución estándar que propone AX2012 solo permite operaciones de lectura, tal y como se expone en este post, que de forma muy resumida permite la publicación de una query interna del sistema en una url.

Se ve que la necesidad de poder ir más allá ha surgido en alguna que otra implantación y algunos profesionales del sector han tenido a bien compartir dos soluciones muy parecidas para cubrir este requisito.

Por un lado existe el llamado Generic Broker para AX2012  publicado en Codeplex por Khalid Khan, que es una aplicación web  simple en .NET que va a recibir y deserializar parámetros en una url para poder invocar servicios del AIF.

Más información de instalación y configuración en los siguientes enlaces:

Creating flexible restful services for AX2012

Creating flexible restful services for AX2012 part 2

Tutorial de uso en vídeo

Por otro lado, existe otro proyecto .NET llamado AxaptaAPI publicado en github por Fabio Filardi que como se explica en su wiki ha sido creado como aplicación intermedia, para exponer los servicios SOAP del AIF de Ax2012 como RESTful Services, facilitando el consumo por cualquier cliente/plataforma y que sean fácilmente extensibles.

Personalmente solo he probado la solución Axapta API y funciona correctamente, dándonos más posibilidades para aplicaciones consumidoras de servicio que están fuera del mundo .NET.

Espero haya servido como ayuda por si se da la necesidad en un futuro.

Generar cadena en JSON desde una tabla en AX2012

json

Adjunto job que puede ser de utilidad si necesitamos generar los registros de una tabla en formato JSON, como por ejemplo para devolver en un servicio custom y que pueda ser consumido por aplicaciones externas al ERP.

Este ejemplo mostrará por pantalla la información de la tabla de compañías (Tabla DATAAREA), incluyendo las columnas id de empresa y nombre en formato JSON.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
static void AX3GenerateJSONFromTable(Args _args)
{
    Common          common;
    str             json;
    SysDictTable    dictTable;
    SysDictField    dictField;
    str             rootNode = 'response';
    str             entityNode = 'dataArea';
    Counter         counter;
    boolean         firstField = true;
    boolean         firstRecord = true;
    ;
    firstRecord = true;
    dictTable = new SysDictTable(tableNum(dataArea));
    common = dictTable.makeRecord();
    json = strFmt('{"%1":{"%2":[',rootNode,entityNode);
    while select common
    {
        if(!firstRecord)
        {
            json += ',';   
        }
        firstField = true;
        json +='{'; 
        for(counter=0;counter<=dictTable.fieldCnt();counter++)
        {            
 
            dictField = new SysDictField(dictTable.id(),dictTable.fieldCnt2Id(counter));            
            if(dictField.name() && (dictField.name() == 'id' || dictField.name() == 'name'))
            {
                if(!firstField)
                {
                    json += ',';   
                }
                json += strFmt('"%1":',dictField.name());
 
                switch(dictField.baseType())
                {
                    case Types::String:
                        json += strFmt('"%1"',common.(dictField.id()));        
                        break;
                    case Types::Int64,Types::Integer:
                        json += strFmt('%1',common.(dictField.id()));                           
                        break;
                    case Types::Enum:
                        if(dictField.extendedFieldId() == extendedTypeNum(NoYesId)
                            || dictField.enumId() == enumNum(NoYes)
                            || dictField.enumId() == enumNum(boolean))
                            json += strFmt('%1',common.(dictField.id()));                           
                        else
                            json += strFmt('"%1"',common.(dictField.id()));                           
                        break;                        
                    default:
                        json += strFmt('"%1"',common.(dictField.id()));                                                       
                        break;
                }
                firstField = false;
            }            
        }        
        json +='}'; 
        firstRecord = false; 
    }
    json += ']}}';
    info(json);
}

Realizar llamadas Post a servicios de AIF en AX 2012

Podemos vernos en la necesidad de realizar llamadas a los servicios de Ax mediante una forma muy simplificada, tal y como es una operación post al protocolo HTTP en el sitio web donde se implementan los servicios de Ax.

Para probarlo, partimos de la base de un servicio custom muy sencillo que devuelve un data contract de tipo cliente en base al id proporcionado en la llamada.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[DataContractAttribute('Customer')]
class AX3AMACustSampleDataContract
{
    CustAccount custAccount;
    CustName custName;
}
 
[DataMemberAttribute('CustAccount')]
public CustAccount custAccount(CustAccount _custAccount = custAccount)
{
    custAccount = _custAccount;
    return custAccount;
}
 
[DataMemberAttribute('CustName')]
public CustName custName(CustName _custName = custName)
{
    custName = _custName;
    return custName;
}

Una clase correspondiente al servicio con la siguiente operación:

1
2
3
4
5
6
7
8
9
10
11
12
[SysEntryPointAttribute(true)]
public AX3AMACustSampleDataContract getCustomer(CustAccount _id)
{
    AX3AMACustSampleDataContract custSample = new AX3AMACustSampleDataContract();
    CustTable custTable;
    ;
    select custTable where custTable.AccountNum == _id;
 
    custSample.custAccount(custTable.AccountNum);
    custSample.custName(custTable.name());
    return custSample;
}

El servicio debe implementarse como puerto mejorado y sobre el sitio AIF en el servidor IIS.

postman_1

En este ejemplo hemos utilizado la autenticación, básica, por lo que se debe configurar el puerto de entrada con estas opciones:

postman_2 postman_3

Hay información detallada de como modificar la autenticación de un servicio en el siguiente enlace:

Como implementar autenticación digest en los servicios WEB de Ax 2012

Para este ejemplo, aunque existen otras opciones igual de válidas, hemos utilizado la extensión “Postman” de Chrome para conectar con el servicio, por su sencillez y agilidad.

Una vez instalado, hay que crear una nueva ejecución con la siguiente información en header, especificando el SOAPAction y el método de autenticación del servicio (Básica en este caso)

postman_4

El body deberá tener la siguiente estructura, incluyendo el usuario, la empresa y los parámetros que espera el método getCustomer en formato XML

postman_5
1
2
3
4
5
6
7
8
9
10
11
12
13
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<h:CallContext xmlns:h="http://schemas.microsoft.com/dynamics/2010/01/datacontracts" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<h:Company>UMSF</h:Company>
<h:Language i:nil="true"/>
<h:LogonAsUser>ClaimUsersAuth\ClaimUser1@contoso.com/h:LogonAsUser>
<h:MessageId i:nil="true"/><h:PartitionKey i:nil="true"/><h:PropertyBag i:nil="true" xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays"/>
</h:CallContext>
</s:Header>
<s:Body>
<AX3AMACustSampleServiceGetCustomerRequest xmlns="http://tempuri.org"><_id>000042</_id></AX3AMACustSampleServiceGetCustomerRequest>
</s:Body>
</s:Envelope>

Por último, este es el resultado que devuelve el post al ser ejecutado.

postman_6

Como implementar autenticación digest en los servicios WEB de Ax 2012

La autenticación normalmente utilizada para utilizar los servicios de Ax  es de tipo Windows, la cual muchas veces y dependiendo de las circunstancias no provee el rendimiento esperado, así que vamos a estudiar la configuración necesaria para poder consumir estos servicios mediante mecanismos de autenticación digest, que nos da un nivel más de seguridad que la autenticación básica y buen rendimiento.

Para comenzar vamos a configurar el sitio web donde se han implementado los servicios para aceptar este tipo de autenticación.

1. Abrimos el Internet Information Services Manager y navegamos hasta el directorio virtual MicrosoftDynamicsAXAif60, desde las diferentes opciones del sitio abrimos el apartado de autenticación y finalmente habilitados la autenticación Digest.

digest_1

2. Acto seguido cerramos el IISManager y ejecutamos el comando iisreset desde el comand prompt de Windows.

3. El siguiente paso implica la creación de un nuevo puerto de entrada desde Ax, para lo cual abriremos el menú “Administración/Configurar/Services and Application Integration Framework/Puertos de entrada. Crearemos un nuevo puerto utilizando el adaptador http con una configuración similar a la siguiente imagen

digest

4. A continuación añadimos las operaciones de servicio que vamos a utilizar mediante el botón operaciones de servicio

digest_3

5. Previo a la activación del puerto, deberemos realizar la configuración del cliente mediante el botón “Configurar” (NO el botón configurar AOS)

6. Tras pulsarlo obviamos la advertencia que nos mostrará el sistema y continuamos.

7. Seleccionamos la sección Bindings dentro del apartado de la izquierda “Configuration” y creamos uno nuevo mediante la opción “New Binding Configuration” con el tipo “basicHttpBinding” y clicamos en Ok.

8. Lo nombramos con “basicHttpBindingWithDigestAuth”

digest_4

9. En la pestaña “Security” debemos establecer lo atributos como en la siguiente imagen:

digest_5

10. El siguiente punto es ir al nodo “reqReplyEndPoint”, marcarlo y modificar el atributo BindingConfiguration para que tenga el valor del binding creado anteriormente “basicHttpBindingWithDigestAuth”

digest_6

11. Cerramos la configuración, guardamos los cambios y activamos el puerto.

12. En este punto ya podremos utilizar el servicio desde nuestra aplicación consola y conectar utilizando este tipo de autenticación.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CustHTTPServiceConsoleAppl.CustHTTPService;
 
namespace CustHTTPServiceConsoleAppl
{
    class Program
    {
        static void Main(string[] args)
        {
           // Create the service client proxy
 
            CustomerServiceClient c1 = new CustomerServiceClient();
            c1.ClientCredentials.HttpDigest.ClientCredential.UserName = "dominio\\usuario";
            c1.ClientCredentials.HttpDigest.ClientCredential.Password = "password";
            c1.ClientCredentials.HttpDigest.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
 
            // Create the customer object to receive the response message.
            AxdCustomer custResp;
 
            // Create the entity key list for the request. 
            EntityKey[] readRespKeys = new EntityKey[1];
            readRespKeys[0] = new EntityKey();
            readRespKeys[0].KeyData = new KeyField[1];
            readRespKeys[0].KeyData[0] = new KeyField();
            readRespKeys[0].KeyData[0].Field = "AccountNum";
 
            // Prompt the user for a customer account number.
            Console.WriteLine("Type in a customer account number and then press Enter:");
            // Add the result to the entity key value.
            readRespKeys[0].KeyData[0].Value = Console.ReadLine();
 
            try
            {
                // Try to read the customer.
                custResp = c1.read(null, readRespKeys);
 
                // Display the information for the customer.
                Console.WriteLine("For Customer: " + custResp.CustTable[0].AccountNum);
                Console.WriteLine("Customer name is: " + custResp.CustTable[0].DirParty[0].Name);
 
            }
            catch (Exception e)
            {
                Console.WriteLine("Exception: " + e.Message);
                    c1.Abort();
            }
            c1.Close();
 
            Console.Read();
 
        }
    }
}

AX3