Hye IT Guys,
Few days back, I was working on loading dynamic control in asp.net. I found a problem. when ever I load a control, Its event is not firing for the first time. I found a solution for this problem i.e. We need to maintain the User Control ID so that its event remain attached with it.
See the below code.
protected void Page_Load(object sender, EventArgs e)
{
string ctrlName = "Controls/ctrlLogin.ascx";
string ctrlID = "uc1";
if (IsPostBack == false)
{
int ID = Convert.ToInt32(Request.QueryString["ID"]);
}
else
{
ctrlName = (string)ViewState["ctrlName"];
if (ctrlName != null)
{
Control uc = LoadControl(ctrlName);
uc.ID = ViewState["ctrlId"].ToString();
divContent.Controls.Add(uc);
}
}
}
Any suggestion or ideas will be appreciated. Thank You
Regards,
IT Guy
Showing posts with label Asp.net Links. Show all posts
Showing posts with label Asp.net Links. Show all posts
Tuesday, June 9, 2009
Tuesday, March 10, 2009
Load user data once with an HttpModule
A couple of years ago, when I was less focused, finished with my book and completely unmotivated to develop anything useful for my own non-day job projects, I struggled trying to shoehorn my apps into the ASP.NET Membership and Profile API's. Probably because of my lack of experience, I became very frustrated at the point where the objects started to relate to the data.
It occurred to me fairly recently that these systems provide a level of abstraction that makes sense in terms of data storage, but you still have to do a fair amount of work in your providers to make sure you aren't pounding the data store. That means caching, of course, but you still might be creating objects in several places (handlers, the page, user controls, etc.), and each time you're going to the well, whether it be by way of the caching mechanism you create or going to the data store.
So why cache at all? Why not get the data once, early in the request cycle, and go back to it when you need it? Oddly enough, it was good old fashioned FormsAuth that made me think of this, where in ASP.NET v1.x we would look up the user and roles, probably in an HttpModule, and access that data from where ever.
For Details View : http://www.uberasp.net/getarticle.aspx?id=51
It occurred to me fairly recently that these systems provide a level of abstraction that makes sense in terms of data storage, but you still have to do a fair amount of work in your providers to make sure you aren't pounding the data store. That means caching, of course, but you still might be creating objects in several places (handlers, the page, user controls, etc.), and each time you're going to the well, whether it be by way of the caching mechanism you create or going to the data store.
So why cache at all? Why not get the data once, early in the request cycle, and go back to it when you need it? Oddly enough, it was good old fashioned FormsAuth that made me think of this, where in ASP.NET v1.x we would look up the user and roles, probably in an HttpModule, and access that data from where ever.
For Details View : http://www.uberasp.net/getarticle.aspx?id=51
Friday, February 20, 2009
Tips for Asp.net
Tip: Do not use the AutoPostBack attribute on the DropDownList control to simply redirect to another page.
There are probably cases when this might make sense, but for the most part it is overkill. Using the autopostback for a redirect requires extra roundtrip to the server. First the autopostback returns to the server and processes everything up to the event handling the postback. Next a Response.Redirect is issued which goes back to the client requesting the client use another page. So you end up with two separate requests + processing just to get a user to another page.
Using the onchange event of the select element, we can do this all on the client. In the sample below, I am simply redirecting to the current page with an updated querystring element. Your logic will vary, but in the case below, I am avoiding the zero index.
0) { window.location = window.location.pathname + '?t=' + this[this.selectedIndex].value;}" runat="Server">
Tip: Never use the ASP.Net Label control.
Ever is a strong word, but except for some quick and dirty style hacks you should never ever use this control. Any text is rendered inside a span control which is usually unnecessary and complicates any CSS styling you may be trying to use. In most cases, you can replace the Label with a Literal and achieve the same results.
Tip: Use the ASP.Net Repeater instead of DataList, DataGrid, and DataView controls
The Repeater is the single most powerful control shipped in ASP.NET. It is versatile and lightweight. There are times (especially prototyping) when the other databound controls make sense to use, but they generate a lot of extra markup and generally complicate the page with all of their events and styling. Using the Repeater, you may write a little more code up front, but you will be rewarded in the long run.
For Details View : http://simpable.com/code/quick-tips-for-asp-net-part-one/
There are probably cases when this might make sense, but for the most part it is overkill. Using the autopostback for a redirect requires extra roundtrip to the server. First the autopostback returns to the server and processes everything up to the event handling the postback. Next a Response.Redirect is issued which goes back to the client requesting the client use another page. So you end up with two separate requests + processing just to get a user to another page.
Using the onchange event of the select element, we can do this all on the client. In the sample below, I am simply redirecting to the current page with an updated querystring element. Your logic will vary, but in the case below, I am avoiding the zero index.
Tip: Never use the ASP.Net Label control.
Ever is a strong word, but except for some quick and dirty style hacks you should never ever use this control. Any text is rendered inside a span control which is usually unnecessary and complicates any CSS styling you may be trying to use. In most cases, you can replace the Label with a Literal and achieve the same results.
Tip: Use the ASP.Net Repeater instead of DataList, DataGrid, and DataView controls
The Repeater is the single most powerful control shipped in ASP.NET. It is versatile and lightweight. There are times (especially prototyping) when the other databound controls make sense to use, but they generate a lot of extra markup and generally complicate the page with all of their events and styling. Using the Repeater, you may write a little more code up front, but you will be rewarded in the long run.
For Details View : http://simpable.com/code/quick-tips-for-asp-net-part-one/
Thursday, February 19, 2009
Using the Data Access Application Block to Execute SQL Statements
Once you have the references set and the correct using or Imports statements in your class files, you will have access to the Data Access Application Blocks SqlHelper class. The SqlHelper class contains static methods that facilitate the execution of common data access tasks, including:
Calling stored procedures or SQL text commands,
Specifying parameter details, and
Returning SqlDataReader, DataSet, XmlReader objects, or single values.
In order to illustrate the advantage of using the Data Access Block, let's take a look at sample code that creates a SqlDataReader object and binds it to a DataGrid without using the Data Access Block. In general, returning a DataReader involves establishing a connection, creating a SqlCommand, and executing the command against the database. The resulting SqlDataReader object can then be bound to a DataGrid:
//create the connection string and sql to be executed
string strConnTxt = "Server=(local);Database=Northwind;Integrated Security=True;";
string strSql = "select * from products where categoryid = 1";
//create and open the connection object
SqlConnection objConn = new SqlConnection(strConnTxt);
objConn.Open();
//Create the command object
SqlCommand objCmd = new SqlCommand(strSql, objConn);
objCmd.CommandType = CommandType.Text;
//databind the datagrid by calling the ExecuteReader() method
DataGrid1.DataSource = objCmd.ExecuteReader();
DataGrid1.DataBind();
//close the connection
objConn.Close();
Now lets look at the same task using the SqlHelper class's static ExecuteReader() method:
//create the connection string and sql to be executed
string strSql = "select * from products where categoryid = 1";
string strConnTxt = "Server=(local);Database=Northwind;Integrated Security=True;";
DataGrid4.DataSource = SqlHelper.ExecuteReader(strConnTxt, CommandType.Text, strSql);
DataGrid4.DataBind();
As you can see, there is considerably less code in the second example. To execute a SQL statement and return a SqlDataReader, the ExecuteReader() method requires only the connection string, command type and SQL to be executed. The SqlHelper class contains all of the "plumbing" necessary to establish a connection, create a SqlCommand and execute the command against the database with a single static method call.
The main advantage of the Application Blocks is that they greatly reduce the amount of code you need to write by encapsulating common tasks in a wrapper class. While at first glance this may not seem that profound of a benefit, realize that writing less code means more than just shorter time needed to write the code. It also means fewer bugs and typos, and an overall lower total cost to produce the software.
Happy coding :)
Calling stored procedures or SQL text commands,
Specifying parameter details, and
Returning SqlDataReader, DataSet, XmlReader objects, or single values.
In order to illustrate the advantage of using the Data Access Block, let's take a look at sample code that creates a SqlDataReader object and binds it to a DataGrid without using the Data Access Block. In general, returning a DataReader involves establishing a connection, creating a SqlCommand, and executing the command against the database. The resulting SqlDataReader object can then be bound to a DataGrid:
//create the connection string and sql to be executed
string strConnTxt = "Server=(local);Database=Northwind;Integrated Security=True;";
string strSql = "select * from products where categoryid = 1";
//create and open the connection object
SqlConnection objConn = new SqlConnection(strConnTxt);
objConn.Open();
//Create the command object
SqlCommand objCmd = new SqlCommand(strSql, objConn);
objCmd.CommandType = CommandType.Text;
//databind the datagrid by calling the ExecuteReader() method
DataGrid1.DataSource = objCmd.ExecuteReader();
DataGrid1.DataBind();
//close the connection
objConn.Close();
Now lets look at the same task using the SqlHelper class's static ExecuteReader() method:
//create the connection string and sql to be executed
string strSql = "select * from products where categoryid = 1";
string strConnTxt = "Server=(local);Database=Northwind;Integrated Security=True;";
DataGrid4.DataSource = SqlHelper.ExecuteReader(strConnTxt, CommandType.Text, strSql);
DataGrid4.DataBind();
As you can see, there is considerably less code in the second example. To execute a SQL statement and return a SqlDataReader, the ExecuteReader() method requires only the connection string, command type and SQL to be executed. The SqlHelper class contains all of the "plumbing" necessary to establish a connection, create a SqlCommand and execute the command against the database with a single static method call.
The main advantage of the Application Blocks is that they greatly reduce the amount of code you need to write by encapsulating common tasks in a wrapper class. While at first glance this may not seem that profound of a benefit, realize that writing less code means more than just shorter time needed to write the code. It also means fewer bugs and typos, and an overall lower total cost to produce the software.
Happy coding :)
Wednesday, February 18, 2009
ASP.NET Supports Valid HTML Attributes in its Tags
While reading an article, I came across a misconception that seems all too common with people using ASP.NET, and they need to stop trusting intellisense!!! I love intellisense, but you can't trust in it entirely. It doesn't know everything, so as a rule, I will state that if html supports something... SO DOES ASP.NET!
So now that I am done ranting I first want to say I am not intending to bash the author or the publisher site. Both have some great content and are valuable to the .NET community. I simply want to step in and provide some clarity by providing some explanation and an alternative solution to the problem. The article is about adding a tooltip to individual items in a dropdown list. There are plenty of reasons to do this, including the one the author states which is that the list might have a fixed width and will display badly in IE. Below is an example of a DropDownList .
I personally prefer the way Firefox handles a tooltip anyway.
The thing that seems to throw everyone off is that some ASP.NET controls don't seem to have many properties when you look at intellisense. A lot of them don't include style, title, or some other commonly used attributes. THEY ARE STILL THERE!! Underneath the hood ASP.NET is basically just the HTML you know and love. So when you go in and try to add the title attribute to a ListItem, you will not see it in the intellisense box.
I've heard plenty of people complain that some controls don't support the style attribute in ASP.NET. If their underlying HTML control supports a tag it supports a tag. For this example since the option tag supports the title attribute it means that the ListItem of a DropDownList also supports the attribute.
Adding title from code behind
_listItem.Attributes.Add("title", _listItem.Text);
If the above code were to somehow run twice it literally would add two separate title tags into the HTML which is not always the best way of handling things. Just figured I would throw this out there so people have a better understanding how the connection between ASP.NET and HTML. Some people try to separate ASP.NET as some new thing when at the end of the day it is really just creating HTML.
Don't let ASP.NET be mystical, it is relatively easy to understand if you just read articles and blogs. Be curious and questioning of everything. Yes, I mean for you to question what I tell you also. Plenty of things I write over a long period of time will be questionable and some will just be outright wrong. Everyone will do it. Yes, even people writing documentation.
So now that I am done ranting I first want to say I am not intending to bash the author or the publisher site. Both have some great content and are valuable to the .NET community. I simply want to step in and provide some clarity by providing some explanation and an alternative solution to the problem. The article is about adding a tooltip to individual items in a dropdown list. There are plenty of reasons to do this, including the one the author states which is that the list might have a fixed width and will display badly in IE. Below is an example of a DropDownList .
I personally prefer the way Firefox handles a tooltip anyway.
The thing that seems to throw everyone off is that some ASP.NET controls don't seem to have many properties when you look at intellisense. A lot of them don't include style, title, or some other commonly used attributes. THEY ARE STILL THERE!! Underneath the hood ASP.NET is basically just the HTML you know and love. So when you go in and try to add the title attribute to a ListItem, you will not see it in the intellisense box.
I've heard plenty of people complain that some controls don't support the style attribute in ASP.NET. If their underlying HTML control supports a tag it supports a tag. For this example since the option tag supports the title attribute it means that the ListItem of a DropDownList also supports the attribute.
Adding title from code behind
_listItem.Attributes.Add("title", _listItem.Text);
If the above code were to somehow run twice it literally would add two separate title tags into the HTML which is not always the best way of handling things. Just figured I would throw this out there so people have a better understanding how the connection between ASP.NET and HTML. Some people try to separate ASP.NET as some new thing when at the end of the day it is really just creating HTML.
Don't let ASP.NET be mystical, it is relatively easy to understand if you just read articles and blogs. Be curious and questioning of everything. Yes, I mean for you to question what I tell you also. Plenty of things I write over a long period of time will be questionable and some will just be outright wrong. Everyone will do it. Yes, even people writing documentation.
Friday, February 13, 2009
Preventing Multiple Logins in ASP.NET
We talked about the fact that the classic ASP Session_OnEnd handler is widely known to be pretty unreliable. However, in ASP.NET the corresponding Global class handler, Session_End, is very reliable. Then we talked about "what if" scenarios, such as what if the ASP.NET worker process was recycled? If so, I reasoned, it didn't matter whether you were using Session, Application or Cache, all of your stuff would be lost. The only exceptions to this would be if you were using the ASP.NET State Server service for your Session, or the SQL Server Session option. In particular, there is a second script available for the SQL Server Session option that does not use the TempDB, and this means that even if the whole machine goes down, when it comes back up, the Session data will still be there. Both StateServer and SQL Server Session options run out of process, so it really doesn't matter if the ASPNET_WP.EXE worker process is recycled - the sessions, which run out of the ASP.NET worker process and rely on the Session Cookie that's stored at the browser, will still be there.
The main issue is that if you put some sort of "lock" on the user record because somebody has logged in, and then they close their browser and you don't have a reliable way of determining that their session has expired so you can remove the lock, you are likely to get calls to your Tech Support desk from users complaining they cannot log in! (trust me, I have good reports that this has happened...)
The big problem, it turns out, is that with StateServer and SQL Server Sessions, the Session_End event in Global is never fired. Only InProc mode fires this. So in order to avoid Tech Support coming after us with hatchets and knives, we would need to come up with some sort of reliable surrogate for the Session_End event. Robbe took off on his own angle here and wrote an excellent article about using the Cache class to handle some of these issues. You can read it here. Robbe also discusses how to use the callback mechanism in the Cache class to handle the situation where the item is removed from the Cache. In fact, he's determined that this even fires when the ASP.NET worker process recycles under normal conditions (such as when specified in machine.config), thereby enabling us to serialize Cache items to a database for later rehydration.
As it often turns out, sometimes the simplest solution to a problem is also the most elegant and even the most scalable. The solution to the multiple login problem that I came up with and present here simply uses the Cache with SlidingExpiration as a surrogate for a Session_End event. First, here's the logic:
1) User logs in, we check the Cache using username+password as the key for the Cache Item. If the Cache item exists, we know that the login is already in use, so we kick them out. Otherwise, we authenticate them (database, etc) and let them in.
2) After we have let them in, we set a new Cache item entry with a key consisting of their username+password, with a sliding expiration equal to the current Session Timeout value. We can also set a new Session variable, Session["user"], with a value of the username+password, so that we can do continuous page request checking and Cache updating on every page request during the user's session. This gives us the infrastructure for "duplicating" the missing Session_End functionality.
3) Now we need a way to update the Cache expiration on each page request. You can do this very elegantly in the Application_PreRequestHandlerExecute handler in Global, because the Session object is available and "live" in this handler. In addition, this event is fired on every page request, so we don't need to put a single line of extra code in any of our pages. We use the Session["user"] value to get this user's key to retrieve their Cache Item, thus resetting it and automatically setting the sliding expiration to a fresh timeout value. Whenever you access a Cache item, its SlidingExpiration property (if properly configured) is automatically updated. When a user abandons their session and no pages are requested for a period of time, the SlidingExpiration of their Cache Item eventually expires, and the item is automatically removed from the Cache, thereby allowing somebody with the same username and password to log in again. No fuss, no muss! Works with InProc, StateServer and SQL Server Session modes!
Now let's take a look at some code as to how this can be implemented, in its most basic form:
In web.config (StateServer mode, with a one minute timeout to make testing easier):
mode="StateServer"
stateConnectionString="tcpip=127.0.0.1:42424"
sqlConnectionString="data source=127.0.0.1;user id=sa;password=letmein"
cookieless="false"
timeout="1"
/>
In Global.asax.cs:
protected void Application_PreRequestHandlerExecute(Object sender, EventArgs e)
{
// Let's write a message to show this got fired---
Response.Write("SessionID: " +Session.SessionID.ToString() + "User key: " +(string)Session["user"]);
if(Session["user"]!=null) // e.g. this is after an initial logon
{
string sKey=(string)Session["user"];
// Accessing the Cache Item extends the Sliding Expiration automatically
string sUser=(string) HttpContext.Current.Cache[sKey];
}
}
In your Login Page "Login" button handler:
private void Button1_Click(object sender, System.EventArgs e)
{
//validate your user here (Forms Auth or Database, for example)
// this could be a new "illegal" logon, so we need to check
// if these credentials are already in the Cache
string sKey=TextBox1.Text+TextBox2.Text;
string sUser=Convert.ToString(Cache[sKey]);
if (sUser==null || sUser==String.Empty){
// No Cache item, so sesion is either expired or user is new sign-on
// Set the cache item and Session hit-test for this user---
TimeSpan SessTimeOut=new TimeSpan(0,0,HttpContext.Current.Session.Timeout,0,0);
HttpContext.Current.Cache.Insert(sKey,sKey,null,DateTime.MaxValue,SessTimeOut,
System.Web.Caching.CacheItemPriority.NotRemovable,null);
Session["user"]=TextBox1.Text+TextBox2.Text;
// Let them in - redirect to main page, etc.
Label1.Text="";
}
else
{
// cache item exists, so too bad...
Label1.Text="";
return;
}
}
You can try logging in with any username / password you want. If you try again, you won't get in (unless you wait long enough for the Cache Item to expire). Each time you try, the SlidingCache timeout property of the Cache item gets updated (same with any page request). You can try logging in from another browser window, or even another machine. It doesn't matter, you won't be able to abuse the Big Brother license login policy.
What about a Web Farm?
There are certainly trade-offs to be considered when dealing with Sessions on a web farm. StateServer normally is set up to act as a central session server for all the servers in a web farm. By definition, you have to pick a machine and all the web.config entries point to the IP address of that machine. However, I know of at least one organization that uses StateServer on each and every machine on a web farm, and sticky IP to make sure that everybody always returns to the machine where their Session was started. While this configuration might seem like "shooting yourself in the foot", it is conceiveable that an organization might opt for this where redundancy, rather than scalability, is the overriding consideration. (Of course, if you have StateServer and Sticky IP on every machine in the farm, and only one SQL Server with no clustering and failover, the jury might still be out on how much redundancy you have actually achieved).
If your overriding concern is that the particular StateServer machine may "go down" then your only other option would be to use the SQL Server session mode and choose the SQL Script "InstallPersistSqlState.sql" which specifically does not use the TempDB (TempDB disappears when a machine is rebooted).
There is no sharing of cache between web applications on a farm. Also it was brought to my attention by reader Paul Abraham (who has provided helpful comments on more than one occasion here) that if we have a multi-processor machine, we can configure it to webgarden mode, in which case we will have more than one worker process. Consequently, we will then have more than one instance of the System.Web.Caching.Cache class operative in our application. (one instance of this class is created per application domain) In this context, we would then have the same problem synchronizing Cache in WebGarden mode that we would in a web farm scenario.
In these situations, you can be creative with CacheDependency and CacheItemRemovedCallback. For example, on each web server (or AppDomain) your cache objects can depend on a special file, and on cache addition or removal touch that file so that cache objects on other web servers can get notified and be removed. Now that I think of it, you could even use the very same file that the dependency is created on to store the data that each server needs to get in order to update its Cache.
There is a bug in ASP.NET 1.0 where multiple web applications having cachedependency on a file at a UNC share is not working. So one workaround is to have one file per web application per web server, and during update you would touch all of them. Another thing to remember about a server farm - if you are sharing Session state with StateServer or SQL Server, the SessionID, which is contained in a browser cookie or munged on the URL does get transmitted for the particular user no matter which server their request lands on.
So if you match the ASP.NET Session ID to the username+Password of the login, you have a method to check the Cache on any of the servers to handle both session checking and timeout updating. There is also an excellent article by David Burgett on MSDN about using in-memory Datasets and a WebService to synchronize data in a farm.
Cache Synchronization Down on the Farm
While creating a shared Cache object among servers on a farm is beyond the scope of this article, it is definitely "do-able" and hopefully the above ideas will give you some food for thought. Synchronization of Cache on a server farm is one thing that Mircrosoft left out of the Cache class. However, based on the ideas brought up in this article, it can be seen that there are likely a number of uses for such an arrangement.
One way to set up Cache synchronization among servers in a web farm is to use SQL Server and have two tables - one with a list of the servers currently active in the web farm, and a second table to hold "Update" information for the cache. This "CacheItems" table would probably need at least three or four columns: a varchar column for the cache "key" (in this case username+password), a DateTime column for current Sliding Expiration value, another DateTime column for Absolute Expiration (if used), and finally an IMAGE column to hold the byte stream from the serialized Object Graph of the Cache item, using the BinaryFormatter., in order to store complex objects from the Cache in the same way that SQL Server Session state does. In this manner it would be possibly not only to synchronize the Cache among servers in a farm, but to actually create a backup "Persistent Cache" datastore from which a rebooting or first - time farm member machine can hydrate its Cache and "join the chorus" , so to speak.
So for example, when a session expires in the Cache on one server, you can make an update using the SQL Server to a Cache persistent storage table. This update can made through a WebRequest which is sent to each of the servers on the farm to a special aspx receiver page that is in each app domain. This receiver page basically gets the "notification" and instructs the page to go to the SQL server and update it's resident copy of the Cache from the SQL Server table described above. Each machine would have a page that is capable of handling this process, and thus every machine on the Farm would have the capability both to update the backup store and notify the other webservers, as well as to receive a notification that it needs to retrieve and process the update record(s) from SQL Server.
The main issue is that if you put some sort of "lock" on the user record because somebody has logged in, and then they close their browser and you don't have a reliable way of determining that their session has expired so you can remove the lock, you are likely to get calls to your Tech Support desk from users complaining they cannot log in! (trust me, I have good reports that this has happened...)
The big problem, it turns out, is that with StateServer and SQL Server Sessions, the Session_End event in Global is never fired. Only InProc mode fires this. So in order to avoid Tech Support coming after us with hatchets and knives, we would need to come up with some sort of reliable surrogate for the Session_End event. Robbe took off on his own angle here and wrote an excellent article about using the Cache class to handle some of these issues. You can read it here. Robbe also discusses how to use the callback mechanism in the Cache class to handle the situation where the item is removed from the Cache. In fact, he's determined that this even fires when the ASP.NET worker process recycles under normal conditions (such as when specified in machine.config), thereby enabling us to serialize Cache items to a database for later rehydration.
As it often turns out, sometimes the simplest solution to a problem is also the most elegant and even the most scalable. The solution to the multiple login problem that I came up with and present here simply uses the Cache with SlidingExpiration as a surrogate for a Session_End event. First, here's the logic:
1) User logs in, we check the Cache using username+password as the key for the Cache Item. If the Cache item exists, we know that the login is already in use, so we kick them out. Otherwise, we authenticate them (database, etc) and let them in.
2) After we have let them in, we set a new Cache item entry with a key consisting of their username+password, with a sliding expiration equal to the current Session Timeout value. We can also set a new Session variable, Session["user"], with a value of the username+password, so that we can do continuous page request checking and Cache updating on every page request during the user's session. This gives us the infrastructure for "duplicating" the missing Session_End functionality.
3) Now we need a way to update the Cache expiration on each page request. You can do this very elegantly in the Application_PreRequestHandlerExecute handler in Global, because the Session object is available and "live" in this handler. In addition, this event is fired on every page request, so we don't need to put a single line of extra code in any of our pages. We use the Session["user"] value to get this user's key to retrieve their Cache Item, thus resetting it and automatically setting the sliding expiration to a fresh timeout value. Whenever you access a Cache item, its SlidingExpiration property (if properly configured) is automatically updated. When a user abandons their session and no pages are requested for a period of time, the SlidingExpiration of their Cache Item eventually expires, and the item is automatically removed from the Cache, thereby allowing somebody with the same username and password to log in again. No fuss, no muss! Works with InProc, StateServer and SQL Server Session modes!
Now let's take a look at some code as to how this can be implemented, in its most basic form:
In web.config (StateServer mode, with a one minute timeout to make testing easier):
stateConnectionString="tcpip=127.0.0.1:42424"
sqlConnectionString="data source=127.0.0.1;user id=sa;password=letmein"
cookieless="false"
timeout="1"
/>
In Global.asax.cs:
protected void Application_PreRequestHandlerExecute(Object sender, EventArgs e)
{
// Let's write a message to show this got fired---
Response.Write("SessionID: " +Session.SessionID.ToString() + "User key: " +(string)Session["user"]);
if(Session["user"]!=null) // e.g. this is after an initial logon
{
string sKey=(string)Session["user"];
// Accessing the Cache Item extends the Sliding Expiration automatically
string sUser=(string) HttpContext.Current.Cache[sKey];
}
}
In your Login Page "Login" button handler:
private void Button1_Click(object sender, System.EventArgs e)
{
//validate your user here (Forms Auth or Database, for example)
// this could be a new "illegal" logon, so we need to check
// if these credentials are already in the Cache
string sKey=TextBox1.Text+TextBox2.Text;
string sUser=Convert.ToString(Cache[sKey]);
if (sUser==null || sUser==String.Empty){
// No Cache item, so sesion is either expired or user is new sign-on
// Set the cache item and Session hit-test for this user---
TimeSpan SessTimeOut=new TimeSpan(0,0,HttpContext.Current.Session.Timeout,0,0);
HttpContext.Current.Cache.Insert(sKey,sKey,null,DateTime.MaxValue,SessTimeOut,
System.Web.Caching.CacheItemPriority.NotRemovable,null);
Session["user"]=TextBox1.Text+TextBox2.Text;
// Let them in - redirect to main page, etc.
Label1.Text="";
}
else
{
// cache item exists, so too bad...
Label1.Text="";
return;
}
}
You can try logging in with any username / password you want. If you try again, you won't get in (unless you wait long enough for the Cache Item to expire). Each time you try, the SlidingCache timeout property of the Cache item gets updated (same with any page request). You can try logging in from another browser window, or even another machine. It doesn't matter, you won't be able to abuse the Big Brother license login policy.
What about a Web Farm?
There are certainly trade-offs to be considered when dealing with Sessions on a web farm. StateServer normally is set up to act as a central session server for all the servers in a web farm. By definition, you have to pick a machine and all the web.config entries point to the IP address of that machine. However, I know of at least one organization that uses StateServer on each and every machine on a web farm, and sticky IP to make sure that everybody always returns to the machine where their Session was started. While this configuration might seem like "shooting yourself in the foot", it is conceiveable that an organization might opt for this where redundancy, rather than scalability, is the overriding consideration. (Of course, if you have StateServer and Sticky IP on every machine in the farm, and only one SQL Server with no clustering and failover, the jury might still be out on how much redundancy you have actually achieved).
If your overriding concern is that the particular StateServer machine may "go down" then your only other option would be to use the SQL Server session mode and choose the SQL Script "InstallPersistSqlState.sql" which specifically does not use the TempDB (TempDB disappears when a machine is rebooted).
There is no sharing of cache between web applications on a farm. Also it was brought to my attention by reader Paul Abraham (who has provided helpful comments on more than one occasion here) that if we have a multi-processor machine, we can configure it to webgarden mode, in which case we will have more than one worker process. Consequently, we will then have more than one instance of the System.Web.Caching.Cache class operative in our application. (one instance of this class is created per application domain) In this context, we would then have the same problem synchronizing Cache in WebGarden mode that we would in a web farm scenario.
In these situations, you can be creative with CacheDependency and CacheItemRemovedCallback. For example, on each web server (or AppDomain) your cache objects can depend on a special file, and on cache addition or removal touch that file so that cache objects on other web servers can get notified and be removed. Now that I think of it, you could even use the very same file that the dependency is created on to store the data that each server needs to get in order to update its Cache.
There is a bug in ASP.NET 1.0 where multiple web applications having cachedependency on a file at a UNC share is not working. So one workaround is to have one file per web application per web server, and during update you would touch all of them. Another thing to remember about a server farm - if you are sharing Session state with StateServer or SQL Server, the SessionID, which is contained in a browser cookie or munged on the URL does get transmitted for the particular user no matter which server their request lands on.
So if you match the ASP.NET Session ID to the username+Password of the login, you have a method to check the Cache on any of the servers to handle both session checking and timeout updating. There is also an excellent article by David Burgett on MSDN about using in-memory Datasets and a WebService to synchronize data in a farm.
Cache Synchronization Down on the Farm
While creating a shared Cache object among servers on a farm is beyond the scope of this article, it is definitely "do-able" and hopefully the above ideas will give you some food for thought. Synchronization of Cache on a server farm is one thing that Mircrosoft left out of the Cache class. However, based on the ideas brought up in this article, it can be seen that there are likely a number of uses for such an arrangement.
One way to set up Cache synchronization among servers in a web farm is to use SQL Server and have two tables - one with a list of the servers currently active in the web farm, and a second table to hold "Update" information for the cache. This "CacheItems" table would probably need at least three or four columns: a varchar column for the cache "key" (in this case username+password), a DateTime column for current Sliding Expiration value, another DateTime column for Absolute Expiration (if used), and finally an IMAGE column to hold the byte stream from the serialized Object Graph of the Cache item, using the BinaryFormatter., in order to store complex objects from the Cache in the same way that SQL Server Session state does. In this manner it would be possibly not only to synchronize the Cache among servers in a farm, but to actually create a backup "Persistent Cache" datastore from which a rebooting or first - time farm member machine can hydrate its Cache and "join the chorus" , so to speak.
So for example, when a session expires in the Cache on one server, you can make an update using the SQL Server to a Cache persistent storage table. This update can made through a WebRequest which is sent to each of the servers on the farm to a special aspx receiver page that is in each app domain. This receiver page basically gets the "notification" and instructs the page to go to the SQL server and update it's resident copy of the Cache from the SQL Server table described above. Each machine would have a page that is capable of handling this process, and thus every machine on the Farm would have the capability both to update the backup store and notify the other webservers, as well as to receive a notification that it needs to retrieve and process the update record(s) from SQL Server.
Friday, February 6, 2009
Reading and Displaying Source of Web Pages
In asp.net 1.1
we have two ways
1.Dim arrbyte() as byte = System.Net.WebClient.DownloadData(URL)
2. Dim strHtml = System.Text.Encoding.Default.GetString(arrbyte)
Second Way is to create Webrequest and get webresponse
public void MySource(Uri weburi)
{
System.Text.StringBuilder sbuild=new StringBuilder();
string temp="";
System.Net.HttpWebRequest webrequest = (HttpWebRequest)System.Net.WebRequest.Create(weburi);
System.Net.HttpWebResponse webresponse=(HttpWebResponse) webrequest.GetResponse();
StreamReader webstream = new StreamReader(webresponse.GetResponseStream(),Encoding.ASCII );
while((temp=webstream.ReadLine())!= null)
{
sbuild.Append(temp + "\r\n");
}
webstream.Close();
Response.Write("OK");
Response.Write (sbuild.ToString() );
In asp.net 2.0
Dim strPageHTML as string= System.Net.WebClient.Dowloadstring(URL)
we have two ways
1.Dim arrbyte() as byte = System.Net.WebClient.DownloadData(URL)
2. Dim strHtml = System.Text.Encoding.Default.GetString(arrbyte)
Second Way is to create Webrequest and get webresponse
public void MySource(Uri weburi)
{
System.Text.StringBuilder sbuild=new StringBuilder();
string temp="";
System.Net.HttpWebRequest webrequest = (HttpWebRequest)System.Net.WebRequest.Create(weburi);
System.Net.HttpWebResponse webresponse=(HttpWebResponse) webrequest.GetResponse();
StreamReader webstream = new StreamReader(webresponse.GetResponseStream(),Encoding.ASCII );
while((temp=webstream.ReadLine())!= null)
{
sbuild.Append(temp + "\r\n");
}
webstream.Close();
Response.Write("OK");
Response.Write (sbuild.ToString() );
In asp.net 2.0
Dim strPageHTML as string= System.Net.WebClient.Dowloadstring(URL)
Wednesday, January 28, 2009
Sunday, January 25, 2009
Saturday, January 24, 2009
Subscribe to:
Posts (Atom)