X++ development

The Visual Studio project and its output have been added to the AOT, so the first stage of development is now complete. You can leave Visual Studio and switch to Microsoft Dynamics AX 2012. The project has been added to the Visual Studio Projects node in the AOT. As we have used C#, the project will be in the C Sharp Projects node.

Look for the DynamicsAxServices.WebServices.ZipCode project and expand some of the nodes to inspect it. It should appear as shown in the following screenshot:

X++ development

As you can see in the preceding screenshot, this is divided into the following two main components:

  • The Project Content node contains the actual C# project source such as properties of the project, the service references, an app.config file, and C# source files
  • The Project Output node contains the assemblies that will be deployed, taking into account the deployment options

In order to use the assemblies that have been created and stored in the AOT, we'll have to deploy them. Let's look at the options that are available.

Deploying managed code

When we create a project in Visual Studio and add it to the AOT, the following deployment options are available:

  • Deploy to Server
  • Deploy to Client
  • Deploy to EP

In our earlier example, we enabled deployment on the client and to the server because these are important in the context of services.

Deploy to Server

When you have enabled deployment to the server, the output of the Visual Studio project will be copied to the VSAssemblies subfolder in the bin folder of the AOS directory. The default path is C:Program FilesMicrosoft Dynamics AX60Server<AOSServer>BinVSAssemblies. After you have deployed assemblies to the server, you should restart the AOS so that they are loaded.

Tip

Hot swapping

When hot swapping is enabled on the AOS, restart is not needed after deployment. This feature is added for the convenience of developers but is not recommended for a production environment. For more info, check out the following article on MSDN: How to: Enable Hot Swapping of Assemblies (http://msdn.microsoft.com/en-us/library/gg889279.aspx).

Deploy to Client

When you have enabled deployment to the client, the output of the Visual Studio project will be copied to the following folder on the client: %localappdata%MicrosoftDynamics AxVSAssemblies. You may have to restart the Microsoft Dynamics AX client after deployment, otherwise the assemblies may not get copied.

The assemblies will be deployed to a client as and when are needed. This comes down to the following three situations:

  • When you use IntelliSense
  • When you compile code that uses the assembly
  • When code runs on the client in which a call is made to the assembly

Obviously, you will want to have the assembly on your client as a developer, otherwise you will not be able to use IntelliSense or compile your code.

Consuming the web service

Now that we have created a service reference in our Visual Studio proxy library and deployed it to Microsoft Dynamics AX, we can use the types in the library from Microsoft Dynamics AX.

First attempt

Let's take a look at the following X++ code that consumes the zip code service to retrieve a place name:

static void Consume_GetZipCodePlaceName(Args _args)
{
    DynamicsAxServices.WebServices.ZipCode.USAZipCodeServiceRef .PostalCodeServiceClient postalServiceClient;
    DynamicsAxServices.WebServices.ZipCode.USAZipCodeServiceRef .PostalCode postalCode;
    System.Exception Exception;

    try
    {
        // Create a service client proxy
        postalServiceClient = new DynamicsAxServices .WebServices.ZipCode.USAZipCodeServiceRef .PostalCodeServiceClient();

        // Use the zipcode to find a place name
        postalCode = postalServiceClient. GetPostCodeDetailByPostCode("10001"); // 10001 is New York

        // Use the getAnyTypeForObject to marshal the System.String to an Ax anyType
        // so that it can be used with info()
        info(strFmt('%1', CLRInterop:: getAnyTypeForObject(postalCode.get_PlaceName())));
    }
    catch
    {
        // Get the .NET Type Exception
        exception = CLRInterop::getLastException();

        // Go through the inner exceptions
        while(exception)
        {
            // Print the exception to the infolog
            info(CLRInterop::  getAnyTypeForObject(exception.ToString()));

            // Get the inner exception for more details
            exception = exception.get_InnerException();
        }
    }
}

When we go through the code bit by bit, we can see that a proxy client is created first. Note that this is the managed type that is created by the SvcUtil tool when adding the service reference:

postalServiceClient = new DynamicsAxServices .WebServices.ZipCode.USAZipCodeServiceRef .PostalCodeServiceClient();

After that, using the following code, we immediately invoke the service operation with a zip code:

postalCode = postalServiceClient. GetPostCodeDetailByPostCode("10001"); // 10001 is New York

Then, there is a simple infolog message that shows the place name using the following code:

info(strFmt('%1', CLRInterop:: getAnyTypeForObject(postalCode.get_PlaceName())));

Notice the CLRInterop::getAnyTypeForObject method, which is used to marshal between the .NET type System.String and the X++ anyType type before submitting it to the infolog.

That's it for consuming services. However, we also have some exception handling that handles any .NET exceptions while invoking the external service, as shown in the following code snippet:

    catch
    {
        // Get the .NET Type Exception
        exception = CLRInterop::getLastException();

        // Go through the inner exceptions
        while(exception)
        {
            // Print the exception to the infolog
            info(CLRInterop::  getAnyTypeForObject(exception.ToString()));

            // Get the inner exception for more details
            exception = exception.get_InnerException();
        }
    }

Fixing configuration issues

Although the preceding code example should suffice, you will get an error message when running it. The error message is shown in the following screenshot:

Fixing configuration issues

What is going on here is that the service is trying to look for the endpoint's configuration in the application's configuration file but does not find it. This is because Microsoft Dynamics AX is acting as the host application here (Ax32.exe). Therefore, the service tries to open the Ax32.exe.config file and look for the endpoint configuration.

It is clear that putting the configuration details of every service that we want to consume into the Ax32.exe.config file is a bit impractical and should be avoided. The solution to this issue is using the AifUtil class to create the service client.

Let's change the preceding code so that it uses the AifUtil class to point to the right configuration file and see what happens then. Start off by declaring a new variable of the System.Type type at the top of the job, as shown in the following code:

System.Type type;

Take a look at the following line of code:

postalServiceClient = new DynamicsAxServices .WebServices.ZipCode.USAZipCodeServiceRef .PostalCodeServiceClient();

Replace the preceding code with the following two lines of code that use the variable that you just declared:

type = CLRInterop::getType('DynamicsAxServices.WebServices.ZipCode.USAZipCodeServiceRef.PostalCodeServiceClient'),
postalServiceClient = AifUtil::createServiceClient(type);

The first line will resolve the .NET type of the service client and pass it to the AifUtil::createServiceClient method. The AifUtil class will then resolve the right configuration file by looking into the VSAssemblies folder for the assembly that contains the specified type. You can see the code of the AifUtil class's createServiceClient method in the following code snippet:

vsAssembliesPath = xApplication::getVSAssembliesPath();
configFilePath = Microsoft.Dynamics.IntegrationFramework.ServiceReference::GetConfigFilePath(serviceClientType, vsAssembliesPath);
serviceClient = Microsoft.Dynamics.IntegrationFramework.ServiceReference::CreateServiceClient(serviceClientType, configFilePath);

When you test these changes, the service should be called correctly and should give you an infolog message that shows New York as the place name.

Deploying between environments

Although the previous code consumes the external service just fine, there is another impractical issue going on when you want to deploy the code across environments.

Suppose that you want to have different versions of your service running on development, test, and production systems. Then, you will probably have three different addresses for each environment. However, the issue here is that you only have one address available in the proxy class library.

To solve this issue, we need to update our X++ code one more time. Start by declaring two new variables that will hold the endpoint and endpoint address:

System.ServiceModel.Description.ServiceEndpoint endPoint;
System.ServiceModel.EndpointAddress endPointAddress;

You may have to add a reference to the System.ServiceModel assembly to the AOT. To do that, go to the AOT, right-click on the References node, and then click on Add Reference. Next, select System.ServiceModel in the grid, click on Select, and finally, click on OK.

Then, add the following three lines of code just before the line that invokes the service operation:

endPointAddress = new System.ServiceModel.EndpointAddress ("http://www.restfulwebservices.net/wcf/USAZipCodeService.svc");
endPoint = postalServiceClient.get_Endpoint();
endPoint.set_Address(endPointAddress);

What the preceding code does is that it creates an endpoint address for the service client that is to be used. When the endpoint is created, it replaces the endpoint address that is currently being used by the service client. Note that in the preceding example, the address should be replaced by a parameter that is stored in the system. This way, you can set the endpoint address depending on the parameter value of that environment.

Final result

After all these changes, the code that consumes the services will look as follows:

static void Consume_GetZipCodePlaceNameWithEndPoint(Args _args)
{
    DynamicsAxServices.WebServices.ZipCode.USAZipCodeServiceRef.PostalCodeServiceClient postalServiceClient;
    DynamicsAxServices.WebServices.ZipCode.USAZipCodeServiceRef .PostalCode postalCode;
    System.ServiceModel.Description.ServiceEndpoint endPoint;
    System.ServiceModel.EndpointAddress endPointAddress;
    System.Exception exception;
    System.Type type;

    try
    {
        // Get the .NET type of the client proxy
        type    = CLRInterop::getType('DynamicsAxServices.WebServices.ZipCode.USAZipCodeServiceRef.PostalCodeServiceClient'),

        // Let AifUtil create the proxy client because // it uses the VSAssemblies path for the config file
        postalServiceClient = AifUtil::createServiceClient(type);

        // Create an endpoint address; this should be a 
        // parameter stored in the system
        endPointAddress = new System.ServiceModel.EndpointAddress("http://www.restfulwebservices.net/wcf/USAZipCodeService.svc");

        // Get the WCF endpoint
        endPoint = postalServiceClient.get_Endpoint();

        // Set the endpoint address.
        endPoint.set_Address(endPointAddress);

        // Use the zipcode to find a place name
        postalCode          = postalServiceClient.GetPostCodeDetailByPostCode("10001"); // 10001 is New York

        // Use the getAnyTypeForObject to marshal the 
        // System.String to an Ax anyType
        // so that it can be used with info()
        info(strFmt('%1', CLRInterop::getAnyTypeForObject(postalCode.get_PlaceName())));
    }
    catch
    {
        // Get the .NET Type Exception
        exception = CLRInterop::getLastException();

        // Go through the inner exceptions
        while(exception)
        {
            // Print the exception to the infolog
            info(CLRInterop::getAnyTypeForObject(exception.ToString()));

            // Get the inner exception for more details
            exception = exception.get_InnerException();
        }
    }
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.247.11