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();
}
Related
Background: I'm creating an app that allows our other techs to quickly install a dozen or so programs within one simple GUI. I am embedding the installers of those apps within my main program. (not that it matters)
I created a separate class file (IsOfficeInstalled.cs) that gets called when a user clicks on a button to install Office. Here's the code in that file:
class IsOfficeInstalled
{
public static bool check()
{
RegistryKey key = Registry.LocalMachine.OpenSubKey(#"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\Winword.exe");
if (key != null)
{
key.Close();
}
return key != null; }}
In my MainWindow.xaml file, I am trying to call the "IsOfficeInstalled" function and perform one of two actions based on the return value. This is where I am confused. The code in my button event looks like the following:
private void btn_InstallOffice_Click(object sender, RoutedEventArgs e)
if (IsOfficeInstalled.check())
{
//If yes, then perform some code
}
else
{
//If no, then perform some different code
}
Where I need help is, getting the true/false result back from the "IsOfficeInstalled" function so my code in the "btn_InstallOffice_Click" function will know which condition to execute.
I'm not sure if I'm understanding your question. Are you looking for a simple if statement?
private void btn_InstallOffice_Click(object sender, RoutedEventArgs e)
{
if (IsOfficeInstalled.check())
{
//If yes, then perform some code
}
else
{
//If no, then perform some different code
}
}
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()%>
I'm using a List<> as a container for some data returned from database, like this:
List<BookInfo> result {get;set;}
protected void SearchButton_Click(object sender, EventArgs e)
{
if (Page.IsValid)
{
result = (new BookInfo()).Search(TextBox1.Text);
ListView1.DataSource = result;
ListView1.DataBind();
}
}
Everything works ok. But when I'm trying to sort this List in other event like this one, it's empty.
protected void ListView1_Sorting(object sender, ListViewSortEventArgs e)
{
IComparer<BookInfo> comparer = new BookInfoOrdering();
if (result != null)
{
result.Sort(comparer);
}
ListView1.DataSource = result;
ListView1.DataBind();
}
I'd just like to know why is it happening. One solution is to call the search method and get the data again but isn't it unnecessary?
You were trapped by the same trap that many people are trapped by many times - it's the page lifecycle. ASP.NET fools you by pretending that you had one environment including server and browser, but that's not the case. HTTP is state-less. If you need to persist state between two requests, you have several options:
Serialize the state into the so-called ViewState such as to send it to the browser and post it back to the server
Save it in a database
Save it in the session
Introduce caching for the data that need to be retrieved, that is, sequential calls to BookInfo.Search with equal parameter values don't issue a new database request (or whatever else is required to get the data)
This would work in a desktop application, but not a asp.net website. Server side data retrieved from one postbacks is not stored for the next postback.
There are several methods for storing data between postbacks. For example using the Session State of the website:
// storing
HttpContext.Current.Session["list"] = result;
// retrieving
List<BookInfo> temp = (List<BookInfo>)HttpContext.Current.Session["list"];
you should also be able to retrieve the list from the ListView where it was bound.
IComparer<BookInfo> comparer = new BookInfoOrdering();
List<BookInfo> temp = (List<BookInfo>)ListView1.DataSource;
temp.Sort(comparer);
ListView1.DataSource = temp;
ListView1.DataBind();
I need to Enable/Disable my SAVE button in real time based on data in my fields.
Is the below an acceptable way on accomplishing this? It feels wrong but I don't know how else I could accomplish this.
Each User Control(CRUD Form) has a BackgroundWorker and the following related methods;
StartBGWorker()
StopBGWorker()
RequiredFieldsValid()
There purpose is self-explanatory, I hope. The process goes in such a way that when a User clicks NEW or EDIT it places a call to StartBGWorker() which creates a new BackgroundWorker and calls RunDataASync() for it. The DoWork() method of the BGWorker looks like this:
void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
ucNavDiagnosis.btnSave.Enabled = Convert.ToBoolean(e.UserState);
}
void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
while (true)
{
bgWorker.ReportProgress(0, RequiredFieldsValid());
System.Threading.Thread.Sleep(500);
}
}
private bool RequiredFieldsValid()
{
// TODO: Add other required fields
return (!memAllergies.Text.Equals(string.Empty));
}
This works but feels "bush-league." Anyway, when the User clicks SAVE or CANCEL a call to StopBGWorker() is placed which Disposes the worker.
As a further disclaimer, this app does not currently use binding. It's a long explanation but that's that, as they say.
How about using the validating events to update the enabled status of your save button?!!
protected virtual void MyTextBox_OnValidating(CancelEventArgs e)
{
this.SaveButton.Enabled = (Validate(MyTextBox));
}
As well, if you are doing validation, I highly recommend looking into MS enterprise library validation to handle informing the user of input errors. Also, check out this.
I have an autocompleate textbox that looks into a data base. Some times while I'm typing I received the following error.
Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
Here is the code
private void tBSearchName_TextChanged(object sender, EventArgs e)
{
try
{
//test length
if (tBSearchName.Text.Length > 3)
{
//prevent db lookups
if (!tBSearchName.Text.ToLower().Contains(oldName) || oldName == String.Empty)
{
//test for a name + first letter of last name
if (Regex.IsMatch(tBSearchName.Text, #"(\w)+\s(\w)+(\.)*"))
{
tBSearchName.AutoCompleteCustomSource = AccessDB.serachByNemberName(tBSearchName.Text);
tBSearchName.AutoCompleteMode = AutoCompleteMode.Suggest;
//prevent db lookups
oldName = tBSearchName.Text.ToLower();
}
}
}
}
catch
{
}
}
My insight is that I should frezz typing into the application while search is done, can some suggest how to do this. Or any other insight on what is happening
It is a bug in Windows Forms's wrapper of autocomplete APIs. Windows Forms does not protect the AutoCompleteCustomSource object from being replaced while it is being enumerated by a background thread created by autocomplete.
Instead of replacing the data store, you can try replace the autocomplete object or use the IAutoCompleteDropDown interface to reset the enumerator.
You can use lock:
private void tBSearchName_TextChanged(object sender, EventArgs e)
{
lock(this) { /* do magic */
}
Do note that it's bad practice to perform long tasks in the event handlers. If the search takes more then 30ms, better use a worker thread.