Wednesday, September 11, 2013

ASP.Net Tutorial Series IV


Sending emails using asp.net - Part 77

Suggested Videos
Part 74 - Logging exceptions as information entry type in windows eventviewer
Part 75 - Logging exceptions to database
Part 76 - Customizing asp.net exception Logging

In the previous sessions of this video series, we have discussed about logging exceptions to database and to the windows eventviewer. In this session, sending an email to the development team or administrator along with logging exceptions.

To compose the email message, use MailMessage calss. To send the email use, SmtpClient class. Both of these classes are present in System.Net.Mail namespace.



public static void SendEmail(string emailbody)
{
   
// Specify the from and to email address
   
MailMessage mailMessage = new MailMessage("from_email@gmail.com", "To_Email@gmail.com");
   
// Specify the email body
    mailMessage.Body = emailbody;
   
// Specify the email Subject
    mailMessage.Subject =
"Exception";

   
// Specify the SMTP server name and post number
   
SmtpClient smtpClient = new SmtpClient("smtp.gmail.com", 587);
   
// Specify your gmail address and password
    smtpClient.Credentials = new System.Net.NetworkCredential()
    {
        UserName =
"from_email@gmail.com", Password = "your_password"
    };
   
// Gmail works on SSL, so set this property to true
    smtpClient.EnableSsl =
true;
   
// Finall send the email message using Send() method
    smtpClient.Send(mailMessage);
}



SendEmail() method can then be called in our Logger class. Pass in the exception string as an input parameter.
SendEmail(sbExceptionMessage.ToString());

If you want the capability of sending emails to be configurable, add the following key in web.config.
<appSettings>
  
<add key="SendEmail" value="true"/>
</appSettings>

Read "SendEmail" key from web.config. If SendEmail is set to true only then, send the email.
string sendEmail = ConfigurationManager.AppSettings["SendEmail"];
if (sendEmail.ToLower() == "true")
{
    SendEmail(sbExceptionMessage.ToString());
}

In this video we discussed about sending emails using gmail smtp server and credentials. In reality organisations have their own SMPT server. If you want to send emails using your own SMTP server, use the respective smtp server address and credentials.

Sending emails in asp.net using SMTP server settings from web.config - Part 78

Suggested Videos
Part 75 - Logging exceptions to database
Part 76 - Customizing asp.net exception Logging
Part 77 - Sending emails using asp.net

In the previous session, we discussed about sending emails using asp.net. All the SMTP server settings were configured in code. In this session, we will discuss about specifying these settings in web.config file.



Web.config settings for SMTP server. All the attributes here are self explanatory.
<system.net>
  
<mailSettings>
    
<smtp deliveryMethod="Network" >
      
<network host="smtp.gmail.com" enableSsl="true" port="587"
       
userName="your_email@gmail.com" password="your_password"/>
   
</smtp>
  
</mailSettings>
</system.net>



Since the SMTP server settings are now configured in web.config. In code all you have to do is
1. Create an instance of the MailMessage class. Specify the FROM & TO email address, subject and Body
2. Create an instance of SmtpClient class, and send the email using Send() method. The SMTP settings will be automatically picked up from web.config file, when sending email.

SendEmail() method code
public static void SendEmail(string emailbody)
{
   
// Specify the from and to email address
   
MailMessage mailMessage = new MailMessage
        (
"from_email@gmail.com", "to_email@gmail.com");
   
// Specify the email body
    mailMessage.Body = emailbody;
   
// Specify the email Subject
    mailMessage.Subject =
"Exception";

    // No need to specify the SMTP settings as these 
    // are already specified in web.config
   
SmtpClient smtpClient = new SmtpClient();
   
// Finall send the email message using Send() method
    smtpClient.Send(mailMessage);
}

Tracing in asp.net - Part 79

Suggested Videos
Part 76 - Customizing asp.net exception Logging
Part 77 - Sending emails using asp.net
Part 78 - Sending emails in asp.net using SMTP server settings from web.config

Tracing enables us to view diagnostic information about a request and is very useful when debugging application problems. 

In a later video session, we will discuss about, how tracing can be used to solve a performance related problem.



Tracing can be turned on or off 
1. At the application level or
2. At the page level

To enable tracing at the application level set "trace" element's "enabled" attribute to "true" in web.config. This enables tracing for all pages in the application.
<trace enabled="true"/>

To disable tracing for specific pages, set Trace="false" in the webform's "Page" directive
<%
@ Page Language="C#" Trace="false" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="WebApplication1.WebForm1" %>



If tracing is enabled at the application level, the trace messages are written to a file called trace.axd. Trace.xd file can only be accessed locally. To make the trace file available to remote users set localOnly="false". Tracing displays sensitive information, that could be useful to a hacker to hack into the server. So set this attribute to "false" only when it is required.
<trace enabled="true" localOnly="false"/>

To append trace messages at the end of the page set pageOutput="true".
<trace enabled="true" pageOutput="true" localOnly="false"/>

Use RequestLimit attribute to control the number of trace requests that are stored on the server. The default is 10. After this limit is reached, the sever will stop writing to the trace file.
<trace enabled="true" pageOutput="true" requestLimit="5" localOnly="false"/>

If you want to log trace messages, even after the requestLimit has reached set mostRecent attribute to true. When this attribute is set to true, the old entries in the trace log are discarded and the new entries get added.
<trace enabled="true" mostRecent="true" requestLimit="3"/>

Writing custom asp.net tracing messages - Part 80

Suggested Videos
Part 77 - Sending emails using asp.net
Part 78 - Sending emails in asp.net using SMTP server settings from web.config
Part 79 - Tracing in asp.net

To write custom asp.net trace messages Trace.Write() and Trace.Warn() methods can be used. The only difference, between these 2 methods is that, the messages written using Trace.Warn() are displayed in red colour, where as the messages written using Trace.Write() are displayed in black colour. In fact, What is the difference between Trace.Write() and Trace.Warn() is a very common asp.net interview question.



Webform1.aspx HTML:
<div style="font-family: Arial">
    <table style="border: 1px solid black">
        <tr>
            <td>
                <b>First Number</b>
            </td>
            <td>
                <asp:TextBox ID="txtFirstNumber" runat="server">
                </asp:TextBox>
            </td>
        </tr>
        <tr>
            <td>
                <b>Second Number</b>
            </td>
            <td>
                <asp:TextBox ID="txtSecondNumber" runat="server">
                </asp:TextBox>
            </td>
        </tr>
        <tr>
            <td colspan="2">
                <asp:Button ID="btnDivide" runat="server" Text="Divide"
                OnClick="btnDivide_Click" />
            </td>
        </tr>
        <tr>
            <td colspan="2">
                <asp:Label ID="lblMessage" runat="server" Font-Bold="true">
                </asp:Label>
            </td>
        </tr>
    </table>
</div>



WebForm1.aspx.cs code behind:
protected void btnDivide_Click(object sender, EventArgs e)
{
   
try
    {
       
int FirstNumber = Convert.ToInt32(txtFirstNumber.Text);
       
int SecondNumber = Convert.ToInt32(txtSecondNumber.Text);

        lblMessage.ForeColor = System.Drawing.
Color.Navy;
       
int Result = FirstNumber / SecondNumber;
        lblMessage.Text = Result.ToString();
    }
   
catch (FormatException formatException)
    {
        lblMessage.ForeColor = System.Drawing.
Color.Red;
        lblMessage.Text =
"Only numbers are allowed";
       
// Check if tracing is enabled
       
if (Trace.IsEnabled)
        {
           
// Write the exception message to the trace log
            Trace.Write(formatException.Message);
        }
    }
   
catch (OverflowException overflowException)
    {
        lblMessage.ForeColor = System.Drawing.
Color.Red;
        lblMessage.Text =
"Numbers must be between " + Int32.MinValue.ToString() + " and " + Int32.MaxValue.ToString();
       
if (Trace.IsEnabled)
        {
            Trace.Warn(overflowException.Message);
        }
    }
   
catch (DivideByZeroException divideByZeroException)
    {
        lblMessage.ForeColor = System.Drawing.
Color.Red;
        lblMessage.Text =
"Denominator cannot be ZERO";
       
if (Trace.IsEnabled)
        {
            Trace.Warn(divideByZeroException.Message);
        }
    }
    catch (Exception exception)
    {
        lblMessage.ForeColor = System.Drawing.
Color.Red;
        lblMessage.Text =
"An unknown problem has occured. Please try later";
       
if (Trace.IsEnabled)
        {
            Trace.Warn(exception.Message);
        }
    }
}

Trace.IsEnabled property can be used to check if tracing is enabled. In the following code Trace.Write() method is invoked only if tracing is enabled. If you don't check, Trace.IsEnabled property prior to writing out trace messages, we don't get an exception. But if you are going to do any sort of significant work to build the trace message, then you can avoid that work by checking the IsEnabled property first.
if (Trace.IsEnabled)
{
    Trace.Write(
"Debugging information");
}

With classic ASP, the only option for printing debugging information is Response.Write(). There are 2 problems with this
1. Actual end users also, can see the debugging information that you have written using Response.Write(). But with tracing, if pageoutput attribute is set to false, then the trace messages are written to the trace.axd file. End users cannot see the trace information.
2. All the Response.Write() statements must be removed, before the application is deployed to production. But with tracing, all you have to do is disable tracing in web.config.

Tracing in asp.net - A real time example - Part 81

Suggested Videos
Part 78 - Sending emails in asp.net using SMTP server settings from web.config
Part 79 - Tracing in asp.net
Part 80 - Writing custom asp.net tracing messages

ASP.NET Page is very slow. What will you do to make it fast?
This is a very common interview question. There are several reasons for the page being slow. We need to identify the cause.

We cannot debug an application on the production server, as we will usually, not have visual studio installed, and as the code is optimized for release builds. 



Step 1:
First find out which is slow, is it the application or the database : If the page is executing SQL queries or stored procedures, run those on the database and check how long do they take to run. Alternatively, SQL profiler, can be used, to inspect the queries and the time they take. If the queries are taking most of the time, then you know you have to tune the queries for better performance. To tune the queries, there are several ways and I have listed some of them below.
    a) Check if there are indexes to help the query
    b) Select only the required columns, avoid Select *.
    c) Check if there is a possibility to reduce the number of joins
    d) If possible use NO LOCK on your select statements
    e) Check if there are cursors and if you can replace them with joins



Step 2:
If the queries are running fast, then we know it is the application code that is causing the slowness. Isolate the page event that is causing the issue by turning tracing on. To turn tracing on, set Trace="true" in the page directive. Once you have tracing turned on you should see trace information at the bottom of the page or in trace.axd file. From the trace information, it should be clear to identify the piece of code that is taking the maximum time.

Stored Procedures used in the Demo
Create proc spGetEmployees
as
begin
 select Id, Name, Gender, DeptName
 
from tblEmployees
end

Create proc spGetEmployeesByGender
as
begin
 select Gender, Count(Gender) as Total
 
from tblEmployees
 
Group by Gender
end

Create proc spGetEmployeesByDepartment
as
begin
 select DeptName, Count(DeptName) as Total
 
from tblEmployees
 
Group by DeptName
end

ASPX page HTML:
<div style="font-family: Arial">
    <b>All employees</b>
    <asp:GridView ID="gvAllEmployees" runat="server">
    </asp:GridView>
    <br /><br />
    <b>Total Employees by Department</b>
    <asp:GridView ID="gvEmployeesByDepartment" runat="server">
    </asp:GridView>
    <br /><br />
    <b>Total Employees by Gender</b>
    <asp:GridView ID="gvEmployeesByGender" runat="server">
    </asp:GridView>
</div>

Code Behind:
protected void Page_Load(object sender, EventArgs e)
{
    Trace.Warn(
"GetAllEmployees() started");
    GetAllEmployees();
    Trace.Warn(
"GetAllEmployees() Complete");

    Trace.Warn(
"GetEmployeesByGender() started");
    GetEmployeesByGender();
    Trace.Warn(
"GetEmployeesByGender() Complete");

    Trace.Warn(
"GetEmployeesByDepartment() started");
    GetEmployeesByDepartment();
    Trace.Warn(
"GetEmployeesByDepartment() Complete");
}

private void GetAllEmployees()
{
    gvAllEmployees.DataSource = ExecuteStoredProcedure(
"spGetEmployees");
    gvAllEmployees.DataBind();
}

private void GetEmployeesByGender()
{
    gvEmployeesByGender.DataSource = ExecuteStoredProcedure(
"spGetEmployeesByGender");
    gvEmployeesByGender.DataBind();
}

private void GetEmployeesByDepartment()
{
    gvEmployeesByDepartment.DataSource = ExecuteStoredProcedure(
"spGetEmployeesByDepartment");
    gvEmployeesByDepartment.DataBind();
}

private DataSet ExecuteStoredProcedure(string spname)
{
   
string CS = ConfigurationManager.ConnectionStrings["DBConnectionString"].ConnectionString;
   
SqlConnection con = new SqlConnection(CS);
   
SqlDataAdapter da = new SqlDataAdapter(spname, con);
    da.SelectCommand.CommandType =
CommandType.StoredProcedure;
   
DataSet DS = new DataSet();
    da.Fill(DS);
   
if (spname == "spGetEmployeesByGender")
    {
        System.Threading.
Thread.Sleep(7000);
    }
    return DS;
}

Set Trace=true, in Page directive:
<%
@ Page Language="C#" Trace="true" AutoEventWireup="true" CodeBehind="WebForm3.aspx.cs" Inherits="AdoDemo.WebForm3" %>

Many a times, the issue is not reproducible on the development environment. The issue happens only on the production server. In this case tracing is an invaluable mechanism to get to the root cause of the issue.

Application pools in IIS - Part 82

Suggested Videos
Part 79 - Tracing in asp.net
Part 80 - Writing custom asp.net tracing messages
Part 81 - Tracing in asp.net - A real time example

In this video we will discuss about
1. What are application pools in IIS
2. Creating application pools in internet information services(IIS)
3. Application pool identities
4. Associating an ASP.NET Web Application with an Application Pool



What are application pools in IIS
An Application Pool can contain one or more web applications. In IIS it is possible to create one or more application pools. Applications in different application pools, runs in its own worker process(w3wp.exe). Errors in one application pool will not affect the applications running in other application pools. For example, if an application pool is recycled, only the applications in that pool are affected(may loose state information if stored inside worker process), and applications in other application pools are unaffected. Deploying applications to different application pools enables us to achieve the degree of application isolation that we need, in terms of availability and security. For example, applications that require high security can be present in one application pool, and the other applications can be in a different application pool. Another example, hosting providers can place competing business applications in different application pools, so that they do not accidentally access the data belonging to their competitor.



Creating application pools in internet information services(IIS)
1. Click on start
2. Type "RUN" and press "ENTER"
3. In the "RUN" window, type "INETMGR"
4. Click "OK"
5. In the IIS Manager window, expand the root node and right click on "Application Pools" and select "Add Application Pool"
6. Provide the "Name" for Application pool and click OK.

Application pool identities
Asp.net applications execute inside asp.net worker process called w3wp.exe. The applications are executed by the worker process, using a windows identity. The windows identity that is used, is dependent on the application pool idenity. The application pool identity can be any of the following built in aaccounts
1. LocalService
2. LocalSystem
3. NetworkService
4. ApplicationPoolIdentity

In addition to these built-in accounts, we can also use a custom account, by specifying the username and password.

By default, when a new application pool is created, it uses ApplicationPoolIdentity. To change the application pool identity
1. Right click on the application pool and select "Advanced Settings"
2. In the "Advanced Settings", click the ellipses button next to "Identity" under "Process Model" section
3. From the "Application Pool Identity" window, select one of the built-in accounts or enter the user and password, if you choose to use a custom account.
4. Finally click "OK"
Changing application pool identity

Local System : Completely trusted account and has very high privileges and can also access network resources.

Network Service : Restricted or limited service account that is generally used to run, standard least-privileged services. This account has less privileges than Local System account. This account can access network resources.

Local Service : Restricted or limited service account that is very similar to Network Service and meant to run standard least-privileged services. This account cannot access network resources.

ApplicationPoolIdentity : When a new Application Pool is created, IIS creates a virtual account with the name of the new Application Pool and run the Application Pool's worker processes under this account. This is also a least previlaged account.

Running an application using a low-privileged account is a good security practice, because, if there is a bug, that cannot be used by a malicious user to hack into your application or your system.

Associating an ASP.NET Web Application with an Application Pool
1. Create a new asp.net web application project with name "SecurityDemo" in C:\
2. Open IIS (Type INETMGR in RUN window, and click OK)
3. Expand IIS root node
4. Expand "Sites"
5. Right click on "Default Web Site" and select "Add Application"
6. Enter the Alias Name for your application
7. Select the physical folder of the application by clicking on the ellipses button next "Physical Path" text box. If you are following along with me, then in the Physical Path text box you should have C:\SecurityDemo\SecurityDemo
8. To associate the application pool, click the "Select" button next to "Application pool" text box, and select the application pool from the drop down list.
Associating an ASP.NET Web Application with an Application Pool

Applications isolation using application pools in IIS - Part 83

Suggested Videos
Part 80 - Writing custom asp.net tracing messages
Part 81 - Tracing in asp.net - A real time example
Part 82 - Application pools in IIS

In this video we will discuss about achieving isolation between applications, by associating with different application pools. Create an asp.net web application with name WebApplication1. Drag and drop a button control on webform1.aspx. Copy and paste the following code in webform1.aspx.cs



protected void Page_Load(object sender, EventArgs e)
{
   
if (!IsPostBack)
    {
        Session[
"Application1_Data"] = "Application 1 Data";
    }
    Response.Write(
"Identity used = " + System.Security.Principal.WindowsIdentity.GetCurrent().Name + "<br/>");
}

protected void Button1_Click(object sender, EventArgs e)
{
   
if (Session["Application1_Data"] != null)
    {
        Response.Write(
"Application1_Data = " + Session["Application1_Data"]);
    }
   
else
    {
        Response.Write(
"Session Data not available");
    }
}



protected void Page_Load(object sender, EventArgs e)
{
    
if (!IsPostBack)
    {
        Session[
"Application2_Data"] = "Application 2 Data";
    }
    Response.Write(
"Identity used = " + System.Security.Principal.WindowsIdentity.GetCurrent().Name + "<br/>");
}

protected void Button1_Click(object sender, EventArgs e)
{
    
if (Session["Application2_Data"] != null)
    {
        Response.Write(
"Application2_Data = " + Session["Application2_Data"]);
    }
    
else
    {
        Response.Write(
"Session Data not available");
    }
}

Create an application pool in IIS with name "WebApplication1Pool".
1. Open IIS (Type INETMGR in RUN window, and press ENTER key)
2. Expand root node in IIS
3. Right click on "Application Pools" and select Add "Application Pool"
4. Enter "WebApplicationPool1" as the name and click OK.

Associate WebApplication1, with "WebApplication1Pool" we just created.
1. In IIS, right click on "Default Web Site" and Select "Add Application"
2. Set Alias="WebApplication1" and Select "WebApplication1Pool" as the application pool.
3. Set the physical path to the directory of WebApplication1.

Along the same lines, associate WebApplication2, with "WebApplication1Pool".

At this point, if you run WebApplication1, by using CTRL+F5, visual studio by default uses built-in asp.net development server. Configure visual studio to use local IIS.
1. Right click on the "WebApplication1" project in solution explorer in visual studio and select "Properties"
2. In the properties window click on "Web" tab.
3. Under "Servers" section, Select "Use Local IIS Web Server" radio button
4. Set project Url=http://localhost/WebApplication1 and save the changes.

Do the same thing for WebApplication2, but set project Url=http://localhost/WebApplication2 and save the changes.

Run both the applications. When you click the button on both the applications, Session data should be available.

Now let us recycle, application pools in IIS.
1. Open IIS
2. Select Application Pools 
3. Right click on WebApplication1Pool, and select "Recycle"

Now click the button controls, on both the applications. Notice that both the applications have lost their session data.

Now create a new application pool in IIS, with name WebApplication2Pool and associate WebApplication2, with this new pool. Run both the applications again. Click the button controls. Session data should be available. Recycle WebApplication1Pool. Click the button controls again on both the applications. Notice, that, only WebApplication1 has lost the session data but not WebApplication2. WebApplication2 belong to WebApplication2Pool. We have not recycled WebApplication2Pool, and hence it retains it's session data.

Application pools in IIS Security - Part 84

Suggested Videos
Part 81 - Tracing in asp.net - A real time example
Part 82 - Application pools in IIS
Part 83 - Applications isolation using application pools

In this video, we will discuss about configuring different levels of security for different application pools, with an example.

In your C:\ drive, create a folder with name "Data". Open a notepad. Copy and paste the following XML into the notepad. Save the notepad as Application1Data.xml in C:\Data folder. Open, another notepad, copy and paste the same XML. Now save the notepad as Application2Data.xml in C:\Data folder. So, at this point, you should have Application1Data.xml and Application2Data.xml in C:\Data folder.



<?xml version="1.0" encoding="utf-8" ?>
<Countries>
  <Country>
    <Id>101</Id>
    <Name>India</Name>
    <Continent>Asia</Continent>
  </Country>
  <Country>
    <Id>102</Id>
    <Name>USA</Name>
    <Continent>North America</Continent>
  </Country>
  <Country>
    <Id>103</Id>
    <Name>UK</Name>
    <Continent>Europe</Continent>
  </Country>
  <Country>
    <Id>104</Id>
    <Name>France</Name>
    <Continent>Europe</Continent>
  </Country>
</Countries>



Create an asp.net web application with name WebApplication1. Drag and drop a GridView, FileUpload, Button and a Label control on to the webform. Set Text="Load Data" for the button control. Remove the Text property of the Label control. Double click the button control to generate the event handler. At this stage, the html of webform1.aspx should be as shown below.
<div>
    <asp:GridView ID="GridView1" runat="server">
    </asp:GridView>
    <br />
    <asp:FileUpload ID="FileUpload1" runat="server" />
    <asp:Button ID="Button1" runat="server"
    Text="Load Data" onclick="Button1_Click" />
    <br />
    <asp:Label ID="Label1" runat="server">
    </asp:Label>
</div>

WebForm1.aspx.cs code:
protected void Page_Load(object sender, EventArgs e)
{
    Response.Write(
"Identity used = " +
        System.Security.Principal.
WindowsIdentity.GetCurrent().Name + "<br/>");
}

protected void Button1_Click(object sender, EventArgs e)
{
   
if (FileUpload1.HasFile)
    {
       
DataSet ds = new DataSet();
        ds.ReadXml(FileUpload1.PostedFile.FileName);
        GridView1.DataSource = ds;
        GridView1.DataBind();
        Label1.Text = "";
    }
   
else
    {
        Label1.Text =
"Please select a file first";
    }
}

Create an application pool with name Application1Pool, and associate WebApplication1, with this pool. We have discussed about creating application pools and associating web applications to an application pool in
Part 82 and Part 83.

Create another asp.net web application with name WebApplication2. Copy and paste the HTML and code of WebForm1 from WebApplication1. Create an application pool with name Application2Pool, and associate WebApplication2, with this pool.

Run WebApplication1, and select Application1Data.xml from C:\Data folder, and click on "Load Data" button. The data should load fine. Now, select Application2Data.xml from C:\Data folder, and click on "Load Data" button. The data should load fine, from Application2Data.xml file as well. Test the same, with WebApplication2.

At this point, both WebApplication1 and WebApplication2, are able to read from Application1Data.xml and Application2Data.xml files. The idea is, we want to allow, WebApplication1 to be able to access only Application1Data.xml and not Application2Data.xml. Along the same lines, WebApplication2 should be able to access only Application2Data.xml and not Application1Data.xml.

The applications are deployed to different application pools. WebApplication1 is deployed to Application1Pool, and WebApplication2 to Application2Pool. So, WebApplication1 is executed using Application1Pool identity - IIS APPPOOL\Application1Pool, and WebApplication2 with Application2Pool identity - IIS APPPOOL\Application2Pool.

At this stage, all we have to do is, set the file permissions accordingly for the application pool identities. 

Deny access to file Application1Data.xml for IIS APPPOOL\Application2Pool identity
1. In C:\Data, right click on Application1Data.xml and select "Properties"
2. Click on the "Security" tab.
3. Click "Edit" button
4. Now click "Add"
5. Click on "Locations" button and select your "computer name" and click OK
6. In the "Enter the object names to select", text box, type IIS APPPOOL\Application2Pool and click "Check Names" button.
7. Click OK
8. In the permissions list, select "Full Control" under "Deny" and click OK.

Along the same lines, Deny access to file Application2Data.xml for  IIS APPPOOL\Application1Pool identity.

With these changes in place now, WebApplication1 should only be able to access Application1Data.xml and WebApplication2, only  Application2Data.xml. Instead of showing the "Yellow screen of death", user friendly error message can be displayed in the label control by catching the security exception as shown below.
protected void Button1_Click(object sender, EventArgs e)
{
   
if (FileUpload1.HasFile)
    {
       
try
        {
           
DataSet ds = new DataSet();
            ds.ReadXml(FileUpload1.PostedFile.FileName);
            GridView1.DataSource = ds;
            GridView1.DataBind();
            Label1.Text = "";
        }
       
catch (System.UnauthorizedAccessException)
        {
            Label1.Text =
"You do not have access to this file";
        }
       
catch (Exception)
        {
            Label1.Text =
"An unexpected error has occured, please contact administrator";
        }
    }
   
else
    {
        Label1.Text =
"Please select a file first";
    }
}

Anonymous authentication in asp.net - Part 85

Suggested Videos
Part 82 - Application pools in IIS
Part 83 - Applications isolation using application pools
Part 84 - Application pools in IIS Security

Authentication is the process of identifying users. Authorization is the process of granting access to those users based on identity. Together, authentication and authorization secures our Web application.

Authentication - Who is the User?
Authorization - What rights the user has? What resources the user can access?

Most of the public web sites, does not ask the user to enter any user name and password. But still, we will be able to access the content of these web sites. ASP.NET Web applications provide anonymous access to resources on the server. Anonymous authentication allows users to access the public areas of the web site, without prompting the users for a user name or password.



Create an asp.net web application. Copy and paste the following code in the Page_Load() event of WebForm1.aspx.cs
Response.Write(
"Application code executed using ");
Response.Write(System.Security.Principal.
WindowsIdentity.GetCurrent().Name + "<br/>");

Response.Write(
"Is User Authenticated: ");
Response.Write(User.Identity.IsAuthenticated.ToString() +
"<br/>");

Response.Write(
"Authentication Type, if Authenticated: ");
Response.Write(User.Identity.AuthenticationType +
"<br/>");

Response.Write(
"User Name, if Authenticated: ");
Response.Write(User.Identity.Name +
"<br/>");



Associate the web application, to the local IIS, instead of using the visual studio built-in asp.net development server. Use the DefaultAppPool as the application pool. For help on these topics, please check the following parts
Part 82 - Application pool in IIS
Part 83 - Applications isolation using application pools in IIS

In IIS 6.0
IUSR_ComputerName is used for providing anonymous access.

In IIS 7.0
IUSR account is used for providing anonymous access.

By default anonymous authentication is enabled in IIS. To verify this
1. Open IIS
2. Expand the root node > Sites > Default Web Site
3. Select your web application
4. In the features window, dobule click "Authentication" icon
5. Notice that, anonymous authentication is enabled by default.

Run the application. Notice, that the application pool identity is used to execute the application code. In the next video session, we will discuss about asp.net impersonation with anonymous access.

To disable anonymous authentication, click "Disable" link under "actions" in the right hand side panel in IIS.

To change the account that is associated with anonymous access, click "Edit" link under actions in the right hand side panel in IIS. Notice, that the default account is IUSR. This can be changed to a custom windows account or Application pool identity.

Anonymous authentication and asp.net impersonation - Part 86

Suggested Videos
Part 83 - Applications isolation using application pools
Part 84 - Application pools in IIS Security
Part 85 - Anonymous authentication

Please watch Part 85, before watching this video. In Part 85, we discussed that IIS provides anonymous access to resources using IUSR account. Once the request is handed over to asp.net, the application code is executed using the application pool identity.

In this video, we will discuss the effects of turning impersonation on, with anonymous access.



In "C:\Data" folder, create an XML file with name Countries.xml. 
<?xml version="1.0" encoding="utf-8" ?>
<Countries>
  <Country>
    <Id>101</Id>
    <Name>India</Name>
    <Continent>Asia</Continent>
  </Country>
  <Country>
    <Id>102</Id>
    <Name>UK</Name>
    <Continent>Europe</Continent>
  </Country>
  <Country>
    <Id>103</Id>
    <Name>US</Name>
    <Continent>North America</Continent>
  </Country>
  <Country>
    <Id>104</Id>
    <Name>France</Name>
    <Continent>Europe</Continent>
  </Country>
</Countries>



Create an asp.net web application. Drag and drop a gridview control and a button control on the webform. Copy and paste the following code in WebForm1.aspx.cs
protected void Page_Load(object sender, EventArgs e)
{
    Response.Write(
"Application code executed using ");
    Response.Write(System.Security.Principal.
WindowsIdentity.GetCurrent().Name + "<br/>");

    Response.Write(
"Is User Authenticated: ");
    Response.Write(User.Identity.IsAuthenticated.ToString() +
"<br/>");

    Response.Write(
"Authentication Type, if Authenticated: ");
    Response.Write(User.Identity.AuthenticationType +
"<br/>");

    Response.Write(
"User Name, if Authenticated: ");
    Response.Write(User.Identity.Name +
"<br/>");
}

protected void Button1_Click(object sender, EventArgs e)
{
   
DataSet ds = new DataSet();
    ds.ReadXml(
"C:\\Data\\Countries.xml");
    GridView1.DataSource = ds;
    GridView1.DataBind();
}

To enable impersonation, set impersonate="true" for the identity element in web.config.
<system.web>
  
<identity impersonate="true" />
</system.web>

Impersonation can also be enabled or disabled from IIS. 
1. Select the web application in IIS
2. Double click on "Authentication" icon
3. Select ASP.NET Impersonation
4. Click "Disable" or "Enable" link under actions in the right hand side panel in IIS.
5. This will automatically change the web.config file.

At this point, if you run the application, you may get an error stating 
HTTP Error 500.24 - Internal Server Error
An ASP.NET setting has been detected that does not apply in Integrated managed pipeline mode.

To correct this, we need to set the "Managed pipeline mode" of the DefaultAppPool to "Classic".

Run the application, and notice that, the application code, is now executed, using 'NT AUTHORITY\IUSR' account, instead of 'IIS APPPOOL\DefaultAppPool'

So, when the application uses anonymous authentication and
1. If IMPERSONATION is disabled, then, the application pool identity is used to execute the application code
2. If IMPERSONATION is enabled, then, 'NT AUTHORITY\IUSR' account is used to execute the application code

When to use Application Pool Identity over IUSR
If there are 2 or more websites hosted on a machine, with IUSR as the anonymous account, then they can access each other's content. If we want to isolate, each applications content, the applications can be deployed to different application pools, and the NTFS file permissions can be set for the respective application pool identity. In fact, we have discussed about this in Part 84 - Application pools in IIS Security.

Windows authentication in asp.net - Part 87

Suggested Videos
Part 84 - Application pools in IIS Security
Part 85 - Anonymous authentication
Part 86 - Anonymous authentication and asp.net impersonation

In Parts 85 and 86 of this video series, we discussed about anonymous authentication. Anonymous authentication is fine for web sites that contain public information, that every one can see. However, if the web site contains private information or performs tasks such as booking tickets, placing orders etc, then the users need to be authenticated and authorised.



In this session, we will discuss about authenticating users, using Windows authentication. Security for an asp.net web application can be configured at 2 places. In IIS and in the application itself.

Windows authentication, identifies and authorizes users based on the server’s user list. Access to resources on the server is then granted or denied based on the user account’s privileges.

Windows authentication is best suited for Intranet Web applications.

The advantage of Windows authentication is that, the Web application can use the exact same security scheme that applies to your corporate network. User names, passwords, and permissions are the same for network resources and Web applications.

We will be using the same project, that we worked with, in Part 86.



To enable windows authentication in IIS.
1. Open IIS (Type INETMGR in RUN window, and press enter)
2. Expand Root Server node > Sites > Default Web Site > WebApplication1
3. Double click "Authentication" icon, in the features window.
4. Notice that "Anonymous Authentication" is enabled by default.
5. Select "Windows Authentication" and click "Enable" link under "Actions" pane.

At this point, we have both anonymous and windows authentication enabled in IIS. We have not configured anything in the application yet. Run the application, and notice that, the user is still using anonymous authentication to access the webform.

So, if both, anonymous and windows authentication are enabled in IIS, and, if we don't have a deny entry for anonymous users, in the web.config file, then the resources on the web server are accessed using anonymous authentication.

Anonymous authentication can be disabled in IIS or in web.config file.

To disable anonymous authentication in web.config file, add the following entry
<authorization>
 
<deny users="?"/>
</authorization>

Run the application now. Notice that the user is authenticated using the windows account, that is used to log into the computer. Also, notice that, the application code is executed using the application pool identity.

If you want to have the application code executed using the logged in user identity, then enable impersonation. Impersonation can be enabled thru IIS or by adding the following element to web.config file.
<identity impersonate="true"/>

If impersonation is enabled, the application executes using the permissions found in your user account. So, if the logged in user has access, to a specific network resource, only then will he be able to access that resource thru the application

Windows authentication and authorization in asp.net - Part 88

Suggested Videos
Part 85 - Anonymous authentication
Part 86 - Anonymous authentication and asp.net impersonation
Part 87 - Windows authentication

In Part 87, we have discussed the basics of windows authentication. In this session, we will continue to discuss about windows authentication. Please watch Part 87, before proceeding.

? and * have special meaning when used in the authorization element in web.config
? (Question Mark) - Indicates anonymous users
* (Star) - Indicates all users



Allowing or denying access to specific users:
When you run the application, with the following authorization list in web.config, only users "Venkat" and "Pragim" are allowed to access the application. If you are logged, into the computer, as any other user, the application prompts the user to provide user name and password. All the other users are denied access to the application.
<authorization>
  
<allow users="Prasad-PC\Venkat, Prasad-PC\Pragim"/>
  
<deny users="*"/>
</authorization>



Using windows roles to control access:
Windows operating system has several roles, like Administrators, Guests, Users etc. It is also possible to control access to resources using these roles in the web.config file. The following authorization list, only allows users belonging to Administrators role. All the other users are denied access.
<authorization>
  
<allow roles="Administrators"/>
  
<deny users="*"/>
</authorization>

How to programmatically check if the user belongs to a specific role?
if (User.IsInRole("Administrators"))
{
 
  // Do Admin Stuff
}
else
{
   
// Do Non-Admin stuff
}

Windows authentication and folder level authorization - Part 89

Suggested Videos
Part 86 - Anonymous authentication and asp.net impersonation
Part 87 - Windows authentication
Part 88 - Windows authentication and authorization

Please watch Parts 87 and 88, before proceeding. In this video we will discuss about folder level authorization, with an example. Consider the project structure, shown in the solution explorer below.
Folder level authorization



Only administrators should be able to access the pages in "Admin" folder. The rest of the pages can be accessed by anyone. To achieve this, add another web.config file to the "Admin" folder and include the following authorization element.
<authorization>
  
<allow roles="Administrators" />
  
<deny users="*" />
</authorization>

Application root level web.config file. This allows access to all authenticated users. 
<authorization>
  
<deny users="?"/> 
</authorization>



A very common asp.net interview question:
Is it possible to have more than one web.config file? If yes, when and why would you use more than one web.config file.
This is one of the classic examples, where we need more than one web.config files.

If you want to execute the application code, using the logged in Administrator account, then enable impersonation, in the web.config file of the Admin folder. With this setting in place, all the pages in the Admin folder are executed using the logged in user account, where as the pages outside of the folder are executed using the identity of the application pool.
<system.web>
  
<authorization>
    
<allow roles="Administrators" />
    
<deny users="*" />
  
</authorization>
  
<identity impersonate="true"/>
</system.web>

It is also possible to impersonate, with a specific user name and password. With this setting, whenever any user belonging to the "Administrators" group requests a page from the Admin folder, the code will be executed using "Venkat" account.
<system.web>
  
<authorization>
    
<allow roles="Administrators" />
    
<deny users="*" />
  
</authorization>
  
<identity impersonate="true" userName="Venkat" password="test"/>
</system.web>

Forms authentication using user names list in web.config - Part 90

Suggested Videos
Part 87 - Windows authentication
Part 88 - Windows authentication and authorization
Part 89 - Windows authentication and folder level authorization

Anonymous authentication is fine for web sites that contain public information that every one can see. We discussed about Anonymous authentication in
Part 85 - Anonymous authentication
Part 86 - Anonymous authentication and impersonation

Windows authentication is used for intranet web applications, where the users are part of a windows domain-based network. We discussed about Windows authentication in Parts 87, 88 and 89.



In this video we will discuss about
1. When to use Forms Authentication
2. How to enable Forms Authentication

When to use Forms Authentication?
Forms authentication is used for internet web applications. The advantage of Forms authentication is that users do not have to be member of a domain-based network to have access to your application. Many internet web sites like Gmail.com, Amazon.com, facebook.com etc uses forms authentication. To access these applications we do not have to be member of their domain-based network.



How to enable Forms Authentication?
Create an asp.net web application project. Add a webform with name Welcome.aspx, and Login.aspx. Add a new folder with name "Registration", to the project. Add Register.aspx web form to the "Registration" folder.

Welcome.aspx HTML:
<h1>Welcome Page</h1>

Login.aspx HTML:
<div style="font-family:Arial">
<table style="border: 1px solid black">
    <tr>
        <td colspan="2">
            <b>Login</b>
        </td>
    </tr>
    <tr>
        <td>
            User Name
        </td>  
        <td>
            :<asp:TextBox ID="txtUserName" runat="server">
            </asp:TextBox>
        </td>  
    </tr>
    <tr>
        <td>
            Password
        </td>  
        <td>
            :<asp:TextBox ID="txtPassword" TextMode="Password" runat="server">
            </asp:TextBox>
        </td>  
    </tr>
    <tr>
        <td>
                   
        </td>  
        <td>
            <asp:Button ID="btnLogin" runat="server" Text="Login" />
        </td>  
    </tr>
</table>
<br />
<a href="Registration/Register.aspx">Click here to register</a>
if you do not have a user name and password.
</div>

Register.aspx HTML:
<h1>Registration Page</h1>

If you run the application now, we will be able to navigate to any page, just by changing the name of the page in the address bar. We are not logged in, but we are still able to access all the pages in the application.

Let us enable forms authentication now. To enable forms authentication, set authentication element's mode attribute to forms in web.config file of the application.
<authentication mode="Forms">
   <forms loginUrl="Login.aspx" timeout="30"
          defaultUrl="Welcome.aspx" protection="All">
    <credentials passwordFormat="Clear">
      <user name="venkat" password="venkat"/>
      <user name="pragim" password="pragim"/>
      <user name="prasad" password="prasad"/>
    </credentials>
  </forms>
</authentication>

<authorization>
  <deny users="?" />
</authorization>

The description of the attributes
loginUrl - The URL of the login Page

timeout - Specifies the number of minutes the authentication cookie persists on the clients’s computer. The default is 30 minutes.
 
defaultUrl - The url the user will be redirected after authentication

Protection - Specifies the protection for authentication cookie stored on the clients’s computer. The default is All, which performs encryption and data validation. Other possible settings are Encryption, Validation, and None.

Double click the login button on the Login.aspx page. Copy and paste the following code in the button click event handler.
// Authenticate againts the list stored in web.config
if (FormsAuthentication.Authenticate(txtUserName.Text, txtPassword.Text))
{
   
// Create the authentication cookie and redirect the user to welcome page
   
FormsAuthentication.RedirectFromLoginPage(txtUserName.Text, chkBoxRememberMe.Checked);
}
else
{
    lblMessage.Text =
"Invalid UserName and/or password";
}

Run the application. Try to navigate to Welcome.aspx or Registration/Register.aspx pages, you will be redirected to Login page. After you login, you will be able to access these pages.

There are 2 problems with this application at the moment.
1. It is not a good practise to store user names and passwords in web.config file. If you want to create the user names and passwords dynamically, you need to change the web.config file. If you change the web.config file at run time, the application restarts and all the session data will be lost, if stored inside the worker process. In a later video session, we will discuss about storing user names and passwords in a database table.

2. At the moment, users are not able to access Register.aspx page, if they are not logged in. If a user does not have user name and password, he should be able to register himself using Register.aspx page. In a later video session, we will discuss about this

Forms authentication in asp.net and user registration - Part 91

Suggested Videos
Part 88 - Windows authentication and authorization
Part 89 - Windows authentication and folder level authorization
Part 90 - Forms authentication using user names list in web.config



In this code sample, we have used validation controls and ADO.NET. If you have not watched the videos on validation controls and ADO.NET, I would strongly encourage you to do so, before continuing with this session.

Please watch Part - 90, before proceeding. In Part - 90, we have discussed the basics of Forms authentication. One of the problems, with the example in Part 90, is that, we are not able to navigate to Registration/Register.aspx page if we are not logged in.

To solve this issue, add another web.config file to the "Registration" folder, and specify the authorization element to allow all users.
<authorization>
  <allow users="*"/>
</authorization>

At this point, without logging into the application, users should be able to navigate to Registration/Register.aspx page.



Copy and paste the following HTML in Register.aspx page.
<div style="font-family:Arial">
<table style="border: 1px solid black">
    <tr>
        <td colspan="2">
            <b>User Registration</b>
        </td>
    </tr>
    <tr>
        <td>
            User Name
        </td>  
        <td>
            :<asp:TextBox ID="txtUserName" runat="server">
            </asp:TextBox>
            <asp:RequiredFieldValidator ID="RequiredFieldValidatorusername"
            runat="server" ErrorMessage="User Name required" Text="*"
            ControlToValidate="txtUserName" ForeColor="Red">
            </asp:RequiredFieldValidator>
        </td>  
    </tr>
    <tr>
        <td>
            Password
        </td>  
        <td>
            :<asp:TextBox ID="txtPassword" TextMode="Password" runat="server">
            </asp:TextBox>
            <asp:RequiredFieldValidator ID="RequiredFieldValidatorPassword"
            runat="server" ErrorMessage="Password required" Text="*"
            ControlToValidate="txtPassword" ForeColor="Red">
            </asp:RequiredFieldValidator>
        </td>  
    </tr>
    <tr>
        <td>
            Confirm Password
        </td>  
        <td>
            :<asp:TextBox ID="txtConfirmPassword" TextMode="Password" runat="server">
            </asp:TextBox>
            <asp:RequiredFieldValidator ID="RequiredFieldValidatorConfirmPassword"
            runat="server" ErrorMessage="Confirm Password required" Text="*"
            ControlToValidate="txtConfirmPassword" ForeColor="Red"
            Display="Dynamic"></asp:RequiredFieldValidator>
            <asp:CompareValidator ID="CompareValidatorPassword" runat="server"
            ErrorMessage="Password and Confirm Password must match"
            ControlToValidate="txtConfirmPassword" ForeColor="Red"
            ControlToCompare="txtPassword" Display="Dynamic"
            Type="String" Operator="Equal" Text="*">
            </asp:CompareValidator>
        </td>  
    </tr>
    <tr>
        <td>
            Email
        </td>  
        <td>
            :<asp:TextBox ID="txtEmail" runat="server">
            </asp:TextBox>
            <asp:RequiredFieldValidator ID="RequiredFieldValidatorEmail"
            runat="server" ErrorMessage="Email required" Text="*"
            ControlToValidate="txtEmail" ForeColor="Red"
            Display="Dynamic"></asp:RequiredFieldValidator>
            <asp:RegularExpressionValidator ID="RegularExpressionValidatorEmail"
            runat="server" ErrorMessage="Invalid Email" ControlToValidate="txtEmail"
            ForeColor="Red" Display="Dynamic" Text="*"
            ValidationExpression="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*">
            </asp:RegularExpressionValidator>
        </td>  
    </tr>
    <tr>
        <td>
                 
        </td>  
        <td>
            <asp:Button ID="btnRegister" runat="server" Text="Register"
            onclick="btnRegister_Click"/>
        </td>  
    </tr>
    <tr>
        <td colspan="2">
            <asp:Label ID="lblMessage" runat="server" ForeColor="Red">
            </asp:Label>
        </td>  
    </tr>
    <tr>
        <td colspan="2">
            <asp:ValidationSummary ID="ValidationSummary1" ForeColor="Red" runat="server" />
        </td>  
    </tr>
</table>
</div>

Copy and Paste the following code in the "Register" button click event.
// If the Page has no validation errors
if (Page.IsValid)
{
    // Read the connection string from web.config.
    // ConfigurationManager class is in System.Configuration namespace
   
string CS = ConfigurationManager.ConnectionStrings["DBCS"].ConnectionString;
   
// SqlConnection is in System.Data.SqlClient namespace
   
using (SqlConnection con = new SqlConnection(CS))
    {
       
SqlCommand cmd = new SqlCommand("spRegisterUser", con);
        cmd.CommandType =
CommandType.StoredProcedure;

       
SqlParameter username = new SqlParameter("@UserName", txtUserName.Text);
       
// FormsAuthentication calss is in System.Web.Security namespace
       
string encryptedPassword = FormsAuthentication.
            HashPasswordForStoringInConfigFile(txtPassword.Text,
"SHA1");
       
SqlParameter password = new SqlParameter("@Password", encryptedPassword);
       
SqlParameter email = new SqlParameter("@Email", txtEmail.Text);

        cmd.Parameters.Add(username);
        cmd.Parameters.Add(password);
        cmd.Parameters.Add(email);

        con.Open();
       
int ReturnCode = (int)cmd.ExecuteScalar();
       
if (ReturnCode == -1)
        {
            lblMessage.Text =
"User Name already in use, please choose another user name";
        }
       
else
        {
            Response.Redirect(
"~/Login.aspx");
        }
    }
}

Run the application. Fill in the required details, and click "Register" button. The user should be added to the database. In the next video session, we will discuss about, authenticating with the credentials we stored in the database.

Forms authentication against users in database table - Part 92

Suggested Videos
Part 89 - Windows authentication and folder level authorization
Part 90 - Forms authentication using user names list in web.config
Part 91 - Forms authentication and user registration

In Part 90, we have discussed about authenticating users against a list stored in web.config file. In Part 91, we have discussed about, registering users, if they do not have a username and password to log in. In this session, we will disuss about authenticating users against a list stored in a database table.

This is continuation to Part 91. Please watch Part 91, before proceeding with this video. Authenticating users against a list stored in web.config file is very easy. FormsAuthentication class exposes a static method Authenticate(), which does all the hardwork of authenticating users.

If we want to authenticate users against a list stored in a database table, we will have to write the stored procedure and a method in the application to authenticate users.



First let us create a stored procedure, that accepts username and password as input parameters and authenticate users.
Create Procedure spAuthenticateUser
@UserName
nvarchar(100)
@Password
nvarchar(100)
as
Begin
 Declare @Count int
 
 
Select @Count = COUNT(UserName) from tblUsers
 
where [UserName] = @UserName and [Password] = @Password
 
 
if(@Count = 1)
 
Begin
 
Select 1 as ReturnCode
 
End
 Else
 Begin
 
Select -1 as ReturnCode
 
End
End



Copy and paste the following private method in Login.aspx.cs page. This method invokes stored procedure 'spAuthenticateUser'. 
private bool AuthenticateUser(string username, string password)
{
   
// ConfigurationManager class is in System.Configuration namespace
   
string CS = ConfigurationManager.ConnectionStrings["DBCS"].ConnectionString;
   
// SqlConnection is in System.Data.SqlClient namespace
   
using (SqlConnection con = new SqlConnection(CS))
    {
       
SqlCommand cmd = new SqlCommand("spAuthenticateUser", con);
        cmd.CommandType =
CommandType.StoredProcedure;

       
// FormsAuthentication is in System.Web.Security
       
string EncryptedPassword = FormsAuthentication.HashPasswordForStoringInConfigFile(password, "SHA1");
       
// SqlParameter is in System.Data namespace
       
SqlParameter paramUsername = new SqlParameter("@UserName", username);
       
SqlParameter paramPassword = new SqlParameter("@Password", EncryptedPassword);

        cmd.Parameters.Add(paramUsername);
        cmd.Parameters.Add(paramPassword);

        con.Open();
       
int ReturnCode = (int)cmd.ExecuteScalar();
       
return ReturnCode == 1;
    }
}

Invoke AuthenticateUser() method, in the login button click event handler
if (AuthenticateUser(txtUserName.Text, txtPassword.Text))
{
   
FormsAuthentication.RedirectFromLoginPage(txtUserName.Text, chkBoxRememberMe.Checked);
}
else
{
    lblMessage.Text =
"Invalid User Name and/or Password";
}

Forms authentication and locking user accounts - Part 93

Suggested Videos
Part 90 - Forms authentication using user names list in web.config
Part 91 - Forms authentication and user registration
Part 92 - Forms authentication against users in database table

Please watch Parts 90, 91 and 92 before proceeding. In this video we will discuss about locking or disabling user accounts, after repeated invalid attempts to login.

For example, if a user enters wrong username and password, he will be given 3 more chances, to enter the correct password. After the 3 chances are elapsed, the account will be locked. After the account is locked, the user will not be able to log in, even, if he provides a correct user name and password.

Most of the banking applications does this for security reasons.



Drop the table, tblUsers, that we have created in Part 90. Recreate tblUsers table using the script below.
Create table tblUsers
(
 [Id]
int identity primary key,
 [UserName]
nvarchar(100),
 [Password] 
nvarchar(200),
 [Email] 
nvarchar(100),
 [RetryAttempts]
int,
 [IsLocked]
bit,
 [LockedDateTime]
datetime
)



Since, we have changed the structure of the table. The stored procedure 'spRegisterUser' that we created in Part 91, will break. The corrected stored procedure is show below.
Alter proc spRegisterUser
@UserName
nvarchar(100),
@Password 
nvarchar 200),
@Email 
nvarchar 200)
as  
Begin  
 Declare @Count int  
 
Declare @ReturnCode int  
 
 
Select @Count = COUNT(UserName)  
 
from tblUsers where UserName = @UserName
 
If @Count > 0
 
Begin  
 
Set @ReturnCode = -1
 
End  
 Else  
 Begin  
 
Set @ReturnCode = 1
 
--Change: Column list specified while inserting
 
Insert into tblUsers([UserName], [Password], [Email])
 
values  (@UserName, @Password, @Email)
 
End  
 Select @ReturnCode as ReturnValue
End  

Stored procedure - 'spAuthenticateUser', that we created in Part 92, needs to be changed as shown below, to support the Account locking functionality.
Alter proc spAuthenticateUser
@UserName
nvarchar(100),
@Password 
nvarchar(200)
as
Begin
 Declare @AccountLocked bit
 
Declare @Count int
 
Declare @RetryCount int
 
 
Select @AccountLocked = IsLocked
 
from tblUsers where UserName = @UserName
 
 
--If the account is already locked
 
if(@AccountLocked = 1)
 
Begin
  Select 1 as AccountLocked, 0 as Authenticated, 0 as RetryAttempts
 
End
 Else
 Begin
 
-- Check if the username and password match
 
Select @Count = COUNT(UserName) from tblUsers
 
where [UserName] = @UserName and [Password] = @Password
 
 
-- If match found
 
if(@Count = 1)
 
Begin
  
-- Reset RetryAttempts 
  
Update tblUsers set RetryAttempts = 0
  
where UserName = @UserName
      
  
Select 0 as AccountLocked, 1 as Authenticated, 0 as RetryAttempts
 
End
  Else
  Begin
  
-- If a match is not found
  
Select @RetryCount = IsNULL(RetryAttempts, 0)
  
from tblUsers
  
where UserName = @UserName
  
  
Set @RetryCount = @RetryCount + 1
  
  
if(@RetryCount <= 3)
  
Begin
   
-- If re-try attempts are not completed
   
Update tblUsers set RetryAttempts = @RetryCount
   
where UserName = @UserName
   
   
Select 0 as AccountLocked, 0 as Authenticated, @RetryCount as RetryAttempts
  
End
   Else
   Begin
   
-- If re-try attempts are completed
   
Update tblUsers set RetryAttempts = @RetryCount,
    IsLocked = 1, LockedDateTime =
GETDATE()
   
where UserName = @UserName

   
Select 1 as AccountLocked, 0 as Authenticated, 0 as RetryAttempts
  
End
  End
 End
End

Copy and Paste the following version of AuthenticateUser() method in Login.aspx.cs page.
private void AuthenticateUser(string username, string password)
{
   
// ConfigurationManager class is in System.Configuration namespace
   
string CS = ConfigurationManager.ConnectionStrings["DBCS"].ConnectionString;
    // SqlConnection is in System.Data.SqlClient namespace
   
using (SqlConnection con = new SqlConnection(CS))
    {
       
SqlCommand cmd = new SqlCommand("spAuthenticateUser", con);
        cmd.CommandType =
CommandType.StoredProcedure;

       
//Formsauthentication is in system.web.security
       
string encryptedpassword = FormsAuthentication.HashPasswordForStoringInConfigFile(password, "SHA1");

       
//sqlparameter is in System.Data namespace
       
SqlParameter paramUsername = new SqlParameter("@UserName", username);
        
SqlParameter paramPassword = new SqlParameter("@Password", encryptedpassword);

        cmd.Parameters.Add(paramUsername);
        cmd.Parameters.Add(paramPassword);

        con.Open();
       
SqlDataReader rdr = cmd.ExecuteReader();
       
while (rdr.Read())
        {
           
int RetryAttempts = Convert.ToInt32(rdr["RetryAttempts"]);
           
if (Convert.ToBoolean(rdr["AccountLocked"]))
            {
                lblMessage.Text =
"Account locked. Please contact administrator";
            }
           
else if (RetryAttempts > 0)
            {
               
int AttemptsLeft = (4 - RetryAttempts);
                lblMessage.Text =
"Invalid user name and/or password. " +
                    AttemptsLeft.ToString() +
"attempt(s) left";
            }
           
else if (Convert.ToBoolean(rdr["Authenticated"]))
            {
               
FormsAuthentication.RedirectFromLoginPage(txtUserName.Text, chkBoxRememberMe.Checked);
            }
        }
    }
}

Invoke AuthenticateUser() method in the click event handler of the login button control.
AuthenticateUser(txtUserName.Text, txtPassword.Text);

Unlocking the locked user accounts - Part 94

Suggested Videos
Part 91 - Forms authentication and user registration
Part 92 - Forms authentication against users in database table
Part 93 - Forms authentication and locking user accounts

In Part 93, of this video series we have discussed about locking user accounts, if a user repeatedly enters the wrong password. The accounts are locked to prevent hackers from guessing passwords and dictionary attacks. Please watch Part 93, before proceeding with this video.



In this video, we will discuss about unlocking the locked user accounts. There are several ways to unlock the user accounts.
Approach 1: The end user calls the technical help desk. The authorised person can issue a simple update query to remove the lock.

Update tblUsers
set RetryAttempts = null, IsLocked = 0, LockedDateTime = null
where username='CallersUserName'

However, running UPDATE queries manually against a production database is not recommended, as it is error prone and we may un-intentionally modify other rows that we do not intend to update.



Approach 2: Another approach would be to provide a web page that lists all the locked user accounts. From this page, the helpdesk agent, can unlock the account by clicking a button. This is not as dangerous as running a manual update query, but still a manual process and may be in-efficient. If you know how to write basic ADO.NET code, this approach should not be very difficult to achieve.
If you are new to ADO.NET, Click here for a video series that I have recorded on ADO.NET

Approach 3: Another approach would be, to create a SQL Server job. This job checks tblUsers table for locked accounts periodically and then unlocks them. The frequency at which the job should run is configurable.

In this video, we will discuss about creating and scheduling the SQL Server Job to unlock user accounts.

First let us write the update query to unlock the user accounts. For example, The organization's policy is that, the user account can only be unlocked after 24 hours, since the account is locked. The update query to satisfy the organization's policy is shown below. DateDiff function is used in the update query.
If you are new to DateTime functions in SQL Server, please check this video by clicking here.

Update tblUsers
set RetryAttempts = null, IsLocked = 0, LockedDateTime = null
where IsLocked = 1
and datediff(HOUR,LockedDateTime,GETDATE()) > 24

Let us now, schedule this update query to run every 30 minutes, every day. This can be very easily done using sql server agent jobs. In this video, we will discuss about creating and scheduling sql server agent jobs, for sql server 2008.
1. Open sql serevr management studio
2. In the object explorer, check if "SQL Server Agent" is running.
3. If "SQL Server Agent" is not running, right click and select "Start".
4. Click on the "+" sign, next to "SQL Server Agent" to expand.
5. Right click on "Jobs" folder and select "New Job".
6. In the "New Job" dialog box, provide a meaningful name. Let us call it, "Unlock user accounts job".
7. Fill in Owner, Category and Description fields accordingly. Make sure the Enabled checkbox is selected.
8. Select "Steps" tab, and click "New" button
9. In the "New Job Step" dialog box, give a meaningful step name. Let us call it "Execute Update Query"
10. Select Transact-SQL Script as "Type"
11. Select the respective Database.
12. In the "Command" text box, copy and paste the UPDATE query, and click OK
13. In the "New Job" dialog box, select "Schedules" and click "New" button
14. In the "New Job Schedule" dialog box, give a meaningful name to the schedule. Let us call it "Run Every 30 Minutes Daily"
15. Choose "Recurring" as "Schedule type"
16. Under "Frequency", set "Occurs" = "Daily" and "Recurs every" = "1" Days.
17. Under "Daily Frequency", set "Occurs every" = "30" Minutes.
18. Finally fill in the schedule start and end dates, under "Duration"
19. Click OK, twice and you are done.

This job, will run every 30 minutes daily, and unlocks the accounts that has been locked for more than 24 hours.

Implementing password reset link in asp.net - Part 95

Suggested Videos
Part 92 - Forms authentication against users in database table
Part 93 - Forms authentication and locking user accounts
Part 94 - Unlocking the locked user accounts



Step 1:
The first step is to design a page, that allows the user to enter their user name, for requesting, the reset of the password. Add a webform , with name "ResetPassword.aspx" to the "Registration" folder. The web.config file in this folder, allows anonymous access to all the pages without having the need to login. We discussed about having multiple web.config files and allowing anonymous access to a set of pages in Part 91 of this video series.
Click here to watch Part 91, before proceeding.



Step 2:
Copy and paste the following HTML on "ResetPassword.aspx" page.
<div style="font-family:Arial">
    <table style="border: 1px solid black; width:300px">
        <tr>
            <td colspan="2">
                <b>Reset my password</b>
            </td>
        </tr>
        <tr>
            <td>
                User Name
            </td>  
            <td>
                <asp:TextBox ID="txtUserName" Width="150px" runat="server">
                </asp:TextBox>
            </td>  
        </tr>
        <tr>
            <td>
                   
            </td>  
            <td>
                <asp:Button ID="btnResetPassword" runat="server"
                Width="150px" Text="Reset Password" onclick="btnResetPassword_Click" />
            </td>  
        </tr>
        <tr>
            <td colspan="2">
                <asp:Label ID="lblMessage" runat="server"></asp:Label>
            </td>  
        </tr>
    </table>
</div>

Step 3:
Create a table "tblResetPasswordRequests" in sql server. This table is going to store a unique GUID (Globally Unique Identifier) along with the user id, each time a user requests a password recovery. This GUID will then be passed as part of the querystring in the link to the password reset page. This link will then be emailed to the email address that is associated with the user id. When a user clicks on the link the page will look up the GUID in "tblResetPasswordRequests" table and get the user id from there allowing the user to change their password. I didn't use, UserId, as the querystring parameter, because it maybe open to abuse.

Create table tblResetPasswordRequests
(
 
Id UniqueIdentifier Primary key,
 UserId
int Foreign key references tblUsers(Id),
 ResetRequestDateTime
DateTime
)

Step 4:
Create a stored procedure to check if the username exists, and to insert a row into "tblResetPasswordRequests" table.
Create proc spResetPassword
@UserName
nvarchar(100)
as
Begin
 Declare @UserId int
 
Declare @Email nvarchar(100)
 
 
Select @UserId = Id, @Email = Email
 
from tblUsers
 
where UserName = @UserName
 
 
if(@UserId IS NOT NULL)
 
Begin
 
--If username exists
 
Declare @GUID UniqueIdentifier
 
Set @GUID = NEWID()
 
 
Insert into tblResetPasswordRequests
  (Id, UserId, ResetRequestDateTime)
 
Values(@GUID, @UserId, GETDATE())
 
 
Select 1 as ReturnCode, @GUID as UniqueId, @Email as Email
 
End
 Else
 Begin
 
--If username does not exist
 
SELECT 0 as ReturnCode, NULL as UniqueId, NULL as Email
 
End
End

Step 5:
Invoke the stored procedure and email the link, to the email address that is registered against the username. Copy and paste the following code in ResetPassword.aspx.cs page.

protected void btnResetPassword_Click(object sender, EventArgs e)
{
   
string CS = ConfigurationManager.ConnectionStrings["DBCS"].ConnectionString;
   
using (SqlConnection con = new SqlConnection(CS))
    {
       
SqlCommand cmd = new SqlCommand("spResetPassword", con);
        cmd.CommandType =
CommandType.StoredProcedure;

       
SqlParameter paramUsername = new SqlParameter("@UserName", txtUserName.Text);

        cmd.Parameters.Add(paramUsername);

        con.Open();
       
SqlDataReader rdr = cmd.ExecuteReader();
       
while (rdr.Read())
        {
           
if (Convert.ToBoolean(rdr["ReturnCode"]))
            {
                SendPasswordResetEmail(rdr[
"Email"].ToString(), txtUserName.Text, rdr["UniqueId"].ToString());
                lblMessage.Text =
"An email with instructions to reset your password is sent to your registered email";
            }
           
else 
            {
                lblMessage.ForeColor = System.Drawing.
Color.Red;
                lblMessage.Text =
"Username not found!";
            }
        }
    }
}

private void SendPasswordResetEmail(string ToEmail, string UserName, string UniqueId)
{
   
// MailMessage class is present is System.Net.Mail namespace
   
MailMessage mailMessage = new MailMessage("YourEmail@gmail.com", ToEmail);
           
           
    // StringBuilder class is present in System.Text namespace
   
StringBuilder sbEmailBody = new StringBuilder();
    sbEmailBody.Append(
"Dear " + UserName + ",<br/><br/>");
    sbEmailBody.Append(
"Please click on the following link to reset your password");
    sbEmailBody.Append(
"<br/>");      sbEmailBody.Append("http://localhost/WebApplication1/Registration/ChangePassword.aspx?uid=" + UniqueId);
    sbEmailBody.Append(
"<br/><br/>");
    sbEmailBody.Append(
"<b>Pragim Technologies</b>");

    mailMessage.IsBodyHtml =
true;

    mailMessage.Body = sbEmailBody.ToString();
    mailMessage.Subject =
"Reset Your Password";
   
SmtpClient smtpClient = new SmtpClient("smtp.gmail.com", 587);

    smtpClient.Credentials = new System.Net.NetworkCredential()
    {
        UserName =
"YourEmail@gmail.com",
        Password =
"YourPassword"
    };
           
    smtpClient.EnableSsl =
true;
    smtpClient.Send(mailMessage);
}

Step 6:
Add a webform with name, "ChangePassword.aspx", to "Registration" folder. Copy and paste the following HTML in the aspx page. In the next video session we will implement ChangePassword page.
<h1>Change Password Page</h1>

Implementing change password page in asp.net - Part 96

Suggested Videos
Part 93 - Forms authentication and locking user accounts
Part 94 - Unlocking the locked user accounts
Part 95 - Implementing password reset link

In this video we will discuss about, implementing change password page in asp.net. When the user clicks on password reset link, the user lands on ChangePassword.aspx page. In Part 95, we discussed about, generating and emailing the password reset link. The password reset link looks as shown below.
http://localhost/WebApplication1/Registration/ChangePassword.aspx?uid=c19b3a4a-7fd2-47dc-9c2a-be541daed8fa



Notice that, ChangePassword.aspx page has a query string "uid". This GUID(Globally unique identifier), is used to look up UserID, for whom the password needs to be changed. After updating the password, delete the row from "tblResetPasswordRequests", so the link becomes invalid after the user has changed his/her password. Since, user id's are integers, they may be open for abuse as it is very easy to use random integers as query string values, to change other users password. 



https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2LM1IZVE0EbHlHo8MqUhctxS_jojZzmVl57ZcD-wHdSzlmQLKQzhalqidKOzfbI23kvxlOIYQp-jnUGcsmfZDmlQNj_FUEsxfSWBs8QGH2-fJN6_5sLZf4j7mbfza4LO3GdeIkT6_9t4P/s1600/tblUsers.png

https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqCwsG4pqr_clj3uCqiUsjDiIxWTcztoe6C61QVdm7N1Rp2Drqrifok-3o-VPIrw_xKADrUCjgI-_7jifD4KiP3F9XH6jAVATe7Luls5nVNAZ2LqaEZbeix1An_stRy5cVHxEod-FNaCQY/s1600/tblResetPasswordRequests.png

Stored Procedure to check, if the password reset link, is a valid link.
Create Proc spIsPasswordResetLinkValid
@GUID
uniqueidentifier
as
Begin
 Declare @UserId int
 
 
If(Exists(Select UserId from tblResetPasswordRequests where Id = @GUID))
 
Begin
  Select 1 as IsValidPasswordResetLink
 
End
 Else
 Begin
  Select 0 as IsValidPasswordResetLink
 
End
End

Stored Procedure to change password
Create Proc spChangePassword
@GUID
uniqueidentifier,
@Password
nvarchar(100)
as
Begin
 Declare @UserId int
 
 
Select @UserId = UserId
 
from tblResetPasswordRequests
 
where Id= @GUID
 
 
if(@UserId is null)
 
Begin
 
-- If UserId does not exist
 
Select 0 as IsPasswordChanged
 
End
 Else
 Begin
 
-- If UserId exists, Update with new password
 
Update tblUsers set
  [Password] = @Password
 
where Id = @UserId
 
 
-- Delete the password reset request row 
 
Delete from tblResetPasswordRequests
 
where Id = @GUID
 
 
Select 1 as IsPasswordChanged
 
End
End

ChangePassword.aspx.cs page code
<div style="font-family: Arial">
<table style="border: 1px solid black">
    <tr>
        <td colspan="2">
            <b>Change Password</b>
        </td>
    </tr>
    <tr>
        <td>
            New Password
        </td>
        <td>
            :<asp:TextBox ID="txtNewPassword" TextMode="Password"
            runat="server"></asp:TextBox>
            <asp:RequiredFieldValidator ID="RequiredFieldValidatorNewPassword"
                runat="server" ErrorMessage="New Password required"
                Text="*" ControlToValidate="txtNewPassword" ForeColor="Red">
            </asp:RequiredFieldValidator>
        </td>
    </tr>
    <tr>
        <td>
            Confirm New Password
        </td>
        <td>
            :<asp:TextBox ID="txtConfirmNewPassword" TextMode="Password" runat="server">
            </asp:TextBox>
            <asp:RequiredFieldValidator ID="RequiredFieldValidatorConfirmNewPassword"
                runat="server" ErrorMessage="Confirm New Password required" Text="*"
                ControlToValidate="txtConfirmNewPassword"
                ForeColor="Red" Display="Dynamic"></asp:RequiredFieldValidator>
            <asp:CompareValidator ID="CompareValidatorPassword" runat="server"
                ErrorMessage="New Password and Confirm New Password must match"
                ControlToValidate="txtConfirmNewPassword" ForeColor="Red"
                ControlToCompare="txtNewPassword"
                Display="Dynamic" Type="String" Operator="Equal" Text="*">
            </asp:CompareValidator>
        </td>
    </tr>
    <tr>
        <td>
                   
        </td>
        <td>
            &nbsp;<asp:Button ID="btnSave" runat="server"
            Text="Save" onclick="btnSave_Click" Width="70px" />
        </td>
    </tr>
    <tr>
        <td colspan="2">
            <asp:Label ID="lblMessage" runat="server">
            </asp:Label>
        </td>
    </tr>
    <tr>
        <td colspan="2">
            <asp:ValidationSummary ID="ValidationSummary1"
            ForeColor="Red" runat="server" />
        </td>
    </tr>
</table>
</div>

ChangePassword.aspx.cs page code
protected void Page_Load(object sender, EventArgs e)
{
   
if (!IsPostBack)
    {
       
if (!IsPasswordResetLinkValid())
        {
            lblMessage.ForeColor = System.Drawing.
Color.Red;
            lblMessage.Text =
"Password Reset link has expired or is invalid";
        }
    }
}

protected void btnSave_Click(object sender, EventArgs e)
{
   
if (ChangeUserPassword())
    {
        lblMessage.Text =
"Password Changed Successfully!";
    }
    else
    {
        lblMessage.ForeColor = System.Drawing.
Color.Red;
        lblMessage.Text =
"Password Reset link has expired or is invalid";
    }
}

private bool ExecuteSP(string SPName, List<SqlParameter> SPParameters)
{
   
string CS = ConfigurationManager.ConnectionStrings["DBCS"].ConnectionString;
   
using (SqlConnection con = new SqlConnection(CS))
    {
       
SqlCommand cmd = new SqlCommand(SPName, con);
        cmd.CommandType =
CommandType.StoredProcedure;

       
foreach (SqlParameter parameter in SPParameters)
        {
            cmd.Parameters.Add(parameter);
        }

        con.Open();
       
return Convert.ToBoolean(cmd.ExecuteScalar());
    }
}

private bool IsPasswordResetLinkValid()
{
   
List<SqlParameter> paramList = new List<SqlParameter>()
    {
       
new SqlParameter()
        {
            ParameterName =
"@GUID",
            Value = Request.QueryString[
"uid"]
        }
    };

   
return ExecuteSP("spIsPasswordResetLinkValid", paramList);
}

private bool ChangeUserPassword()
{
   
List<SqlParameter> paramList = new List<SqlParameter>()
    {
       
new SqlParameter()
        {
            ParameterName =
"@GUID",
            Value = Request.QueryString[
"uid"]
        },
       
new SqlParameter()
        {
            ParameterName =
"@Password",
            Value =
FormsAuthentication.HashPasswordForStoringInConfigFile(txtNewPassword.Text, "SHA1")
        }
    };

   
return ExecuteSP("spChangePassword", paramList);
}

No comments:

Post a Comment