0

Login Error

As you can see, I want to catch the exception if the user is tampering the Login Button if there are no values in the fields or if it doesn't match info in the database.

For example: The field has no values and I click Login button once, it says the error. After I clicked OK button, I click Login button again and now it says, "ExecuteReader requires an open and available Connection. The connection's current state is closed."

I use 3 tier Architecture Windows Application.

BEL:

    public SqlDataReader Login(BELLogin bellog)
    {
        SqlCommand cmd = new SqlCommand();
        cmd.Connection = Con.getcon();
        cmd.CommandType = CommandType.Text;
        cmd.CommandText = "SELECT username,password FROM tbl_login WHERE username = @Username AND password = @Password";
        cmd.Parameters.AddWithValue("@Username", bellog.Acctname);
        cmd.Parameters.AddWithValue("@Password", bellog.Password);
        SqlDataReader dr = cmd.ExecuteReader();
        return dr;
    }

BAL:

public class BELLogin
{
    public string Acctname { get; set; }
    public string Password { get; set; }
}

DBConnection:

public SqlConnection getcon()
    {
        if (con.State == System.Data.ConnectionState.Closed)
            con.Open();
        else if (con.State == System.Data.ConnectionState.Open)
            con.Close();
        return con;
    }

    public DataTable ExeReader(SqlCommand cmd)
    {
        getcon();
        cmd.Connection = getcon();
        SqlDataReader dr = cmd.ExecuteReader();
        DataTable dt = new DataTable();
        dt.Load(dr);
        return dt;
    }

GUI:

private void btn_login_Click(object sender, EventArgs e)
    {
        BELog.Acctname = txb_accName.Text;
        BELog.Password = txb_password.Text;

        SqlDataReader dr;
        dr = BALog.Login(BELog);

        if (txb_accName.Text == "" || txb_password.Text == "")
        {
            MessageBox.Show("Some fields are empty. Please fill up all fields before clicking LOGIN button.", "Login Status", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
        else
        {
            if (dr.HasRows == true)
            {
                dr.Read();
                Inventory Inv = new Inventory();
                Inv.Show();
                this.Hide();
            }
            else
            {
                MessageBox.Show("You have entered your password or account name incorrectly. Please check your password and account name and try again.", "Login Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
        dr.Close();
}

Logging in is ok but what if the user tampering the button? Thank you for helping me :D

Liam
  • 27,717
  • 28
  • 128
  • 190
  • That appears to have 4 tiers? Just saying – Liam Oct 14 '16 at 07:34
  • This seems overly complex for what is read data from DB, return it. Why not just one method? You're issue is that you want to open the connection use it then close it all in one call really. If you simplified the structure this will make this much easier to use – Liam Oct 14 '16 at 07:36
  • 1
    The first call to `getcon` method from `ExeReader` method is redundant, as it is called again on the next line, this time assigning the returned value. You should remove the unnecessary call. I further recommend reviewing this code for possible "over-engineering"... – CoolBots Oct 14 '16 at 07:53
  • 1
    1. Do not reuse connections like this, it is bad practice and unnecessary. 2. Wrap all type instances that implement `IDisposable` in `using` blocks so the resources are released. In your case `SqlConnection`, `SqlCommand`, `SqlDataReader`, `DataTable`. See [Best Practices - Executing Sql Statements](http://stackoverflow.com/documentation/.net/3589/ado-net/14261/best-practices-executing-sql-statements). – Igor Oct 14 '16 at 07:53
  • 1
    From a security standpoint you should **never** store your user passwords (anywhere, not DB, not files, not registry, etc. just do not store them). You need to store the hash, not the password, and compare hashes. – Igor Oct 14 '16 at 07:55
  • 1
    Your design is really bad. – mybirthname Oct 14 '16 at 08:00
  • Harsh ^, but kinda true. – Liam Oct 14 '16 at 09:25
  • @Liam - precisely what I am advocating. `You need to store the hash....`. I should have probably added `in plain text`. – Igor Oct 14 '16 at 09:31
  • Ha, prob should of read that more carefully @Igor :) – Liam Oct 14 '16 at 09:35
  • I'm very sorry for all you for being a bad programmer. Actually, I learned C# in beginner tutorials in YouTube and some articles in StackOverflow. I don't really know the word like hash(basically I knew this in KickAss Torrent). And the providing the security of the information, I don't REALLY know how I can protect them. I'm still learning this p.l.. I'm sorry :'( – Anjello Joshua Oct 15 '16 at 01:26
  • I used 3 tier Architecture because they say it can prevent SQL injection. And to easily track the code (coz it has a architecture)and minimize code effort. – Anjello Joshua Oct 15 '16 at 01:52

4 Answers4

1

You need to change your code in gui like this:

//Put code to get the reader inside else clause and close the reader in the same else clause. Also ideally you should return if you encounter. I have added it and commented it.

//Off course you would need to put more effort to make this code better. You will get to that as you get more experience. For now this should make your app work.

private void btn_login_Click(object sender, EventArgs e)
{
     BELog.Acctname = txb_accName.Text;
     BELog.Password = txb_password.Text;

     if (txb_accName.Text == "" || txb_password.Text == "")
     {
         MessageBox.Show("Some fields are empty. Please fill up all fields before clicking LOGIN button.", "Login Status", MessageBoxButtons.OK, MessageBoxIcon.Error);
         //return;
     }
     else
     {
         SqlDataReader dr;
         dr = BALog.Login(BELog);

         if (dr.HasRows == true)
         {
             dr.Read();
             Inventory Inv = new Inventory();
             Inv.Show();
             this.Hide();
         }
         else
         {
             MessageBox.Show("You have entered your password or account name incorrectly. Please check your password and account name and try again.", "Login Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
         }

          dr.Close();
    }
}
The Shooter
  • 733
  • 3
  • 9
1
  1. Do not reuse connections like this, it is bad practice and unnecessary.
  2. Wrap all type instances that implement IDisposable in using blocks so the resources are released. In your case SqlConnection, SqlCommand, SqlDataReader, DataTable.
  3. From a security standpoint you should never store your user passwords (anywhere, not DB, not files, not registry, etc. just do not store them). You need to store the hash, not the password, and compare hashes
  4. Adhere to loose the coupling / high cohesion principle. Essentially expose as little as possible (especially implementation details) from your methods / classes so they can be easily reused and changed. Currently you are passing around and sharing DB objects, this will make your code brittle and very difficult to track down where problems are. Here is your code with a little bit of refactoring, notice that if you have another issue with a connection during login it would now be very easy to figure out where that might be.

    // place in new code file public class UserManager{ public BELLogin FindLogin(string userName, string password){ if(string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password)) return null;

        using(var connection = new SqlConnection("connectionStringPointerFromAppConfigHere"))
        using(SqlCommand cmd = new SqlCommand("SELECT username,password FROM tbl_login WHERE username = @Username AND password = @Password", connection))
        {
            connection.Open();
            cmd.Parameters.AddWithValue("@Username", bellog.Acctname).SqlDbType = SqlDbType.VarChar;
    
            // BAD practice! Use a secure hash instead and store that not the password!
            cmd.Parameters.AddWithValue("@Password", bellog.Password).SqlDbType = SqlDbType.VarChar;
            using(SqlDataReader dr = cmd.ExecuteReader())
            {
                if(dr.Read())
                    return new BELLogin() {Acctname = dr.GetString(0), Password = dr.GetString(1)}; // passed in is same as in datareader
            }
        }
        return null;
    }
    

    }

From your login form class

private void btn_login_Click(object sender, EventArgs e)
{
    if (string.IsNullOrEmpty(txb_accName.Text) || string.IsNullOrEmpty(txb_password.Text))
    {
        MessageBox.Show("Some fields are empty. Please fill up all fields before clicking LOGIN button.", "Login Status", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
    else
    {
        var manager = new UserManager();
        var user = manager.FindLogin(txb_accName.Text, txb_password.Text);

        if (user != null)
        {
            Inventory Inv = new Inventory();
            Inv.Show();
            this.Hide();
        }
        else
        {
            MessageBox.Show("You have entered your password or account name incorrectly. Please check your password and account name and try again.", "Login Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }
}
Graham
  • 7,431
  • 18
  • 59
  • 84
Igor
  • 60,821
  • 10
  • 100
  • 175
  • Sir! Your first code doesn't read `bellog`. I create file like you said, "new project > class library > UserManager.cs". – Anjello Joshua Oct 15 '16 at 03:02
  • @AnjelloJoshua - I wrote `place in new code file`, not place in a new project. It can be a file that lives in the same project as your existing one. – Igor Oct 15 '16 at 09:37
  • Sir @Igor your code works but it doesn't work if the username and password didn't match in the database? How do I do that? – Anjello Joshua Oct 17 '16 at 02:25
  • @AnjelloJoshua - Define `doesn't work`. What does the code do that it should not do when it can't find the user in the database? – Igor Oct 17 '16 at 09:38
  • This is my scenarion: Tampering Login button without values, the code works but putting values that exist and isn't exist in the database got error says,"Connection property has not been initialized." – Anjello Joshua Oct 18 '16 at 01:13
  • That's probably because you did not replace `"connectionStringPointerFromAppConfigHere"` with your connection string. See [Connection Strings and Configuration Files](https://msdn.microsoft.com/en-us/library/ms254494(v=vs.110).aspx) for how to properly retrieve a connection string. – Igor Oct 18 '16 at 09:42
  • I replaced Sir! `using (var conn = new SqlConnection("Data Source = DESKTOP-ANJELLO\\SQLEXPRESS; Initial Catalog = db_AlakdanaMain; Persist Security Info = True; User Id = sa; Password = mm4;")) ` – Anjello Joshua Oct 19 '16 at 01:02
  • @AnjelloJoshua - then you probably did not copy/paste correctly. Notice that the `SqlCommand` constructor of takes a string AND a connection. `new SqlCommand("sql statement", connection)`, you probably did not pass the connection as the 2nd parameter. – Igor Oct 19 '16 at 09:45
  • Sir! @Igor I got an error: "The parameterized query '(@Username varchar(8000),@Password varchar(8000))SELECT username' expects the parameter '@Username', which was not supplied." I searched this prob but I don't understand it. [http://stackoverflow.com/questions/23448403/the-parameterized-query-expects-the-parameter-units-which-was-not-supp] – Anjello Joshua Oct 20 '16 at 01:37
  • @AnjelloJoshua - You need to literally copy the entire `UserManager` class above and use it as is with the exception of the connection string which you can change. There are no syntax errors in it. The reason for your latest error is your did not add a SqlParameter with the name `@Username` – Igor Oct 20 '16 at 09:42
  • Sir! @Igor when I copy/paste the entire `UserManager` it says again the `bellog` does not read so I type `BELLogin bellog = new BELLogin();`. I create the new code file in the same project(RightClick Solution > Add > New Project > ClassLibrary > Rename to UserManager). – Anjello Joshua Oct 20 '16 at 10:47
  • @AnjelloJoshua - `I create the new code file in the same project...` <- no, that's **not** how to do it. Right click on the **existing project** (not solution!), and select `Add -> Class...`. Then copy and paste the code there. Do not create a new project until you start to understand how to start sharing code across them. – Igor Oct 20 '16 at 10:49
  • Sir! @Igor I already created the `Class file` and add `using BEL;` `using `using System.Data;` `using System.Data.SqlClient;` Here's the code `` – Anjello Joshua Oct 20 '16 at 11:04
  • I add `BELLogin bellog = new BELLogin();` above `connection.Open();` – Anjello Joshua Oct 20 '16 at 11:07
  • @AnjelloJoshua - the point is you should not be creating new projects. delete the extra project(s) you created, there should only be 1 project in your solution explorer. In this project is where you add the new code file. – Igor Oct 20 '16 at 11:09
  • So I will not make `BEL`? But `BEL` is a project in the Solution. So you mean I will turn `BEL` and `BAL` into a `Class file` not a project file? – Anjello Joshua Oct 20 '16 at 11:28
  • @AnjelloJoshua - [What is a Visual Studio Project](http://www.google.com/search?q=visual+studio+what+is+a+project) – Igor Oct 20 '16 at 11:32
  • I get your point so I will turn my `BEL`, `BAL` into Class files not projects. I did that.. My scenario: Your code `UserManager` doesnt read `bellog` and error says, "does not exist in current context" so I put `BELLogin bellog = new BELLogin();` to become `bellog` exist and it's fine. When I run it, it says now, `...SELECT username' expects the parameter '@Username', which was not supplied.` – Anjello Joshua Oct 20 '16 at 11:55
  • You said `There are no syntax errors in it. The reason for your latest error is your did not add a SqlParameter with the name @Username`. I followed ALL YOU'VE SAID. Correct me if I'm wrong and I'm NOT TOTALLY deserve to say this but I think your code has logical error Sir @Igor. – Anjello Joshua Oct 20 '16 at 11:55
  • Sir! @Igor uhmm.. I already fix your code but now I want to know what king coding you use in my question. Like me I use 3 tier architecture, I think your code is safe to use and not hard when finding errors. Please reply! Thank you again Sir! – Anjello Joshua Oct 30 '16 at 15:49
0

Your btn_login_Click method appears to call BALog.Login(BELog) prior to checking if there are any valid values in the username and password textboxes. Simply move the validation to the top of btn_login_Click method, and return if the fields are empty:

    if (txb_accName.Text == "" || txb_password.Text == "")
    {
        MessageBox.Show("Some fields are empty. Please fill up all fields before clicking LOGIN button.", "Login Status", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return;
    }

The code in the else portion of that if statement can stay where it is, just not inside an else. The method will exit due to return statement if there are no valid values in the username and password textboxes.

As others have suggested, you should review your code to ensure you really want this structure; but if you do want to keep it that way, this simple fix will solve your problem.

CoolBots
  • 4,770
  • 2
  • 16
  • 30
-1

Just get rid of your DBConnection object, it's not really doing anything and just making your structure complex:

public BELLogin Login(BELLogin bellog)
{
    SqlConnection conn = new SqlConnection(connectionsString);
    try
    { 
        using (SqlCommand cmd = new SqlCommand())
        {
           conn.Open();
           cmd.Connection = conn;
           cmd.CommandType = CommandType.Text;
           cmd.CommandText = "SELECT username,password FROM tbl_login WHERE username = @Username AND password = @Password";
           cmd.Parameters.AddWithValue("@Username", bellog.Acctname);
           cmd.Parameters.AddWithValue("@Password", bellog.Password);
           //really this should be in a using as well. 
           //You be better off reading your data 
           //into a class and returnig the class not the reader.
           using (SqlDataReader dr = cmd.ExecuteReader())
           {
               BELLogin obj = new BELLogin();
               while(dr.Read())
               {
                    //populate obj
               }
               return obj;
           }
       }
   }
   finally
   {
       conn.Close();
       conn.Dispose();
   }
}

also the way you use it can cause memory leaks as your not explicitly disposing and closing your connections. Always dispose Sql objects in C#. Be wary of exceptions too. Any exceptions in your code will not close the connection, etc. This will result in memory leaks and connections locking

Liam
  • 27,717
  • 28
  • 128
  • 190