Leron
Leron

Reputation: 9876

Implementing Audit trails in asp.net Web Forms based on the observer pattern

I want to keep track of some user activites in my ASP.NET Web Forms application. For example I'll use the method protected void btnSearch_Click(object sender, EventArgs args) as it is represents more of my business needs and programming problems.

Just to make it more clear. What I want to achieve is, when the btnSearch_Click is executed to make an INSERT to a database table containing some basic information like which exact action was executed, when was executed, who executed it and what was the search word. I'll have a lot of actions that I would like to keep track so the structure of the table is still to be decied but this is the general idea.

My first thought was using some static class like:

public static class UserHistory
{
  public static void Log(string methodName, string user, string message)
  {
    //execute SQL INSERT
  }
}

and then in the event handlers where I need to keep history track just adding :

UserHistory.Log("btnSearch_Click", "SomeUser", "The user searched for cats");

In fact this already seems acceptable to me and it's not that I don't like something in particular in this method but first - this is pretty common tasks, such kind of tasks often already has some patterns helping you to implement the logic in the best possible way and second, I'm not very experienced and I see opportunity here to expand my knowlege by doing something which maybe in this particular case is not that benefitals but good to know anyways.

So this leads me to the Observer pattern. To be honest working with delegates always was troublesome for me, but I'm trying to learn also I know that it's imrpotant to show that you made your own effrots to solve some problem so I'm gonna explain my understanding on I should do this using this pattern but would appretiate some complete answer that I can relate to.

The simplies example that I found was an answer from Jon Skeet where he implemented two classes:

 class Observable
    {
        public event EventHandler SomethingHappened;

        public void DoSomething()
        {
            EventHandler handler = SomethingHappened;
            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }
    }

and

class Observer
{
    public void HandleEvent(object sender, EventArgs args)
    {
        Console.WriteLine("Something happened to " + sender);
    }
}

and the concrete exmaple to show how this works was:

    static void Main(string[] args)
    {
        Observable observable = new Observable();
        Observer observer = new Observer();
        observable.SomethingHappened += observer.HandleEvent;

        observable.DoSomething();

    }

Maybe this contains enough information to implement this pattern for web forms but I'm unable to detect different roles here in the cotntext of Web Forms.

What I expect is to have a single place where to register all the event that I want to keep track on, something like this:

    protected void Page_Load(object sender, EventArgs e)
    {
        observable.SomethingHappened += btnSearch_Click;
        observable.SomethingHappened += btnLogin_Click;
    }

but if I try exactly like this, it's literally doing nothing. I don't even go in:

        public void DoSomething()
        {
            EventHandler handler = SomethingHappened;
            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }

which is logical, but if I have to call this methid explicitly from each method where I want to log something then what is the difference with using a plain old static method instead? Also, I would like to pass a parameter (the string message) which I also can't figure out how to do. So it's obvius that I have poor understanding on how to work with delegates/events but would appreciate if someone helps me with implementing this. For example can be used the btnSearch_Click event handler where I want to pass the search string as parameter.

Upvotes: 1

Views: 1421

Answers (1)

Andy Hopper
Andy Hopper

Reputation: 3678

You don't have to implement the Observer pattern yourself in this case; the ASP.NET pipeline already implemented it for you in the form of callbacks on the Application object; in this particular case, the PostAuthorizeRequest callback should handle your needs fairly well.

First, a quick sidebar on the WebForms pipeline: The way UI interactions are handled in WebForms is through an abstraction called PostBacks. It looks to you as if you are handling events that are being raised from your .aspx/.ascx controls, while in reality what is happening is that a request (a POST containing specially-named fields) is being sent to the Web server. A new instance of your page is created to handle that request, the WebForms infrastructure detects that it's a PostBack, and invokes the event handler. (For more, see the MSDN docs)

What does this mean for you? Well, if you hook the PostAuthorizeRequest callback for your application inside Global.asax (or in the codebehind Global.asax.cs), you can examine the request after the user has been identified and they have been granted the authorization to execute the request. In particular, you'll want to pay attention to three things: first, what type of request is this? If it is not a POST, it's not a PostBack and you can stop your custom processing here. Second, the actual path being requested - this is the WebForms page the user is interacting with; if it's not a page for which you're interested in auditing events, stop here. Lastly, what is the target of the PostBack? This can be determined by examining the value of HttpContext.Request.Form["__EVENTTARGET"]. If it's the name of a control you're interested in, you can now log an interaction.

What could this look like in practice? Assume you have a lookup table of tuples that map a page + control name to a message. In my example, I'll use the variable auditMap that is an IDictionary, and for simplicity's sake, I'll use the concatenation of the page path and control name (e.g.: "MyPage.aspx::btnSearch") to perform a lookup for an audit message:

void Application_PostAuthorizeRequest(object sender, EventArgs e)
{
    if (HttpContext.Current.Request.HttpMethod != "POST") return;

    String key = String.Concat(HttpContext.Current.Request.Path, "::", HttpContext.Current.Request.Form["__EVENTTARGET"]);
    String message;
    if (!auditMap.TryGetValue(key, out message) return;

    auditService.LogEvent(message);
    // Can also pass HttpContext.Current.User.Identity.Name into a format string, etc.
}

Upvotes: 2

Related Questions