Anda di halaman 1dari 69

Introduction

First of all I would like to thank all the readers who read and vote for my article. In
Beginner's Guide series, I have written some articles on state management. Probably this
is my last article on state management. Now coming back to the topic of this article
"Exploring Session in ASP.Net" . This article will give you a very good
understanding of session. In this article I have covered basic of session, different types of
storing session object, Session behavior in web farm scenarios , Session on Load
Balancer etc. I have also explained details of Session Behavior in a Live production
environment. Hope you will enjoy this article and provide your valuable suggestion and
feedback.

What is Session ?
Web is Stateless, which means a new instance of the web page class is re-created each
time the page is posted to the server. As we all know HTTP is a stateless protocol,
it can't hold the client information on page. If user inserts some information, and move to
the next page, that data will be lost and user would not able to retrieve the information.
So what we need? we need to store information. Session provides that facility to store
information on server memory. It can support any type of object to store along with our
custom object. For every client Session data store separately, means session
data is stored as per client basis. Have a look at the following diagram.

Fig : For every client session data store separately

State Management using session is one of the asp.net best features, because it is secure,
transparent from users and we can store any kind of object with in it. Along with
advantages, some times session can causes performance issue for heavy traffic sites
because its stored on server memory and clients read data from the server itself. Now lets
have a look at the advantages and disadvantages of using session in our web
application.
Advantages and Disadvantages of Session ?
Following are the basic advantages and disadvantages of using session. I have describe in
details with each type of session at later point of time.

Advantages :

• It helps to maintain user states and data to all over the application.
• It can easily be implemented and we can store any kind of object.
• Stores every client data separately.
• Session is secure and transparent from user.

Disadvantages :

• Performance overhead in case of large volume of user, because of session data


stored in server memory.
• Overhead involved in serializing and De-Serializing session Data. because In case
of StateServer and SQLServer session mode we need to serialize the object
before store.

Besides these, there are many advantages and disadvantages of session that are based of
session Types. I have Discussed all of them.

Storing and Retrieving values from Session


Storing and Retrieving values in session are quite similar with ViewState. We can
interact with Session State with System.Web.SessionState.HttpSessionState class,
because this provides built in Session Object with Asp.Net Pages.

Following code is used for storing a value to session

Collapse
//Storing UserName in Session
Session["UserName"] = txtUser.Text;

Now, let see how we can retrieve values from Session

Collapse
//Check weather session variable null or not
if (Session["UserName"] != null)
{
//Retrieving UserName from Session
lblWelcome.Text = "Welcome : " + Session["UserName"];
}
else
{
//Do Something else
}

we can also store some other object in Session. Following Example shows how to store a
DataSet in session.

Collapse
//Storing dataset on Session
Session["DataSet"] = _objDataSet;

and following code shows how we can retrieve that dataset from the session

Collapse
//Check weather session variable null or not
if (Session["DataSet"] != null)
{
//Retrieving UserName from Session
DataSet _MyDs = (DataSet)Session["DataSet"];
}
else
{
//Do Something else
}

Ref & for more Information: Read Session Variable Section

Session ID
Asp.Net use 120 bit identifier to track each session. This is secure enough and can't
be reverse engineered. When client communicate with server, only session id is
transmitted, between them. When client request for data, ASP.NET looks on to session
ID and retrieves corresponding data. This is done in following steps,

• Client hits web site and some information is stored in session.


• Server creates a unique session ID for that clients and stored in Session State
Provider .
• Again client request For some information with that unique session ID from
Server.
• Server,looks on Session Providers, and retrieve the serialized data from state
server and type cast the object .

Just have a look on the pictorial flow,


Fig : Communication of Client, web server, and State Provider

Ref. & for more Information: SessionID in MSDN

Session Mode and State Provider


In ASP.NET there are following session mode available,

• InProc
• StateServer
• SQLServer
• Custom

For every session State, there is Session Provider. Following diagram will show you how
they are related.

Fig : Session State Architecture

we can choose the session State Provider based on which session state we are selecting.
When ASP.NET request for any information based on session ID, session state and its
corresponding provider are responsible for sending the proper information based on user.
Following tables show, the session mode along with there provider Name.

Session State
State Provider
Mode
In-Memory
InProc
Object
StateServer Aspnet_state.exe
SQLServer DataBase
Custom CustomProvider

apart from that, there is another mode, "Off". If we select this option the session will be
disabled for the application. But our objective is to use session, so we will look into that
four session State Mode.

Ref. & for more Information: Session State Providers

Session States
If we consider about session state, It means all the settings that you have made for your
web application for maintaining the session. Session State, it self is a big thing, It says all
about your session configuration, Either in web.config or from code behind. In
web.config, <SessionState> elements used for setting the configuration of session.
Some of them are Mode, Timeout, StateConnectionString, Custom provider etc. I
have discussed about each and ever section of connection string. Before I discussed
Session Mode, just take a brief overview of Session Event

Session Event
There are two types of session events available in asp.net

• Session_Start
• Session_End

you can handle both this event in global.asax file of your web application. When a
new session initiate session_start event raised and Session_End event raised when a
session is abandoned or expired.

Collapse
void Session_Start(object sender, EventArgs e)
{
// Code that runs when a new session is started

void Session_End(object sender, EventArgs e)


{
// Code that runs when a session ends.

}
Ref. and for more Information :
Application and Session Events

Session Mode
I have already discussed about the session mode in ASP.NET, Following are the different
types of session modes available in ASP.Net.

• Off
• InProc
• StateServer
• SQLServer
• Custom

If we set Session Mode="off" in web.config, Session will be disabled to all over the
application. For this we need to configure web.config in following way

InPorc Session Mode :


This is the default session mode in Asp.Net. Its stores session Information in Current
Application Domain. This is the best session mode which is based on web application
Performance. But main disadvantage is that, It will lose the data if we restart the server.
There are some more advantages and disadvantages of InProc session mode. I will come
to those points again .

Overview of InProc Session Mode :


As I have already discussed InProc mode session data will be stored on the current
application domain. So It is easily and quickly available.
So, InProc session mode store its session data in a memory object on that application
domain. This is handled by worker process in application pool. So If we restart the
server we will lose the session data. If Client request for the data , state provide read the
data from In-Memory Object and return it to the client. In web.config we have to
mention Session mode and also we have to set the Timeout.

This Session TimeOut Setting keeps session alive for 30 minute. This can be configurable
from Code behind too.

Collapse
Session.TimeOut=30;

There are two type of session events available in asp.net Session_Start() and
Session_End. It is the only mode that supports the Session_End() event. These events
will call after the session timeout period is over. The general flow for the InProc Session
State is some thing like this.
Now, when the Session_End() will call that depends on Session Time Out. This is a
very fast mechanism because no serialization occurs for storing and retrieving data, and
data are staying inside the same application domain.

When Should we use InProc Session Mode ?


InProc is the default session mode. It can be very helpful for a small web sites and
where the number of user are very less, We should avoid InProc in case of Web
Garden (I will come to this topic in details) Scenario .

Advantages and Disadvantages


Advantages :

• It store Session data in memory object of current application domain. So


accessing data is very fast and data is easily available.
• There is not requirements of serialization to store data in InProc Session
Mode.
• Implementation is very easy, just similar to using View State.

Disadvantages :

although InProc Session is fastest, common and default mechanism, It has lots of
limitation.

• If the worker Process or application domain recycles all session data will be lost.
• Though its fastest, but more session data and more users can affects performance,
because of memory.
• we can't use it in web Garden scenarios .
• This session mode is not suitable for web farm scenarios also.

So as per above discussion, we can conclude InProc is very fast session storing
mechanism but suitable for small web application. InProc Session Data will get lost if we
Restart the server, application domain recycles It is also not suitable for Web Farm and
Web Garden Scenarios.

Now have a look that what are the other option available to overcome these problem.
First Come to StateServer Mode.

StateServer Session Mode :


Overview of StateServer Session Mode :

This is also called Out-Proc session mode. StateServer uses a stand-alone Windows
Services, which is Independent to IIS and can also run on a separate server. This session
state is totally managed by aspnet_state.exe. This server may runs on the same
system, but it's out side of that main application domain where your web application is
running. This allow if you restart your asp.net process restarted your session data will be
alive. This approaches has several disadvantages due to the overhead of serialization
and de-serialization, its also increases the cost of data access because of every time
when user retrieves session data, our application hits a different process.

Configuration for StateServer Session Mode

In StateServer the Session data is stored in a separate Server which is Independent to


IIS and Its handled by aspnet_state.exe. This process is run as windows Services. you
can start this service from windows MMC or from command prompt.
By default "Startup Type" of ASP.NET state service is set to manual, we have to set it
as "Automatic" startup type.

from command from just typing "net start aspnet_state". By default this services
listen TCP Port 42424 , but we can change the port from registry editor as given in
below picture .
Now have a look on the web.config configuration for StateServer Setting. For State
Server Setting we need have to specify the stateConnectionString. This will identify
the system that is running state server. By default stateConnectionString used ip as
127.0.0.1 (localhost) and Port 42424.

When we are using StateServer, we can configure stateNetworkTimeOut attributes to


specify the maximum number of seconds to wait for the service to respond before
canceling the request. Default time is 10 seconds.

For using StateServer, the object which we are going to store that should be serialized
and at the time of retrieving we need to De-Serialize. I have described it with an
Example.

How StateServer Session Mode Works?

We used StateServer Session Mode to avoid unnecessary session data loss during restart
of web Server. StateServer is maintained by process aspnet_state.exe as a windows
Services. This process maintains the all the session data. But need to serialize the data
before storing it in StateServer Session Mode.
As shown in above figure, that client request to the web server, then web server stores the
session data on state server. StateServer may be the current system or may be the
different system. But it is totally independent of IIS. Destination of StateServer
will depend on the web.config stateConnectionString attribute settings. If we set it
as 127.0.0.1:42424, It will store data on that local system itself. For change the
StateServer destination, we need to change the IP. and make sure, aspnet_state.exe is
up and running on that system. other wise you will get the following exception while
trying to store data on session.

When we are storing any object on session, that should be serialized. That data will be
stored to StateServer system using State Provider. and at the time of retrieving the data,
State provider will return the data. The complete flow is given in the below picture.
Example Of StateServer Session Mode :

Here is one simple example of using StateServer Session mode. I have created this
sample web application directly on the IIS so that we can easily understand its usage.

Step 1 : Open Visual Studio > File > New > Web Sites . Choose Location as
HTTP and create the web application .

Now if you open the IIS you will see a Virtual Directory created with the name of
your web application , as in my case it is StateServer.
Step 2 : Create s simple UI that will take Roll No and Name of a student . We will
store the name and roll in a state server session. I have also create one same class
"StudentInfo" . This class is given below

Collapse
[Serializable]
public class StudentInfo
{
//Default Constructor
public StudentInfo()
{

}
/// <summary>
/// Create object of student Class
/// </summary>
/// <param name="intRoll">Int RollNumber</param>
/// <param name="strName">String Name</param>
public StudentInfo(int intRoll, string strName)
{
this.Roll = intRoll;
this.Name = strName;
}

private int intRoll;


private string strName;
public int Roll
{
get
{
return intRoll;
}
set
{
intRoll = value;
}
}

public string Name


{
get
{
return strName;
}
set
{
strName = value;
}
}
}

Now , have a look on the code behind code. I have just added two button one for storing
session and another for retrieving session.
Collapse
protected void btnSubmit_Click(object sender, EventArgs e)
{

StudentInfo _objStudentInfo = new


StudentInfo(Int32.Parse( txtRoll.Text) ,txtUserName.Text);
Session["objStudentInfo"] = _objStudentInfo;
ResetField();
}
protected void btnRestore_Click(object sender, EventArgs e)
{
StudentInfo _objStudentInfo = (StudentInfo)
Session["objStudentInfo"];
txtRoll.Text = _objStudentInfo.Roll.ToString();
txtUserName.Text = _objStudentInfo.Name;

Step 3 : Please Configure your web.config for state server. As I have already
discussed. And Please make sure aspnet_state.exe is up and running on that
configured server.

Step 4 : Run the Application

Enter the data, Click on Submit.

Now there are following Test that I have made which will totally clear your doubts that
how exactly StateServer is useful.

First :Remove the [ Serializable ] key word from the studentinfo class and try to
run the application. When you will click on Submit Button you will get following error
Which clearly says that you should have to serialize the object before store.

Second: Run the Application, Store data by clicking on Submit Button. Restart IIS
Now, In case of InProc, you have already lost your session data, But Its StateServer,
Click on Restore Session, You will get your original data. Because State server data does
not depend on IIS. Its keeps it separately.

Third : Stop the aspnet_state.exe from the Windows Services MMC and Submit the
Data. You will get following error,

Because your State Server Process is not running.

So, Please keep in mind about those three points .

Advantages and Disadvantages.

So based on the above discussion

Advantages :

• Its keeps the data separate from IIS so, any Issue with IIS does not hamper
Session data.
• It is useful in web farm and web garden scenarios.

Disadvantages :

• Process is slow due to Serialization and De-Serialization


• State Server always need to be up and running.

Now, I am stopping here on StateServer, You will find some more interesting points on
Load balancer, Web Farm, Web Garden Section.

Ref & for more Information:

• State Server Session Mode


• Asp.Net Session State

SQL Server Session Mode :


Overview of SQL Server Session Mode :

This session mode provide us more secure and reliable Session management in
asp.net. In this session mode, the Session data is serialized and stored in the SQL
Server database. Main disadvantages of this session storage methods is overhead
related with Data Serialization and De-Serialization. It is the best option for
using in the web farms.
To setup SQL Server we need to take help of two sql Script.

• For Installing: InstallSqlState.sql


• For Un-Installing: UninstallSQLState.sql

The most easiest way to configure SQL Server, is using aspnet_regsql command.

I have explained the detailed use of these file in configuration section. This is the most
useful state management in the web farm scenario.

When should we use SQL Server Session Mode ?

• SQL Server Session mode is more reliable and secure session state management.
• Its keeps data in a centralized location (database).
• We should use SQL server session mode when we need to implement Session
with some more security.
• If there happens to be frequent server Restart we can implement SQL server.
• This is perfect mode that fits in web farm and web garden scenarios (I have
explained in details later) .
• we can use SQL server Session mode when we need to share session between two
different application .

Configuration for SQL Server Session Mode

In SQL Server Session mode, we are storing session data in a SQL Server, so we need to
first provide the database connection string in web.config . sqlConnectionString
attribute is used to provide the connection string in web.config.

After setup the connection string we need to configure the SQL Server. I am explaining
how to configure your your SQL server using aspnet_regsql command.

Step 1: From Command prompt, Go to your Framework Version Directory

E.g :c:\windows\microsoft.net\framework\<version>.

Step 2 : Run aspnet_regsql command with following parameters

Have a look on the parameter and there uses

Parameters Description
-ssadd Add support for SQLServer mode session
state.
-sstype p
P is stands for Persisted. Its persist the session
data on server
-S Specify Server Name
-U Specify User Name
-P Specify Password

After run you will get the following message,

that's all .

Step 3 : Open SQL Server Management Studio, Check, A new database ASPState has
been created and there should be two tables,

• ASPStateTempApplications
• ASPStateTempSessions

Now, Just change the configuration string of the State Server Example and Run the same
application that I have explained in State Server.

Just store Roll and User Name and Click on Submit button, and open
ASPStateTempSessions Table from SQL Server Management Studio.. WoW... here is
your session data,
Now. do the following Test that I have already explained in State Server Mode.

1. Remove The Serialize Key word from StydentInfo Class


2. Reset IIS and Click on Restore Session
3. Stop the SQL Server Services

I think I have explained the SQL Server Session mode well.

Advantages and Disadvantages


Advantages :

• Session data do not affected if we restart the IIS.


• It is the most reliable and secure session management.
• It keeps data located centrally , It can be easily accessible from other application.
• It is very useful in web farm and web garden scenarios.

Disadvantages :

• Processing is very slow in nature.


• Object serialization and de-serialization creates overhead for application.
• As the session data is handled in different server, so we have to take care of SQL
server. It should be always up and running.

Ref & for more Information : Read SQL Server Mode

Custom Session Mode


Overview of Custom Session Mode :

Generally we use either of InProc, StateServer or SQL Server Session mode for our
application, but we also need to know the fundamental of Custom Session mode. This
session mode is quite interesting, because Custom session gives full control to us to
create every thing even session ID. you can write your own algorithm to generate session
ID.

You can implement custom providers that store session data in other storage mechanisms
simply by deriving from SessionStateStoreProviderBase Class. You can also
Generate New Session Id by Implementing ISessionIDManager.
This are the following methods are called during implementation of Custom Session

In Initialize methods we can set the Custom Provider. This will initialize the connection
with that specified provider. SetItemExpireCallback used to set SessionTimeOut, we
can register any methods that will call at the time of session expire. InitializeRequest
is called on every request and CreateNewStoreData used to create a new instance of
SessionStateStoreData .

When should we use Custom Session Mode ?

we can use custom session mode in following of the cases,

• We want to store session data rather than SQL Server.


• When we have to use some Existing table to store session data.
• When we need to create our own session ID.

What configuration do we need for it?

We need to configure our web.config like below,

If you want to Explore some thing more please Check Further study section

Advantages and Disadvantages


Advantages :

• We can use some existing Table for the store session data, It is useful when we
have to use some old database rather than SQL Server.
• It's not depending on IIS , So Restarting web server does not make any effects on
session data.
• We can crate our own algorithm for generating Session ID.

Disadvantages :

• Processing of Data is very slow.


• Creating a custom state provider is a low-level task that needs to be handled
carefully to ensure security.

Its always recommended to use any third party provider rather than creating your own.

Ref and More Information : Custom Mode

If you want to know more about details session Mode please Read this MSDN Article

Overview of Production Deployment


Generally Production environments means when we deploy the application on our live
production server. This is a major and Big Challenge for the web developer to deploy
there application on Live Server. because in a Big production environment there are no of
user and its not possible to handle the load of so many users by a single server. Here the
concepts came of Web Farm, Load Balancer , Web Garden etc.

Just few month back I have deployed one of our web application In a live production
environment which is accessible by million of user and there were more than 10 Active
Directory, more than 10 Web Server Over Load Balancer and Many DB Server,
Exchange Server, LCS Server etc. If we look at the number of web server, this is
multiple. The major risk involves in multiple server is Session Management. Following
picture shows the general diagram for a Production environments.
I will try to explain the different scenario that you need to keep in mind while deploying
your application.

Application Pool :

This is one of the most important thing that you should create for your own application in
Production environment. Application pools used to separate sets of IIS worker
processes that share the same configuration. Application pools enable us to isolate our
web application for better security, reliability, and availability. The worker process serves
as the process boundary that separates each application pool so that when one worker
process or application is having an issue or recycles, other
applications or worker processes are not affected.

Identity Of Application Pool

Application pool identity configuration is an important aspect of security in IIS 6.0 and
IIS 7.0, because it determines the identity of the worker process when the process is
accessing resource. This Settings comes from IIS 6.0 In IIS 7.0 there are Three predefine
Identity , that are same with IIS 6.0.

Applicationpool Identity Description


LocalSystem is a built-in account that has
administrative privileges on the server. It
can access both local and remote
LocalSystem resources. For any kind accessing of server
files or resources we have to set the
Identity of application pool to Local
System.
LocalServices Built-in account has
LocalServices
privileges of an authenticated local user
account. It does not have any network
access permission
This is the default Identity of Application
NetworkServices Pool NetworkServices has privileges of
authenticated local user account.

Creating and Assigning Application Pool

Open IIS Console, Right Click on Application Pool Folder > Create New

Give the Application Pool ID and Click Ok.

Now, Right Click on the Virtual Directory (I am using StateServer Web sites) and assign
the StateServerAppPool to StateServer Virtual Directory.
So, this StateServer Web sites will run independently with StateServerAppPool. So
any problem related with other application does not affects your Application. This is the
main advantages of creating application pool separately.

Web Garden

By default Each Application Pool runs with a Single Worker Process (W3Wp.exe). We can
Assign multiple Worker Process With a Single Application Pool. An Application Poll
with multiple Worker process called Web Gardens. Many worker processes with
same Application Pool can sometimes provide better throughput performance and
application response time. And Each Worker Process Should have there own Thread
and Own Memory space.

As Given in Picture, in IIS Server there may be multiple Applicationpool and each
application pool having at least a single Worker Process. Web Garden should contain
multiple Worker process.
There are some Certain Restriction to use Web Garden with your web application. If we
use Session Mode to "in proc", our application will not work correctly because session
will be handled by different Worker Process. For Avoid this Type of problem we should
have to use Session Mode "out proc" and we can use "Session State Server" or
"SQL-Server Session State".

Main Advantage : The worker processes in a Web garden share the requests that arrive
for that particular application pool. If a worker process fails, another worker process can
continue to process requests.

How To Create Web Garden ?

Right Click on the Application Pool > Go To Performance Tab > Check Web
Garden Section (Highlighted in Picture )

By default it would be 1 , Just change it to more than one .

How Session Depends on Web Garden ?

I have already discuss that, InProc is handled by Worker Process. Its keeps data insides
its memory object. Now If we have multiple Worker Process, then It would be very
difficult to handled the session . because, Each and every Worker process has it own
memory, so If my first request goes to WP1 and its keep my session data and Second
Request goes to WP2 and I am trying to retrieve session data, it will not able to return .
Which will throw error. So please avoid Web Garden in InProc Session Mode.

we can use StateServer or SQLServer Session mode in case of Web Garden , because I
have already explained these two session mode does not depends on Worker Process . In
my example, I have also explain, If you restart the IIS then also you are able to get
session data.
In Short ,

Session Mode Recommended


InProc No
StateServer Yes
SQLServer Yes

Web Farm and Load Balancer:


This is the most common term that is used in production deployment . This terms comes,
when we are using Multiple Web Server for deploying our product. The main reason
behind the scene is to distribute the load over the multiple server. Load balancer is used
to distribute the load on those server.

If we check the diagram, Client request the url and it will hit a Load Balancer, which
decides which server to access. Load balancer will distribute the traffic over the all web
server.

Now how does it affects session

Handling Session in Web Farm and Load Balancer Scenarios

Handling session is one of the most challenging job in web farm .

InProc : In InProc session mode, session data stored in In-Memory Object of worker
process. So each and every server have its own Worker process and they keep session
data inside the memory.
If One server is down in time and request come to different server, user is not able to get
session data. So, it is not recommended to use InProc in Web Farm .

StateServer :I have already explained that what a state server is, how to configure a
StateServer etc. Now From this Web farm scenarios you can easily understand that how
much it is important, because all session data will be stored in a Single location .
Remember, In a web farm, make sure you have the same <machinekey> in all your web
servers. and Other things are all same as I have describe earlier. All web.config having
the same configuration (stateConnectionString) for Session State.

SQL Server : This is another approach even best one that we can use in web farm. We
need to configure the data base first. The steps I have already covered .

as shown in the above picture, all web server session data will be stored in a single SQL
Server Database. And it can be easily accessible. Keep one thing in mind, you should
serialize object in both state server and SQL Server mode. Any time if one of the web
server goes down, Load balancer distribute the loads to other server and that user can able
to read session data from server, because data is stored in a centralized DB server.

In summary, we can use either of state server or SQL server session mode in web farm .
We should avoid the InProc

Session And Cookies


Clients use cookies to work with session. because the client needs to present the
appropriate session ID with each request. we can do it in following ways
Using cookies: ASP.NET creates a special cookies named ASP.NET_SessionId
automatically when the session collection is used. This is the default. Session ID is
transmitted through that cookies .

Cookie Munging : Some older browser doest not support cookies or user may disable
cookies in there browser, in that case ASP.Net transmitted session ID in a specially
modified (or “munged”) URL.

How Cookie Munging Works ?

When user request for a page on a server, Server encoded the session id and add it with
every href link in page. When user click any links ASP.NET decodes that session id
and passes it to the page that user requesting. Now the requesting page can retrieve any
session variable. This all happens automatically, if ASP.NET detects that the users browser
does not support cookies.

How to Implement Cookie Munging ?

For that we have to make session state Cookie less.

Removing Session From Session Variable


Following are the list of methods that are used to removing the session .

Method Description
Session.Remove(strSessionName); Remove an Item from Session State Collection
Session.RemoveAll() Remove all items from session collection
Remove all items from session collection Note: There
Session.Clear() is no difference between Clear and RemoveAll.
RemoveAll() calls Clear(), internally.
Session.Abandon() Cancels the Current Session

Enabling and Disabling Session :


For performance optimization we can enable or disable session. because each and every
page read and write access of the page, and this involves some performance overhead. So
its always better to enable and disable session based on requirements rather than make it
enable always. we can enable and disable the session State in two ways:

• Page Level
• Application Level

Page Level :

we can disable session state in page level using EnableSessionState attributes with in
Page directive.

This will disable the session activities for that particular page

Same way we can make it read only also , It will permit to access session data, but it will
not allow writing data on session.

Application Level :

Session state can be disabled for all over the web application using EnableSessionState
property in Web.Config .

Generally we are use Page level, because some of page may not require any session data
or may be only read the session data.

Ref. & for more Information : How To Disable ASP.Net Session State in
ASP.NET

Summary
Now hope you are familiar with Session, Use of it, how to apply it in web farms etc in
ASP.NET 2.0. So as a summary,

• The In-Process(InProc) Session provider is the fastest method, because of


everything stored inside the memory. Session data will be loss if we restart web
server or if Worker Process Recycles. You can use in small web application
where number of users are less. Do not use InProc in Web Farm.
• In StateServer Session modes Session data maintain by aspnet_state.exe. Its
keeps session data out of Web server. So any issue with web server does not
affect session data. You need to Serialized object before storing data in
StateServer Session. we can use it in web farm also.
• SQLServer Session modes store data in SQL Server, we need to provide the
connection string. Here we also need to serialize the data before storing it to
session. This is very useful in production environment with web farm mode.
• we can use Custom provider for custom data source or when we need to use some
existing table to store session data. We can also create our custom sessionID in
Custom mode. But it is not recommended to create your own custom provider. Its
recommended to use any third party provider.

Forms Authentication and Role based Authorization: A


Quicker, Simpler, and Correct Approach

Problem Space
Sad, but true, “Forms authentication in ASP.NET does not directly support role based
authorization”. If you have ended up implementing Forms authentication along with
configuring authorization rules for “users” and “roles” in the web.config, you are going
to see the access rules working fine for “users”, but, not working at all for “roles”. You
might have thought, there must be some way to specify user roles in the famous
FormsAuthentication.RedirectFromLoginPage(), or, any other method. But, there
isn't!

Background
This is really surprising because, in real life, most applications (if not all) actually require
authorization of system resources based upon user roles, not user names. So, if you are
going to use Forms authentication in your upcoming ASP.NET application, and you need
to implement role based authorization in your system, you have a problem.

Wait, this is not entirely true, because of two reasons:

Reason 1: Since ASP.NET 2.0, we have Membership. It includes Membership (User)


service, Role service, and Profile (User properties) service. And, using Membership, you
can easily implement Role based authorization in your ASP.NET application.

Reason 2: Even if you don't use Membership, you can write some code to implement
Role based authorization in Forms authentication. Basically, you need to create the
authentication ticket yourself and push the user roles in the “UserData” property after
authenticating the user. Also, you need to retrieve user roles from the same “UserData”
property in the authentication ticket and set it in the current User property in the
subsequent requests. This trick works, and many have done this already.

So, What is this Article About?


Well, this article assumes that you did use Forms authentication directly instead of
ASP.NET Membership in your application for some good reasons. Consequently, you
implemented Role based authorization as suggested by lots of articles on the web (like
this one). But I tell you, you probably ended up doing an incorrect and incomplete
implementation, and you might have problems in the near future.

This article is going to address the problems with the suggested implementation
approaches, and provide you a correct, smart, and quick way of implementing Role based
authorization in case you are not using ASP.NET Membership in your system. All you'll
need is 5 minutes to implement this!

Please take a look at this article before you proceed, in case you are new to ASP.NET and
wondering about Forms Authentication.

OK, So What is the Problem with the Suggested


Approaches?
As was said already, the suggested approaches for implementing Role based
authorization have some problems, and I realized those while trying to implement them in
one of my ASP.NET applications. I did what was suggested in one of those articles, and
found that the authorization was working fine. But, in order to fulfill a client request, I
had to increase the cookie timeout property in the <forms> element and set it to “120”
(120 minutes), and found that, the timeout value change didn't have any impact on the
application. Exploring this, I was surprised to see that the system was never reading the
increased value; rather, it was always reading “30”, the default value.

I was curious to investigate this issue and found another problem. I specified
cookieless="UseUri" in the <forms> element, to test whether the Forms authentication
worked (by writing authentication ticket in the request URL) if cookies are disabled in
the client’s browser. Surprise again, now the system stopped authenticating the user!

Besides, I had a quick look at the authentication/authorization code (that was written to
implement Role based authorization as suggested), and thought, why do I have to write
all these codes? It should be fairly easy for anybody to implement it just by changing one
or two lines of code.

So, I decided to write my own code, and share it with you!

How Easy Is It for You to Use my Implementation?


Well, I assume that you already have implemented Forms authentication in your
application and configured stuff in the web.config. So, to implement Role based
authorization, now you just need to do following three easy things, requiring a maximum
of five minutes in total to implement.
• Add a reference to RoleBasedFormAuthentication.dll (which you can download
from this article, along with the source code) in your web site/project.
• Instead of calling the following method after authenticating the user:

Collapse

FormsAuthentication.RedirectFromLoginPage(userName,createPersista
ntCookie);

call the following method:

Collapse

FormsAuthenticationUtil.RedirectFromLoginPage(userName,
commaSeperatedRoles,
createPersistantCookie);

• Add the following code in the Global.asax file, or, change the code if it is already
there:

Collapse

protected void Application_AuthenticateRequest(Object


sender,EventArgs e)
{
FormsAuthenticationUtil.AttachRolesToUser();
}

That’s it, you are done.

Curious? Here are the Details


I created my version of the authentication/authorization code and had overcome the three
mentioned issues, as follows:

Solving the “timeout” Problem

While creating the FormsAuthenticationTicket object, we need to provide five


parameters. Take a look at the following method which creates the authentication ticket:

Collapse
/// <summary>
/// Creates and returns the Forms authentication ticket
/// </summary>
/// <param name="userName">User name</param>
/// <param name="commaSeperatedRoles">Comma separated roles for the
users</param>
/// <param name="createPersistentCookie">True or false
/// whether to create persistant cookie</param>
/// <param name="strCookiePath">Path for which the authentication
ticket is valid</param>
private static FormsAuthenticationTicket
CreateAuthenticationTicket(string userName,
string commaSeperatedRoles, bool createPersistentCookie, string
strCookiePath)
{
string cookiePath = strCookiePath == null ?
FormsAuthentication.FormsCookiePath : strCookiePath;
//Determine the cookie timeout value from web.config if specified
int expirationMinutes = GetCookieTimeoutValue();
//Create the authentication ticket
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1, //A dummy ticket version
userName, //User name for whom the ticket is issued
DateTime.Now, //Current date and time
DateTime.Now.AddMinutes(expirationMinutes), //Expiration date and
time
createPersistentCookie, //Whether to persist cookie on client side.
If true,
//The authentication ticket will be issued for new sessions from
the same client
//PC
commaSeperatedRoles, //Comma separated user roles
cookiePath); //Path cookie valid for
return ticket;
}

Note the third parameter DateTime.Now.AddMinutes(expirationMinutes). Here, we


expect the expirationMinutes variable’s value to be read from the timeout property in
the <forms> section. But, unfortunately, like the
FormsAuthentication.FormsCookiePath property (that reads the path configuration
value specified in the <forms> section), FormsAuthentication or any other class does
not give you any way to read the timeout property value. I don't know why.

So, I had to implement and use the following method to read the timeout property from
web.config (if it is specified) and set the value while creating the
FormsAuthenticationTicket object.

Collapse
/// <summary>
/// Retrieves cookie timeout value in the <forms></forms>
/// section in the web.config file as this
/// value is not accessible via the FormsAuthentication or any other
built in class
/// </summary>
/// <returns></returns>
private static int GetCookieTimeoutValue()
{
int timeout = 30; //Default timeout is 30 minutes
XmlDocument webConfig = new XmlDocument();
webConfig.Load(HttpContext.Current.Server.MapPath("web.config"));
XmlNode node = webConfig.SelectSingleNode("/configuration/" +
"system.web/authentication/forms");
if (node != null && node.Attributes["timeout"] != null)
{
timeout = int.Parse(node.Attributes["timeout"].Value);
}
return timeout;
}

After doing this, the system was able to read the “timeout” value from the web.config
properly and set it in the authentication ticket object.

Solving the “cookieless” Problem

If the “cookieless” property in the web.config is set to "UseUri", or if for any reason
the browser doesn't support cookies, or, if the browser has cookie support but disabled in
the settings, the Forms authentication writes the authentication ticket in the URL and
reads the ticket back on subsequent requests.

So, while we create the authentication ticket ourselves in order to implement Role based
authorization, we need to implement the same logic, otherwise we will have problems.
So, we need to determine whether we have to embed the ticket within a Cookie, or, we
have to write the ticket to the URL based on the situation described above. The following
code does this:

Collapse
/// <summary>
/// Creates Forms authentication ticket and writes it in URL or embeds
it within Cookie
/// </summary>
/// <param name="userName">User name</param>
/// <param name="commaSeperatedRoles">Comma separated roles for the
users</param>
/// <param name="createPersistentCookie">True or false whether
/// to create persistant cookie</param>
/// <param name="strCookiePath">Path for which
/// the authentication ticket is valid</param>
private static void SetAuthCookieMain(string userName, string
commaSeperatedRoles,
bool createPersistentCookie, string strCookiePath)
{
FormsAuthenticationTicket ticket =
CreateAuthenticationTicket(userName, commaSeperatedRoles,
createPersistentCookie, strCookiePath);
//Encrypt the authentication ticket
string encrypetedTicket = FormsAuthentication.Encrypt(ticket);
if (!FormsAuthentication.CookiesSupported)
{
//If the authentication ticket is specified not to use cookie,
set it in the URL
FormsAuthentication.SetAuthCookie(encrypetedTicket, false);
}
else
{
//If the authentication ticket is specified to use a cookie,
//wrap it within a cookie.
//The default cookie name is .ASPXAUTH if not specified
//in the <forms> element in web.config
HttpCookie authCookie = new
HttpCookie(FormsAuthentication.FormsCookieName,
encrypetedTicket);
//Set the cookie's expiration time to the tickets expiration
time
authCookie.Expires = ticket.Expiration;
//Set the cookie in the Response
HttpContext.Current.Response.Cookies.Add(authCookie);
}
}

The following piece of code does the main trick here:

Collapse
if (!FormsAuthentication.CookiesSupported)
{
//If the authentication ticket is specified not to use cookie, set
it in the URL
FormsAuthentication.SetAuthCookie(encrypetedTicket, false);
}

The FormsAuthentication.SetAuthCookie() method may be a misleading one. As the


name suggests, it seems to create the Forms authentication cookie with the authentication
ticket. Yes, it does. But, if cookies are not supported in the browser, it sets the encrypted
authorization ticket content into the URL. So now, if the browser doesn't support cookies,
Forms authentication and Role based authorization will work fine for us.

Please note that after changing the code as above, we also need to modify the code where
user roles are set on subsequent requests (in the Application_AuthenticateRequest()
event in Global.asax).

Collapse
/// <summary>
/// Adds roles to the current User in HttpContext
/// after forms authentication authenticates the user
/// so that, the authorization mechanism can authorize
/// user based on the groups/roles of the user
/// </summary>

if (HttpContext.Current.User != null)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
if (HttpContext.Current.User.Identity is FormsIdentity)
{
FormsIdentity id =
(FormsIdentity)HttpContext.Current.User.Identity;
FormsAuthenticationTicket ticket = (id.Ticket);
if (!FormsAuthentication.CookiesSupported)
{
//If cookie is not supported for forms authentication,
then the
//authentication ticket is stored in the URL, which is
encrypted.
//So, decrypt it
ticket = FormsAuthentication.Decrypt(id.Ticket.Name);
}
// Get the stored user-data, in this case, user roles
if (!string.IsNullOrEmpty(ticket.UserData))
{
string userData = ticket.UserData;
string[] roles = userData.Split(',');
//Roles were put in the UserData property in the
authentication ticket
//while creating it
HttpContext.Current.User =
new System.Security.Principal.GenericPrincipal(id,
roles);
}
}
}
}

I just added the following piece of code after FormsAuthenticationTicket ticket =


(id.Ticket);.

Collapse
if (!FormsAuthentication.CookiesSupported)
{
//If cookie is not supported for forms authentication, then the
//authentication ticket is stored in the URL, which is encrypted.
//So, decrypt it
ticket = FormsAuthentication.Decrypt(id.Ticket.Name);
}

So, this was the solution to the “cookieless” problem.

Decoupling the Codes in a Reusable DLL

The golden principle of “Encapsulation” says that you should encapsulate your
complexities to the outside world. So, why don't we encapsulate all this dirty nonsense
code into a box? Why don't we stay clean?

Being inspired to follow this principle, I created a Class Library


(“RoleBasedFormAuthentication”) and moved the entire authentication and authorization
related code there. I created a FormsAuthenticationUtil class inside the class library,
and implemented the following core reusable private methods inside it:

Private Methods
Collapse
/// <summary>
/// Creates and returns the Forms authentication ticket
/// </summary>
private static FormsAuthenticationTicket CreateAuthenticationTicket(…)

/// <summary>
/// Creates a Forms authentication ticket using the private
/// method CreateAuthenticationTicket() and writes
/// it in URL or embeds it within Cookie
/// </summary>
private static void SetAuthCookieMain(…)

/// <summary>
/// Creates a Forms authentication ticket and sets it within URL or
Cookie
/// using the SetAuthCookieMain() private method, and redirects
/// to the originally requested page
/// </summary>
private static void RedirectFromLoginPageMain(…)

The above three are the core methods that are being used by the public methods
exposed to the outside world. The following are the public methods (with their
overloaded versions) implemented inside the class:

Public Methods

Collapse
/// <summary>
/// Creates Forms authentication ticket and redirects
/// to the originally requested page. Uses the
/// RedirectFromLoginPageMain() private method
/// </summary>
public static void RedirectFromLoginPage(…)

/// <summary>
/// Creates a Forms authentication ticket and writes it
/// in URL or embeds it within Cookie. Uses the
/// SetAuthCookieMain() private method
/// </summary>
public static void SetAuthCookie(…)

/// <summary>
/// Adds roles to the current User in HttpContext
/// after forms authentication authenticates the user
/// so that, the authorization mechanism can authorize
/// user based on the groups/roles of the user
/// </summary>
public static void AttachRolesToUser()

These public methods are being called by the client web application to implement
Forms authentication and Role based authorization. Decoupling and implementing all
authorization and authorization related logic inside the class library allows us to
implement Role based authorization in our ASP.NET applications:

• In a small amount of time.


• In the correct way.
• In a cleaner and smarter way.

The Sample Project


Download the sample ASP.NET web site application (created using Visual Studio 2008,
Framework 3.5) and unzip it (FormsAuthorization.zip) into a convenient location. Open
the web site using Visual Studio, or, create an IIS site/virtual directory pointing to the
web root folder of the sample web site. Assuming that you have created the IIS
site/virtual directory, do the following to verify the authentication and Role based
authorization along with the mentioned issues.

Testing Authorization

• Hit the following URL in the browser:


http://localhost/FormsAuthorization/Admin/Default.aspx. The system will redirect
you to the login page. Provide “Administrator/123” as the login credential and
press “Login”. You will get a page where the “Hello Admin” message is
displayed.

Hit the same URL again by logging out, or, opening a new browser window/tab.
But, this time, provide “John/123” as the credential. The system will not let you
access the page; rather, the login screen will remain there intact.

Looking at the web.config file of the web site, you will see that only the “Admin”
role is allowed to access this URL and all other users are denied access. That is
why John’s credential (who is a member of the “User” role) cannot access the
URL that belongs to only the “Admin” role.

Collapse

<location path="Admin">
<system.web>
<authorization>
<allow roles="Admin"/>
<deny users="*"/>
</authorization>
</system.web>
</location>

• Hit the following URL in the browser:


http://localhost/FormsAuthorization/User/Default.aspx. The system will redirect
you to the login page. Provide “John/123” as the login credential and press
“Login”. You will get a page where a “Hello John” message is displayed.

Hit the same URL again by logging out, or, opening a new browser window/tab.
But, this time provide “Administrator /123” as the credential. The system will not
let you access the page; rather, the login screen will remain there intact.

Looking at the web.config file of the web site, you will see that only the “User”
role is allowed to access this URL and all other users are denied access. That is
why Admin’s credential (who is a member of the “Admin” role) cannot access the
URL that belongs to only the “User” role.

Collapse

<location path="User">
<system.web>
<authorization>
<allow roles="User"/>
<deny users="*"/>
</authorization>
</system.web>
</location>

• Hit the following URL in the browser:


http://localhost/FormsAuthorization/Public/Default.aspx. The system will display
“Hello, this is a public page”. As you can understand, this is a public page and no
credential is required to access this page.

Looking at the web.config file of the web site, you will see that all users are
allowed to access this URL. So, no login credential is required to access it.

Collapse

<location path="Public">
<system.web>
<authorization>
<allow users="*"/>
</authorization>
</system.web>
</location>

Testing the “timeout” Property

• Change the “timeout” property value and set it to “1” in the web.config file.

Collapse

<forms name="login" timeout="1" loginUrl="Login.aspx"></forms>


• Hit the following URL in the browser:
http://localhost/FormsAuthorization/Admin/Default.aspx and login using
“Administrator/123” as the login credential.
• Don't do anything for the next 1+ minute, and when a minute has passed, refresh
the page. The system will redirect you to the login page, because, the
authentication cookie has expired in the mean time. This indicates that the system
can read and apply the “timeout” property correctly from the web.config.

Testing the “cookieless” Property

• Change the “cookieless” property value and set it to “UseUri” in the


web.config.

Collapse

<forms name="login" timeout="120"


loginUrl="Login.aspx" cookieless="UseUri"></forms>

• Hit the following URL in the browser:


http://localhost/FormsAuthorization/Admin/Default.aspx and login using
“Administrator/123” as the login credential. The system will log you in
successfully.
• Take a look at the URL in the address bar. This should look something like the
following:

Collapse

http://localhost/FormsAuthorization/
(F(Oz5JC7onSkVsmb6....))/Admin/Default.aspx

You can see that the authentication ticket has been encrypted and included in the
URL (the actual URL should be a large one, and to save space, the remaining
parts of the encrypted ticket in the URL has been omitted using some dots). This
indicates that the system was able to write the authentication ticket in the URL
and perform authentication and authorization correctly.

Custom MembershipProvider and RoleProvider


Implementations that use Web Services

Introduction
In the latest release of ASP.NET 2.0, we have been introduced to a new set of controls
that allow the developer to add a reasonably rich security model and UI to an application
with very little effort. All these controls use providers that are loaded via configuration in
the web.config file.
By default, the developer is directed to using a local SQLEXPRESS database, however
she/he can easily redirect the default SQL based providers to use a different database via
a connection string. And it is simple to use the aspnet_regsql tool to create a new (or
update an existing) database with the database schema required.

Other third party providers have been created so that developers can use MySql[^],
ODBC[^] or even the good old web.config[^] to store the role/membership data and there
are plenty of resources out there for writing providers that will work with other databases
or alternate database schemas. However all of the providers published to date, AFAIK,
require that the application that is to be secured can access the database directly, this is
not always ideal or even possible in a commercial environment which has implemented
an n-tier structure and that the application can only access databases via a web service.

What I intend to demonstrate here is how to write your own set of providers that will be
compatible with the supplied ASP.NET controls and will be able to use a provider that is
being handled by a web service.

Requirements
Since we are implementing a web service, it would be nice if more than one application
could use the web service but use it to talk to different databases or even a different set of
providers. Also some implementations of the providers, e.g. the default SQL providers
SqlRoleProvider[^] and SqlMembershipProvider[^], allow you to store the roles and
users for more than one application within the same database.

With these requirements in mind, we need to make sure that we design our provider to
allow the developer to control which provider to use when the request reaches the web
service and the URL to the web service, choosing the application name is already a
property of the base provider classes. We are also lazy and wish to write as little code as
possible and use the framework to do the grunt work, that way we can concentrate on the
more exacting customer requirements which never stay still.

Implementing the RoleProvider Web Service


The role provider is the simplest of the two providers that we need to implement. Below
is a list of methods that the base role provider class supports that we need to support in
our web service:

Collapse
public abstract class RoleProvider : ProviderBase
{
// Methods
protected RoleProvider();
public abstract void AddUsersToRoles(string[] usernames,
string[] roleNames);
public abstract void CreateRole(string roleName);
public abstract bool DeleteRole(string roleName,
bool throwOnPopulatedRole);
public abstract string[] FindUsersInRole(string roleName,
string usernameToMatch);
public abstract string[] GetAllRoles();
public abstract string[] GetRolesForUser(string username);
public abstract string[] GetUsersInRole(string roleName);
public abstract bool IsUserInRole(string username,
string roleName);
public abstract void RemoveUsersFromRoles(string[] usernames,
string[] roleNames);
public abstract bool RoleExists(string roleName);

// Properties
public abstract string ApplicationName { get; set; }
}

The property is our only bit of state so we can pass that as a parameter in each call. Since
we want to be able to use any provider to implement this service, we need to have some
sort of configuration to allow developers to add new providers to a mix. Luckily this is
already provided to us by the framework and the entries that can be added in the
web.config of the web service application:

Collapse
<roleManager enabled="true">
<providers>
<clear />
<add applicationName="/"
connectionStringName="TrustSecurity"
name="AspNetSqlRoleProvider"
type="System.Web.Security.SqlRoleProvider" />
</providers>
</roleManager>

In order for us to use this configuration, we can use the


System.Web.Security.Roles.Providers collection with a provider name as set by the
name attribute in the web.config. Given that we have the provider name that we wish to
use and the application name we wish to use with that provider, we can use the following
method to select a provider and set the application name of that provider:
Collapse
protected System.Web.Security.RoleProvider GetProvider(
string providerName, string applicationName)
{
System.Web.Security.RoleProvider provider;
if ((providerName != null) &&
(System.Web.Security.Roles.Providers[providerName] != null))
{
provider = System.Web.Security.Roles.Providers[providerName];
}
else
{
provider = System.Web.Security.Roles.Provider;
}

if (applicationName != null)
{
provider.ApplicationName = applicationName;
}

return provider;
}

Using the above method now makes it very simple to define our web service interface:

Collapse
[WebMethod(Description="")]
public void AddUsersToRoles(string providerName,
string applicationName, string[] usernames, string[] roleNames)
{
GetProvider(providerName, applicationName).AddUsersToRoles(
usernames, roleNames);
}

[WebMethod(Description = "")]
public void CreateRole(string providerName, string applicationName,
string roleName)
{
GetProvider(providerName, applicationName).CreateRole(roleName);
}

[WebMethod(Description = "")]
public bool DeleteRole(string providerName, string applicationName,
string roleName, bool throwOnPopulatedRole)
{
return GetProvider(providerName, applicationName).DeleteRole(
roleName, throwOnPopulatedRole);
}

[WebMethod(Description = "")]
public string[] FindUsersInRole(string providerName,
string applicationName, string roleName, string usernameToMatch)
{
return GetProvider(providerName, applicationName).FindUsersInRole(
roleName,
usernameToMatch);
}

[WebMethod(Description = "")]
public string[] GetAllRoles(string providerName, string
applicationName)
{
return GetProvider(providerName, applicationName).GetAllRoles();
}

[WebMethod(Description = "")]
public string[] GetRolesForUser(string providerName,
string applicationName, string username)
{
return GetProvider(providerName, applicationName).GetRolesForUser(
username);
}

[WebMethod(Description = "")]
public string[] GetUsersInRole(string providerName,
string applicationName, string roleName)
{
return GetProvider(providerName, applicationName).GetUsersInRole(
roleName);
}

[WebMethod(Description = "")]
public bool IsUserInRole(string providerName, string applicationName,
string username, string
roleName)
{
return GetProvider(providerName, applicationName).IsUserInRole(
username, roleName);
}

[WebMethod(Description = "")]
public void RemoveUsersFromRoles(string providerName,
string applicationName, string[] usernames, string[] roleNames)
{
GetProvider(providerName, applicationName).RemoveUsersFromRoles(
usernames, roleNames);
}

[WebMethod(Description = "")]
public bool RoleExists(string providerName, string applicationName,
string roleName)
{
return GetProvider(providerName, applicationName).RoleExists(
roleName);
}

Implementing the Custom RoleProvider Class


A custom role provider implementation requires that we implement the required methods
provided by the System.Web.Security.RoleProvider abstract class. We also need
to add our custom configuration, as highlighted below, to the web.config and extract that
via the Initialize method:

Collapse
<roleManager defaultProvider="WebServiceRoleProvider" enabled="true">
<providers>
<clear />
<add applicationName="/"
name="WebServiceRoleProvider"
type="ManyMonkeys.SecurityProviders.WebServiceRoleProvider"
roleProviderUri="http://localhost/WTS/RoleProvider.asmx"
remoteProviderName="AspNetSqlRoleProvider" />
</providers>
</roleManager>
Collapse
public override void Initialize(string name,
System.Collections.Specialized.NameValueCollection config)
{
if (config["roleProviderUri"] != null)
{
service.Url = config["roleProviderUri"];
}

_ApplicationName = config["applicationName"];
if (string.IsNullOrEmpty(_ApplicationName))
{
_ApplicationName = ProviderUtility.GetDefaultAppName();
}

_RemoteProviderName = config["remoteProviderName"];

base.Initialize(name, config);
}

Now that the custom configuration has been extracted and our web service consumer has
been instantiated and initialized, the implementation of the rest of the required methods
looks like this:

Collapse
public override void AddUsersToRoles(string[] usernames,
string[] roleNames)
{
service.AddUsersToRoles(_RemoteProviderName,
_ApplicationName, usernames, roleNames);
}

public override void CreateRole(string roleName)


{
service.CreateRole(_RemoteProviderName,
_ApplicationName, roleName);
}
public override bool DeleteRole(string roleName,
bool throwOnPopulatedRole)
{
return service.DeleteRole(_RemoteProviderName,
_ApplicationName, roleName, throwOnPopulatedRole);
}

public override string[] FindUsersInRole(string roleName,


string usernameToMatch)
{
return service.FindUsersInRole(_RemoteProviderName,
_ApplicationName, roleName, usernameToMatch);
}

public override string[] GetAllRoles()


{
return service.GetAllRoles(_RemoteProviderName,
_ApplicationName);
}

public override string[] GetRolesForUser(string username)


{
return service.GetRolesForUser(_RemoteProviderName,
_ApplicationName, username);
}

public override string[] GetUsersInRole(string roleName)


{
return service.GetUsersInRole(_RemoteProviderName,
_ApplicationName, roleName);
}

public override bool IsUserInRole(string username,


string roleName)
{
return service.IsUserInRole(_RemoteProviderName,
_ApplicationName, username, roleName);
}

public override void RemoveUsersFromRoles(string[] usernames,


string[] roleNames)
{
service.RemoveUsersFromRoles(_RemoteProviderName,
_ApplicationName, usernames, roleNames);
}

public override bool RoleExists(string roleName)


{
return service.RoleExists(_RemoteProviderName,
_ApplicationName, roleName);
}

Implementing the MembershipProvider Web Service


All of the techniques used for the role provider implementation can be used to help
implement the MembershipProvider. However, by looking at the requirements of the
MembershipProvider, it is obvious this going to be a bit more complicated:

Collapse
public abstract class MembershipProvider : ProviderBase
{
// Methods
public abstract bool ChangePassword(string username,
string oldPassword, string newPassword);
public abstract bool ChangePasswordQuestionAndAnswer(
string username, string password,
string newPasswordQuestion,
string newPasswordAnswer);
public abstract MembershipUser CreateUser(string username,
string password, string email, string passwordQuestion,
string passwordAnswer, bool isApproved,
object providerUserKey, out MembershipCreateStatus status);
public abstract bool DeleteUser(string username,
bool deleteAllRelatedData);
public abstract MembershipUserCollection FindUsersByEmail(
string emailToMatch, int pageIndex, int pageSize,
out int totalRecords);
public abstract MembershipUserCollection FindUsersByName(
string usernameToMatch, int pageIndex, int pageSize,
out int totalRecords);
public abstract MembershipUserCollection GetAllUsers(
int pageIndex, int pageSize, out int totalRecords);
public abstract int GetNumberOfUsersOnline();
public abstract string GetPassword(string username,
string answer);
public abstract MembershipUser GetUser(
object providerUserKey, bool userIsOnline);
public abstract MembershipUser GetUser(string username,
bool userIsOnline);
public abstract string GetUserNameByEmail(string email);
public abstract string ResetPassword(string username,
string answer);
public abstract bool UnlockUser(string userName);
public abstract void UpdateUser(MembershipUser user);
public abstract bool ValidateUser(string username,
string password);

// Properties
public abstract string ApplicationName { get; set; }
public abstract bool EnablePasswordReset { get; }
public abstract bool EnablePasswordRetrieval { get; }
public abstract int MaxInvalidPasswordAttempts { get; }
public abstract int MinRequiredNonAlphanumericCharacters
{ get; }
public abstract int MinRequiredPasswordLength { get; }
public abstract int PasswordAttemptWindow { get; }
public abstract MembershipPasswordFormat PasswordFormat
{ get; }
public abstract string PasswordStrengthRegularExpression
{ get; }
public abstract bool RequiresQuestionAndAnswer { get; }
public abstract bool RequiresUniqueEmail { get; }
}

First, the read only properties look like they could cause trouble if we have to pass them
to the web service in each method call or fetch them from the web service each time they
are requested, however it seems that these properties are only applicable to the UI and so
there is no real need to pass these values to and from the web service in this exercise.
Secondly, there is an object called MembershipUser and collections of these objects that
we are going to find some way of passing back and forth. Unfortunately, when we look at
the MembershipUser object we see that this is not going to be as simple as exposing the
object in the WebMethod due to the number of read-only properties that are to be
transferred back and forth and can only be set by the constructor. A simple class that
contains all of the required properties can be used for this purpose:

Collapse
public class MembershipUser
{
public MembershipUser()
{
}

private string comment;

public string Comment


{
get { return comment; }
set { comment = value; }
}

private DateTime creationDate;

public DateTime CreationDate


{
get { return creationDate; }
set { creationDate = value; }
}

private string email;

public string Email


{
get { return email; }
set { email = value; }
}

private bool isApproved;

public bool IsApproved


{
get { return isApproved; }
set { isApproved = value; }
}
private bool isLockedOut;

public bool IsLockedOut


{
get { return isLockedOut; }
set { isLockedOut = value; }
}

private bool isOnline;

public bool IsOnline


{
get { return isOnline; }
set { isOnline = value; }
}

private DateTime lastActivityDate;

public DateTime LastActivityDate


{
get { return lastActivityDate; }
set { lastActivityDate = value; }
}

private DateTime lastLockoutDate;

public DateTime LastLockoutDate


{
get { return lastLockoutDate; }
set { lastLockoutDate = value; }
}

private DateTime lastLoginDate;

public DateTime LastLoginDate


{
get { return lastLoginDate; }
set { lastLoginDate = value; }
}

private DateTime lastPasswordChangedDate;

public DateTime LastPasswordChangedDate


{
get { return lastPasswordChangedDate; }
set { lastPasswordChangedDate = value; }
}

private string passwordQuestion;

public string PasswordQuestion


{
get { return passwordQuestion; }
set { passwordQuestion = value; }
}
private string providerName;

public string ProviderName


{
get { return providerName; }
set { providerName = value; }
}

private object providerUserKey;

public object ProviderUserKey


{
get { return providerUserKey; }
set { providerUserKey = value; }
}

private string userName;

public string UserName


{
get { return userName; }
set { userName = value; }
}
}

We now need methods that will allow us to convert between the different
MembershipUser objects and to create an array objects that can be transferred. We need
to note that when we create a System.Web.Security.MembershipUser object, we need
to provide the name of the provider that the object requires in its internal methods:

Collapse
protected System.Web.Security.MembershipProvider GetProvider(
string providerName, string applicationName)
{
System.Web.Security.MembershipProvider provider;
if ((providerName != null) &&
(System.Web.Security.Membership.Providers[providerName] != null))
{
provider = System.Web.Security.Membership.Providers[providerName];
}
else
{
provider = System.Web.Security.Membership.Provider;
}

if (applicationName != null)
{
provider.ApplicationName = applicationName;
}

return provider;
}

protected MembershipUser ConvertUser(


System.Web.Security.MembershipUser user)
{
if (user == null) return null;
MembershipUser membershipUser = new MembershipUser();
membershipUser.Comment = user.Comment;
membershipUser.CreationDate = user.CreationDate;
membershipUser.Email = user.Email;
membershipUser.IsApproved = user.IsApproved;
membershipUser.IsLockedOut = user.IsLockedOut;
membershipUser.IsOnline = user.IsOnline;
membershipUser.LastActivityDate = user.LastActivityDate;
membershipUser.LastLockoutDate = user.LastLockoutDate;
membershipUser.LastLoginDate = user.LastLoginDate;
membershipUser.LastPasswordChangedDate =
user.LastPasswordChangedDate;
membershipUser.PasswordQuestion = user.PasswordQuestion;
membershipUser.ProviderName = user.ProviderName;
membershipUser.ProviderUserKey = user.ProviderUserKey;
membershipUser.UserName = user.UserName;
return membershipUser;
}

protected System.Web.Security.MembershipUser
ConvertUser(System.Web.Security.MembershipProvider provider,
MembershipUser user)
{
if (user == null) return null;
System.Web.Security.MembershipUser membershipUser =
new System.Web.Security.MembershipUser(provider.Name,
user.UserName,
user.ProviderUserKey,
user.Email,
user.PasswordQuestion,
user.Comment,
user.IsApproved,
user.IsLockedOut,
user.CreationDate,
user.LastLoginDate,
user.LastActivityDate,
user.LastPasswordChangedDate,
user.LastLockoutDate);
return membershipUser;
}

protected List<MembershipUser>
BuildUserList(System.Web.Security.MembershipUserCollection
collection)
{
if (collection == null) return null;
List<MembershipUser> list = new List<MembershipUser>();
foreach (System.Web.Security.MembershipUser user in collection)
{
list.Add(ConvertUser(user));
}
return list;
}
With these helper classes in place, we can create our web service interface:

Collapse
[WebMethod(Description = "")]
public bool ChangePassword(string providerName,
string applicationName, string username,
string oldPassword, string newPassword)
{
return GetProvider(providerName, applicationName).ChangePassword(
username, oldPassword, newPassword);
}

[WebMethod(Description = "")]
public bool ChangePasswordQuestionAndAnswer(string providerName,
string applicationName, string username, string password,
string newPasswordQuestion, string newPasswordAnswer)
{
return GetProvider(providerName,
applicationName).ChangePasswordQuestionAndAnswer(username,
password, newPasswordQuestion, newPasswordAnswer);
}

[WebMethod(Description = "")]
public MembershipUser CreateUser(string providerName,
string applicationName, string username, string password,
string email, string passwordQuestion, string passwordAnswer,
bool isApproved, object providerUserKey,
out System.Web.Security.MembershipCreateStatus status)
{
return ConvertUser(GetProvider(providerName,
applicationName).CreateUser(
username, password, email, passwordQuestion, passwordAnswer,
isApproved,
providerUserKey, out status));
}

[WebMethod(Description = "")]
public bool DeleteUser(string providerName, string applicationName,
string username, bool
deleteAllRelatedData)
{
return GetProvider(providerName,
applicationName).DeleteUser(username,
deleteAllRelatedData);
}

[WebMethod(Description = "")]
public List<MembershipUser> FindUsersByEmail(string providerName,
string applicationName, string emailToMatch,
int pageIndex, int pageSize, out int totalRecords)
{
return BuildUserList(GetProvider(providerName,
applicationName).FindUsersByEmail(emailToMatch, pageIndex,
pageSize, out totalRecords));
}
[WebMethod(Description = "")]
public List<MembershipUser> FindUsersByName(string providerName,
string applicationName, string usernameToMatch,
int pageIndex, int pageSize, out int totalRecords)
{
return BuildUserList(GetProvider(providerName,
applicationName).FindUsersByName(usernameToMatch, pageIndex,
pageSize, out totalRecords));
}

[WebMethod(Description = "")]
public List<MembershipUser> GetAllUsers(string providerName,
string applicationName, int pageIndex,
int pageSize, out int totalRecords)
{
return BuildUserList(GetProvider(providerName,
applicationName).GetAllUsers(pageIndex, pageSize,
out totalRecords));
}

[WebMethod(Description = "")]
public int GetNumberOfUsersOnline(string providerName,
string applicationName)
{
return
GetProvider(providerName,
applicationName).GetNumberOfUsersOnline();
}

[WebMethod(Description = "")]
public string GetPassword(string providerName,
string applicationName, string username, string answer)
{
return GetProvider(providerName, applicationName).GetPassword(
username, answer);
}

[WebMethod(Description = "")]
public MembershipUser GetUserByUserName(string providerName,
string applicationName, string username, bool userIsOnline)
{
return ConvertUser(GetProvider(providerName,
applicationName).GetUser(username, userIsOnline));
}

[WebMethod(Description = "")]
public MembershipUser GetUserByKey(string providerName,
string applicationName, object providerUserKey,
bool userIsOnline)
{
return ConvertUser(GetProvider(providerName,
applicationName).GetUser(providerUserKey, userIsOnline));
}

[WebMethod(Description = "")]
public string GetUserNameByEmail(string providerName,
string applicationName, string email)
{
return
GetProvider(providerName,
applicationName).GetUserNameByEmail(email);
}

[WebMethod(Description = "")]
public string ResetPassword(string providerName,
string applicationName, string username, string answer)
{
return GetProvider(providerName,
applicationName).ResetPassword(username, answer);
}

[WebMethod(Description = "")]
public bool UnlockUser(string providerName,
string applicationName, string userName)
{
return
GetProvider(providerName, applicationName).UnlockUser(userName);
}

[WebMethod(Description = "")]
public void UpdateUser(string providerName,
string applicationName, MembershipUser user)
{
System.Web.Security.MembershipProvider provider =
GetProvider(providerName, applicationName);
provider.UpdateUser(ConvertUser(provider, user));
}

[WebMethod(Description = "")]
public bool ValidateUser(string providerName,
string applicationName, string username, string password)
{
return GetProvider(providerName, applicationName).ValidateUser(
username, password);
}

Implementing the Custom MembershipProvider Class


A custom membership provider implementation requires that we implement the required
methods provided by the System.Web.Security.MembershipProvider abstract class.
We also need to add our custom configuration, as highlighted below, to the web.config
and extract that via the Initialize method:

Collapse
<membership defaultProvider="WebServiceMembershipProvider"
userIsOnlineTimeWindow="20">
<providers>
<clear />
<add name="WebServiceMembershipProvider"
type="ManyMonkeys.SecurityProviders.WebServiceMembershipPro
vider"
enablePasswordRetrieval="false"
enablePasswordReset="true"
requiresQuestionAndAnswer="true"
passwordFormat="Hashed"
applicationName="/"
roleProviderUri="http://localhost/WTS/MembershipProvider.as
mx"
remoteProviderName="AspNetSqlMembershipProvider"/>
</providers>
</membership>
Collapse
public override void Initialize(string name,
System.Collections.Specialized.NameValueCollection config)
{
if (config["roleProviderUri"] != null)
{
service.Url = config["roleProviderUri"];
}
_ApplicationName = config["applicationName"];
if (string.IsNullOrEmpty(_ApplicationName))
{
_ApplicationName = ProviderUtility.GetDefaultAppName();
}

_EnablePasswordRetrieval = ProviderUtility.GetBooleanValue(
config, "enablePasswordRetrieval", false);
_EnablePasswordReset = ProviderUtility.GetBooleanValue(config,
"enablePasswordReset", true);
_RequiresQuestionAndAnswer =
ProviderUtility.GetBooleanValue(config,
"requiresQuestionAndAnswer", true);
_RequiresUniqueEmail = ProviderUtility.GetBooleanValue(config,
"requiresUniqueEmail", true);
_MaxInvalidPasswordAttempts = ProviderUtility.GetIntValue(config,
"maxInvalidPasswordAttempts", 5, false, 0);
_PasswordAttemptWindow = ProviderUtility.GetIntValue(config,
"passwordAttemptWindow", 10, false, 0);
_MinRequiredPasswordLength = ProviderUtility.GetIntValue(config,
"minRequiredPasswordLength", 7, false, 0x80);
_MinRequiredNonalphanumericCharacters =
ProviderUtility.GetIntValue(config,
"minRequiredNonalphanumericCharacters", 1, true, 0x80);
_PasswordStrengthRegularExpression =
config["passwordStrengthRegularExpression"];

if (config["passwordFormat"] != null)
{
_PasswordFormat =
(MembershipPasswordFormat)Enum.Parse(
typeof(MembershipPasswordFormat), config["passwordFormat"]);
}
else
{
_PasswordFormat = MembershipPasswordFormat.Hashed;
}
_RemoteProviderName = config["remoteProviderName"];

base.Initialize(name, config);
}

Methods are also required to turn the MembershipUser object from the web service
consumer into a real System.Web.Security.MembershipUser object that will be
recognized by the controls:

Collapse
static private MembershipProvider.MembershipUser
ConvertUser(System.Web.Security.MembershipUser user)
{
if (user == null) return null;
MembershipProvider.MembershipUser membershipUser =
new MembershipProvider.MembershipUser();
membershipUser.Comment = user.Comment;
membershipUser.CreationDate = user.CreationDate;
membershipUser.Email = user.Email;
membershipUser.IsApproved = user.IsApproved;
membershipUser.IsLockedOut = user.IsLockedOut;
membershipUser.IsOnline = user.IsOnline;
membershipUser.LastActivityDate = user.LastActivityDate;
membershipUser.LastLockoutDate = user.LastLockoutDate;
membershipUser.LastLoginDate = user.LastLoginDate;
membershipUser.LastPasswordChangedDate =
user.LastPasswordChangedDate;
membershipUser.PasswordQuestion = user.PasswordQuestion;
membershipUser.ProviderName = user.ProviderName;
membershipUser.ProviderUserKey = user.ProviderUserKey;
membershipUser.UserName = user.UserName;
return membershipUser;
}

private System.Web.Security.MembershipUser
ConvertUser(MembershipProvider.MembershipUser user)
{
if (user == null) return null;
System.Web.Security.MembershipUser membershipUser =
new System.Web.Security.MembershipUser(this.Name,
user.UserName,
user.ProviderUserKey,
user.Email,
user.PasswordQuestion,
user.Comment,
user.IsApproved,
user.IsLockedOut,
user.CreationDate,
user.LastLoginDate,
user.LastActivityDate,
user.LastPasswordChangedDate,
user.LastLockoutDate);
return membershipUser;
}
private System.Web.Security.MembershipUserCollection
BuildUserCollection(MembershipProvider.MembershipUser[] list)
{
if (list == null) return null;
System.Web.Security.MembershipUserCollection collection =
new System.Web.Security.MembershipUserCollection();
foreach (MembershipProvider.MembershipUser user in list)
{
collection.Add(ConvertUser(user));
}
return collection;
}

With the help of these helper methods, it is now a trivial task to finish our
implementation once we have instantiated the web service consumer:

Collapse
public override bool ChangePassword(string username,
string oldPassword, string newPassword)
{
return service.ChangePassword(_RemoteProviderName,
_ApplicationName, username, oldPassword, newPassword);
}

public override bool ChangePasswordQuestionAndAnswer(


string username, string password,
string newPasswordQuestion, string newPasswordAnswer)
{
return service.ChangePasswordQuestionAndAnswer(
_RemoteProviderName, _ApplicationName, username,
password, newPasswordQuestion, newPasswordAnswer);
}

public override MembershipUser CreateUser(string username,


string password, string email, string passwordQuestion,
string passwordAnswer, bool isApproved,
object providerUserKey, out MembershipCreateStatus status)
{
MembershipProvider.MembershipCreateStatus newStatus;
MembershipUser user =
ConvertUser(service.CreateUser(_RemoteProviderName,
_ApplicationName, username, password, email,
passwordQuestion, passwordAnswer, isApproved,
providerUserKey, out newStatus));
status =
(MembershipCreateStatus)Enum.Parse(typeof(MembershipCreateStatus),
newStatus.ToString());
return user;
}

public override bool DeleteUser(string username,


bool deleteAllRelatedData)
{
return service.DeleteUser(_RemoteProviderName,
_ApplicationName, username, deleteAllRelatedData);
}

public override MembershipUserCollection FindUsersByEmail(


string emailToMatch, int pageIndex, int pageSize,
out int totalRecords)
{
return BuildUserCollection(service.FindUsersByEmail(
_RemoteProviderName, _ApplicationName, emailToMatch,
pageIndex, pageSize, out totalRecords));
}

public override MembershipUserCollection FindUsersByName(


string usernameToMatch,
int pageIndex, int pageSize, out int totalRecords)
{
return BuildUserCollection(service.FindUsersByName(
_RemoteProviderName, _ApplicationName, usernameToMatch,
pageIndex, pageSize, out totalRecords));
}

public override MembershipUserCollection GetAllUsers(


int pageIndex, int pageSize, out int totalRecords)
{
return BuildUserCollection(service.GetAllUsers(
_RemoteProviderName, _ApplicationName, pageIndex,
pageSize, out totalRecords));
}

public override int GetNumberOfUsersOnline()


{
return service.GetNumberOfUsersOnline(_RemoteProviderName,
_ApplicationName);
}

public override string GetPassword(string username, string answer)


{
return service.GetPassword(_RemoteProviderName,
_ApplicationName, username, answer);
}

public override MembershipUser GetUser(string username,


bool userIsOnline)
{
return ConvertUser(service.GetUserByUserName(
_RemoteProviderName, _ApplicationName,
username, userIsOnline));
}

public override MembershipUser GetUser(object providerUserKey,


bool userIsOnline)
{
return ConvertUser(service.GetUserByKey(_RemoteProviderName,
_ApplicationName, providerUserKey, userIsOnline));
}

public override string GetUserNameByEmail(string email)


{
return service.GetUserNameByEmail(_RemoteProviderName,
_ApplicationName, email);
}

public override string ResetPassword(string username,


string answer)
{
return service.ResetPassword(_RemoteProviderName,
_ApplicationName, username, answer);
}

public override bool UnlockUser(string userName)


{
return service.UnlockUser(_RemoteProviderName,
_ApplicationName, userName);
}

public override void UpdateUser(MembershipUser user)


{
service.UpdateUser(_RemoteProviderName,
_ApplicationName, ConvertUser(user));
}

public override bool ValidateUser(string username,


string password)
{
return service.ValidateUser(_RemoteProviderName,
_ApplicationName, username, password);
}

That's it, we now have a pair of providers that can use any other provider
implementations via a web service and thus require that our application does not require
direct access to the database server.

Anda mungkin juga menyukai