Session_End isn't guarenteed to fire - if you're not using InProc sessions for example, then it won't fire at all. If your application recycles or dies, again, it won't fire.
Your best bet would be to have this code in a shared method that you can call from numerous places:
- In your LoginStatus control you can set the LoggingOut event - call your method there to handle people who log out sensibly.
- If you're using InProc sessions, in your Session_End event, but make sure you check to see if they are logged out already (as you've seen).
- If you're not using InProc sessions, you'll need to get a little more creative. Perhaps look at having an event that fires every now and then (perhaps on Session_Start which does fire regardless) that goes through and clears out those users who's last active time is older than the session timeout (as mentioned by Greg).
Unforunately the Membership class gives you some useful details, but not all of them:
GetNumberOfUsersOnline
This will "Gets the number of users currently accessing an application." - great, but the only methods that will get users either:
GetAllUsers // Gets all the users from the storage provider (can be paged)
FindUsersByName
FindUsersByEmail
Sadly none of these have a property to only return "active users" as per the count.
Looking at the members of MembershipUser there isn't a "IsOnline" property - only LastLogonDate and LastActivtyDate - due to the disconnected nature of the web, this is probably as good as you're going to get.