Auditing Part 4 - Quit being a slob

Before I start, I'd like to think my colleagues Alex Robson and Craig Israel for helping with the design. It'd have ended up containing 200% more suck without their assistance. Also, the finished product is part of Nvigorate, so whenever Alex updates that project, it'll be there. What is currently up there however is very out of date. It's improved greatly.

Welcome to Part 4, the last in my auditing series. Here's the previous ones, you drunken slob, Mr. I'm-Too-Lazy-To-Click-In-The-Archive:

Everything is almost wrapped up! Our list-o-crap has now turn into a sentence-o-crap, not even worthy of a list anymore! What is that sentence, you ask? Why, it's coming up! Pay attention! Here it comes! Right now! Firing our auditor is still noisy, as it takes place in our functions that are handling unrelated tasks. So let's clean it up!

I can't carry it for you Mr. Frodo, but I can carry you

Auditing is more of something that the function should support, but it'd be nice to not have to muddle up our business code with it, even if it is just one line.

So what's a way we could do this? Why, attributes of course! If you're not familiar with adding attributes to a function/class, here's the quick version. If you know this, tough. I'm not going to identify when I'm not talking about it, so you have to wade through these words anyways. Deal.

First, an attribute is represented (in C# that is - noone cares what it looks like in VB) by square brackets above your function/class. Like this:

   1: [MyClassAttribute]
   2: public MyClass
   3: {
   4:     [MyFuncAttribute]
   5:     public void MyFunc()
   6:     {
   7:         /* do stuff */
   8:     }
   9: }

See? Easy. Applying attributes is called "decorating" your class or function. So, we're going to go about creating some attributes that will fire up our AuditManager when our function is accessed. This is known as Aspect Oriented Programming, or AOP. But how do we do this? We're going to use an AOP framework. There are several out there, but Nvigorate already makes use of PostSharp, so we're going to use that one. Why PostSharp? Most AOP frameworks have the attributes being evaluated at run-time. This can, unfortunately, lead to some serious run-time speed hits. PostSharp, however, is run pre-compilation, and actually injects your code in the appropriate places. This means no run-time overhead, but more setup on the developers side. To do this, it has to be physically installed on the machine doing the compilation. Also, any project that makes use of the attributes (or aspect, as I'll refer to it from here on), even if not directly using PostSharp libraries, will need a reference to the PostSharp DLLs from the GAC.

So, you've installed PostSharp, and added a reference to the DLLs in your consuming project. Let the fun begin!

Insert witty subtitle here

We'll start with the basic information our aspect needs - the action, the username, the object to be audited, and the date. Aka, the same 4 things we've been looking at this entire series. One of the restrictions though about making this an aspect, is that they can't be generic! Oh noes! Well, the good news is we can still fire our AuditManager, using Reflection, and get our generic call! Yay! There's another issue too, but we took care of it when we wrote AuditManager. Remember how I had us make TWO TrackAction calls? We ended up with TrackAction for instances, and TrackActionEnumerable for collections? Well, if we hadn't done that, our reflection calls would've actually failed, as it wouldn't have been able to distinguish between a generic type and a collection of a generic type. But we're so smart, we knew that ahead of time, so we're good. What we'll do is check if our information object implements IEnumerable or not, then we'll know which one to call.

Now, I don't feel like writing raw Reflection code. Yet again, I'll be using the Reflector portion of Nvigorate to handle that for me. One of things we'll have to pass to the call is a collection of arguments. Let's think about how we'll retrieve each of those:

  • Action - well, this is just an enum type, so we can pass that right in the attribute itself. We'll add it to the constructor, then save it in a private field.
  • Date - we'll just use DateTime.Now, of course. If the auditors themselves wish to use a different timestamp, they can.
  • Information - I'm going to force a restriction here. I'm saying that consumers have to have this passed in on the function call. You're welcome to change this behavior if you desire. But I think separating your DAL from the rest of your code will end up with a cleaner design, and then this won't be an issue!
  • Username - hmmm....this one is tricky. And gee, it's like I did them out of order for a reason! Guess what, I did, suckers! That way I can make this it's own topic. Let's discuss it more.

What's in a name?

Ah, getting the username. And the system username is useless. We need to know the user who's logged in and making the call. There's a LOT of options here. Maybe it's the current user principal. That could be in a windows app, or in a web app. Maybe it's passed along the function call. Maybe it's in your request header.

The point is, we have NO idea where it could be. And trying to define every possibility is asking for failure. This becomes pretty obvious pretty quick that it's an area we need to leave open for the consuming application to define.

We should make a base class for our aspect then. Since it'll be incomplete, lacking an actual way to get the user name, it should be abstract too. "AuditAspectBase" sounds good. We'll define an abstract function then, let's call it "GetUser", that developers will have to implement to use our system. Now, we don't have to care! Yay!

The last thing our class needs to do is make sure that our concrete aspect definitions can get to the information it needs. Lucky enough, PostSharp allows us to grab all kinds of good information about the function call. From it, we grab the instance the function was called on, the parameters the function has, and the values of those. There's more too, but those are the important ones. All of this is passed through a "MethodExecutionEventArgs" object, which is defined in the PostSharp assembly. In the interest of making the consuming developer know as little about PostSharp as possible, we're going to take out the important bits, and store them in our base aspect for easier consumption.

Give me bits, dammit!

Alright, alright, alright. I've been making with lots of the werdz and none of the codez. The good news is, there's nothing left to discuss! Here's our AuditAspectBase:

   1: [Serializable]
   2: public abstract class AuditAspectBase : OnMethodBoundaryAspect
   3: {
   4:     private AuditAction _action;
   5:     private string _functionArgumentName;
   6:     private object _information;
   8:     protected object Instance { get; set; }
   9:     protected object[] FunctionArguments { get; set; }
  10:     protected ParameterInfo[] FunctionParameters { get; set; }
  12:     protected AuditAspectBase(AuditAction action, string functionArgumentName)
  13:     {
  14:         _action = action;
  15:         _functionArgumentName = functionArgumentName;
  16:     }
  18:     public sealed override void OnEntry(MethodExecutionEventArgs eventArgs)
  19:     {
  20:         Instance = eventArgs.Instance;
  21:         FunctionArguments = eventArgs.GetReadOnlyArgumentArray();
  22:         FunctionParameters = eventArgs.Method.GetParameters();
  24:         _information = GetArgumentValueByName(_functionArgumentName);
  26:         CallAudit();
  27:     }
  29:     public object GetArgumentValueByName(string name)
  30:     {
  31:         foreach (ParameterInfo info in FunctionParameters)
  32:         {
  33:             if (info.Name == name)
  34:             {
  35:                 return FunctionArguments[info.Position];
  36:             }
  37:         }
  39:         return null;
  40:     }
  43:     private void CallAudit()
  44:     {
  45:         object[] args = new object[]
  46:                             {   _action,
  47:                                 GetUser(),
  48:                                 _information,
  49:                                 DateTime.Now
  50:                             };
  52:         if(_information is IEnumerable)
  53:         {
  54:             Reflector.CallMethod(typeof(AuditManager), "TrackActionEnumerable", args, _information.GetType().GetElementType());
  55:         }
  56:         else
  57:         {
  58:             Reflector.CallMethod(typeof(AuditManager), "TrackAction", args, _information.GetType());
  59:         }
  61:     }
  63:     public abstract string GetUser();
  64: }

But I'm such a nice guy, that even though you're an ungrateful jerk, I'm going to go through this with you anyways.

First you'll notice it's marked as Serializable. This is required by PostSharp, so do it. Next you'll notice we inherit from "OnMethodBoundaryAspect". This is a PostSharp class (Laos specifically) that will help make things simpler for us. It'll handle the weaving of the aspect for us. If you don't know what that means, don't worry about it. But this specific one, will allow us to provide "hooks" into our function calls. It's going to give us functions we can override, like "OnEntry", "OnExit", etc. Feel free to check the class out. But the one we're going to focus on is "OnEntry". This does mean there is some room here to get fancy - perhaps you're only interested in auditing when the function exits, or want to make sure your audit doesn't write if an exception occurs. Things like this, and more, are possible. But, our implementation is only going to make use of OnEntry. We're getting a smidge ahead of ourselves though.

What do we need in our constructor? Well, from our previous list, we only need two things given to us - the action, and the function parameter that will have our object we're auditing. Using the information PostSharp will give us, we really only need the parameter name. So, our constructor will require those two, and we'll save them in private fields.

Next is our OnEntry method. We'll override the base one, and I'm going to seal it because I don't want it screwed with. We're going to get the calling instance, the arguments, and the parameter names from the event args, and store them in public properties. Our consuming aspects might need this to get the user name. We'll also need to get the object that's being audited. Then, we can call our auditors. We're going to break both of those into their own functions.

First is GetArgumentValueByName. Pretty obvious what it does. It'll loop through our parameter info, and once it finds a match, it'll return the argument itself. If it can't find a match, it'll return null. Because our attributes can't be generic, it can only return an "object". This function is pretty useful though, so lets make it public. It'll come in handy again soon.

The last piece is actually firing up our audit manager, in a function called "CallAudit". We want to control when this happens, so we'll make it private. The first thing we need to do, is create our argument array. Both of the AuditManager's TrackAction calls take the same 4 parameters in the same order. So, we'll grab the action, call our abstract GetUser function that our subclass will have defined, grab the object we're auditing, and our time stamp.

Next we'll check if our object is a collection, by seeing if it's of type IEnumerable. If it is, we'll use Reflector (part of Nvigorate, for those of you not following along) to call TrackActionEnumerable. If not, we'll just call the regular TrackAction. Reflector.CallMethod() has several overloads, including ones to call generic functions. First we tell it the type of our class we want to call (AuditManager), then the function name, then we pass in our arguments. If it's our collection, we need to get the type of collection, then the type of the elements it holds. Else, we can just get the type.

Last thing there is our abstract "GetUser" function. All we need from it is a string, which would be the username.

2 + 2 = 4

Time to add it all up, and see what we get! We're done now with our base aspect, so how do we implement and consume it? Easy. Let's make a couple of simple ones that are liable to have lots of reuse potential. Well, one obvious one is using this in an app. All we need to do this is:

   1: [Serializable]
   2: public class AspNetAuditAspect : AuditAspectBase
   3: {
   4:     public AspNetAuditAspect(AuditAction action, string functionArgumentName) : base(action, functionArgumentName)
   5:     {
   6:     }
   8:     public override string GetUser()
   9:     {
  10:         return HttpContext.Current.User.Identity.Name;
  11:     }
  12: }

Doesn't get much simpler. To consume it then, all we do is:

   1: [AspNetAuditAspect(AuditAction.Update, "data")]
   2: public void SaveMyData(MyFirstType data)
   3: {
   4:     MyDataLayer.WriteData(data);
   5: }
   7: [AspNetAuditAspect(AuditAction.Update, "data")]
   8: public void SaveMyDataCollection(List<MyFirstType> data)
   9: {
  10:     MyDataLayer.WriteDataCollection(data);
  11: }

Our aspect, and audit manager, and auditors themselves All handle the rest! Wether it's a collection or an instance. That's pretty sweet.

Here's another good aspect you might want to make use of (and yes, both of these will be included in Nvigorate when it gets updated):

   1: [Serializable]
   2:  public class FunctionArgumentAuditAspect : AuditAspectBase
   3:  {
   4:      private string UserNameArgument;
   6:      public FunctionArgumentAuditAspect(AuditAction action, string functionArgumentName, string userNameArgument)
   7:          : base(action, functionArgumentName)
   8:      {
   9:          UserNameArgument = userNameArgument;
  10:      }
  12:      public override string GetUser()
  13:      {
  14:          return GetArgumentValueByName(UserNameArgument).ToString();
  15:      }
  16:  }

This will handle the case where the user name is in the function call itself. Perhaps it's a webservice function or some such. So, that'd obviously need to be passed in on the constructor for our aspect. Then, using our GetArgumentValueByName function we defined in our base aspect, retrieving that value is simple. All we need to do then, to consume it this time, is this:

   1: [FunctionArgumentAuditAspect(AuditAction.Update, "data", "username")]
   2: public void SaveMyDataService(string username, MyFirstType data)
   3: {
   4:     MyDataLayer.WriteData(data);
   5: }

Now, we've managed to get the code noise out of our function, and auditing just "happens" for us. Our code is cleaner, better organized, and more flexible. This will make it easier to implement, easier to maintain, and easier to enhance.

This is the end, my only friend, the end

My favorite part is when I was done writing all this. Hopefully you can now go forth and audit like a man! Let's see that smile!

No more tears!

That'll do horse. That'll do.

Auditing Part 3 - Because you can't get a build error with config files

Before I start, I'd like to think my colleagues Alex Robson and Craig Israel for helping with the design. It'd have ended up containing 200% more suck without their assistance. Also, the finished product is part of Nvigorate, so whenever Alex updates that project, it'll be there. What is currently up there however is very out of date. It's improved greatly.

One last note - today's blog post was created with:

All religions, all the time!

Harnessing this amazing new technology allows me to create content that references all religions equally, so noone feels left out or offended! Onto the post!

If you are religious, I highly recommend you call your preacher/pastor/rabbi/spongebob/etc, because Hell may have just frozen over!!!

That's right! I actually made a third part in a series. It's crazy. Here's links to the first two parts, in case you suck and didn't know about my awesome blog before now:

That's also right, that's a bulleted list mother-fucker. And that's also also right, I dropped the f-bomb on my blog. Deal with it (unless you're my boss, and in that case, I mean, c''s not like you're honestly surprised are you? and second of all, this isn't even my blog! It's just a coincidence/result-of-a-hacker/internet-explorer-bug!)

So what's our next step? Once again, let's break out the Bag O' Crap and see:

  • What if we want multiple auditors for a single type? We'll have to add more and more TrackAction calls.
  • Firing our auditor - it's in the middle of our functions. That adds a bit of code noise, and can make it harder to track down.

Ah yes. That top one. This is gonna be a long post. I was in a good mood until I realized I had to go through this with you. I hate you so much. It's not my fault you're not me and don't remember doing all this already.


So, currently, you'd have to explicitly call a concrete implementation for each of your auditors. So that's dumping more code in your existing code, and if you need to fire multiple auditors, that's multiple calls. It's going to get messy, and you're going to have to remember when to call which auditors, and make sure you didn't forget any, and when you add a new auditor go and plaster it everywhere, blah blah blah.

What we have here is a single set of logic. We know the relationship of auditors to the objects we're editing. We need to store all this mess in a single place. Let's call this place our "AuditManager" ("OMG THATS THE CLASS NAME IT'S GONNA BE I CAN JUST FEEL IT"). Now, we could have this so-called "AuditManager" ("WOAH IT'S USED TWICE IT'S SO GONNA BE THE CLASS NAME") contain all the logic explicitly. Now when we add auditors, we're restricted to changing our code in 1 place. But how about we just make it so we don't have to edit any code at all?



Seriously, quit it with the caps, jack ass. If you weren't so busy being a jack ass as to not read the title, you'd know where this is going. Go ahead, read it. I'll wait.


Yeah. That's right, speak in a small font. You should be embarrassed. We're gonna put all that stuff in config files, and let our AuditManager (yes it's the class name shut up for god/jesus/buddha/spongebob/etc's sake) determine which auditors to fire up.

It's like code, but instead of semi-colons you use less-than and greater-than symbols

If you seriously believe this heading, you should stop right now. Maybe go to a "your money buys you pre-built opinions to mimic!" platform.

Now noone likes over-abundant XML, so we're gonna try to make this as simple as possible, and only focus on must-have features. Due to the complexity of the upcoming pieces, we're gonna end with the full feature-set. This is *not* the order it was written in - it expanded and changed throughout development. Unlike in Part 1, I think it'd be more confusing to show the natural development of this piece. We'd constantly keep jumping around. And in part 1  it was a design decision, and this is more of a feature set. So, I think it's less crucial, and if you disagree, go write your own damn blog talking about how stupid I am.

And just in case you're unfamiliar with config files, we're talking about embedding this in our respective app.config or web.config files (depending on what type of solution you are building).

First, we'll need a root containing tag. We'll call it "AuditorConfiguration" (wow, it's like the name tells you what it means! How novel!). Inside it, what do we need? Well, our auditor definitions. Well call this block "Auditors". And then we'll list each "Auditor" inside its separately. Now what do we need to know about each auditor? Well, we will need to know the type of the auditor itself (so we can fire it up), as well as for what type it should handle.

Let's discuss that for a second. Done. LAWL, GET IT I LITERALLY WAITED A SECOND.

Sorry. I pulled a you.

Anywhos, It's going to do more than just "handle a single type". What if you want ALL your business objects to be audited, regardless of anything evarz, but maybe here and there you want add something extra? And maybe you have a case or two where you don't want ANY other auditors to handle it? So, this is more than just a single type. If you put a base class in here, that all your business objects inherit from, they'll all get audited. We'll make sure our AuditManager checks for inheritance. For the other case, we'll need to be able to explicitly state in our config file that it's an exclusive auditor...why let's make the attribute "exclusive"! This "naming crap what it means" is neat!

Now, let's say you have all your auditors in a single assembly, but your project is tiered. Maybe sometimes you need to audit things on the front end of the system, but other times on the back end. Depending on where in your technology stack you are you will save your audits differently. You don't want to have to write multiple auditors that do the same thing. So, we're going to leave this completely undefined, but we're going to support the ability to embed a "config" section for your auditor. You'll have different config files depending on where your auditor lives, and each one can specify how that data gets saved. How you interpret that in your auditor and want to use that is up to you. I recommend going with something that doesn't suck.

However, we'll need to make a quick change to support this. We should add something to our base class, or we won't have a way to know how to pass the custom config in! All we need to add to our AuditorBase<T> is:

   1: public abstract void LoadConfiguration(XElement xml);

Making it abstract will force our consumers to provide a definition. This is done to hopefully force users to think about how they want to save their audits, and come up with something flexible. You could make it virtual if you don't want to enforce that. If you're not familiar with defining your own config schema and options, you will be by the time we're done today, so you can do so easily. Or you can just parse the XML that will get passed. I'd like to get more in-depth, but any solution is really going to be more domain-specific, and what this system does (or at least it's goal) is provide a flexible way of auditing regardless of your domain.

So, it sounds like our configuration section is coming along nicely. Let's take a peek at what it looks like so far:

   1: <AuditorConfiguration>
   2:     <Auditors>
   3:       <Auditor type="MySolution.Auditors.MyFirstTypeAuditor, MySolution.Auditors" 
   4:                handlesType="MySolution.BusinessObjects.MyFirstType, MySolution.BusinessObjects"
   5:                exclusive="true">
   6:         <Config>
   7:           [custom config shizzle here if needed]
   8:         </Config>
   9:       </Auditor>
  10:       <Auditor type="MySolution.Auditors.MyGenricAuditor`1, MySolution.Auditors" 
  11:                handlesType="MySolution.BusinessObjects.MyBaseType, MySolution.BusinessObjects">
  12:         <Config>
  13:           [custom config shizzle here if needed]
  14:         </Config>
  15:       </Auditor>
  16:     </Auditors>
  17:   </AuditorConfiguration>

Time for another round of "explain the made up crap" - This is saying you have a solution. Everything in this solution will start with the "MySolution" namespace. There are two projects in this solution that this is concerned with - "MySolution.BusinessObjects" and "MySolution.Auditors". Those are your assembly names as well. All your business objects inherit from "MyBaseType". So, for naming our types, we give the full namespace name for our class, then a comma, then the assembly name where they live. This is called a fully qualified domain name, usually abbreviated as FQDN. But you already knew that. But the window cleaner behind you in your Skyscraper of Power has been reading this article with you, and he was confused. I'm always helping out the little people.

"MyFirstTypeAuditor" hasn't changed since we last saw it in Part 2, except for defining LoadConfiguration. But what's this "MyGenericAuditor`1" you ask? More specifically, what the hell is that "`1"? That's how you define generic types in a config file. Here's our class definition for "MyGenericAuditor":

   1: public class MyGeneralAuditor<T> : AuditorBase<T> where T : MyBaseType
   2: {
   3:     public override void TrackAction(AuditAction action, string user, T information, DateTime? when)
   4:     {
   5:         MyDataLayer.WriteData(new AuditLog()
   6:         {
   7:             Action = action,
   8:             User = user,
   9:             Information = SerializeObject(information),
  10:             Date = when ?? DateTime.Now
  11:          });
  12:     }
  14:     public override void LoadConfiguration(XElement xml) {}
  15: }

Notice that this auditor is generic itself! Crazy sauce! We've specified, though, that it only works on types that come from our base type, cleverly called "MyBaseType". Also notice that our magical "SerializeObject()" function has made a return. So, since the definition for this class is generic, and you can't put "<" or ">" in an attribute definition for XML, we do that with ~1. Note that if our class definition had multiple generic types (say "MyGenericSomething<T, P, S>"), it would be defined in a config file like "MyGenericSomething~1~2~3". But, our AuditManager that we'll write soon won't be supporting that, so it's irrelevant to us at the moment. Just trying to be nice to those who don't know. Geez, lay off.

There's another feature I think we should support in our config too though. Maybe we have some business objects we DON'T want to audit? A very possible realistic use of this is putting it at the entry points to your data layer. But your AuditLog class will go through there too! Hello, infinite loop, our old friend. Or maybe you have notifications as well that you log to a database, but don't care about auditing. So, we need to tell our AuditManager to ignore some stuff. Let's give it two ways to do that - by types, or by namespace. That'd expand our config block to look like:

   1: <AuditorConfiguration>
   2:     <Auditors>
   3:         <Auditor type="MySolution.Auditors.MyFirstTypeAuditor, MySolution.Auditors"
   4:                  handlesType="MySolution.BusinessObjects.MyFirstType, MySolution.BusinessObjects"
   5:                  exclusive="true">
   6:             <Config>
   7:                 [custom config shizzle here if needed]
   8:             </Config>
   9:         </Auditor>
  10:         <Auditor type="MySolution.Auditors.MyGenricAuditor`1, MySolution.Auditors"
  11:                  handlesType="MySolution.BusinessObjects.MyBaseType, MySolution.BusinessObjects"> 
  12:             <Config>
  13:               [custom config shizzle here if needed]
  14:             </Config>
  15:         </Auditor>
  16:     </Auditors>
  17:     <IgnoreNamespaces>
  18:         <IgnoreNamespace namespace="MySolution.Notifications" />
  19:     </IgnoreNamespaces>
  20:     <IgnoreTypes>
  21:         <IgnoreType type="MySolution.BusinessObjects.AuditLog, MySolution.BusinessObjects" />
  22:     </IgnoreTypes>
  23: </AuditorConfiguration>

Alrighty then. That looks pretty good. I think we're done here.

"But what about--" nope. Not happening. We're done. I'm tired of your ruining my life.


Go colon-right-parenthesis yourself somewhere else.

Microsoft WANTS you to put crap in a config file

Swear to god/jesus/buddha/spongebob/etc they do. Why? Because they give you some classes that keep you from having to parse all the XML yourself. It'll turn your XML into objects, which is spiffy. Easy to use too. At each node of the configuration block, we define a new class that defines what attributes and children it can have. I'm going to go through this pretty quickly. It's not difficult, and MSDN can explain stuff for you in more detail if you need it. Everything that you need is contained in the "System.Configuration" namespace. Add a reference to the dll in your project if you don't already have one.

We'll start at the top level, and keep defining our classes as we go down. If you're playing along at home, doing it in this order is kinda bass-ackwards, since we'll have not-yet-defined class names. But it's easier to explain this way, so deal.

The first block is the AuditorConfiguration. It's defined as such:

   1: public class AuditConfigurationSection : ConfigurationSection
   2:     {
   3:         [ConfigurationProperty("Auditors")]
   4:         public AuditorCollection Auditors
   5:         {
   6:             get { return ((AuditorCollection)(base["Auditors"])); }
   7:         }
   9:         [ConfigurationProperty("IgnoreTypes")]
  10:         public IgnoreTypesCollection IgnoreTypes
  11:         {
  12:             get { return ((IgnoreTypesCollection)(base["IgnoreTypes"])); }
  13:         }
  15:         [ConfigurationProperty("IgnoreNamespaces")]
  16:         public IgnoreNamespacesCollection IgnoreNamespaces
  17:         {
  18:             get { return ((IgnoreNamespacesCollection)(base["IgnoreNamespaces"])); }
  19:         }
  20:     }

So, your root section needs to inherit from "ConfigurationSection". Then, each of your properties in this class that are represented in the config file, need to have the attirbute "ConfigurationProperty", which takes 1 parameter - what it's named in the config itself. Each of our sections from our top level contain multiples, so that's why they're each named "Collection". We don't want to set them in our client code, just read what's there, so we're only going to define a getter (and no setters). To retrieve them, we call "base" with an string index, that is the same as the parameter you passed to the "ConfigurationProperty". Then you'll want to cast that to your property type (in our case, our various collection classes).

Let's move to our "AuditorCollection" class then. It looks like this:

   1: [ConfigurationCollection(typeof(Auditor), AddItemName = "Auditor")]
   2:     public class AuditorCollection : ConfigurationElementCollection
   3:     {
   4:         protected override ConfigurationElement CreateNewElement()
   5:         {
   6:             return new Auditor();
   7:         }
   9:         protected override object GetElementKey(ConfigurationElement element)
  10:         {
  11:             return ((Auditor)(element)).TypeName;
  12:         }
  13:     }

Since this is a collection, you inherit from "ConfigurationElementCollection". You decorate this class with a "ConfigurationCollection" attribute, which takes one parameter - the type of of the actual configuration element it contains (which we'll name Auditor to match our XML). We're also going to specify what tag name it should look for to add new items, which is "Auditor" again. We do that with "AddItemName" property.

"ConfigurationElementCollection" also requires us to override two functions. The first is "CreateNewElement", and all we do there is return our "Auditor" configuration element class that we haven't defined yet. The other is "GetElementKey". We know our elements are all of type "Auditor" in this collection, so we'll cast it, and grab a property that we'll treat as the key and return it. In our case, it'll be the name of the type of auditor it represents.

Lastly, lets define our "Auditor" element. Here are teh coedz:

   1: public class Auditor : ConfigurationElement
   2:     {
   3:         private XElement _configurationXml = new XElement("blank");
   5:         [ConfigurationProperty("type", DefaultValue = "", IsKey = true, IsRequired = true)]
   6:         public string TypeName
   7:         {
   8:             get
   9:             {
  10:                 return (string)(base["type"]);
  11:             }
  12:             set
  13:             {
  14:                 base["type"] = value;
  15:             }
  16:         }
  18:         [ConfigurationProperty("handlesType", IsRequired = true)]
  19:         public string HandlesType
  20:         {
  21:             get
  22:             {
  23:                 return (string)(base["handlesType"]);
  24:             }
  25:             set
  26:             {
  27:                 base["handlesType"] = value;
  28:             }
  29:         }
  31:         [ConfigurationProperty("exclusive", IsRequired = false)]
  32:         public bool Exclusive
  33:         {
  34:             get
  35:             {
  36:                 return (bool)(base["exclusive"]);
  37:             }
  38:             set
  39:             {
  40:                 base["exclusive"] = value;
  41:             }
  42:         }
  44:         public Type AuditorType
  45:         {
  46:             get
  47:             {
  48:                 return Reflector.GetType(TypeName);
  49:             }
  50:         }
  52:         public XElement ConfigurationXml
  53:         {
  54:             get
  55:             {
  56:                 return _configurationXml;
  57:             }
  58:         }
  60:         protected override bool OnDeserializeUnrecognizedElement(string elementName, System.Xml.XmlReader reader)
  61:         {
  62:             _configurationXml = (XElement)XElement.ReadFrom(reader);
  64:             return true;
  65:         }
  66:     }

Our single element inherits from "ConfigurationElement". We have a private field for that user-defined config block. We tag our properties that are attributes in our XML properly, marking key and required as needed. Each one exposes a get and a set, which just go against the base collection. We also made a Type property that uses Reflector. Reflector is a part of Nvigorate, and is one of it's most useful features. Reflector simplifies all your reflection calls, as you can see.

The last thing we do is override "OnDeserializeUnrecognizedElement". This will be raised whenever that user-defined config block is hit. So, we take that, and store it in our field.

We repeat this for IgnoreTypes and IgnoreNamespaces. I'll show them here for you, but they both follow the same setup as AuditorCollection and Auditor, except simpler, so I'm not going to explain them.

   1: [ConfigurationCollection(typeof(IgnoreType), AddItemName = "IgnoreType")]
   2:     public class IgnoreTypesCollection : ConfigurationElementCollection
   3:     {
   4:         protected override ConfigurationElement CreateNewElement()
   5:         {
   6:             return new IgnoreType();
   7:         }
   9:         protected override object GetElementKey(ConfigurationElement element)
  10:         {
  11:             return ((IgnoreType)(element)).Type;
  12:         }
  13:     }
  15:     public class IgnoreType : ConfigurationElement
  16:     {
  17:         [ConfigurationProperty("type", DefaultValue = "", IsKey = true, IsRequired = true)]
  18:         public string Type
  19:         {
  20:             get
  21:             {
  22:                 return (string)(base["type"]);
  23:             }
  24:             set
  25:             {
  26:                 base["type"] = value;
  27:             }
  28:         }
  29:     }
  31:     [ConfigurationCollection(typeof(IgnoreNamespace), AddItemName = "IgnoreNamespace")]
  32:     public class IgnoreNamespacesCollection : ConfigurationElementCollection
  33:     {
  34:         protected override ConfigurationElement CreateNewElement()
  35:         {
  36:             return new IgnoreNamespace();
  37:         }
  39:         protected override object GetElementKey(ConfigurationElement element)
  40:         {
  41:             return ((IgnoreNamespace)(element)).Namespace;
  42:         }
  43:     }
  45:     public class IgnoreNamespace : ConfigurationElement
  46:     {
  47:         [ConfigurationProperty("namespace", DefaultValue = "", IsKey = true, IsRequired = true)]
  48:         public string Namespace
  49:         {
  50:             get
  51:             {
  52:                 return (string)(base["namespace"]);
  53:             }
  54:             set
  55:             {
  56:                 base["namespace"] = value;
  57:             }
  58:         }
  59:     }

"Waaaah, you're going too fast! I need more detail!" Stuff it, you big baby. This blog isn't for babies. It's for real men who punch live animals to death every night, or they don't get to eat.

Last thing we'll have to do, is let the configuration manager know to link up our configuration classes with the config elements. In the configuration/configSections of your config file, add this:

   1: <section name="AuditorConfiguration" type="MySolution.AuditingConfiguration.AuditConfigurationSection, MySolution.AuditingConfiguration" />

Once again, I'm just making up a namespace, but what you have to do is just put the FQDN in for the "type" attribute of where your "root" class lives.

Less code = less chance for you to screw it all up

Damn this is a long post. I should've broken it up into multiples, but you'd be left with an incomplete solution. Not that I think you should use anything until the series is finished, but you could if you wanted to. Phew.

So, last piece! Praise be to jesus/buddha/god/ganesh/spongebob/etc! .We have all our auditors defined and configured and all that crap. We've mentioned that we're gonna use an "AuditManager" class to read the config and do the magic. The goal is so that we only have to call TrackAction ONCE in our consuming code, instead of calling all the auditors individually. There's no reason to change our signature on that function either. We'll need two TrackAction's, just like in our auditors - one for instances, and one for collections.

However - in the next post, you'll see we're actually going to be calling the AuditManager using reflection. I'm jumping the gun a tad, because I'm saving us the headache of having to go back and rework this piece. How this is going to affect our design is minimal, but you'll bitch at me if I don't explain it. Since we'll be calling AuditManager.TrackAction with reflection, and TrackAction will be a generic call with two overloads where the only changed parameter IS the generic one, it's going to fail for us. It won't be able to identify between "T information" and "IEnumerable<T> information". So what we're going to do is instead have TrackAction and TrackActionEnumerable.

Before our AuditManager can do anything though, it's going to need that config data! That's easy. Remember, MS wants you to use configs. And we don't want to have to load that config data over and over and over, so we're going to make it static. We'll have a private static field that will save our information for us. In our public property, when we try to access that private field, if it's null, we'll load it from the config. Else, we'll just return the private field. All you need is this:

   1: private static AuditConfigurationSection _configSection = null;
   3: protected static AuditConfigurationSection ConfigSection
   4: {
   5:     get
   6:     {
   7:         if (_configSection == null)
   8:         {
   9:             _configSection = (AuditConfigurationSection)ConfigurationManager.GetSection("AuditorConfiguration");
  10:         }
  12:         return _configSection;
  13:     }
  14: }


"ConfigurationManager" is also contained in the "System.Configuration" namespace I mentioned earlier. We're going to grab our configuration, then cast it to our classes we defined earlier.

Now to start going through it. The good news for you, is that this is also easy. The good news for me, is that means you're less likely to whine at me. You access the config section just like you'd expect! The "ConfigurationManager" at this point has organized your object hierarchy so that you can just loop through your properties. You'll see that in just a second, but first let's think of the overall approach we're going to need to do here. Our two track action functions will first need to make sure our "ConfigSection" property isn't null. If it is, then that means the consuming application didn't define anything in the config (or possibly named the section incorrectly). If it's not null, we'll need to make sure then that the type isn't in the ignore list. If it passes both of those however, then we'll want to loop through all the auditor definitions and find which ones match, create an instance of them, and call TrackAction on each one.

So let's start with our easier part of this functionality - checking to see if it's an ignored type. "IsIgnoredType" sounds like a good function name for me. Our TrackAction call is generic, so let's make our IsIgnoredType generic as well. I'll show you the function, then we'll discuss it.

   1: private static bool IsIgnoredType<T>()
   2: {
   3:     Type objectType = typeof(T);
   5:     foreach (IgnoreNamespace ignoreNamespace in ConfigSection.IgnoreNamespaces)
   6:     {
   7:         if(objectType.Namespace == ignoreNamespace.Namespace)
   8:         {
   9:             return true;
  10:         }
  11:     }
  13:     foreach (IgnoreType ignoreType in ConfigSection.IgnoreTypes)
  14:     {
  15:         Type ignoredObjectType = Reflector.LoadType(ignoreType.Type);
  17:         if(ignoredObjectType.Equals(objectType))
  18:         {
  19:             return true;
  20:         }
  21:     }
  23:     return false;
  24: }

First thing to note is that it's static. There's no reason for us to have to instantiate our AuditManager. All of our functions will be static.

The first thing we do in our function is create a Type object of our generic parameter. We'll then start at our ignored namespaces. For each of our ignored namespaces defined in our config, we'll see if it matches the Type's namespace property. If it does, we exit the function with a return value of true.

If our type passes that check, then we'll move on to our ignored types. We'll loop through them. For each defined one, we'll once again use Reflector (that handy dandy part of Nvigorate I mentioned earlier) to load our type, then call the "Equals" function on the Type object to see if it matches our current type. The reason we load the type and call the equals function, instead of doing something simpler like say, just checking the type names, is because we want to make sure they are indeed the EXACT same type. We don't want to run into issues where classes get named the same and end up ignoring the wrong ones. Once again - if we find a match, we exit returning true.

Then if our type passes these checks, we return false.

The last thing we need to do is retrieve all our matching auditors. We're going to call that "GetAuditors". It will also be a generic function. And when it's done, it should return a list of our auditors. Now, we won't know the concrete auditor types, but that's why they have a base class in common. So, we'll return a "List<AuditorBase<T>>", that way our TrackAction calls can still fire TrackAction on each one.

Once again, I'm going to show you the code then explain it.

   1: private static List<AuditorBase<T>> GetAuditors<T>()
   2: {
   3:     Type objectType = typeof(T);
   4:     List<AuditorBase<T>> auditors = new List<AuditorBase<T>>();
   6:     foreach (Auditor a in ConfigSection.Auditors)
   7:     {
   8:         Type configuredObjectType = Reflector.LoadType(a.HandlesType);
  10:         bool typeMatch = configuredObjectType.Equals(objectType);
  11:         if (typeMatch || objectType.IsSubclassOf(configuredObjectType))
  12:         {
  13:             Type auditorType = Reflector.LoadType(a.TypeName);
  15:             if (auditorType != null)
  16:             {
  17:                 AuditorBase<T> auditor = null;
  18:                 if (auditorType.IsGenericTypeDefinition)
  19:                 {
  20:                     auditor = (AuditorBase<T>)Reflector.MakeGenericInstance(auditorType, objectType);
  21:                 }
  22:                 else
  23:                 {
  24:                     auditor = (AuditorBase<T>)Activator.CreateInstance(auditorType);
  25:                 }
  27:                 auditor.LoadConfiguration(a.ConfigurationXml);
  29:                 if(a.Exclusive)
  30:                 {
  31:                     auditors.Clear();
  32:                     auditors.Add(auditor);
  33:                     return auditors;
  34:                 }
  36:                 auditors.Add(auditor);
  37:             }
  38:         }
  39:     }
  41:     return auditors;
  42: }
  43:     }

First things first, we create a Type object of our generic parameter. Next, we create an empty list of our return value. This way, at the end of our function, we can just return this parameter, and our consuming code doesn't have to worry about doing null checks.

Next we'll loop through all the defined auditors. For each one, we'll use Reflector to load the type, then check if the types match. If they do, OR if our type we're auditing is a subclass of this auditor's handled type, then we need to create an instance and add it to our list. If not, we continue on to the next defined auditor.

So, let's say we've found a match. Then we'll use Reflector (yet again) to load the auditor's type itself. We'll do a null check first - if you find this failing, make sure you've added a reference to the project where you've defined your auditors. Then we'll need to see if it's a generic auditor (remember, defined in the config file with the "`1" syntax). If it is, then we'll use Reflector to make an instance of that for us, passing the auditor's type as well as the generic parameter type (which will be the type created by our generic parameter that the function is running under). If not, then we'll just use the regular "Activator.CreateInstance" call.

Whichever way we load our auditor, we'll of course want to cast it to our AuditorBase<T>. Next, we'll want to pass our auditor the configuration XML that we found. Lastly, we'll want to check if this auditor is exclusive or not. If it is, then we should clear our list, only add this one, and exit. If not, then just add it to the list, and continue to the next one.

Last thing to do, is to create our actual TrackAction calls! we defined earlier what they need to do, so let's take a look at the final product, using our spiffy new functions:

   1: public static void TrackAction<T>(AuditAction action, string user, T information, DateTime? when)
   2: {
   3:     if (ConfigSection != null && !IsIgnoredType<T>())
   4:     {
   5:         GetAuditors<T>().ForEach(a => a.TrackAction(action, user, information, when));
   6:     }
   7: }
   9: public static void TrackActionEnumerable<T>(AuditAction action, string user, IEnumerable<T> information, DateTime? when)
  10: {
  11:     if (ConfigSection != null && !IsIgnoredType<T>())
  12:     {
  13:         GetAuditors<T>().ForEach(a => a.TrackAction(action, user, information, when));
  14:     }
  15: }

You'll notice we're using LINQ there to iterate through, and call our individual TrackAction calls. You could do this with a regular "foreach" call as well, but this obviously looks cleaner. That's it!

Loose weight and impress the opposite sex!

So, let's check our consuming code now!

   1: AuditManager.TrackAction(Action.Update, currentUser, myData, DateTime.Now);
   3: (or)
   5: AuditManager.TrackActionEnumerable(Action.Update, currentUser, myData, DateTime.Now);

No matter how many auditors we add now, we only have to make one simple call. We no longer have to touch this code.

However, the fact that there is still code there is uggo-fied. That's our last major hurdle to clean up. But how could we remove actually calling our code? That's kind of important. Maybe if we could decorate the functions we needed to audit...hmm....


Designed by Posicionamiento Web | Bloggerized by GosuBlogger