viernes, 24 de junio de 2016

Calling the DocumentHandling service

Today we're obtaining files via WCF web services from Visual Studio, the DocumentHandling service to be exact.
Activate the standard AX DocumentHandling service!
Now remember that these files are all available via a shared directory, and so before launching into using this service do investigate if it's worth obtaining the directory from DocuType, file name and file type from the DocuValue entity

From Visual Studio register the service from the WSDL URI and add the service reference to your project.  Remember that we will need to change the server and port in our project when it's time to move our project reference to the production environment.

Now all you would need to do is find a RecId from the DocuRef entity.  In the example below we can see a document associated with an Item Lot number:
Document Management is activated for multiple entities from Lots to Sales Invoices

Pseudo code below.  I have a paranoia with the Client object where we could leave connections open and therefore no garbage collection. Adapt the below to your requirements.:
using XXXProj.DocumentHandlingServiceReference;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections;
using System.Configuration;
using System.IO;
using System.Text;

/// <summary>
/// Download PDF Quality Certificate associated with lot '140801-033711'.
/// RecId 5637152126 (DEVELOPMENT environment)
/// </summary>
[TestMethod]
public void TestCertificate_GetPDF1()
{
  // We have a doc associated with InventBatch, lot '140801-033711' - See table DocuRef
  Int64 CERT_RECID1 = 5637152126;
        
  DocumentFileDataContract docuContract = new DocumentFileDataContract();

  // Create a client only for as long as we need to.  Note the exception handling with client.
  using (DocumentHandlingServiceClient client = new DocumentHandlingServiceClient())
  {
    try
    {
      // *NO* company context for Document Management
      //CallContext context = this.setCompanyContext("CONT");

      //Execute as another user, and he's called 'Bob'
      RunAsBob(client.ClientCredentials);

      // Set the AX AIF service endpoint.
      client.Endpoint.Address = setAXEnvironment(client.Endpoint.Address);
      
      // Obtain file, String format, encoded in base 64
      docuContract = client.getFile(null, CERT_RECID1);
      client.Close();
    }
    catch (System.ServiceModel.CommunicationException e)
    {
      client.Abort();
      Assert.Fail(e.ToString());
    }
    catch (TimeoutException e)
    {
      client.Abort();
      Assert.Fail(e.ToString());
    }
    catch (Exception e)
    {
      client.Abort();
      Assert.Fail(e.ToString());
    }
  }

  Assert.IsNotNull(docuContract);
  Assert.IsTrue(docuContract.RecId > 0, "Document not found - " + CERT_RECID1.ToString());
  Assert.IsNotNull(docuContract.Attachment, "Document is empty");

  // Let's save the document in a temporary directory
  string documentAttachment = docuContract.Attachment;
  string file = "C:\\TEMP\\file.pdf";
  byte[] ba = System.Convert.FromBase64String(documentAttachment);
  System.IO.File.WriteAllBytes(file, ba);
}

Helper or shared methods:
using System.Configuration;
using System;
using System.ServiceModel;

/// <summary>
/// Execute as user 'Bob'.  Data saved in app.config xml file.
/// </summary>
/// <param name="clientCredentials"></param>
protected static void RunAsBob(System.ServiceModel.Description.ClientCredentials clientCredentials)
{
  clientCredentials.Windows.ClientCredential.Domain = ConfigurationManager.AppSettings["Domain"];
  clientCredentials.Windows.ClientCredential.UserName = ConfigurationManager.AppSettings["UserName"];
  clientCredentials.Windows.ClientCredential.Password = ConfigurationManager.AppSettings["Password"];
}

/// <summary>
/// Assign the environment's Host/Port.  Are we testing DEVELOPMENT or PRODUCTION?
/// e.h.: srvax2012:8202
/// </summary>
/// <param name="address">client.EndpointAddress</param>
/// <returns></returns>
protected static System.ServiceModel.EndpointAddress setAXEnvironment(System.ServiceModel.EndpointAddress address)
{
  var newUriBuilder = new UriBuilder(address.Uri);
  newUriBuilder.Host = ConfigurationManager.AppSettings["NEW_ENDPOINT_HOST"];
  newUriBuilder.Port = System.Int16.Parse(ConfigurationManager.AppSettings["NEW_ENDPOINT_PORT"]);
  address = new EndpointAddress(newUriBuilder.Uri, address.Identity, address.Headers);
  return address;
}

/// <summary>
/// Context - Select Company.  DataAreaId: CONT/CONZ/TEST/DAT/...
/// </summary>
/// <param name="dataAreaId"></param>
/// <returns></returns>
private CustPackingSlipServiceReference.CallContext setCompanyContext(String dataAreaId)
{
  CustPackingSlipServiceReference.CallContext context = new CustPackingSlipServiceReference.CallContext();
  context.Company = dataAreaId;
  context.MessageId = Guid.NewGuid().ToString();
  return context;
}

lunes, 13 de junio de 2016

Call an AIF service operation via Job

Simulate a call to an AIF service operation via a Job in AX2012.  Thus avoiding having to attach to the server process when debugging.
The OperationContext below was mostly 'ignored' when being called from the Job.
static void ZZZ_SimulateAIFServiceCall(Args _args)
{
    // Sales Invoice find() operation
    SalesSalesInvoiceService    salesInvSvc;
    AifQueryCriteria            qryCriteria;
    AifCriteriaElement          criteriaEle;
    AifOperationContext         opContext;
    SalesSalesInvoice           salesInvoice;

    // Filter to find a customer invoice, in company 'HAL'
    criteriaEle = AifCriteriaElement::newCriteriaElement(
                                    "CustInvoiceJour",
                                    "RecId",
                                    AifCriteriaOperator::Equal,
                                    "5637156576");
    qryCriteria = AifQueryCriteria::construct();
    qryCriteria.addCriteriaElement(criteriaEle);
    salesInvSvc = SalesSalesInvoiceService::construct();
    // Simulate operation context
    opContext = new AifOperationContext(
        "XXXX",                       // ActionId (?)
        "SalesSalesInvoice",          // AifDocument that we are simulating a call to
        14005,                        // ClassId - classes/SalesSalesInvoice(?)
        "find",                       // Method name
        "find",                       // Op. method name
        "AccountsReceivableServices", // AIFPort.name
        "HAL",                        // ***Company / DataAreaId
        AifMessageDirection::Inbound, // ***Inbound / Outbound
        null);                        // Map of parameters in Request?
    salesInvSvc.setOperationContext(opContext);
    // Simulate AIF operation call
    salesInvoice = salesInvSvc.find(qryCriteria);

    info(strFmt(@'Invoice exists: %1', salesInvoice.existsCustInvoiceJour()));
}
Don't forget to generate the CIL when making changes to the service classes and calling AIF from outside of AX!