Category Archives: Visual studio

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.

User Group and access manager


I was faced with the issue to create a user management in one of my applications, which allowed the user to only gain access to certain features of the Application.

Inititally, all the business required was to set up a user level, and then according to that level they would want the program to be hardcoded to allow access to users with atleast that access level..

Sounded a bit abnormal as the number of users was not small..it was medium and potential to grow, especially when the application had a possibility to be setup for other similar organizations.

So I then created this user group management, when you could create groups, and assign which features or programs the group had access to. Users could then be alloted to those groups. However certain users might be required to have access to some indiviadul features only, so that was also covered. I created this and applied it to a Windows forms, using 3rd party controls (Devexpress) and with its ribbon tabs, i could only show those tabs and those feature buttons to which the user had access to.

I had already started this from home, and created the management console in pure windows forms. Not as nifty as the 3rd party controls, but then serves the purpose in a similar way.
I created it as a DLL with its own database, however I was challenged with creating the strongly typed database to have access only to the database defined in the application refrencing this library.

The only catch to it is that the User’s ID has to be in an integer format. Looking at this tool, It can be integrated into the management features, and can be used straigt from the box (just need to setup the database).

I have uploaded this to Codeplex and can be found at:
http://www.codeplex.com/AlterGearUserGrpMgmt

And yes I have a Name : AlterGear

This is filed under the LGPL Licence, so feel free to change anything in it and crete your own library from it.

At the moment, most of the code is within the form classes, So my next level to this is to split assignments into pure cs files so that the library can be used to build your own UI, rather than relying on the one provided within it. (Its not that bad)

Visual studio handy Shortcuts


Considering your code and you want to comment a block of code using your keyboard!!
Well…no worries…
Select the code you want to comment
Press Ctrl + K + C
to Uncomment the code
Press Ctrl + K + U

Unfortunately unlike eclipse it dosent use the same shortcut to comment and uncomment the code.

Another good editing shortcut:
Have you ever tried to delete a line, and then started to use backspaces or del keys to format the code?
Well…if you do want to remove the line completely….even without selecting the whole line
press Shift + Del and the line will dissapear without affecting anything else

Collapse all Regions / Blocks
Ctrl + M + O

some good shortcuts given at http://visualstudiohacks.com/navtricks