views:

440

answers:

4

Background:

I have a web application for which I need to do database auditing for insert/deletes/updates (and maybe reads). I use LINQ as my ORM. Using some ideas I found on the web I've come up with a way of using attributes to decorate my entities which have associated audit tables. The audit table itself is required to include the same columns with the same types as the original table in addition to fields for the id and name of the current user, modification type, modification time, and whether the operation was successful or not. The auditing occurs during SubmitChanges -- my data context is abstract and I inherit and override SubmitChanges in my concrete implementation. The abstract data context actually derives from an AbstractAuditableDataContext which extends DataContext and adds a CurrentUser property with placeholders for the current user id and name. By default these are 0 and "system" for the instances where there isn't a logged in user -- say during registration or login when certain fields of the user table may be updated. The application is written in C# using ASP.NET MVC.

Problem:

What's the best way to populate the current user property of my derived data context? Should I create a utility class that gets injected in the AuditUtility that checks to see if the CurrentUser has been set and, if not, fills it in. For testing I'd mock this out, but in the live application it would probably use lazy-loading and get/set it in the session. Or should I modify the data context factory (used by all controllers) to perform this functionality. I already use a mock factory during unit testing so this wouldn't involve creating new classes. Or should the derivation be done outside the factory and the current user injected in during context creation. This would allow me to do "on behalf of" auditing.

I realize that this is somewhat subjective, but I'd appreciate any thoughts/experiences you might contribute.

Thanks.

A: 

What is the scope of your DataContext (Application, Session, Request, per BusinessObject..)? If it varies, you may not want to cache the current user within the DataContext at all (or set it during creation). I would probably use a Property within the DataContext that retrieved the current user from Session (one way or another) whenever it is needed.

Aaron Hoffman
The data context scope is per action -- it's pretty short-lived.
tvanfosson
+1  A: 

If you're using Windows or Forms auth, you could check the HttpContext without passing anything in. If you're not in a web context, grab the user from the Thread. Maybe:

if(HttpContext.Current != null)
{
    //grab the user from the HttpContext
}
else
{
    //grab the user from the Thread
}
Corbin March
But where would you put this code? When creating the data context or each time when the utility is invoked?
tvanfosson
The path of least resistance might be adding it in your SubmitChanges() implementation. This eliminates the need to pass anything in and your DataContext consumers remain pleasantly ignorant of the details.
Corbin March
+1  A: 

System.Threading.Thread.CurrentPrincipal should give you the answer you are looking for.

leppie
A: 

I ended up creating an CurrentUserUtilityBase class that has a GetAuditUser method that takes the current data context and retrieves the user object that corresponds to the current user name in the HttpContext.User.Identity. It uses this object to extract the id and display name of the current user and create and return an AuditUser object containing these properties.

My implementing class uses a factory to obtain an instance of my data context and invokes the base class method on this data context. The factory methods for my data context's use the current user utility to inject the current user for the context into the context after it is created.

tvanfosson