Two Auditing Questions

Posts   
 
    
psandler
User
Posts: 540
Joined: 22-Feb-2005
# Posted on: 04-Sep-2007 04:37:37   

Frans,

Auditing looks very cool. Nice job!

I have sucessfully implemented auditing in previous versions by overriding adapter methods, and I am basically trying to achieve the same functionality using the new auditing framework.

I think I'm going the route of having a single audit class that determines which audit entity to append to the transaction. So I will use a switch statement in my audit class that (for example) adds a CustomerAuditEntity to the transaction of a CustomerEntity. So basically I'm going to have ALL entities instantiate a audit object, but only those that I want to audit completely will actually save a second entity. In addition, even those entities that won't use full auditing will have CreatedByUserId, CreatedDate, UpdatedByUserId, UpdatedDate on them, which can be modified in the audit object (please correct me if I'm wrong about that).

Given that, I thought this would be the best method of adding the audit entity to all classes (from the docs):

By overriding the Entity method CreateAuditor. This is a protected virtual (Protected Overridable) method which by default returns null / Nothing. You can override this method in a partial class or user code region of the Entity class to create the Auditor to use for the entity. The LLBLGen Pro runtime framework will take care of calling the method. One way to create an override for most entities is by using a template. Please see the LLBLGen Pro SDK documentation for details about how to write templates to generate additional code into entities and in separate files. Also please see Adding Adding (sic) your own code to the generated classes for details.

So, since all entities will involve an auditor, I will override this method in CommonEntityBase and return my auditor class.

Question 1: do you see any downside to doing it this way in lieu of using DI?

What I'm struggling with right now is how I'm going to get the userId of the logged in user into this mechanism. I downloaded the examples for Northwind, but the method you use to get the userId won't work in my scenario. I'd like to be able to keep the auditing independent of either the Web or Winform UI. So I need a way of getting the userId "into" the auditor entity.

Doing it "the old way" (i.e. overriding adapter methods), I would simply make the userId part of the derived adapter's constructor, and thus all the manager classes that involved saving would have to pass a userId as a parameter, and I would have access to it in the adapter.

Question 2: any thoughts on an elegant way of doing the same thing, which requires a userId in order to Save/Audit an entity? (I hope that question makes sense)

As I'm writing this is occurs to me that I could:

  1. Add a userId property to the CommonEntityBase
  2. Keep my derived adapter's ctors that require a userId
  3. Cast each entity that gets saved to CommonEntityBase and set the userId property before calling save.
  4. Test the userId property of each entity in the auditor, and throw an exception if it's not set.

That would be a lot of casting, but I think it could work.

(EDIT: 5 seconds after posting, it occured to me that this won't work for recursive saves. disappointed )

Anyway, your input (or anyone's) would be appreciated.

Thanks,

Phil

P.S. The upgrade to 2.5 took me all of 5 minutes, and the prefetch path optimizations work great. simple_smile

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 04-Sep-2007 06:26:46   

So, since all entities will involve an auditor, I will override this method in CommonEntityBase and return my auditor class.

Circular reference?

Question 1: do you see any downside to doing it this way in lieu of using DI?

No as I see, except that "where do you set your AuditorToUse without get a circular reference problem?" In your case I would use DI for each entity I want to audit. If there is similar code, make an AuditorHelperClass to reuse that code. Other way may be apply DI to IEntity2 instances and ignore (set AuditorToUse = null) for those entities wont be audit.

Question 2: any thoughts on an elegant way of doing the same thing, which requires a userId in order to Save/Audit an entity? (I hope that question makes sense)

What about make an custom Constructor that receives a userID? You would call that property some in you BL or GUI: myEntity.AuditorToUse = MyAuditorClass(thisUser);

Only some ideas... I'll think carefully about your scenario and post later wink

David Elizondo | LLBLGen Support Team
psandler
User
Posts: 540
Joined: 22-Feb-2005
# Posted on: 04-Sep-2007 17:59:55   

daelmo wrote:

So, since all entities will involve an auditor, I will override this method in CommonEntityBase and return my auditor class.

Circular reference?

Hmm, I don't think so. Each entity would get my auditor object, and the object would decide (for each entity) which auditEntity (if any) to use.

So:

  1. CustomerEntity gets MyAuditor
  2. MyAuditor adds CustomerAuditEntity as the additional entity to save
  3. CustomerAuditEntity gets MyAuditor
  4. MyAuditor does not add an additional entity to save

I may not have thought this all the way through, though.

daelmo wrote:

In your case I would use DI for each entity I want to audit. If there is similar code, make an AuditorHelperClass to reuse that code. Other way may be apply DI to IEntity2 instances and ignore (set AuditorToUse = null) for those entities wont be audit.

I think the auditor itself is sort of the audithelper class. It does a few things to each entity, and then adds an additional entity in certain cases. Because 95%+ of the auditing code will be the same for each entity, I thought it would make sense to have a single auditor class.

After thinking about this a bit more, another option I see is to have two audit classes, one base and one derived. The auditbase will just modify the fields CreatedByUser, CreatedDate, etc., and the derived class will also add an addtional entity to save (still based on type).

I will then use DI to add the base object to all IEntity2 objects, and the derived version to all entities that implement IFullyAuditable (or some interface I create). I will then use the interface plugin in the designer to specify which entities get this interface. I'm actually not sure how to set this up, since IFullyAuditable entities will also be IEntity2, but it seems like it would be possible.

This might be overkill, though, for what I am trying to do.

daelmo wrote:

What about make an custom Constructor that receives a userID? You would call that property some in you BL or GUI: myEntity.AuditorToUse = MyAuditorClass(thisUser);

That occured to me, but I have no idea how to get that userId into the entity at runtime. How would I use that constructor if my objects are getting created via DI, and/or created via the CommonEntityBase?

daelmo wrote:

Only some ideas... I'll think carefully about your scenario and post later wink

Thanks for your reply--looking forward to hearing any additional thoughts you may have.

Phil

psandler
User
Posts: 540
Joined: 22-Feb-2005
# Posted on: 05-Sep-2007 22:59:09   

Anyone have any other thoughts on this? I think my main problem is how I'm going to get the userId into the entities without creating a dependency from my business layer into my web project.

Thanks,

Phil

Brandt
User
Posts: 142
Joined: 04-Apr-2007
# Posted on: 09-Nov-2007 15:03:56   

psandler wrote:

Anyone have any other thoughts on this? I think my main problem is how I'm going to get the userId into the entities without creating a dependency from my business layer into my web project.

Thanks,

Phil

I know this one is old but I wanted to comment because I was working on the same problem. To get the user information into the BL, create an interface in the business layer that defines the user information needed for auditing. In the presentation layer create a class that implements that interface and pass that object to the business layer when persisting. That way you do not create a circular reference and it can be used with any type of architecture. To be specific, I usually create a CurrentUser object in session that holds all of the user information and a single method returns the IUserAuditInfo object containing the Name, UserName, Machine Name, Machine IP and so on.

psandler
User
Posts: 540
Joined: 22-Feb-2005
# Posted on: 09-Nov-2007 15:53:01   

Brandt wrote:

I know this one is old but I wanted to comment because I was working on the same problem. To get the user information into the BL, create an interface in the business layer that defines the user information needed for auditing. In the presentation layer create a class that implements that interface and pass that object to the business layer when persisting. That way you do not create a circular reference and it can be used with any type of architecture. To be specific, I usually create a CurrentUser object in session that holds all of the user information and a single method returns the IUserAuditInfo object containing the Name, UserName, Machine Name, Machine IP and so on.

Thanks for your reply. I actually ended up doing auditing the "old fashioned" way, by overriding methods in the dataaccessadapter. I have no complaints, as this method works very well and is very testable.

In my web layer, I do almost exactly as you describe--I keep a domain object in session that describes the user logged in. I then use this object during saves.

The problem comes when I need to make this object (or just the userId) accessible to the audit entity. Daelmo suggested that I make it part of the constructor of the audit class, but I have no idea how to do this using DI in LLBL.

That was pretty much where I got stuck, and decided to go a different route. However, I'd still like to know if what I'm trying to do is possible for future projects.

Thanks,

Phil

GizmoTN76
User
Posts: 36
Joined: 22-Oct-2007
# Posted on: 09-Nov-2007 16:03:56   

I generally use a custom principal and identity object (System.Security.Principal) to carry around userid information like this. You typically associate the principal object with your UI thread and the user identification and permissions follow you around so you can access them everywhere without the need to explicitely pass them.

EricT
User
Posts: 1
Joined: 19-Jun-2008
# Posted on: 20-Jun-2008 01:48:10   

Regarding the last post by GizmoTN76:

I ran into a very similar problem where I do have a custom prinicpal and identity object to "carry around userid information". And that I have "associated the principal object with my UI thread". No problems there.

The problem occurs when Dependency Injection (DI) kicks in, where it seems a (new?) thread is spawn to run the Auditor class (please correct me if I'm wrong). This new thread is now just a GenericPrincipal (not the same custom principal as in the UI thread). So now there is no way to tell the DI-spawned, GenericPrincipal thread its credentials in order to fetch the correct userid information.

What I would like is to somehow link the custom principal and the userid information from the UI thread to the DI-generated thread that runs the Auditor, so that the Auditor can check user-specific settings for knowing which actions to audit.

Is there any way around this problem?

I have spent a good day looking at 1.) LLBLGen Forum 2.) Google .. to no avail.

Thanks in advance!

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 21-Jun-2008 06:12:27   

Brandt wrote:

psandler wrote:

Anyone have any other thoughts on this? I think my main problem is how I'm going to get the userId into the entities without creating a dependency from my business layer into my web project.

Thanks,

Phil

I know this one is old but I wanted to comment because I was working on the same problem. To get the user information into the BL, create an interface in the business layer that defines the user information needed for auditing. In the presentation layer create a class that implements that interface and pass that object to the business layer when persisting. That way you do not create a circular reference and it can be used with any type of architecture. To be specific, I usually create a CurrentUser object in session that holds all of the user information and a single method returns the IUserAuditInfo object containing the Name, UserName, Machine Name, Machine IP and so on.

That's a good idea.

I think you also could have some _IUserInfo _interface:

public interface IUserInfo
{
     ...
     public UserEntity GetCurrentUser()
     ...
}

Then you could implement IWebUserInfo:IUserInfo, IWinFormsUserInfo:IUserInfo, etc., then your AuditorClass know about IUserInfo and call it when needed:

IUserInfo _myUserInfoMechanism;

public IUserInfo
{
     set 
     {
          _myUserInfoMechanism = value;
     }
}

...

public override void AuditInsertOfNewEntity(IEntityCore entity)
{
     UserEntity currentUser = _myUserInfoMechanism.GetCurrentUser();
     ...
}

Now, where to set that IUserInfo to your Auditor? well, you could create a custom property at CommonEntityBase2, or set it at runtime (at your BL, or GUI), or specifying it at configFile and then use reflection, etc, that's up to your scenario.

David Elizondo | LLBLGen Support Team
daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 21-Jun-2008 06:25:25   

EricT wrote:

Injection (DI) kicks in, where it seems a (new?) thread is spawn to run the Auditor class (please correct me if I'm wrong). This new thread is now just a GenericPrincipal (not the same custom principal as in the UI thread). So now there is no way to tell the DI-spawned, GenericPrincipal thread its credentials in order to fetch the correct userid information.

What I would like is to somehow link the custom principal and the userid information from the UI thread to the DI-generated thread that runs the Auditor, so that the Auditor can check user-specific settings for knowing which actions to audit.

Is there any way around this problem?

Could you please elaborate more on this? Maybe an example?

David Elizondo | LLBLGen Support Team
xc_lw2000
User
Posts: 48
Joined: 12-Dec-2006
# Posted on: 15-Jan-2009 16:49:06   

How about store your userId in session and in your auditor class access sesstion value by System.Web.HttpContext.Current.Session["userId"]