MySQL connection throws null reference

2019-07-24 19:06发布

问题:

My MySQL connection throws null reference although this code worked well one year ago.
The line where the debugger indicates the exception contains
"connection = new MySqlConnection(connectionString);":

DBConnect MySqlConnection = new DBConnect();

string[] resultArray = MySqlConnection.GetValidUser(
    "tbl_user",
    tbEmail.Text, 
    tbPassword.Text
);

//THROWS null reference exception in method 'private bool OpenConnection()'
//A first chance exception of type 'System.ArgumentException' occurred in System.Data.dll

This is my DBConnect class:

class DBConnect
{
    private MySqlConnection connection;
    private string server;
    private string database;
    private string uid;
    private string password;

    public DBConnect()
    {
        server = "x.x.x.x";
        database = "...";
        uid = "...";
        password = "...";

        string connectionString = "SERVER=" + server + ";" +
                                  "DATABASE=" + database + ";" +
                                  "UID=" + uid + ";" +
                                  "PASSWORD=" + password + ";";

        connection = new MySqlConnection(connectionString);
    }

    private bool OpenConnection()
    {
        try
        {
            connection.Open();
            return true;
        }
        catch (MySqlException ex)
        {
            switch (ex.Number)
            {
                case 0:
                    MessageBox.Show("Cannot connect to MySQL server.");
                    break;

                case 1045:
                    MessageBox.Show("Invalid username or password.");
                    break;
            }
            return false;
        }
    }

    public string[] GetValidUser(string dbTable, string dbUsername, string dbPassword)
    {
        string query = "SELECT id,email,password FROM " + dbTable +
                       " WHERE email='" + dbUsername +
                       "' AND password='" + dbPassword + "'";

        string[] resultArray = new string[3];

        if (this.OpenConnection() == true)
        {
            MySqlCommand cmd = new MySqlCommand(query, connection);
            MySqlDataReader dataReader = cmd.ExecuteReader();

            while (dataReader.Read())
            {
                resultArray[0] = dataReader.GetInt32(0).ToString();
                resultArray[1] = dataReader.GetString(1);
                resultArray[2] = dataReader.GetString(2);
            }

            dataReader.Close();
            this.CloseConnection();
        }
        return resultArray;
    }
}

The original code for the database class can be found here.

回答1:

Among other things, it sounds like you have problems with the connection string - from comments:

While "connection = new MySqlConnection(); connection.ConnectionString = connectionString;" throws an exception the statement "connection = new MySqlConnection();" does not...

The difference here is simply: in the latter you aren't setting the connection string - so it sounds like your connection string is not correctly escaping the values (most likely, the password); you could try:

var cs = new DbConnectionStringBuilder();
cs["SERVER"] = server;
cs["DATABASE"] = database;
cs["UID"] = uid;
cs["PASSWORD"] = password;
var connectionString = cs.ConnectionString;


回答2:

This is not an answer to the NullReferenceException - we're still working through that in the comments; this is feedback for the security parts.

The first thing we can look at is SQL injection; this is very easy to fix - see below (note I've tidied some other things too)

// note: return could be "bool" or some kind of strongly-typed User object
// but I'm not going to change that here
public string[] GetValidUser(string dbUsername, string dbPassword)
{
    // no need for the table to be a parameter; the other two should
    // be treated as SQL parameters
    string query = @"
SELECT id,email,password FROM tbl_user
WHERE email=@email AND password=@password";

    string[] resultArray = new string[3];

    // note: it isn't clear what you expect to happen if the connection
    // doesn't open...
    if (this.OpenConnection())
    {
        try // try+finally ensures that we always close what we open
        {
            using(MySqlCommand cmd = new MySqlCommand(query, connection))
            {
                cmd.Parameters.AddWithValue("email", dbUserName); 
                // I'll talk about this one later...
                cmd.Parameters.AddWithValue("password", dbPassword); 

                using(MySqlDataReader dataReader = cmd.ExecuteReader())
                {
                    if (dataReader.Read()) // no need for "while"
                                           // since only 1 row expected
                    {
                        // it would be nice to replace this with some kind of User
                        //  object with named properties to return, but...
                        resultArray[0] = dataReader.GetInt32(0).ToString();
                        resultArray[1] = dataReader.GetString(1);
                        resultArray[2] = dataReader.GetString(2);

                        if(dataReader.Read())
                        { // that smells of trouble!
                            throw new InvalidOperationException(
                                "Unexpected duplicate user record!");
                        }
                    }
                }
            }
        }
        finally
        {
            this.CloseConnection();
        }
    }
    return resultArray;
}

Now, you might be thinking "that's too much code" - sure; and tools exist to help with that! For example, suppose we did:

public class User {
    public int Id {get;set;}
    public string Email {get;set;}
    public string Password {get;set;} // I'll talk about this later
}

We can then use dapper and LINQ to do all the heavy lifting for us:

public User GetValidUser(string email, string password) {
    return connection.Query<User>(@"
SELECT id,email,password FROM tbl_user
WHERE email=@email AND password=@password",
      new {email, password} // the parameters - names are implicit
    ).SingleOrDefault();
}

This does everything you have (including safely opening and closing the connection), but it does it cleanly and safely. If it method returns a null value for the User, it means no match was found. If a non-null User instance is returned - it should contain all the expected values just using name-based conventions (meaning: the property names and column names match).

You might notice that the only code that remains is actually useful code - it isn't boring plumbing. Tools like dapper are your friend; use them.


Finally; passwords. You should never store passwords. Ever. Not even once. Not even encrypted. Never. You should only store hashes of passwords. This means that you can never retrieve them. Instead, you should hash what the user supplies and compare it to the pre-existing hashed value; if the hashes match: that's a pass. This is a complicated area and will require significant changes, but you should do this. This is important. What you have at the moment is insecure.