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.
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.
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;
Related
Through the information I have found searching here on stackoverflow, I have what I think is 90% of this solved but because of how OleDbConnection is converted to define SqlConnection, the call to the class with the connection and test script wants definition I am unsure how to provide.
I've used these pages to get what I have so far
How to create an SqlConnection in C# to point to UDL file
Calling an SQL Connection method in C#
private static string CONNECTION_NAME = #"C:\Temp\DisplayDB.udl";
public static class MyConnection
{
public static SqlConnection GetSqlConnection()
{
var udlInfo = new OleDbConnection($"File Name={CONNECTION_NAME}");
return CreateSqlConnection(udlInfo);
}
public static SqlConnection CreateSqlConnection(OleDbConnection udlInfo)
{
try
{
string CONNECTION_STRING = $"Database={udlInfo.Database};Server={udlInfo.DataSource};User ID=User;Password=13245;Integrated Security=True;connect timeout = 30";
var connection = new SqlConnection(CONNECTION_STRING);
connection.Open();
return connection;
}
catch
{
Console.WriteLine($"{CONNECTION_NAME} Not found");
return null;
}
}
}
private void DBCheck()
{
// The line below is my issue, mouseover error of ".CreateSqlConnection"
// says there is no argument given that corresponds to the required
// formal parameter 'udlInfo' of
// 'MainWindow.MyConnection.CreateSqlConnection(OleDbConnection)'
using (var con = MyConnection.CreateSqlConnection())
{
con.Open();
var command = new SqlCommand("IF DB_ID ('CodeTest') IS NULL " +
"BEGIN " +
"USE MASTER " +
"CREATE DATABASE CodeTest" +
" END", con);
var reader = command.ExecuteReader();
reader.Close();
con.Close();
}
}
I expect the WPF to use the Database and Server from the UDL file to make the SqlConnection so I can run queries and commands. I understand the security part of UDL in plain text but I do not want hard coded values as this application will be used in various environments nor do I want those values to need definition on each launch of the app.
I am getting the error as
Input string was not in a correct format.
newRow["col_frm_bin_id"] = CF.ExecuteScaler("Select location_name from wms_storage_bin where mkey = " + e.Record["from_bin"] + "");
public string ExecuteScaler(string StrQuery)
{
DB.EConnection();
cmd = new SqlCommand(StrQuery, DB.conn);
cmd.Connection = DB.conn;
int val=Convert.ToInt32(cmd.ExecuteScalar());
DB.conn.Close();
string ret = val.ToString();
return ret;
}
I tried with converting but still it didn't worked
Your return column name sounds like its a string variable, Change it with int type column, or remove Convert.ToInt32 from code side
public string ExecuteScaler(string StrQuery)
{
DB.EConnection();
cmd = new SqlCommand(StrQuery, DB.conn);
cmd.Connection = DB.conn;
string ret=cmd.ExecuteScalar().ToString();
DB.conn.Close();
return ret;
}
i think you should do like this but this is not good practice and also not safe
your mkey value should be in between quotes
mkey = '" + e.Record["from_bin"] + "'
newRow["col_frm_bin_id"] = CF.ExecuteScaler("Select location_name from wms_storage_bin where mkey = '" + e.Record["from_bin"] + "'");
public string ExecuteScaler(string StrQuery)
{
DB.EConnection();
cmd = new SqlCommand(StrQuery, DB.conn);
cmd.Connection = DB.conn;
int val=Convert.ToInt32(cmd.ExecuteScalar());
DB.conn.Close();
string ret = val.ToString();
return ret;
}
but sending parameters is best practice
I'll try summarize various pieces of information from other answers and comments.
First, your existing code is open to Sql injections. This is a very bad thing. To avoid the risk of Sql injection you shoul use Parametrized queries. See for instance here.
That means your ExecuteScaler method should not take a string as its argument, but instead a SqlCommand (I have corrected the spelling of scalar):
public string ExecuteScalar(SqlCommand query) { ... }
Your current implementation of ExecuteScaler is also at risk of leaking SqlConnetions. If an exception is thrown in this method before the DB.conn.Close() line, the connection will not be closed. For instance, in the case you described in the question, the following line is the prime suspect:
int val = Convert.ToInt32(cmd.ExecuteScalar());
With your current call to the method, you seem to be fetching something that is a string from the database. Unless that string is convertible to Int32, this line will throw an exception, and the connection will not be closed. To fix this, you should at the minimum add a try { ... } finally { ... } block:
public string ExecuteScalar(SqlCommand query)
{
try
{
DB.EConnection();
query.Connection = DB.conn;
string ret = query.ExecuteScalar().ToString();
return ret;
}
finally
{
if(DB.conn.State == ConnectionState.Open)
DB.conn.Close();
}
}
I would also suggest that you create separate versions of ExecuteScalar for different expected return types. Perhaps:
public string GetStringScalar(SqlCommand query)
public int GetInt32Scalar(SqlCommand query)
etc.
The calling code then needs to be changed:
string locName = null;
using (SqlCommand locNameCommand = new SqlCommand(#"
select location_name
from wms_storage_bin
where mkey = #mkey
"))
{
locNameCommand.Parameters.AddWithValue("mkey", e.Record["from_bin"]);
locName = GetStringScalar(locNameCommand);
}
newRow["col_frm_bin_id"] = locName;
Depending in what kind of password I use to store in the database my program crashes on "Consulta" method when its trying to do Reader.close() inside the first "if".
For example if I set as password "1234" I have no problems, with "prueba" instead the crash happens and it doesnt even go to "catch".
This is the method to save a new user, which is in a button event.
private void bbtnGuardar_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e)
{
DB con = new DB(path);
MD5 md5 = MD5.Create();
byte[] hash = md5.ComputeHash(Encoding.Default.GetBytes(tbPassword.Text));
String encryptedPassword = Encoding.Default.GetString(hash);
string query = "INSERT INTO IEM182_LOGIN (USUARIO, PASSWORD) VALUES " +
"('" + tbUsuario.Text + "', '" + encryptedPassword + "');";
try
{
con.Consulta(query);
MessageBox.Show("Nuevo usuario dado de alta correctamente");
limpiar_usuario();
}
catch
{
try
{
con.Cerrar();
query = "SELECT * FROM IEM182_LOGIN WHERE USUARIO = '" + tbUsuario.Text + "'";
con.Consulta(query);
if (con.Reader.Read())
{
MessageBox.Show("El nombre de usuario ya existe");
}
}
catch
{
MessageBox.Show("El usuario contiene caracteres no validos");
}
con.Cerrar();
}
This is the database class with some methods.
class DB
{
private SqlDataReader rdr;
private string path;
private SqlConnection con;
private SqlCommand cmd;
public DB(string cadenaConexion)
{
path = cadenaConexion;
con = new SqlConnection(path);
}
public void Consulta(string query)
{
if (Conexion.State == System.Data.ConnectionState.Open)
{
cmd = new SqlCommand(query, con);
Reader.Close();
Reader = cmd.ExecuteReader();
}
else
{
con.Open();
cmd = new SqlCommand(query, con);
Reader = cmd.ExecuteReader();
}
}
public SqlDataReader Reader
{
get { return rdr; }
set { rdr = value; }
}
public void Cerrar()
{
con.Close();
}
public SqlConnection Conexion
{
get { return con; }
set { con = value; }
}
This is your problem:
byte[] hash = md5.ComputeHash(Encoding.Default.GetBytes(tbPassword.Text));
String encryptedPassword = Encoding.Default.GetString(hash);
There is absolutely no guarantee that the bytes you get back from a hash algorithm forms a legal unicode string, in fact I would say that it is a very slim chance that this will produce usable strings at all. Seems you have found a few odd cases that do. This is just by chance.
You should not pipe those bytes through Encoding.Default.GetString, instead you should use something like Base 64 encoding:
String encryptedPassword = Convert.ToBase64String(bytes);
This will produce a string that doesn't have oddball characters that won't survive a roundtrip through the database.
To get the hash bytes back you decode using the same class:
byte[] hash = Convert.FromBase64String(encryptedPassword);
Now, is this the only problem with your code?
No, it isn't.
The second problem, that coupled with the above one, will throw a spanner into your SQL execution is this:
string query = "INSERT INTO IEM182_LOGIN (USUARIO, PASSWORD) VALUES " +
"('" + tbUsuario.Text + "', '" + encryptedPassword + "');";
You should never form a SQL through string concatenation, you should instead use parameters.
Since you've made a method, Consulta that does this querying, actually modifying your code to use parameters is quite a bit of changes but to execute the above SQL, with parameters, you would do something like this:
string query = "INSERT INTO IEM182_LOGIN (USUARIO, PASSWORD) VALUES " +
"(#username, #password);";
var cmd = new SqlCommand();
cmd.CommandText = query;
cmd.Parameters.AddWithValue("#username", tbUsuario.Text);
cmd.Parameters.AddWithValue("#password", encryptedPassword);
cmd.ExecuteNonQuery();
Exactly how you go about changing your Consultas method to handle this is up to you, but this is the way you must do it!
So to fix the problem I was having I used another encrypting method
// byte array representation of that string
byte[] encodedPassword = new UTF8Encoding().GetBytes(password);
// need MD5 to calculate the hash
byte[] hash = ((HashAlgorithm)CryptoConfig.CreateFromName("MD5")).ComputeHash(encodedPassword);
// string representation (similar to UNIX format)
string encoded = BitConverter.ToString(hash)
// without dashes
.Replace("-", string.Empty)
// make lowercase
.ToLower();
And fixed the first "try/catch" adding another try/catch to call the con.Consulta() method and to catch the invalid characters.
Thanks you very much https://stackoverflow.com/users/267/lasse-v-karlsen
Assuming that I call the method below with the right credentials:
private bool Connect(string username, string password)
{
string CONNSTRING = "Provider = MSDAORA; Data Source = ISDQA; User ID = {0}; Password = {1};";
OleDbConnection conn = new OleDbConnection();
string strCon = string.Format(CONNSTRING, username, password);
conn.ConnectionString = strCon;
bool isConnected = false;
try
{
conn.Open();
if (conn.State.ToString() == "Open")
isConnected = true;
}//try
catch (Exception ex)
{
lblErr.Text = "Connection error";
}//catch
finally
{
conn.Close();
}//finally
return isConnected;
}
I have successfully open the connection in my method below:
private bool ValidateUserCode(string usercode)
{
UserAccountDefine def = new UserAccountDefine();
UserAccountService srvc = new UserAccountService();
UserAccountObj obj = new UserAccountObj();
bool returnVal = false;
bool isValid = Connect(def.DB_DUMMY_USERCODE, def.DB_DUMMY_PASSWORD);
if (isValid)
{
obj.SQLQuery = string.Format(def.SQL_LOGIN, usercode.ToLower(), DateTime.Now.ToString("MM/dd/yyy"));
DataTable dt = srvc.Execute(obj, CRUD.READALL);
if (dt.Rows.Count == 1)
{
returnVal = true;
}
}
return returnVal;
}
The question is how can I determine the connection status in ValidateUserCode() method?
How can I close it afterwards?
Note:
I explicitly declare the string variables in UserAccountDefine(); so you don't have to worry about that.
I already tried declaring a new OleDbConnection conn inside the ValidateUserCode() to but the conn.State always returning "Closed".
UPDATE
I have a system with 2-layer security feature. 1st is in application and 2nd is on database. If a user logs in to the application, the username and password is also used to log him/her in to the database. Now, the scenario is when a user forgot his/her password, we can't determine the fullname, email and contact (which are maintained in the database) of the user. I just know his usercode. To determine the contact details, I have to open an active connection using a DUMMY_ACCOUNT.
Note that I never maintain the password inside the database.
First of all, you call Close() in your finally block, which means that at any point in your second method, the connection would be closed. Moreover, even if you don't Close() it,since conn is a local variable in Connect(), when you're back in ValidateUserCode(), the connection is already up for garbage collection, and when it's Dispose()d, it also closes automatically.
I sugges you either make it a member, pass it as an out parameter, return it by the Connect() method (and return null for failure, or something, if you don't like exceptions)..or redesign the code.
private OleDbConnection Connect(string username, string password)
{
string CONNSTRING = "Provider = MSDAORA; Data Source = ISDQA; User ID = {0}; Password = {1};";
OleDbConnection conn = new OleDbConnection();
string strCon = string.Format(CONNSTRING, username, password);
conn.ConnectionString = strCon;
try
{
conn.Open();
if (conn.State.ToString() == "Open")
return conn;
}//try
catch (Exception ex)
{
lblErr.Text = "Connection error";
}//catch
finally
{
//you don't want to close it here
//conn.Close();
}//finally
return null;
}
I am not sure how this information helps you.
I had similar problem while using OLEDB connection for Excel Reading. I didn't knew the answer. So, just I added a global variable for OleDbConnection initialized to null.
In my method, I used to check that null, if not close it and again open it.
if (con != null)
{
con.Close();
con.Dispose();
}
try
{
con = new OleDbConnection(connectionString);
}
catch (Exception ex)
{
MessageBox.Show("oledbConnection = " + ex.Message);
}
try
{
con.Open();
}
catch (Exception ex)
{
MessageBox.Show("connection open = " + ex.Message + "\n");
}
I could able to continue after this. You can try, if it works for you its good!
I'm not sure I follow the question quite right. My answer is based on the premise that you want to open/retrieve a connection, take an action, and close/release the connection afterward.
The code you include does not do that well. Typical DAO code resembles this pseudocode, in my case taken from some boilerplate code I use.
public DataSet FetchDataSet(string sql, IDictionary paramHash) {
var cnn = AcquireConnection();
var rtnDS = new DataSet();
try
{
var cmd = cnn.CreateCommand();
cmd.CommandText = sql;
SetParameters(cmd, paramHash);
IDbDataAdapter ida = new DataAdapter { SelectCommand = cmd };
LogSql(sql, paramHash, "FetchDataSet");
ida.Fill(rtnDS);
}
catch (Exception ex)
{
DebugWriteLn("Failed to get a value from the db.", ex);
throw;
}
finally
{
ReleaseConnection(cnn);
}
return rtnDS;
}
Note that the code above is strictly about communicating with the database. There is no assessment of whether the data is right or wrong. You might have a DAO that is a subclass of the one that contains the above code, and it might do this:
public MyItemType FindSomeValue(long Id)
{
const string sql = #"SELECT something from somewhere where id=:id";
var myParams = new Dictionary<string, long> { { "id", Id } };
var ds = FetchDataSet(sql, myParams);
return (from DataRow row in ds.Tables[0].Rows
select new Item
{
Id = Convert.ToInt64(row["ID"], CultureInfo.InvariantCulture),
Name = row["NAME"].ToString()
}).FirstOrDefault();
}
In fact, the above is pseudocode from a DAO implementation that I've used for years. It makes data access relatively painless. Note that there is some real code behind those methods like SetParameters (30 - 80 lines or so), and I have a bunch of other protected methods like FetchScalar, ExecuteSQL, etc.
Using SQL Membership Provider for ASP.NET membership. I'm using first.last as the username, which is created programmatically from the user details filled in on a form.
When user submits the form, I want to be able to check if the username exists, and change it to username1 if it does, check username1 exists, and make it username2 if it exists, etc. until it is a unique username.
I don't know how to do stored procedures, so I'm trying to use a SQLDataReader to check if username exists.
The problem is my loop. The logic is basically to set a boolean and keep looping and adding 1 to the counter, until it doesn't find a duplicate. I have stepped through this many times, and even when it sets the boolean to false, it keeps looping.
Ideas please?
Code behind:
protected void Membership_add()
{
SqlConnection con = new SqlConnection(connectionString);
string NewUserNameString = FirstName.Text + "." + LastName.Text;
//Check for duplicate aspnet membership name and add a counter to it if exists
// Check for valid open database connection before query database
bool match = true;
SqlDataReader _SqlDataReader = null;
string TestNameString = NewUserNameString;
string selectDupeString = "SELECT UserId FROM aspnet_Users WHERE UserName = '" + TestNameString + "'";
SqlCommand SQLdatareaderCmd = new SqlCommand(selectDupeString, con);
int UserNameCounter = 0;
con.Open();
while (match = true)
{
//Open the connection
try
{
//Read the table
_SqlDataReader = SQLdatareaderCmd.ExecuteReader();
}
catch (Exception ex)
{
lblDatareaderEx.Text = "An Exception occurred. " + ex.Message + " " + ex.GetType().ToString();
}
if (_SqlDataReader.HasRows)
{
//match = true;
//increase counter by 1 for each record found and change First.Name to First.Namex
TestNameString = NewUserNameString;
UserNameCounter = UserNameCounter + 1;
TestNameString = TestNameString + UserNameCounter.ToString();
_SqlDataReader.Close();
_SqlDataReader.Dispose();
selectDupeString = "SELECT UserId FROM aspnet_Users WHERE UserName = '" + TestNameString + "'";
SQLdatareaderCmd = new SqlCommand(selectDupeString, con);
}
else
{
// close sql reader
_SqlDataReader.Close();
_SqlDataReader.Dispose();
//get out of loop
match = false;
}
}
con.Close();
con.Dispose();
}
This line:
while (match = true)
does an assignment.
If you want your code to work you have to do a comparison:
while (match == true)
Or, since your variable is already a bool, you can just use the variable directly:
while(match)
At the moment you're setting match rather than comparing it's value.
Try setting while (match = true) to while (match == true)
If you break your code out into smaller blocks, the code becomes simpler and easer to read.
private string MembershipAddUser(string firstName, string lastName)
{
string username = firstName + "." + lastName;
int i = 0;
while (UserExists(username))
{
i++;
username = firstName + "." + lastName + i.ToString();
}
return username;
}
private bool UserExists(string username)
{
string sql = "SELECT COUNT(*) FROM dbo.aspnet_Users WHERE UserName = #UserName";
SqlConnection connection = new SqlConnection(connectionString);
SqlCommand command = new SqlCommand(sql, connection);
command.Parameters.AddWithValue("#UserName", username);
using (connection)
{
connection.Open();
int count = (int) command.ExecuteScalar();
return (count != 0);
}
}