Is there anyway to detect when a user logins if there is already another session with the same username, and block him from logging in again or send him a message?
You could always implement the events in global.asax.
Implement Application_Start() to setup a System.Collections.Dictionary (or at your preference) and store that in the Application[] collection, when a user logsin, add the username. Remove from the collection in Session_End(). Remember to use the 'lock' keyword while working with the collection :)
Have fun!
Example:
[page.aspx]
public partial class page : System.Web.UI.Page {
protected bool Login(string userName) {
System.Collections.Generic.List<string> d = Application["UsersLoggedIn"]
as System.Collections.Generic.List<string>;
if (d != null) {
lock (d) {
if (d.Contains(userName)) {
// User is already logged in!!!
return false;
}
d.Add(userName);
}
}
Session["UserLoggedIn"] = userName;
return true;
}
protected void Logout() {
Session.Abandon();
}
}
[global.asax]
<%# Application Language="C#" %>
<script RunAt="server">
void Application_Start(object sender, EventArgs e) {
Application["UsersLoggedIn"] = new System.Collections.Generic.List<string>();
}
void Session_End(object sender, EventArgs e) {
// NOTE: you might want to call this from the .Logout() method - aswell -, to speed things up
string userLoggedIn = Session["UserLoggedIn"] == null ? string.Empty ? (string)Session["UserLoggedIn"];
if (userLoggedIn.Length > 0) {
System.Collections.Generic.List<string> d = Application["UsersLoggedIn"]
as System.Collections.Generic.List<string>;
if (d != null) {
lock (d) {
d.Remove(userLoggedIn);
}
}
}
}
</script>
I've implemented this where when a user logs in it sets a flag in the DB that they are logged in. It was an int representing how many times they are logged in. We allowed two. Then would just check that when validating the user.
You can, by keeping track of users logged in, in your global.asax by using the Application object.
In the Session_Start method or your login method, you can check if the user is stored in the Application object.
On the Session_End method or in your logoff method, you'll need to remove the user from the Application object.
Don't store it in the DB if you cannot identify user logout event (they may click logout, close the tab, close the whole browser, or may just shutdown the computer...).
Use session to do the same checking instead.
You could store the SessionID of a user in a database. On each login, store a combination of Unique username and SessionID into the database. In the masterpage you include the query to the database, to check wether the last login for the currently used username was from the same session. If not, abandon the session and redirect to the login page.
The behaviour I posted should log out the second user. You may change the Session.Abandon to your desired behaviour
Related
I want to know if i can count a specific session variable.
I want to make a count of how many users are currenty logged in to the site and how many users are visiting the site right now.
Here is what i did (code)
public void Session_OnStart()
{
Application.Lock();
Application["UsersOnline"] = (int)Application["UsersOnline"] + 1;
Application.UnLock();
}
public void Session_OnEnd()
{
Application.Lock();
Application["UsersOnline"] = (int)Application["UsersOnline"] - 1;
Application.UnLock();
}
This code works fine, But now I got no idea how to count users that are logged in. I want to do something like that:
public void Session_OnStart()
{
if (Session["IsLoggedIn"] == "true")
{
Application.Lock();
Application["UsersLoggedIn"] = (int)Application["UsersLoggedIn"] + 1;
Application.UnLock();
}
}
and then if the session is closed it will subtract 1 from the Application["UsersLoggedIn"]. My problem is that I cant count the 'IsLoggedIn' session on session start, because it is null and hence it wont work. So now we get to my quistion, Is there anyway to trigger the count of that application variable? Like to create an event when that session is true and then tell the application to add +1 to the counter on that event? Im sorry if my quistion wasnt clear, Please ask me for more deatils if its not clear and you just had no idea what i want from you.
Thank you for your help!
Dirty, hacky, inefficient, solution (assumes use of forms authentication):
public void Global_BeginRequest(object sender, EventArgs e)
{
if(
Context.User != null &&
!String.IsNullOrWhiteSpace(Context.User.Identity.Name) &&
Context.Session != null &&
Context.Session["IAMTRACKED"] == null
)
{
Context.Session["IAMTRACKED"] = new object();
Application.Lock();
Application["UsersLoggedIn"] = Application["UsersLoggedIn"] + 1;
Application.UnLock();
}
}
At a high level, this works by, on every request, checking if the user is logged in and, if so, tags the user as logged in and increments the login. This assumes users cannot log out (if they can, you can add a similar test for users who are logged out and tracked).
This is a horrible way to solve your problem, but it's a working proto-type which demonstrates that your problem is solvable.
Note that this understates logins substantially after an application recycle; logins are much longer term than sessions.
I think the session items are client sided.
You can create a query to count the open connections (hence you're working with a MySQL database.)
Another option is to use external software (I use the tawk.to helpchat, which shows the amount of users visiting a page in realtime).
You could maybe use that, making the supportchat invisible, and only putting it on paging which are accesible for loggedin users.
OR
Execute an update query which adds/substracts from a column in your database (using the onStart and OnEnd hooks).
That is the problem that you cannot do it using Session[] variables. You need to be using a database (or a central data source to store the total number of active users). For example, you can see in your application, when the application starts there is no Application["UsersOnline"] variable, you create it at the very instance. That is why, each time the application is started, the variable is initialized with a new value; always 1.
You can create a separate table for your application, and inside it you can then create a column to contain OnlineUsers value. Which can be then incremented each time the app start event is triggered.
public void Session_OnStart()
{
Application.Lock();
Application["UsersOnline"] = (int)Application["UsersOnline"] + 1;
// At this position, execute an SQL command to update the value
Application.UnLock();
}
Otherwise, for every user the session variable would have a new value, and you would not be able to accomplish this. Session variables were never designed for such purpose, you can access the variables, but you cannot rely on them for such a task. You can get more guidance about SQL commands in .NET framework, from MSDN's SqlClient namespace library.
Perhaps I am missing something, but why not something like this:
public void Session_OnStart()
{
Application.Lock();
if (Application["UsersOnline"] == null )
{
Application["UsersOnline"] = 0
}
Application["UsersOnline"] = (int)Application["UsersOnline"] + 1;
Application.UnLock();
}
Maybe I'm missing something, but is there a reason you don't just want to use something like Google Analytics?
Unless you're looking for more queryable data, in which case I'd suggest what others have; store the login count to a data store. Just keep in mind you also have to have something to decrement that counter when the user either logs out or their session times out.
Try this. It may help you.
void Application_Start(object sender, EventArgs e)
{
Application["cnt"] = 0;
Application["onlineusers"] = 0;
// Code that runs on application startup
}
void Session_Start(object sender, EventArgs e)
{
Application.Lock();
Application["cnt"] = (int)Application["cnt"] + 1;
if(Session["username"] != null)
{
Application["onlineusers"] = (int)Application["onlineusers"] + 1;
}
else
{
Application["onlineusers"] = (int)Application["onlineusers"] - 1;
}
Application.UnLock();
// Code that runs when a new session is started
}
now you can display the number of users(Without Loggedin):
<%=Application["cnt"].ToString()%>
and number of online users:
<%=Application["onlineusers"].ToString()%>
Is there a way to force ASP.NET to sign out from it's authentication when the browser is closed or the user types in a new address?
If the browser is left open then I need to keep the user authenticated, so a long timeout on the authentication ticket is preferable.
Not sure if this is still an issue but this resolved the issue for me. Just add the following to the Page_Load event of your Start Page:
protected void Page_Load(object sender, EventArgs e)
{
if (Request.UrlReferrer == null || string.IsNullOrEmpty(Request.UrlReferrer.AbsolutePath))
{
Session.Abandon();
FormsAuthentication.SignOut();
FormsAuthentication.RedirectToLoginPage();
}
}
I've been working on this for some time, reading around and seeing various posts and answers, conjecture and speculation.
This solution does have a significant caveat - Popup windows ("BURN HIM!!!" I hear you cry) are required, as is JavaScript. However, given that you have a need to implement this so securely, I'd hazard a guess that the users would accept this to maintain security, and you could code for JavaScript being enabled.
STEP 1 - Verify that popups are enabled - If not, redirect to instructions on how to enable
in the Global.asax
Setup a session variable to verify if popups have been checked:
void Session_Start(object sender, EventArgs e)
{
// Code that runs when a new session is started
Session["popupsChecked"] = false;
}
in Page.aspx / masterPage.master
Check the session variable, if false (first visit to the site this session), inject the JavaScript to check for popup availability:
if (!IsPostBack)
{
if (!(bool)Session["popupsChecked"])
{
Page.Header.Controls.Add(new LiteralControl("<script src=\"/js/popupCheck.js\" type=\"text/javascript\"></script>"));
}
}
The popupCheck.js file (The file "enablePopups.aspx" is instructions on how to enable popups for the site in question)
$(function () {
result = window.open("/static/popupCheck.aspx", "popped", "width=10, height=10, location=no, menubar=no, status=no, toolbar=no, scrollbars=no, resizable=no");
if (result != null) {
// Not blocking
if (window.location.pathname != "/static/enablePopups.aspx") {
window.location = "/";
};
}
else {
//blocking
if (window.location.pathname != "/static/enablePopups.aspx") {
window.location = "/static/enablePopups.aspx";
};
}
});
And finally, the popupCheck.aspx
<head runat="server">
<title>Popup Checker</title>
<script language="javascript" type="text/javascript">
window.close();
</script>
</head>
<body>
<form id="form1" runat="server">
<div>
Popup windows are enabled, please close this window.
</div>
</form>
</body>
</html>
With the following in the codebehind - This will stop any further checks to see if popup windows are enabled:
protected void Page_Load(object sender, EventArgs e)
{
Session["popupsChecked"] = true;
}
So at this point we now have "forced" the user to enable popups for this site - As said, the site content should justify this annoyance in my opinion
STEP 2 - Check for where they're going, if they're off, popup the logoff
In your main javascript block, file, data, or however you're doing it now days
var siteLinkClicked = false;
var isAuthenticated = false;
$(function() {
// Check if user is authenticated
if ($("#someElementThatOnlyAppearsWhenLoggedIn").length) { isAuthenticated = true; };
// Check if any of the links clicked are in the site
$("a, input").click(function () { // -- You may need more then "a" and "input" tags, or exceptions
siteLinkClicked = true;
});
$(window).bind("beforeunload", function (event) {
if (siteLinkClicked == false && isAuthenticated == true) {
// popup the logout page
window.open("/popupLogout.aspx", "popupLogout", "status=0,location=0,directories=0,menubar=0,scrollbar=0,resizable=0,width=400,height=200")
};
});
});
And the popupLogout.aspx
We have some javascript to check for the parent (opener) location, if it's the same as this popup (i.e. the user has clicked refresh - Or you've missed an element for siteLinkClicked) then just close the window without doing anything:
$(function () {
setTimeout(checkParent, 5000); // Give some time for the new window to load if they've navigated away - A counter could be added, logging off in 5... 4... 3... You get the idea.
window.blur(); // And stick this to the back
});
function checkParent() {
var openerLocation = null;
try {
openerLocation = window.opener.location.hostname
} catch (e) {
openerLocation = null;
}
if (openerLocation == window.location.hostname) {
window.close();
} else {
$("#<%= cmdLogout.ClientID %>").click();
// setTimeout(window.close(), 1000); // Removed and add a redirect to static "loggedOut.html page
};
};
If the location is different / undefined then fire off a button click, then close the window:
<asp:Button Text="logout..." runat="server" ID="cmdLogout" OnClick="cmdLogout_click" />
And the button then triggers the following code:
protected void cmdLogout_click(object sender, EventArgs e)
{
System.Web.Security.FormsAuthentication.SignOut();
Session.Abandon();
Response.Redirect("~/static/loggedOut.htm"); // Added, can self close if needed in the HTML
}
Final limitation reiteration
Without JavaScript or popups this method will not work, and for me at least, I'll be working in a controlled environment where security is paramount so this solution is worth the annoyance of the end users.
If you have to go to these extreme ends, then I can only assume that the data and application are sensitive enough to force the users to have to enable javascript and have to enable popups.
It seems like you could set a long expiration time and also set it not to persist to get the exact behavior you seem to be looking for. For example:
FormsAuthentication.SetAuthCookie("username", true);
This sets it to persist so that when you close your browser, it will preserve the cookie so that even if they close their browser and load your site again, it will make it so that they don't have to login so long as the expiration time hasn't occurred.
On the other hand:
FormsAuthentication.SetAuthCookie("username", false);
This sets the cookie to not persist. When you close your browser, the cookie is deleted thereby forcing a login the next time you load it whether the expiration time happened or not. For more info here, see FormsAuthentication.SetAuthCookie
I want to set one variable from session to null
Session["my_variable"] = null;
I tried with OnUnload like this
protected override void OnUnload(EventArgs e)
{
base.OnUnload(e);
Session["my_variable"] = null;
}
but it doesn't work right, it sets the variable while the user is on page but I want to set it null whet is leaves page
Your function/event will be fired as soon as page is served to client, because it is a server side event. You might use Viewstate but still it cannot be implemented as navigation does not send any event to server while clientside scripting can help.
This functionality can be implemented by java script on-unload event. You have to send a request to server to remove or put null in particular session value ( as session is key-value object ). Your question is best explained at Asp Forum
But be careful. This event might fire only on navigation by some browser. you might have to do some code-work to implement what you want.
Why dont you are making use of ViewState inplace of Session Variable.
viewstate variable automatically get removed once you leave page
here is example how to use it
protected DataSet MyDataSet
{
get
{
if(ViewState["MyDataSet"] == null)
{
return null;
}
else
{
return (DataSet)ViewState["MyDataSet"];
}
}
set
{
ViewState["MyDataSet"] = value;
}
}
I have a webpage with a button that does postback, connects to an external database to download some data and perform some database updates. The issue I have is the possibility that two or more people runs this download simultaneously or while the function is still running, which may cause problems.
How do I create some form of semaphore so that if the second person clicks the button, he'll get a message saying it's currently being updated?
Use a proper transaction on your external database and apply row locking there as needed; the DB system should handle the concurrency just fine.
At the first sight, I would use an ASP.NET Application variable enclosed in a lock statement to check and update it if needed.
protected void Page_Load(object sender, EventArgs e)
{
if (!GetSem_SomeoneIsDownloading())
{
PerformDownload();
ClearSem_SomeoneIsDownloading();
}
else
{
DisplayMessageSomeoneIsDownloadingAlready();
}
}
bool GetSem_SomeoneIsDownloading()
{
bool isSomeoneDownloading;
Application.Lock();
isSomeoneDownloading = (bool)(Application["SomeoneIsDownloading"] ?? false);
if (!isSomeoneDownloading)
Application["SomeoneIsDownloading"] = true;
Application.UnLock();
return isSomeoneDownloading;
}
void ClearSem_SomeoneIsDownloading()
{
Application.Lock();
Application["SomeoneIsDownloading"] = false;
Application.UnLock();
}
Ok, this is a weird one. I've created a simple sample site to demonstrate the issue. In it, I have a Default.aspx Page that has a button on it:
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
<p><asp:Button OnClick="ButtonClick" Text="Button" runat="server" />
</p>
<asp:Label ID="output" runat="server" />
</asp:Content>
The code behind just sets the label text on the button click:
protected void ButtonClick(object sender, EventArgs e)
{
output.Text = "Button Click!!";
}
I then have an IHttpModule that gets called for every request:
public class SampleModule : IHttpModule
{
public void Dispose()
{
}
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(context_BeginRequest);
}
private void context_BeginRequest(object sender, EventArgs e)
{
HttpApplication application = sender as HttpApplication;
if(application == null)
{
return;
}
HttpContext context = application.Context;
if(context == null)
{
return;
}
string text = "queryStringParam";
var value = context.Request[text];
var boolValue = value == null;
}
}
Again, this is just a demo, but the point here is, I'm accessing the Request, to get the value off the query string. If I run this in Cassini, it all works fine. However when I run this in IIS, this is what happens. When I run the site at:
http://mysamplesite.dev/
and then click on the button, nothing happens. The page just reloads, but my event handler for the button never gets called, and subsequently the label text never gets updated. However if I then run it at:
http://mysamplesite.dev/Default.aspx
and then click the button, it works fine and my event handler does get called!
After digging around some I changed the code in the module to this:
string text = "queryStringParam";
var value = context.Request.QueryString[text];
var boolValue = value == null;
Notice, here I'm accessing the QueryString property directly, and not going to context.Request. When I changed it to this, it works fine regardless of whether or not I had Default.aspx in the url?!
Next step I did was, I looked at Reflector to see what the code of the HttpRequest indexer property actually does:
public string this[string key]
{
get
{
string str = this.QueryString[key];
if (str != null)
{
return str;
}
str = this.Form[key];
if (str != null)
{
return str;
}
HttpCookie cookie = this.Cookies[key];
if (cookie != null)
{
return cookie.Value;
}
str = this.ServerVariables[key];
if (str != null)
{
return str;
}
return null;
}
}
Seems harmless enough, it just checks various collections for me, so I don't need to check each one individually. So then I'm wondering, which one of those calls breaks it. I then changed my module to this:
string text = "queryStringParam";
var value = context.Request.QueryString[text];
var boolValue = value == null;
var value2 = context.Request.Form[text];
var boolValue2 = value2 == null;
And now it's broken again! So to make a long story short, just by accessing the Form collection on the request in the IHttpModule, I somehow screw up the PostBack, and the event never gets fired.
Does anyone have any idea why this is happening? I'm more of an ASP.Net MVC guy, I don't know ASP.Net and all the shtick it pulls behind the scenes well enough to really have a clue as to why this is happening.
give your button an ID (no luck)
try fiddler and see what the traffic looks like - something funny is going on here.
When a request is made to "http://mysamplesite.dev/Default.aspx", the context_BeginRequest event handler is only called once, as you would expect. However, when a request is made to "http://mysamplesite.dev/", for some reason context_BeginRequest is called twice.
For "http://mysamplesite.dev/", the first pass through context_BeginRequest loads the Form values properly when context.Request["queryStringParam"] is executed, but the second pass through does not (I checked the private _form value on context.Request using reflection). During this time, the Page_Load method for the page is only called once.
So, the event is not being handled properly because ASP.NET handles a request for "/" a little bit differently than it handles a request for "/Default.aspx", by firing the BeginRequest for the module twice instead of once. As to why ASP.NET does this, I'm not really sure. You would probably need to profile the ASP.NET request and see what methods are being called on HttpRequest to see why the form values don't get passed properly the second time context_BeginRequest is called (and additionally why this second call is even made in the first place).
Note: this must be an internal ASP.NET thing (maybe a Server.Transfer from "/" to "/Default.aspx" or something like that) because Fiddler2 only shows one request coming from the browser for "http://mysamplesite.dev/".