Skip to main content

Capturing A User's Last Login Date Into The CME

Requirement: Indentify a user's last login date into the CME and store it for the purpose of later creating a report with different users and their last login date.

This post only covers the storing of a last login and not the report itself.

There are several ways of intercepting the when a user logs in:
  1. HTTP Module
  2. Global.asax
  3. Event System
I tried the Global.asax and event system approaches with more or less success. I did not try the HTTP Module, which I believe would work the best.

For storing the last login date, I used Application Data on the User object.

1. HTTP Module
The idea is to write a HTTP Module that intercepts each request made to the CME. The module would check if the session has just been initiated, and if so, it would store the current DateTime in the current User object as AppData. One could get the User object by using the Core Service.

2. Global.asax
First of all, this approach doesn't really work :-(

The idea is to intercept the session object creation in the CME global.asax, then during this method identify the current User by using the Core Service, then store DateTime.Now into the user's AppData.

When placing such a Global.asax in the CME web-app's %TRIDION_HOME%\web\WebUI\WebRoot folder, the CME won't really load and I got timeout exceptions on GetList.

I used the code below:

public void Session_OnStart(object sender, EventArgs e)
{
    using (var client = new CoreServiceSession())
    {
        try
        {
            UserData user = client.GetCurrentUser();
            var adapter = new ApplicationDataAdapter("LastLogin", DateTime.Now);
            client.SaveApplicationData(user.Id, new ApplicationData[] { adapter.ApplicationData });
        }
        catch (Exception ex)
        {
            // handle error or ignore it
        }
    }
}

3. Event System
The idea here is to create an Event System handler that intercepts the load of a User object. When the user logs into the CME, its User object is loaded, therefore the event handler is called.

The challenge of this approach is to identify which load event to intercept and which to ignore. There is no easy way to detect a new session. I tried using the Tridion Session.ContextData to store a flag, then check for its presence in a subsequent handler call, but it seems the Session is recreated for each handler call, so the Session.ContextData I receive is always empty across different events.

Finally, I settled for a kind of hybrid approach, where I cache a User login for a period of time (let's say 1hour). The event handler won't save another last login date until this hour expires.

I noticed the User load handler is called many times! I mean, with every selection of a different Folder in the CME, the handler is called about 10-15 times. Obviously, this might pose a performance issue, so be careful what you do in the handler code.

Sample event handler code below: 

[TcmExtension("Last Login Event System")]
public class Events : TcmExtension
{

    public Events()
    {
        EventSystem.Subscribe<User, LoadEventArgs>(OnUserLoad, EventPhases.TransactionCommitted);
    }

    private void OnUserLoad(User user, LoadEventArgs eventArgs, EventPhases phase)
    {
        Logger.Write("OnUserLoad occurred", "Last Login Event System", LoggingCategory.General, TraceEventType.Warning);
        Session session = user.Session;
        if (session.User.Id.Equals(user.Id))
        {
            DateTime dateNow = DateTime.Now;
            if (isUpdate(user, dateNow))
            {
                ApplicationDataAdapter adapter = new ApplicationDataAdapter("LastLogin", dateNow);
                user.SaveApplicationData(adapter.ApplicationData);
            }
        }
    }

    private bool isUpdate(User user, DateTime dateNow)
    {
        ApplicationData appData = user.LoadApplicationData("LastLogin");
        if (appData == null)
        {
            return true;
        }

        DateTime lastLogin = appData.GetAs<DateTime>();
        if (dateNow.Subtract(lastLogin).TotalHours > 1)
        {
            return true;
        }

        return false;
    }
}

Comments

Nivlong said…
Woot! That was almost too clear and easy to follow and understand, Mihai. Thanks for the background and example.

With a little help from Google, I realize I didn't know what I didn't know. But now... I know! :-D

I describe how I got background on the Event System and AppData here: http://www.tridiondeveloper.com/sdl-tridion-event-system-newbie-perspective. If I can help other blog, others can help me code.
Mihai Cădariu said…
Isn't it cool that you can simply Google for Tridion samples these days? :)
Speaking of which, I just blogged about Core Service client sample code: http://yatb.mitza.net/2012/03/core-service-client-sample-code.html

Popular posts from this blog

Running sp_updatestats on AWS RDS database

Part of the maintenance tasks that I perform on a MSSQL Content Manager database is to run stored procedure sp_updatestats . exec sp_updatestats However, that is not supported on an AWS RDS instance. The error message below indicates that only the sa  account can perform this: Msg 15247 , Level 16 , State 1 , Procedure sp_updatestats, Line 15 [Batch Start Line 0 ] User does not have permission to perform this action. Instead there are several posts that suggest using UPDATE STATISTICS instead: https://dba.stackexchange.com/questions/145982/sp-updatestats-vs-update-statistics I stumbled upon the following post from 2008 (!!!), https://social.msdn.microsoft.com/Forums/sqlserver/en-US/186e3db0-fe37-4c31-b017-8e7c24d19697/spupdatestats-fails-to-run-with-permission-error-under-dbopriveleged-user , which describes a way to wrap the call to sp_updatestats and execute it under a different user: create procedure dbo.sp_updstats with execute as 'dbo' as

Content Delivery Monitoring in AWS with CloudWatch

This post describes a way of monitoring a Tridion 9 combined Deployer by sending the health checks into a custom metric in CloudWatch in AWS. The same approach can also be used for other Content Delivery services. Once the metric is available in CloudWatch, we can create alarms in case the service errors out or becomes unresponsive. The overall architecture is as follows: Content Delivery service sends heartbeat (or exposes HTTP endpoint) for monitoring Monitoring Agent checks heartbeat (or HTTP health check) regularly and stores health state AWS lambda function: runs regularly reads the health state from Monitoring Agent pushes custom metrics into CloudWatch I am running the Deployer ( installation docs ) and Monitoring Agent ( installation docs ) on a t2.medium EC2 instance running CentOS on which I also installed the Systems Manager Agent (SSM Agent) ( installation docs ). In my case I have a combined Deployer that I want to monitor. This consists of an Endpoint and a

Debugging a Tridion 2011 Event System

OK, so you wrote your Tridion Event System. Now it's time to debug it. I know this is a hypothetical situtation -- your code never needs any kind of debugging ;) but indulge me... Recently, Alvin Reyes ( @nivlong ) blogged about being difficult to know how exactly to debug a Tridion Event System. More exactly, the question was " What process do I attach to for debugging even system code? ". Unfortunately, there is no simple or generic answer for it. Different events are fired by different Tridion CM modules. These modules run as different programs (or services) or run inside other programs (e.g. IIS). This means that you will need to monitor (or debug) different processes, based on which events your code handles. So the usual suspects are: dllhost.exe (or dllhost3g.exe ) - running as the MTSUser is the SDL Tridion Content Manager COM+ application and it fires events on generic TOM objects (e.g. events based on Tridion.ContentManager.Extensibility.Events.CrudEven