Category Archives: .Net

AX 2012 License generator UI


So i had created a UI for the license generator.

To create a license in 2012 this was the command:

axutil genlicense /file:c:\licenses\MyLicense.txt /certificatepath:c:\certificates\MyCertificate.pfx /licenseCode:MyModule /customer:MyCustomer /serialnumber:1231232123 /password:MySecretPassword123 /expirationdate:12/30/2018 /usercount:5

However, having the password and certificate path information available wasnt exactly a good idea at that time (and probably is still relevant. Why would you want to distribute those two together no?)

So came the the License generator, with the password hard coded and everything else seemingly invisible to the person punching the licenses without having someone with knowledge of the axutil and more importantly

What is this black box ? and why cant I just click somewhere

Moving forward a few years later and opening up the code out, (yes the password has been taken out of the code). Its now stored as well, although not very secure, but enough to keep prying eyes out for a bit. (this means rooms for improvement)

The code is published at GitHub under AxLicenseGenerator (See the readme in that link)

Or you can Download Ax License Generator 1.1

There is lots more information there, or message me if you need more information.

Happy Licensing

LicGenerator_V1.1
License Generator v 1.1
Advertisements

AX 2012 – Model dependencies and Install Order – Part 2 – The app


Credit Nasa

From the follow up from Part 1, this post is more about how to find what models are dependent on what. I have created a console app which outputs csv files and can be downloaded here.

The inner workings:

So we know that ModelElement and ModelElementData hold the AOT objects for the respective models.

ModelElementData is the granular data. So a table field, a class method, a form control are stored here. They link to their parent using the ParentHandle field, or straight to the Main AOT object using the RootHandle.

The app takes every layer, from the ISV onwards , analyses each model and the model element data in them. It then looks if the element is used by any other layer below it OR if the root element is used in any other layer below or in the same layer.

How to use the App:

  • Set the minimum access layer – If you dont care about the SYS /SYP then set the Layer id to that of the ISV. Ofcourse if you work from VAR and beyond, then you probably dont care about ISV / ISP. So look at the Layer table and set the minimum access layer
    Set this in the Tag “MinApplicationLayer” inside the App.config file
  • Connection String – You dont need to set this up, unless you want to default to a specific Ax Database. This is good if you only care about one application. When the App runs, it will ask for user input for the database server and name. Not entering it will revert to the connection string in the App.config file

This is still a command line application, the output is still on the screen. But that’s the next phase of this app, to output the data to files or on screen. And also to make it power shell friendly

Ok, how the app works now:

Run the Exe in a command prompt: AxModel.Common.Exe (I will work on the name, I promise), put the database server, and the database name, and after  a few verbose output data in a CSV format is spit on the screen.

Copy that to excel and it should be all good from there.

The verbose output also outputs the dependencies if you want to look at it, which may be helpful.

Limitations: If you are referencing objects from one model to another in code, like a method in another object is  called from a class, then that will not be detected. This is mainly because cross reference will need to be looked at and is out of scope at this stage. I am focusing on installation dependencies

The code is available on GitHub and I should be updating it more as I conduct more tests and get more input about this.

Download Binaries

Dynamics Ax Logging – Log4net


Earlier I had posted about how to achieve File based logging in Ax, however after working with log4net on another project with .net I decided to implement debugging with Ax2012. This is because popping messages in the info log isint a very refined, besides at a customers site, after turning a few configuration keys on, the logging messages can be traced without writing extra code.

Log4Net: This is a very versatile library, if you look at the configuration files or Google around this, you will notice that it supports a wide variety of logging facilities and filters that can be applied. The overhead of using this library isint much.

The advantage of using is that the logging messages can be left inside the code (and you can even mark their category, and it can be configured in a certain way so that the debugging mechanism can be switched turned on or off for that category.

Implementing log4net in AX2012:

You could follow one of these posts and run an independent c# console project and test the log4net framework like in this blog. I wont be going through that part. What i will be showing is how to implement a library once created and added to AX2012, as AX2012 has its own configuration file which will need to be amended.

The C# project:

Create a C# project, add it to the AOT and add the log4net.dll to the c# project. Note that if you use Nuget there are issues adding it into Ax as the dll needs to be referenced with Ax too. So NuGet will not exactly help.

The code is something like this for me:

 

namespace SHSAx.Logger
{
    public enum LogType
    {
        Debug,
        Info,
        Warn,
        Error,
        Fatal
    }

    public class AxLogging
    {
        private static ICollection configure = XmlConfigurator.Configure();
        private static readonly ILog log = LogManager.GetLogger("DynamicsAx_Logging");

        private readonly ILog localILog;
        //private ILog log = null;

        //Reloads config file so that any change made to the log4net section doesnt require an AOS restart
        private static void ReloadConfiguration()
        {
            ConfigurationManager.RefreshSection("log4net");
            configure = XmlConfigurator.Configure();
        }

        public AxLogging()
        {
            ReloadConfiguration();
            localILog = log;
            //log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
        }

        public AxLogging(string logName)
        {
            ReloadConfiguration();
            localILog = LogManager.GetLogger(logName);
        }

        //Logging for custom log Name
        public void WriteO(LogType logType, string message)
        {
            this.WriteO(logType, message, null);
        }
        public void WriteO(LogType logType, string message, Exception ex)
        {
            switch (logType)
            {
                case LogType.Debug:
                    localILog.Debug(message, ex);
                    break;
                case LogType.Info:
                    localILog.Info(message, ex);
                    break;
                case LogType.Warn:
                    localILog.Warn(message, ex);
                    break;
                case LogType.Error:
                    localILog.Error(message, ex);
                    break;
                case LogType.Fatal:
                    localILog.Fatal(message, ex);
                    break;
                default:
                    break;
            }
        }

        //Static logging - using base log name i.e. DynamicsAx_Logging
        public static void Write(LogType logType, string message)
        {
            Write(logType, message, null);
        }
        public static void Write(LogType logType, string message, Exception ex)
        {
            switch (logType)
            {
                case LogType.Debug:
                    log.Debug(message, ex);
                    break;
                case LogType.Info:
                    log.Info(message, ex);
                    break;
                case LogType.Warn:
                    log.Warn(message, ex);
                    break;
                case LogType.Error:
                    log.Error(message, ex);
                    break;
                case LogType.Fatal:
                    log.Fatal(message, ex);
                    break;
                default:
                    break;
            }
        }
    }
}

Create a config file called log4net.config (and this can be duplicated across to the AOS Server – More later)
Add the log4Net assembly to AX references. This is because the log4net assembly doesn’t get uploaded to the server directory, this double up is required as the log4net dll may not be stored in the csharp project in the AOT. Once this is done, then we are ready to call it from X++ or from other C# projects.

<?xml version="1.0" encoding="utf-8" ?>
  <log4net>
	<appender name="LogFileAppender" type="log4net.Appender.RollingFileAppender">
		<param name="File" value="C:\Temp\AxLogs\MyFirstLoggerAx000001.log"/>
		<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
		<appendToFile value="true" />
		<rollingStyle value="Size" />
		<maxSizeRollBackups value="2" />
		<maximumFileSize value="1MB" />
		<staticLogFileName value="true" />
		<layout type="log4net.Layout.PatternLayout">
			<param name="ConversionPattern" value="%d [%t] %-5p %c %m%n"/>
		</layout>
	</appender>
    <appender name="LogFileAppender2" type="log4net.Appender.RollingFileAppender">
		<param name="File" value="C:\Temp\AxLogs\MyFirstLoggerAx000002.log"/>
		<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
		<appendToFile value="true" />
		<rollingStyle value="Size" />
		<maxSizeRollBackups value="2" />
		<maximumFileSize value="1MB" />
		<staticLogFileName value="true" />
		<layout type="log4net.Layout.PatternLayout">
			<param name="ConversionPattern" value="%d [%t] %-5p %c %m%n"/>
		</layout>
	</appender>
	<appender name="LogFileAppenderPickList" type="log4net.Appender.RollingFileAppender">
		<param name="File" value="C:\Temp\AxLogs\MyFirstLoggerAx__PickList.log"/>
		<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
		<appendToFile value="true" />
		<rollingStyle value="Size" />
		<maxSizeRollBackups value="2" />
		<maximumFileSize value="1MB" />
		<staticLogFileName value="true" />
		<layout type="log4net.Layout.PatternLayout">
			<param name="ConversionPattern" value="%d [%t] %-5p %c %m%n"/>
		</layout>
	</appender>
	<appender name="TraceAppender" type="log4net.Appender.TraceAppender">
		<layout type="log4net.Layout.PatternLayout">
			<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
		</layout>
	</appender>

	<root>
		<level value="ALL" />
		<appender-ref ref="TraceAppender" />
		<appender-ref ref="LogFileAppender" />
	</root>

	<logger name="ShashiTest">
		<level value="NONE" />
		<appender-ref ref="LogFileAppender2" />
	</logger>
	<logger name="ThisIsMyLog">
	    <level value="Fatal" />
		<appender-ref ref="LogFileAppender2" />
	</logger>
	<logger name="Picking_list_posting">
	    <level value="WARN" />
		<appender-ref ref="LogFileAppenderPickList" />
	</logger>
</log4net>

The app.config doesnt need to be appended here, but adding it here would document it upto some level on where / what to edit in the Main config file

<?xml version="1.0"?>
<configuration>
	<configSections>
		<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/> <!--This is required for log4net to work-->
	</configSections>
	<log4net configsource="log4net.config"/> <!--picks up the config from log4net.config we created earlier-->
	<startup>
		<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
	</startup>
</configuration>

Now when you were creating the c# project you would have realized that you added parts to the configsections tag in the app.config and also added the log4net tags. These settings need to be transferred into the Ax config file. So in the applications server directory you will see a file ax32serv.exe.config , this is the file where you need to add the values too (: this is the same file where AIF services are registered)
Make the changes to the ax32serv.exe.config file so that we can start programming.

Log4Net’ing from X++
I create a Class called Class1 (a very original name)

public static server void writeLog()
{
    //This uses the default log name - which is hardcoded in my project
    System.Exception exc;
    SHSAx.Logger.AxLogging logging;

    SHSAx.Logger.AxLogging::Write(SHSAx.Logger.LogType::Info, "this is an info");
    SHSAx.Logger.AxLogging::Write(SHSAx.Logger.LogType::Debug, "this is a debug");
    SHSAx.Logger.AxLogging::Write(SHSAx.Logger.LogType::Error, "this is an Error");
    SHSAx.Logger.AxLogging::Write(SHSAx.Logger.LogType::Fatal, "this is an Fatal issue");
    SHSAx.Logger.AxLogging::Write(SHSAx.Logger.LogType::Warn, "this is a Warning");
}
public static server void WriteLog1()
{
    //This uses the the lognames specified dynamically (See constructor)
    SHSAx.Logger.AxLogging logging;

    logging = new SHSAx.Logger.AxLogging("Picking_list_posting");
    logging.WriteO(SHSAx.Logger.LogType::Info, "1. Started process");
    logging.WriteO(SHSAx.Logger.LogType::Info, "2. Finding inventory");
    logging.WriteO(SHSAx.Logger.LogType::Info, "3. Check inventory");
    logging.WriteO(SHSAx.Logger.LogType::Warn, "3. Batch no. not specified");
    logging.WriteO(SHSAx.Logger.LogType::Info, "3. Allocating batch BT00001");
    logging.WriteO(SHSAx.Logger.LogType::Info, "4. Posting oprder");

    logging = new SHSAx.Logger.AxLogging("ThisIsMyLog");
    logging.WriteO(SHSAx.Logger.LogType::Debug, "THIS IS MINE...!!!!!! DEBUG");
    logging.WriteO(SHSAx.Logger.LogType::Error, "THIS IS MINE...!!!!!! ERROR");
    logging.WriteO(SHSAx.Logger.LogType::Fatal, "THIS IS MINE...!!!!!! FATAL");
    logging.WriteO(SHSAx.Logger.LogType::Info, "THIS IS MINE...!!!!!! INFO");
    logging.WriteO(SHSAx.Logger.LogType::Warn, "THIS IS MINE...!!!!!! WARN");

    logging = new SHSAx.Logger.AxLogging("ShashiTest");
    logging.WriteO(SHSAx.Logger.LogType::Debug, "My Test - Debug");
    logging.WriteO(SHSAx.Logger.LogType::Error, "My Test - Error");
    logging.WriteO(SHSAx.Logger.LogType::Fatal, "My Test - Fatal");
    logging.WriteO(SHSAx.Logger.LogType::Info, "My Test - Info");
    logging.WriteO(SHSAx.Logger.LogType::Warn, "My Test - Warn");

}

I called these methods from a job to show the results. Note that this is a Server side solution. If you want this to be a client side then you may have to deploy the DLL to every client PC. For me calling a method on the server works well and all logs can be written on the server side.
You could dynamically create lognames based on the user, and then create an appender to capture only those messages.

Once done restart the AOS service and run the job.
MyFirstLoggerAx000001.log

2013-06-04 09:05:23,655 [71] INFO  DynamicsAx_Logging this is an info
2013-06-04 09:05:23,655 [71] DEBUG DynamicsAx_Logging this is a debug
2013-06-04 09:05:23,655 [71] ERROR DynamicsAx_Logging this is an Error
2013-06-04 09:05:23,655 [71] FATAL DynamicsAx_Logging this is an Fatal issue
2013-06-04 09:05:23,655 [71] WARN  DynamicsAx_Logging this is a Warning
2013-06-04 09:05:23,686 [71] WARN  Picking_list_posting 3. Batch no. not specified
2013-06-04 09:05:23,702 [71] FATAL ThisIsMyLog THIS IS MINE...!!!!!! FATAL
2013-06-04 09:05:23,718 [71] DEBUG ShashiTest My Test - Debug
2013-06-04 09:05:23,733 [71] ERROR ShashiTest My Test - Error
2013-06-04 09:05:23,733 [71] FATAL ShashiTest My Test - Fatal
2013-06-04 09:05:23,733 [71] INFO  ShashiTest My Test - Info
2013-06-04 09:05:23,733 [71] WARN  ShashiTest My Test - Warn

MyFirstLoggerAx000002.log

2013-06-04 09:05:23,702 [71] FATAL ThisIsMyLog THIS IS MINE...!!!!!! FATAL
2013-06-04 09:05:23,718 [71] DEBUG ShashiTest My Test - Debug
2013-06-04 09:05:23,733 [71] ERROR ShashiTest My Test - Error
2013-06-04 09:05:23,733 [71] FATAL ShashiTest My Test - Fatal
2013-06-04 09:05:23,733 [71] INFO  ShashiTest My Test - Info
2013-06-04 09:05:23,733 [71] WARN  ShashiTest My Test - Warn

MyFirstLoggerAx__PickList.log

2013-06-04 09:05:23,686 [71] WARN  Picking_list_posting 3. Batch no. not specified

Log result:

NOTE: to pick up the new C# project in X++ you may have to restart the AOS service. Also because we are reloading the configuration every time we call the logging class, there will be overheads. It will nice if the config file doesnt need to be changed.

Hopefully this can be turned into a more streamlined approach and we can get a common class / interface that we all can use to do some logging.

Ax 2012 AIF Calling the CustCustomerService.Create method from WCF


The CustCustomerService (External name CustomerService) can be used to create a customer in Ax through AIF.
When using the http adapter and consuming this service in a .Net Application, this is a tried and tested way to do it.
Notice the DirPartyTable assignment in the code. You do not need to specify the DirParty member, but that is where the customer name is held in Ax 2012

static void WebServiceTest()
{
    using (Ax2012WebService.CustomerServiceClient custClient = new Ax2012WebService.CustomerServiceClient())
    {
        var context = new Ax2012WebService.CallContext() { Company = "ceu" };
        var customers = new Ax2012WebService.AxdCustomer
            {
                CustTable = new Ax2012WebService.AxdEntity_CustTable[] 
                    { 
                        new Ax2012WebService.AxdEntity_CustTable() 
                        { 
                            CustGroup="10", 
                            TaxGroup="CA", 
                            DirParty = new Ax2012WebService.AxdEntity_DirParty_DirPartyTable[1] 
                            {
                                new Ax2012WebService.AxdEntity_DirParty_DirOrganization() 
                                    { LanguageId = "en-us", Name = "SS 001" }
                            }
                        }
                    }
            };
        custClient.create(
            context,
            customers
            );
    }
}

Dynamics Ax 2012 – AIF Import CSV File – Part 3: Import CSV file using AIF


This is the final part to the serieds of Importing CSV files via AIF

Part 1: Consume Web Service
Part 2: Create Item from File adapter 
Part 3: Import CSV file with items Through AIF

In the past posts I consumed a webservice InventItemService and created items in the Released products. This was used as a proof of what should happen when the data is sent to Ax. Part 2 looked at doing the same thing using the File system adapter. here we created an XML file, added the item id’s and then put the file in the folder, the batch did its magic and added the items to the Released Products list

A certain drawback to the that was we had to create the XML files manually, or use an external transformer to do it. AX 2012 has a new feature where we can add transformation Libraries in the form of Either XSLT or .Net libraries.

We will be taking the Inbound port created in Part 2 of this series, and add a transformation library, and instead of dropping an XML file into the inbound directory, we will drop a CSV file

A good read about the process I am about to show is: About the AIF Pipeline [AX 2012]

Create Transformation Library

In Order to create a transformation library, we need to implement an interface found in Microsoft.Dynamics.IntegrationFramework
This DLL can be found in the directory: C:\Program Files\Microsoft Dynamics AX\60\Client\Bin\ 
More information about the Transformation can be found at: Walkthrough: Creating a .NET Assembly Transform [AX 2012]

What we need to do is create a Visual studio project, of a Class Library type

Create Class Library

Add a reference to the Microsoft.Dynamics.IntegrationFramework.Dll using the link above, and create a new class which implements the interface ITransform found in Microsoft.Dynamics.IntegrationFramework.Transform.ITransform

Then create a method which implements the method Transform. This method takes in 3 parameters

1. input (System.IO.Stream) : This is the input stream which is be the file itself
2. output (System.IO.Stream marked as out): This is the output stream that will be returned back to AX. We need to populate this stream
3. configuration (string): This is a custom configuration string that can be added to the port and can be used by the transformation. in this case we will not be using it.

At this stage the class should look like this:

public class LoonItemTransform : Microsoft.Dynamics.IntegrationFramework.Transform.ITransform
{
	public void Transform(System.IO.Stream input, System.IO.Stream output, string configuration)
	{
	    //Need to populate the oputput stream here
	    throw new NotImplementedException();
	}
}

Okay So we have a transformation Library, now we need to add code to read the data from the CSV file, which will be sent in the input stream as a parameter.
The CSV file we are going to add has 2 fields, the itemId and the name.
The Itemd also doubles up as the Product
The code should look like this, Its just reading a file and splitting the line contents.

public class LoonItemTransform : Microsoft.Dynamics.IntegrationFramework.Transform.ITransform
{
    public void Transform(System.IO.Stream input, System.IO.Stream output, string configuration)
    {
        StreamReader sreader = new StreamReader(input);
        string[] dataArray;

        string lineIn = sreader.ReadLine();

        while (lineIn != null)
        {
            dataArray = lineIn.Split(','); //0 is itemid, 1 is name

            lineIn = sreader.ReadLine();
        }
        sreader.Close();

        //Create XML for items

        //Serialize item data to xml string

        //Create Envelope and add header information, and add item data

        //serilalize data to outputstream
    }
}

So at this stage we have the data from our CSV file.We now need to somehow create an XML file similar to the one we created in Part 2. For this we will taking help of the XSD files we had take earlier and I will show you how to create a .Net class out of it. We will add these classes into the Library, and populate it. Serializing these classes will give us the XML file desired.

Creating Classes from XSD

What we did in the previous part was create an XML file that conforms to a certain XSD file. There are 3 XSD files in total that we used for creating the XML file. What we well do is create classes using the XSD files, populate them using the CSV file and serialize them.Upon serialization we will get the same output as the previous post.

In our Visual studio project we will first create 2 folders (to separate the objects generated into 2 different namespaces) ItemXSD and SharedXSD

Open the Visual studio command prompt and navigate to the directory where the XSD files were saved.

For the first instance, we need to create classes for the InventItemService. This XSD (item.xsd) is dependant on the SharedTypes.xsd We will specify both these XSD files and pass it to the XSD.exe command.

In the terminal type the following command:

xsd Item.xsd SharedTypes.xsd /classes /namespace:LooneyTrans.ItemXSD

This creates a file Item_SharedTypes.cs. Place this file in the folder ItemXSD of the project
Note: If you are creating the project in VB .Net, you can use the options in XSD.exe to output a vb file instead

Now create the code for the Envelope

xsd Message.xsd /classes /namespace:LooneyTrans.SharedXSD

Place the output file Message.cs in the folder SharedXSD of the project.

After doing so we should be able to access the object AxdEntity_InventTable, just like we did in Part 1 using web services.
We need to extend this one to adding an envelope which sets the Action for the document. The code should now look like this

public class LoonItemTransform : ITransform
{
    public void Transform(System.IO.Stream input, System.IO.Stream output, string configuration)
    {
        StreamReader sreader = new StreamReader(input);
        string[] dataArray;

        string lineIn = sreader.ReadLine();
        //InventTable collection
        List<ItemXSD.AxdEntity_InventTable> inventTables = new List<ItemXSD.AxdEntity_InventTable>();
        //List<ItemXSD.AxdEntity_InventTable> inventTables = new List<ItemXSD.AxdEntity_InventTable>();
        /*
         There seems to be an issue with the code formatting by wordpress here. Replace 'LT' by the less than sign, and 'GT' by greater than sign. The invent table collection should be
         List'LT'ItemXSD.AxdEntity_InventTable'GT' inventTables = new List'LT'ItemXSD.AxdEntity_InventTable'GT'();
        */
        while (lineIn != null)
        {
            dataArray = lineIn.Split(','); //0 is itemid, 1 is name
            //Create inventTable for each line
            ItemXSD.AxdEntity_InventTable inventTable = new ItemXSD.AxdEntity_InventTable();
            inventTable.ItemId = dataArray[0];
            inventTable.Product = dataArray[0];
            inventTable.NameAlias = dataArray[1];
            //Insert inventTable to collection
            inventTables.Add(inventTable);

            lineIn = sreader.ReadLine();
        }
        sreader.Close();

        ItemXSD.AxdItem item = new ItemXSD.AxdItem();
        item.InventTable = inventTables.ToArray();

        //Serialize item data to xml string
        StringWriter stringWriterItems = new StringWriter();
        System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(ItemXSD.AxdItem));
        serializer.Serialize(stringWriterItems, item);

        //Put it inside the envelope
        SharedXSD.EnvelopeType envelope = new SharedXSD.EnvelopeType();
        SharedXSD.HeaderType header = new SharedXSD.HeaderType()
                                        {
                                            MessageId = Guid.NewGuid().ToString("B"),
                                            Action = @"http://schemas.microsoft.com/dynamics/2008/01/services/ItemService/create"
                                        };
        envelope.Header = header;
        SharedXSD.BodyType bodyType = new SharedXSD.BodyType();

        //Load item data xml string into an XML Document
        XmlDocument xmlDocumentItems = new XmlDocument();
        xmlDocumentItems.LoadXml(stringWriterItems.ToString());

        //Set the body to the Item XML Document
        bodyType.MessageParts = xmlDocumentItems.DocumentElement;
        envelope.Body = bodyType;

        //serilalize to outputstream
        System.Xml.Serialization.XmlSerializer mainSerializer = new System.Xml.Serialization.XmlSerializer(typeof(SharedXSD.EnvelopeType));
        mainSerializer.Serialize(output, envelope);
    }
}

We now need to compile the project and load the resulting DLL into AX.

Loading the Transformation Library

Going back to the Previous post where we created the Inbound port using the File System Adapter, we need to tell it to use our transformation library. To do so, first deactivate the port, and then set the Inbound Transforms to true.

Set Inbound transforms

From the inbound transform screen, we need to add the Library to the Main Transformation Library. Click the Manage Transformsand add the Library that we created and give it a name and description. Note that you need to set a class on the Manage transforms. This means that we can have more than one transform method in the class library and create a record for each of those.

Create Manage Transforms

We just added our transformation to the main repository and need to reference it in the Inbound transforms for this port

Create Inbound Transform

Notice that the configuration is a text field which can be loaded from a file. This is the text that is passed onto the Transform method created earlier.

After activating this, we should be ready to drop our file and start out batch job
So the File we are passing in look like this:

SS001,SS1ItemName
SS002,SS2ItemName

After dropping the File into the Inbound directory, the batch picks it up and pushes it into the Message Queue.

Item waiting in Queue

As you can see the message that has been created by the transformation Library is similar to the one we used in the Previous post.
When running the batch job again, this queue is processed and the items are pushed into the Released Product table

Result: Items SS001 and SS002 added to Released Products

This concludes the series of Importing a CSV file into AX using the File System Adapter. (*tears of joy rolling out now*)

Android Ax App – Part 1–Set up the intermediate WCF service


I came across a blog by Joris De Gruyter http://daxmusings.blogspot.com/2011/11/10-minute-app-windows-phone-7-app.html and decided to use that to connect to an Android device.

This is part 1 of 2 to explain the process of creating the Android application to connect to Ax via AIF services.

Unlike the blog mentioned above, I had to enable REST in the WCF service, and output it as JSON. This is because android has a better JSON support than SOAP (ksoap can be used to consume the WCF services, but I have found JSON to be a much easier approach)

To enable REST services, I installed the WCFRestContrib package. This is available from NuGet and can be installed using

PM> Install-Package wcfrestcontrib

We can create the WCF Service using the no configuration mode. This link defines how to create a WCF service with no configuration. Which means most of the web.config is deleted, and another identifier is attached to the SVC file. This however relies of transport security as there is no SOAP envelope around this. WCFRESTContrib allows to create a custom authentication system. The authentication data can be retrieved from the HTML headers and verified.

So the WCF service should look like this, it enables a GET method and has a certain URI, and outputs the data in JSON

namespace WcfService1
{
  [ServiceContract]
  [ServiceAuthentication] //WCFRESTContrib attribute - authentication per session (or at method level for per call) public interface IApi1 {
  [WebGet(UriTemplate = "/api1/appName", ResponseFormat = WebMessageFormat.Json)]
  [OperationContract]
  string ApplicationName();

  [WebGet(UriTemplate = "/api1/items", ResponseFormat = WebMessageFormat.Json)]
  [OperationContract]
  List GetAllItemIds();
}
}

The Service interface markup (this allows for a noconfiguration WCF Service)

<%@ ServiceHost 
    Language="C#" 
    Debug="true" 
    Service="WcfService1.Api1" 
    CodeBehind="Api1.svc.cs"
    Factory="System.ServiceModel.Activation.WebServiceHostFactory"
    %>

The Service file itself

namespace WcfService1
{
    [WebAuthenticationConfiguration(
    typeof(WcfService1.ApiAuthnetication),
    typeof(ApiAuthneticationValidator),
    true,
    "My Application")]
    public class Api1 : IApi1 {
    public string GetUsername()
    {
        object authValue;
        string authStr = "", username = "", password = "";
        try {
            var keys = OperationContext.Current.IncomingMessageProperties.Keys;
            OperationContext.Current.IncomingMessageProperties.TryGetValue("httpRequest", out authValue);
            if (authValue != null)
            {
                System.ServiceModel.Channels.HttpRequestMessageProperty p = (System.ServiceModel.Channels.HttpRequestMessageProperty)authValue;
                authStr = p.Headers.Get("Authorization");
                ApiAuthnetication.GetUsernamePwd(authStr, out username, out password);
            }
        }
        catch (Exception e)
        {
            return e.Message;
        }
        return username;
    }
    public string ApplicationName()
    {
        string username = this.GetUsername();

        return username;
    }
    public List GetAllItemIds()
    {
        return Item.GetItems(this.GetUsername());
    }
    }
}

Notice the header attributes. This needs to be defined and is based on the WCFRESTContrib package. The authentication classes created is. Notice that the Username and password are being sent in the header with the tag “Authorization” this can be renamed to anything you like

namespace WcfService1
{
    public class ApiAuthnetication : WcfRestContrib.ServiceModel.Dispatcher.IWebAuthenticationHandler {
    public static bool GetUsernamePwd(string authenticationStr, out string username, out string password)
    {
        bool result = true;
        username = "";
        password = "";
        try {
            var values = authenticationStr.Split(':');
            username = values[0];
            password = values[1];
        }
        catch {
            result = false;
        }
        return result;
    }

    public System.Security.Principal.IIdentity Authenticate(
            System.ServiceModel.Web.IncomingWebRequestContext request,
            System.ServiceModel.Web.OutgoingWebResponseContext response,
            object[] parameters,
            System.IdentityModel.Selectors.UserNamePasswordValidator validator,
            bool secure,
            bool requiresTransportLayerSecurity,
            string source)
   {
       string authHeader = "";
       string userName = "";
       string password = "";
       if (request.Headers.HasKeys() == true && request.Headers.AllKeys.Contains("Authorization") == true)
       {
           authHeader = request.Headers.Get("Authorization");
           //var values = authHeader.Split(':');
           //userName = values[0];
           //password = values[1];
           GetUsernamePwd(authHeader, out userName, out password);
       }
       validator.Validate(userName, password);
       return new GenericIdentity(userName, this.GetType().Name);
   }
}

   public class ApiAuthneticationValidator : System.IdentityModel.Selectors.UserNamePasswordValidator
   {
       public override void Validate(string userName, string password)
       {
          //Do your custom authentication here
          if (userName.Equals("claimguy@myapp.com", StringComparison.InvariantCultureIgnoreCase) == false
                  || password.Equals("claimguy@myapp.com", StringComparison.InvariantCultureIgnoreCase) == false)
              throw new Exception("Authentication Failed");
       }
   }
}

The code to get the InventTable data is pretty similar to Joris’s blog. Here is mine:

namespace WcfService1
{
    public class Item {
        public string ItemId { get; set; }
        public string Name { get; set; }

        public static List GetItems(string username)
        {
            List items = new List();
            string itemIdFrom = "1000"; //I cheated here 
            string itemIdTo = "1105";   //I cheated here again 
            //Fetch items from AX here 
            using (ItemService.ItemServiceClient client = new ItemService.ItemServiceClient())
            {
                ItemService.QueryCriteria qc = new ItemService.QueryCriteria();
                ItemService.CriteriaElement criteria = new ItemService.CriteriaElement();
                criteria.DataSourceName = "InventTable";
                criteria.FieldName = "ItemId";
                criteria.Operator = ItemService.Operator.Range;
                criteria.Value1 = itemIdFrom;
                criteria.Value2 = itemIdTo;
                qc.CriteriaElement = new ItemService.CriteriaElement[] { criteria };

                ItemService.CallContext context = new ItemService.CallContext();
                context.Company = "ceu";
                context.MessageId = Guid.NewGuid().ToString();
                context.LogonAsUser = string.Format("LoonAx\\{0}", username);

                var axItems = client.find(context, qc);
                if (axItems.InventTable != null)
                {
                    foreach (var inventTable in axItems.InventTable)
                    {
                        Item item = new Item() { ItemId = inventTable.ItemId, Name = inventTable.NameAlias };
                        items.Add(item);
                    }
                }
            }
            /* //Mocking for AIF
            for (int i = 0; i < 10; i++) { Item item = new Item() { ItemId = (1000 + i + 1).ToString(), Name = (1000 + i +          1).ToString() + " - name" }; items.Add(item); }*/ return items;
        }
    }
}

To make the connection secure (since we are sending the authentications via plain text in the HTML headers, this service should ideally be secure)

In the next blog I will go through the Android app to consume this service

Dynamics Ax 2012 – AIF Import CSV File – Part 1: Consume Web service


I will start a series for AIF 2012 to use the Inbound port file adapter settings structured:

Part 1: Consume Web Service
Part 2: Create Item from File adapter
Part 3: Import CSV file with items Through AIF

The aim is to start from a web service to consume an AIF service, and then move that to a file system adapter. I will that take that a step further by adding a .Net transformation library so that we can import a CSV file to do the same. All throughout the process, there will be no change done within Dynamics Ax itself.

The service I am going to consume throughout the series is the InventItemService , and i will create an item.

With AX2012, we need to create a product and then release it. So for this demonstration, I will assume that the Products have been created, but have not been released yet.The AIF webservice InventItemService.create will do this for us.

Create and Consume web service:

I have found consuming AIF as web services easier than File adapter, which is why I start with this and then move this over to the File adapter.

Setting the AIF HTTP port to expose the InventItemService.Create:

Add InventItemService.create to the port

Once the Inbound port is activated, we can consume this in Visual studio. I shall create a Console application, and hard code the items to be created.

In Visual studio, add the service reference using the URI field of the Inbound Port.

static void Main(string[] args)
{
	//Initialise the Service
	using (LoonItemService.ItemServiceClient client = new LoonItemService.ItemServiceClient())
	{
		//Initizlise parameters used for the create method
		LoonItemService.CallContext callContext = new LoonItemService.CallContext() { Company = "ceu" };
		LoonItemService.AxdItem axdItem = new LoonItemService.AxdItem();

		LoonItemService.AxdEntity_InventTable inventTable = new LoonItemService.AxdEntity_InventTable();
		inventTable.ItemId = "SS001";
		inventTable.NameAlias = "SS001NAMEAlias";
		inventTable.Product = "SS001";

		List lstInventTable = new List();
		lstInventTable.Add(inventTable);
		axdItem.InventTable = lstInventTable.ToArray();

		//Call the AIF create method
		var result = client.create(callContext, axdItem);

		//Display the result
		foreach (var item in result)
		{
			foreach (var data in item.KeyData)
			{
				Console.WriteLine(string.Format("{0} : {1}", data.Field, data.Value));
			}
		}
	}
}

For a more detailed process on how to use the inventItemService.create look at this link: How to create an item using ItemServices and EcoResProductServices in AX 2012
On Running the code above, the Item is displayed in the Console, and if you check AX, the item SS001 is now a part of the Released products.

Item SS001 added to the released products

This is what we will replicate across to the File System adapter. in Part 2 and 3

Printing Pdf from .Net


I recently came across printing crystal reports, and they always were unreliable when it came to printing a huge number of those documents. Even though I implemented my own queue, and processed them one by one, they always tended to fail at System.Drawing.Printing.something
With generally the handle being invalid.

I had a look at Adobe’s own command line printing, but it always brought its windows up, and i could never manage to print in duplex mode, or change certain settings.

I can around a workaround with this, GhostView, and from the same creators comes GhostPrint
a tool called gsprint.exe has a pretty nifty way of telling what you can do, and set the printer, colour, duplex, landscape/portrait with arguments and made it very simple. the fact that this also has a pdf merging tool is fabulous.

The only issue i had was to determine if a document is to be printed in landscape or potrait.
But since i was creating the PDF’s from Crystal Reports, I could determine those details using the ReportDocument class.

gsprint is a part of gsview which can be found at
http://pages.cs.wisc.edu/~ghost/gsview/

It has a good documentation and pretty easy to follow.

If you do have any issues do post in a message

Crystal report COM Exception


If you get this error:
{System.Runtime.InteropServices.COMException (0x8002000B)}

You probably have issues with using parameters .
I had an issue when the report was changed…and the parameters were deleted.
Since there were no parameters defined in the report, the exception occoured (aprt from things going haywire and no one knew what was happening. Rumor also went that they were stripped off their permissions to print)