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

Toolkit - Dynamic Content Queries

This post if part of a series about the  File System Toolkit  - a custom content delivery API for SDL Tridion. This post presents the Dynamic Content Query capability. The requirements for the Toolkit API are that it should be able to provide CustomMeta queries, pagination, and sorting -- all on the file system, without the use third party tools (database, search engines, indexers, etc). Therefore I had to implement a simple database engine and indexer -- which is described in more detail in post Writing My Own Database Engine . The querying logic does not make use of cache. This means the query logic is executed every time. When models are requested, the models are however retrieved using the ModelFactory and those are cached. Query Class This is the main class for dynamic content queries. It is the entry point into the execution logic of a query. The class takes as parameter a Criterion (presented below) which triggers the execution of query in all sub-criteria of a Criterio

A DD4T.net Implementation - Custom Binary Publisher

The default way to publish binaries in DD4T is implemented in class DD4T.Templates.Base.Utils.BinaryPublisher and uses method RenderedItem.AddBinary(Component) . This produces binaries that have their TCM URI as suffix in their filename. In my recent project, we had a requirement that binary file names should be clean (without the TCM URI suffix). Therefore, it was time to modify the way DD4T was publishing binaries. The method in charge with publishing binaries is called PublishItem and is defined in class BinaryPublisher . I therefore extended the BinaryPublisher and overrode method PublishItem. public class CustomBinaryPublisher : BinaryPublisher { private Template currentTemplate; private TcmUri structureGroupUri; In its simplest form, method PublishItem just takes the item and passes it to the AddBinary. In order to accomplish the requirement, we must specify a filename while publishing. This is the file name part of the binary path of Component.BinaryConten

Scaling Policies

This post is part of a bigger topic Autoscaling Publishers in AWS . In a previous post we talked about the Auto Scaling Groups , but we didn't go into details on the Scaling Policies. This is the purpose of this blog post. As defined earlier, the Scaling Policies define the rules according to which the group size is increased or decreased. These rules are based on instance metrics (e.g. CPU), CloudWatch custom metrics, or even CloudWatch alarms and their states and values. We defined a Scaling Policy with Steps, called 'increase_group_size', which is triggered first by the CloudWatch Alarm 'Publish_Alarm' defined earlier. Also depending on the size of the monitored CloudWatch custom metric 'Waiting for Publish', the Scaling Policy with Steps can add a difference number of instances to the group. The scaling policy sets the number of instances in group to 1 if there are between 1000 and 2000 items Waiting for Publish in the queue. It also sets the