Saturday, July 18, 2009

Silverlight freezing when calling XMLHttpRequest?

I had a javascript handler for a Silverlight event that modifies the XAML, uses XMLHttpRequest synchronously, then modifies the XAML again, and I noticed that the browser would sometimes not show the first XAML change while the XMLHttpRequest was pending.

Example XAML:
<?xml version="1.0" encoding="utf-8" ?> 
<Canvas xmlns="http://schemas.microsoft.com/client/2007">
  <TextBlock Name="Txt1" Canvas.Left="25" Canvas.Top="25" FontSize="12" >Click the box</TextBlock>
  <Rectangle Name="Rect1" Canvas.Left="25" Canvas.Top="40" Width="200" Height="35" Fill="#8080FF" MouseLeftButtonDown="rect1_onMouseDown" />
</Canvas>
Example javascript:
function rect1_onMouseDown(sender, mouseEventArgs) {
  var txt1 = sender.findName("Txt1");
  var rect1 = sender.findName("Rect1");

  txt1.text = "In Progress...";
  rect1.Cursor = "Wait";

  var xmlhttp = new XMLHttpRequest();
  xmlhttp.open("GET", "long-running-url.asp", false);
  xmlhttp.send(null);

  rect1.Cursor = "Default";
  txt1.text = "Complete.";
}

Firefox shows "In Progress..." and then "Complete", but IE waits until everything is finished and then shows "Complete". So when exactly does Silverlight redraw itself when changes are made?

The answer is that changes are not drawn immediately after they are made. They are made the next available chance when no scripts are actively running.

Example that does not redraw on either Firefox or IE.
function rect1_onMouseDown(sender, mouseEventArgs) {
  var txt1 = sender.findName("Txt1");
  var rect1 = sender.findName("Rect1");

  txt1.text = "In Progress...";
  rect1.Cursor = "Wait";

  // keep busy for 0.5 sec
  var start = new Date();
  while ((new Date()).getTime() - start.getTime() < 500) {
    // do nothing
  }
  
  rect1.Cursor = "Default";
  txt1.text = "Complete.";
}

So if you want a redraw while an XMLHttpRequest is pending, do it asynchronously.
function rect1_onMouseDown(sender, mouseEventArgs) {
  var txt1 = sender.findName("Txt1");
  var rect1 = sender.findName("Rect1");

  txt1.text = "In Progress...";
  rect1.Cursor = "Wait";

  var fnOnStateChange = function() {
    if (xmlhttp.readyState == 4) {
      rect1.Cursor = "Default";
      txt1.text = "Complete.";
    }
  };
  var xmlhttp = new XMLHttpRequest();
  xmlhttp.onreadystatechange = fnOnStateChange;
  xmlhttp.open("GET", "long-running-url.asp", true);
  xmlhttp.send(null);
}

Here are a few more examples shown live. The first and third will be redrawn inconsistently and the second one will always redraw during the intermediate "In Progress..." stage. Click the download links below to see the source code for this.

Download source.html, source.xaml

Friday, December 12, 2008

Calling MySQL stored procedures from C# with Connector/Net

Have ever got this error when calling a stored procedure?
MySql.Data.MySqlClient.MySqlException: SELECT command denied to user 'your_user'@'localhost' for table 'proc'
  at MySql.Data.MySqlClient.MySqlStream.OpenPacket () [0x00000] 
  at MySql.Data.MySqlClient.NativeDriver.ReadResult (System.UInt64& affectedRows, System.Int64& lastInsertId) [0x00000] 
  at MySql.Data.MySqlClient.MySqlDataReader.GetResultSet () [0x00000] 
  at MySql.Data.MySqlClient.MySqlDataReader.NextResult () [0x00000] 
Let me explain what is going on here. The first thing Connector/Net does when you call a stored procedure is select the full text of the stored procedure from the server, parse it, and cache information about the parameters. It then uses that information to build the statement for the actual stored procedure call.

It does this by either calling
SELECT * FROM mysql.proc WHERE db LIKE 'mydb_name' AND name LIKE 'MyProcedure';

or by calling
SELECT * FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA LIKE 'mydb_name' AND ROUTINE_NAME LIKE 'MyProcedure';

Why does it do this?

Let me give you a little background. Microsoft SQL Server has a nice feature in that it gives you a lot of flexibility when you call stored procedure. The syntax is
EXEC MyProcedure @MyParam1=123, @MyParam2='blah';

The parameters can appear in any order and any of the parameters can be specified as optional in the stored procedure. This is nice when you want to add a new parameter to a stored procedure and release it to your website without taking downtime. First you run an ALTER statement to release the new stored procedure with a new optional parameter, then you release the new application code which passes the new parameter to the stored procedure. Everything works fine when the application code and stored procedure are not in sync because the new parameter is optional.

MySQL's syntax is different. It is
CALL MyProcedure(123, 'blah');

The parameters have to appear in the right order and there is no support for optional parameters.

Now let's look at how stored procedures are called from the application code. Calling Microsoft SQL Server from C# code looks like this
string connStr = System.Configuration.ConfigurationManager.ConnectionStrings["MyConnectionString"].ConnectionString;
using (System.Data.SqlClient.SqlConnection conn = new System.Data.SqlClient.SqlConnection(connStr)) {
  System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand("MyProcedure", conn);
  cmd.CommandType = System.Data.CommandType.StoredProcedure;
  cmd.Parameters.Add("@MyParam1", System.Data.SqlDbType.Int).Value = 123;
  cmd.Parameters.Add("@MyParam2", System.Data.SqlDbType.VarChar, 40).Value = "blah";
  conn.Open();
  cmd.ExecuteNonQuery();
}
MySQL Connector/Net mimics this syntax fairly closely.
string connStr = System.Configuration.ConfigurationManager.ConnectionStrings["MyConnectionString"].ConnectionString;
using (MySql.Data.MySqlClient.MySqlConnection conn = new MySql.Data.MySqlClient.MySqlConnection(connStr)) {
  MySql.Data.MySqlClient.MySqlCommand cmd = new MySql.Data.MySqlClient.MySqlCommand("MyProcedure", conn);
  cmd.CommandType = System.Data.CommandType.StoredProcedure;
  cmd.Parameters.Add("@MyParam1", MySql.Data.MySqlClient.MySqlDbType.Int32).Value = 123;
  cmd.Parameters.Add("@MyParam2", MySql.Data.MySqlClient.MySqlDbType.String).Value = "blah";
  conn.Open();
  cmd.ExecuteNonQuery();
}
So in order for Connector/Net to figure out what order to put the parameters when it creates the CALL statement it gets the stored procedure definition from the server and looks at the order that the parameters appear in the original CREATE PROCEDURE statement.

There are a couple of problems with this.
  1. The account the application code is running under may not have select permissions on mysql.proc and if you are using shared web hosting it may not be possible to give your account these permissions.
  2. There is a performance penalty for retrieving and storing the stored procedure bodies.
So what I recommend doing is building the CALL statement yourself as a parameterized query.

My recommended way to call MySQL stored procedures
string connStr = System.Configuration.ConfigurationManager.ConnectionStrings["MyConnectionString"].ConnectionString;
using (MySql.Data.MySqlClient.MySqlConnection conn = new MySql.Data.MySqlClient.MySqlConnection(connStr)) {
  MySql.Data.MySqlClient.MySqlCommand cmd = new MySql.Data.MySqlClient.MySqlCommand();
  cmd.Connection = conn;
  cmd.CommandText = "CALL MyProcedure(@MyParam1, @MyParam2);";
  cmd.Parameters.AddWithValue("@MyParam1", 123);
  cmd.Parameters.AddWithValue("@MyParam2", "blah");
  // Or if you prefer this style:
  //cmd.Parameters.Add("@MyParam1", MySql.Data.MySqlClient.MySqlDbType.Int32).Value = 123;
  //cmd.Parameters.Add("@MyParam2", MySql.Data.MySqlClient.MySqlDbType.String).Value = "blah";
  conn.Open();
  cmd.ExecuteNonQuery();
}
Connector/Net will go through the CommandText and replace @MyParam1 and @MyParam2 with the values you provide so the statement that gets sent to the MySQL server is just CALL MyProcedure(123, 'blah'); It's a good idea to do it with parameters because the library code will properly escape single quote characters. If you don't do this, the SQL statement could end up with syntax errors, or worse, end users might be able to run malicious SQL queries against your MySQL server.

Output parameters

Connector/Net does not handle parameters with Direction = System.Data.ParameterDirection.Output unless CommandType = System.Data.CommandType.StoredProcedure. In our case we're using CommandType.Text so we can only use ParameterDirection.Input.

So what do we do if we want to call a stored procedure that has an output parameter?

We use MySQL User Variables. This is the statement we will send to the MySQL server.
CALL MyProcedure(@MyOutputParam1, @MyOutputParam2, 123, 'blah');
SELECT @MyOutputParam1, @MyOutputParam2;


But before we can make this work there are a couple things we need to take care of. One is that we need to include Allow User Variables=true; in the connection string.

Server=localhost; Port=3306; Database=your_database; Uid=your_user; Pwd=your_password; Connect Timeout=30; Allow User Variables=true;

If we don't do this Connector/Net will throw an exception. This is because it checks that the parameter names in the CommandText match up with the parameters that have been added to the list and it doesn't have a way to distinguish between user variables and parameters. If we set Allow User Variables=true; it will skip this check.

The other thing we have to take care of is the data type of the user variables. Connector/Net does not handle result sets with user variables in them properly.

To see for yourself, try running this query:
SET @a = true, @b = 222, @c = -1, @d = 'abcd';
SELECT @a, @b, @c, @d, true, 222, -1, 'abcd';
Connector/Net returns the first three columns in the result set as byte[] and sets the contents of the byte array to be the raw bytes sent back by the MySQL server. The fourth column is a normal string and the rest of the columns are all handled properly.

The MySQL Command Line Client, on the other hand, handles user variables properly. You can see it here.
mysql> SET @a = true, @b = 222, @c = -1, @d = 'abcd';
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @a, @b, @c, @d, true, 222, -1, 'abcd';
+------+------+------+------+------+-----+----+------+
| @a   | @b   | @c   | @d   | TRUE | 222 | -1 | abcd |
+------+------+------+------+------+-----+----+------+
| 1    | 222  | -1   | abcd |    1 | 222 | -1 | abcd |
+------+------+------+------+------+-----+----+------+
1 row in set (0.00 sec)
I was curious what was going on so I intercepted the response sent back by the MySQL server to see if I could tell why Connector/Net was failing. It sends the column definitions first and then the contents of each row. I could see that the row data was the same for column @a and column TRUE, column @b and column 222, and so on, but the header containing the column definitions were different. I didn't investigate further and instead looked for a workaround.

Here is what I did to make it work.

CALL MyProcedure(@MyOutputNum1, @MyOutputString2, 123, 'blah');
SELECT CAST(@MyOutputNum1 AS SIGNED), @MyOutputString2;


If your parameters are not signed integers you will need to cast them as something else. Read about using CAST and CONVERT. Note that SIGNED and UNSIGNED are both 64 bit integers.

Now that I know what to do I can create the application code.
string connStr = System.Configuration.ConfigurationManager.ConnectionStrings["MyConnectionString"].ConnectionString;
using (MySql.Data.MySqlClient.MySqlConnection conn = new MySql.Data.MySqlClient.MySqlConnection(connStr)) {
  MySql.Data.MySqlClient.MySqlCommand cmd = new MySql.Data.MySqlClient.MySqlCommand();
  cmd.Connection = conn;
  cmd.CommandText = "CALL MyProcedure(@MyOutputNum1, @MyOutputString2, ?MyParam1, ?MyParam2); SELECT CAST(@MyOutputNum1 AS SIGNED), @MyOutputString2;";
  // I am using the ?param style to make it easy to differentiate between user variables and parameters. @MyParam1 would also work.
  cmd.Parameters.AddWithValue("?MyParam1", 123);
  cmd.Parameters.AddWithValue("?MyParam2", "blah");
  conn.Open();
  MySql.Data.MySqlClient.MySqlDataReader rdr = cmd.ExecuteReader();
  // If MyProcedure returns a result set that will come first so you will need: 
  //   while (rdr.Read()) {...} 
  //   and rdr.NextResult();
  // Now get the output parameters.
  long myOutputNum1 = -1;
  string myOutputString2 = null;
  if (rdr.Read()) {
    int i = 0;
    if (rdr.FieldCount > i && !rdr.IsDBNull(i)) myOutputNum1 = rdr.GetInt64(i);
    i++;
    if (rdr.FieldCount > i && !rdr.IsDBNull(i)) myOutputString2 = rdr.GetString(i);
  }
}
That worked.

Saturday, December 6, 2008

Javascript and C# encryption

If you are unable to use SSL and you want to use encryption, one option is to use javascript to encrypt and send your data. Then on the server side you can decrypt the data, process it, and send back an encrypted response to be decrypted by javascript.

I have created a page which does this. I used AES (also known as Rijndael) for the private key encryption algorithm. The .NET framework has a built-in class library implementation of this called System.Security.Cryptography.RijndaelManaged and there are some javascript implementations out there on the web. I copied one of them and made some modifications to it, such as adding Base64 encoding.

The private key is hard coded in the server side code and must be entered into a textbox on the page by the user.

Download the code

Running T-SQL queries from your browser against Microsoft SQL Server

In a previous blog post I wrote about how to run MySQL queries from your browser. I thought I would post the source code for running T-SQL queries against Microsoft SQL Server as well, in case anyone wants it.

Download source code

I also made a version that encrypts data before transmitting it.

Friday, December 5, 2008

Running MySQL queries from your browser

Download the code. You will also need to download the Connector/Net binaries.

phpMyAdmin has a place for you to run MySQL queries but there are a few reasons you might want to have a your own admin page on your site for running ad hoc queries.
  1. You might want your queries to run under a different user than the one used by phpMyAdmin. See here for why you may need to run your stored procedure creation scripts this way.
  2. You might want to test a query going through Connector/Net.
  3. Maybe you just want a way to run queries faster without having to log in.
Here is a snippet from a simple page I created to run MySQL queries against my site.
protected void btnSubmit_OnClick(object sender, System.EventArgs e) {
  if (txtKey.Text == "some random string") {
    StringBuilder sb = new StringBuilder();
    using (MySqlConnection conn = new MySqlConnection("Server=localhost; Port=3306; ...")) {
      MySqlCommand cmd = new MySqlCommand(txtSql.Text, conn);
      conn.Open();
      MySqlDataReader rdr = cmd.ExecuteReader();
      while (...) {
        ...
        sb.Append(results of query);
        ...
      }
    }
    litOutput.Text = sb.ToString();
  }
}
Download the real code

Security considerations
  1. Remember to put in a random string for your key in the page.
  2. You may want to remove or disable the page when you are not using it.
  3. Use ssl encryption whenever you visit the page. If you don't have an ssl certificate for your site you can create a self signed certificate in cPanel if your hosting provider lets you. Mine lets me create and import certificates but they don't let me turn on ssl for my site unless I pay for an upgrade so I cannot actually do anything with them. A self signed certificate will give you a warning in your browser but the transmission will still be encrypted. If you are like me and can't enable ssl for your site another option is to encrypt the data in javascrypt before sending it to your server side code.
    Download source code of a version that uses encryption.

Wednesday, November 26, 2008

MySQL stored procedure permissions and cPanel

To set up my site I created all my MySQL tables and stored procedures by running a script in phpMyAdmin, and then I deployed my application files. This resulted in the following error:
execute command denied to user 'my_user'@'localhost' for routine 'my_database.MyProc'
Description: HTTP 500. Error processing request.

Stack Trace: 

MySql.Data.MySqlClient.MySqlException: execute command denied to user 'my_user'@'localhost' for routine 'my_database.MyProc'
  at MySql.Data.MySqlClient.MySqlStream.OpenPacket () [0x00000] 
  at MySql.Data.MySqlClient.NativeDriver.ReadResult (System.UInt64& affectedRows, System.Int64& lastInsertId) [0x00000] 
  at MySql.Data.MySqlClient.MySqlDataReader.GetResultSet () [0x00000] 
  at MySql.Data.MySqlClient.MySqlDataReader.NextResult () [0x00000]
The problem is that cPanel only allows you to grant permissions for a subset of actions.
 SELECT                       CREATE
 INSERT                       ALTER
 UPDATE                       DROP
 DELETE                       LOCK TABLES
 INDEX                        REFERENCES
 CREATE TEMPORARY TABLES      CREATE ROUTINE
The key one that they inexplicably did not add is EXECUTE.

CREATE ROUTINE, ALTER ROUTINE, and EXECUTE were all added to MySQL in version 5.0.3 and have been available since March 2005. I am using shared web hosting that was set up with the latest version of MySQL (version 5.0.67, which came out in August 2008) and the latest version of cPanel (version 11 with the latest build from November 2008). So if I do a little math in my head I can see that sometime in the last three and a half years, the people who make cPanel became aware that MySQL added support for stored procedures and realized that they needed to add a way for people to grant MySQL users the CREATE ROUTINE permission but they still haven't done anything about the EXECUTE permission.

It is not an ideal situation.

What I would like is to have an admin user that has full permissions to do everything on a particular database -- including make schema changes and create stored procedures -- and another user that only has permission to SELECT from tables and EXECUTE routines. The user with the limited permissions is the one that I put in my web.config and use from my web application code.

So I tried granting the permissions by manually executing the command in phpMyAdmin.
GRANT EXECUTE ON my_database.* TO my_user@localhost;
It didn't work.
#1044 - Access denied for user 'my_admin_user'@'localhost' to database 'my_database'
This is another thing that cPanel has not done correctly. My admin user should have been set up with the GRANT permission on my_database. Now there is no way for me to give a user EXECUTE permission. It won't even work if I try using the DEFINER syntax in MySQL.
DELIMITER $$

DROP PROCEDURE IF EXISTS MyProc$$

CREATE DEFINER = my_user@localhost PROCEDURE MyProc()
BEGIN

-- <procedure body>

END $$

DELIMITER ;
I get an error.
#1227 - Access denied; you need the SUPER privilege for this operation 
So what do I do?

The Solution

Create the stored procedures using the account with limited permissions. MySQL keeps track of which account was used to create each stored procedure and automatically allows that account permission to execute it.

Here is a step by step guide to the process.
  1. Go into cPanel and check the box for CREATE ROUTINE for your my_user account.
  2. Upload a script to your website that will create your stored procedures using my_user.
    Here is a script that you can use.
  3. Call your script from your browser.
  4. Remove the script from your website.
  5. Go into cPanel and uncheck the box for CREATE ROUTINE.

Saturday, November 22, 2008

Xml Deserialization: a home grown method

Download source code Download source code
If you have ever called System.Xml.Serialization.XmlSerializer.Deserialize and got this exception: System.InvalidOperationException: '' was not expected, you know that getting the xml namespaces right for deserialization can sometimes be a trial and error process.

If you have access to the class you want to deserialize to you can add a custom attribute to define the xml namespace to use for serialization.
[System.Xml.Serialization.XmlTypeAttribute(Namespace="MyNamespace")]
public class MyClass {
    ...
}
If the class is in one of the built in libraries or in a third party library and you can't modify it you can modify the xml to add a namespace attribute instead.
<MyClass xmlns="MyNamespace">
Another alternative is to use different deserialization code that is more forgiving about things like xml namespaces and case sensitivity. I have created one that uses the XML DOM parser System.Xml.XmlDocument and System.Reflection to iterate through the public fields and properties of an object and find the xml elements and attributes that best match the field names.
// simplified snippet
public static T XmlStrToObjBestWeCan<T>(string xml) {
    System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
    doc.LoadXml(xml);
    foreach (System.Reflection.FieldInfo field in typeof(T).GetFields()) {
        foreach (object attribute in field.GetCustomAttributes(typeof(System.Xml.Serialization.XmlElementAttribute), false)) {
            ...
        }
        foreach (System.Xml.XmlNode child in doc.DocumentElement.ChildNodes) {
            ...
        }
Download complete file

You can download the code and include it in your project to see how it compares to the built in deserializer.