Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Building a Simple Reporting System with Unity

0.00/5 (No votes)
29 May 2014 1  
Make changement of reporting easier. Unity help us

Introduction

I'm in one week of sick leave. I'd the time to learn more about Unity. Here introduce a real world example for talking about how i used Unity - A reporting system where i tried to make our work easier than before.

First of all, what are the real needs in my reporting system?

  • Sending reports to clients at the pre-defined times.
  • Each client report could have its customized design and the customized data require.
  • Each report could be sent more than once per day.
  • Each report could be sent manually.
  • The pre-defined time of export of report should be able to be changed mannually.
  • A log system which traces if report is exported or not. If not, what happened and any exceptions?
Second is how to simplify this system when new more reports will be added in the future. Objectif is how to minimize our works when more reports should be added in the system.

Background

Developing such system is not difficult. Even an junior developer could finish this system in one week if there aren't too much different customized design. But along with more clients and more report types being added, C# project becomes more complicated to manage. We could potentially encounter the problems when we add new report into system :

  • Each time we add a new report, we add a new method in an existed class? No! If you don't want have a class which contains 10,000 lines code!
  • Each time we add a new report, we add a new class to export new report? Yeah, it's better than a new method. But do you need to change code from other part for defining the time, add client name?
  • Wherever you add your new reporting code, do you need to care about log system? If you're a newer of the team and you don't understand well how this system works?
  • Could you write in a seperate project and test your code, then use only 10 seconds for immigrating perfectly into the reporting system?

For avoiding potential problems mentionned above, what we need is creating a system that once the system architect is created :

  1. If we have a new client, we write our code wherever, we test it.
  2. We copy this new class into a sub directory of our project. (Don't touch anywhere else of this project!)
  3. We add only one attribute onto the class for defining time. (You work always only in this class, don't touche anywhere else of this project)
  4. Log? Don't care about that. Something in the project already did it for you
  5. We rebuild project and run it. It works with your new reporting!

Apparently, the advantage is everyone can add a new report class into the system without knowing how system works, without knowing how timer system works and without knowing how log system works.

Using the code

Build a such system, we have several basic missions to do :

  • System loads automatically report classes under the sub directory "Reporting" (or we could say under the namespace "xxxx.Reporting"). Then registered them into UnityContainer
  • System should take care of log system once any reporting instance is invoked. Using interception of Unity.
  • How we should do if some of methods in the class don't want to be logged? CustomAttribute or IAttributeHandler of Unity
  • System loads automatically reportings default exporting time. Custom attributes and Register an instance of time manager into UnityContainer

Loading reports classes into unity container

Loading classes is often a job of Reflection. But instead of loading assemblies, the important thing is registering what we loaded into a unity container.

var classes = AllClasses.FromLoadedAssemblies().Where(t => t.Namespace == "ReportingWithUnity.Reporting" && t.IsClass && t.GetInterfaces().Contains(typeof(IReporting)));

foreach (Type type in classes)
{
    _container.RegisterType(typeof(IReporting),
                            type,
                            type.FullName,
                            new InterceptionBehavior<PolicyInjectionBehavior>(),
                            new InterceptionBehavior<LoggingInterceptionBehavior>(),
                            new Interceptor<InterfaceInterceptor>());

    RegisterDefaultClocks(type);
} 

The code above used AllClasses to load all report classes which inherit IReporting interface. IReporting interface contains a GenerateReport() method for used by system. All reporting classes should inherite this interface for that they could be recognized by reporting system. In another word, reporting system will only run classes which inherited IReporting interface and those which is under Namespace ReportingWithUnity.Reporting.

Log system

When the container register all reporting classes, it register them with an InterceptionBehavior<LoggingInterceptionBehavior>. This is for helping system log the methods of reporting classes when the methods are invoked. For more information of Interception using Unity, please go visite office site http://msdn.microsoft.com/en-us/library/dn178466(v=pandp.30).aspx

Briefly, this interception behavior allowed log functions run before and after an execution of a method. It helps separate log system and report classes. Even we don't write any log information in our customized reporting class, system will also trace the information.

The code below shows Invoke method defined in LoggingInterceptionBehavior.

        public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
        {
            // Before invoking the method on the original target.
            if(!input.InvocationContext.ContainsKey("IfLog"))
            {
                WriteLog(string.Format("Invoking method {0} in Class {1}", input.MethodBase, input.Target.ToString()));
            }

            Stopwatch sw = new Stopwatch();
            sw.Start();
            var result = getNext()(input, getNext);
            sw.Stop();

            // After invoking the method on the original target.
            if (result.Exception != null)
                WriteLog(string.Format("Method {0} threw an Exception {1}",
                                       input.MethodBase, result.Exception.Message));
            else if (!input.InvocationContext.ContainsKey("IfLog"))
            {
                WriteLog(string.Format("Method {0} in Class {1} returned {2} ; Time used {3}",
                        input.MethodBase,
                        input.Target.ToString(),
                        result.ReturnValue ?? "",
                        sw.Elapsed));
            }

            return result;  
        } 

In the code above, getNext()(input, getNext) serves for jump to the next interception behavior. If another behavior existed after the current, the next behavior will execute. If no other behaviors existed, the method of GenerateReport will be invoked. The sequence of executions between interception behaviors and target method is : <Behavior1 <Behavior2 <Behavior3 <Target Method> Behavior3> Behavior2> Behavior1>. The interception behaviors are registered in a pipeline.

If we don't want to log all method of report classes registered in the unity container, what could we do?

I tried to use a custom attribute, and analyse if this attribute hooked on the method which is invoked during the execution of LoggingInterceptionBehavior. Unfortunately, all classes entered into the LoggingInterceptionBehavior are type of IReporting. I didn't be able to catch any custom attribute.

So i've to use another way to realize what i wanted : An attribute written on the method of reporting classes distinguish if this is a method need to be logged. This way is realized by using InterceptionBehavior<PolicyInjectionBehavior>. This behavior makes use of policy definitions to insert handlers into a pipeline when a client calls an object instantiated by the container. What is our handler?

    class IgnoreLogHandler : ICallHandler
    {
        public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
        {
            input.InvocationContext.Add("IfLog", false);
            var result = getNext().Invoke(input, getNext);
            return result;
        }

        public int Order
        {
            get;
            set;
        }
    } 

How could we invoke this handler? We need to a custom attribute but inherited from Unity's interception handler attribute.

    public class IgnoreLogAttribute : HandlerAttribute
    {
        private readonly int _order;

        public IgnoreLogAttribute(int order = 1)
        {
            _order = order;
        }

        public override ICallHandler CreateHandler(IUnityContainer container)
        {
            return new IgnoreLogHandler() { Order = _order };
        }
    }  

We need to use this attribute to invoke PolyciInjectionBehavior for insert IgnoreLogHandler behavior into the interception pipeline. If we add this attribute on the head of a method, this method will not be logged. Here is the code of use IgnoreLogAttribute in a reporting class:

    public class Elgoog : IReporting
    {
        public Elgoog()
        {

        }

        [IgnoreLog]
        public bool GenerateReport()
        {
            SingletonLogger.Instance.AddLog("I'm Elgoog, inverse is Google");
            return true;
        }
    } 

From now on, we could add a new report class into the system without knowing how system works and without knowing how log system works.

Pre-defined timer and loose-couple between timer setting and reporting classes

Everything's done? No, not really. In the system, generating reports with the flexible time is very important. Our object of system is that we just modify our report class and do not touch other parts of the system. It means that we could only define our default time of generating report in our reporting class.

If we have an user interface, we want to change timer settings, it's none of buiseness of our reporting class, this is the job of system. So at beginning of the luanch of system, system load only default timer settings from reporting classes. And system register this timer setting into another part. If we want to modify timer settings during the execution of program, we change timer settins at this "another" part.

We just need to another custom attribute. This doesn't really have relation with unity container, but TimerManager is registered like an instance in the unity container.

    [AttributeUsage(AttributeTargets.Class)]
    public class ReportClockAttribute : Attribute
    {

        /// <summary>
        /// 
        /// </summary>
        /// <param name="dateTimes">Format like 06:23:12</param>
        public ReportClockAttribute(params string[] dateTimes)
        {
            foreach (var dt in dateTimes)
            {
                if (dt.Length != 8)
                    throw new ArgumentException("Format is not good. 00:01:12 is a good format");
            }
        }
    } 

The code below shows how we use this attribute in the reporting classes.

    [ReportClock("12:15:35", "12:15:40", "12:15:45")]
    public class MacrohardReporting : IReporting
    {
        public MacrohardReporting()
        {
        }

        public bool GenerateReport()
        {
            SingletonLogger.Instance.AddLog("I'm Macrohard, inverse is Microsoft");
            return true;
        }
    } 

Until now, everything difficult's done.

Points of Interest

A real world example of using Unity. I didn't use too much techniques of Unity. But it's still cool for me. Because i didn't use often Unity in my projects. Because sometimes Unity reduce the performance of the programme. I work for Financial company, most of my project need to a very high performance program. I could not use Unity functions anywhere, it's cool to find a good way to practice using Unity. Btw, the problems mentionned at beginning of this articles were the real problem i met.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here