Wednesday, November 08, 2006

DropDownList in GridView


The problem I encountered was being able to use an updateable GridView using data from multiple tables. The two main tables are called Category and Forum. A forum can only belong to one category, but a category can contain multiple forums. To make things a bit more difficult, I used a associative table to relate the two called CategoryForum.

My goal is to be able to manage my forums without have to know the category ID of each category. It should be simple enough for a novice to use. My architectural solution had several components. Of course, I had the GridView. Then I had two ObjectDataSources using a web service to return forum and category data. The forum data includes a category ID via an INNER JOIN in the query. The category data is just the list of all categories.

First, I override the Page's OnInitComplete method to capture the update event:



   1:  protected override void OnInitComplete(EventArgs e)

   2:  {

   3:    base.OnInitComplete(e);

   4:    gridView.RowUpdating +=

   5:      new GridViewUpdateEventHandler(gridView_RowUpdating);

   6:  }


Next, I wrote some code for the overridden method. This code will store my DropDownList's data in the updating data objects field:

void gridView_RowUpdating(Object sender, GridViewUpdateEventArgs e) {
int index = gridView.EditIndex;
GridViewRow row = gridView.Rows[index];
DropDownList categoryDropDown =
(DropDownList) row.FindControl("ddlCategoryName");
e.NewValues["CategoryId"] =
int.Parse(categoryDropDown.SelectedValue);
}

That's all the code-behind code I needed. In design view I have two ObjectDataSources wired up to a web service that simply delivers a DataSet to each. One DataSet is my Forum data, and the other is Category data. Here's the EditItemTemplate code for the DropDownList. The datasource for the GridView is the ForumData. I used the Category datasource for the DropDownList.

<asp:TemplateField HeaderText="Category" SortExpression="CategoryName">
<ItemTemplate>
<%# Eval("CategoryName") %>
</ItemTemplate>
<EditItemTemplate>
<asp:DropDownList runat="server"
ID="ddlCategoryName" DataTextField="CategoryName"
DataValueField="CategoryId" DataSourceID="objCategories" />
</EditItemTemplate>
</asp:TemplateField>

Encrypting/Decrypting QueryStrings In .NET

I was messing around with a secure way to pass data via a querystring today. In business you sometimes don't have a choice. What I came up with was a secure two-way encryption/decryption class. I can now encode/decode any piece of data without having knowledge of the key.


The Methods
I have an error page in an ASP.NET application. The page is basically going to act as a catch all page for errors in my forum application. I want to be able to send it exception strings without having to rely upon Server.GetLastError. I researched some of the System.Security.Cryptography namespace for a seed-based algorithm. DES seemed to fit that bill perfectly. MSDN even went so far as to write the Encrypt and Decrypt methods for me.



1 // Encrypt the string.
2 public byte[] Encrypt(string PlainText, SymmetricAlgorithm key)
3 {
4 // Create a memory stream.
5 MemoryStream ms = new MemoryStream();
6
7 // Create a CryptoStream using the memory stream and the
8 // CSP DES key.
9 CryptoStream encStream = new CryptoStream(ms, key.CreateEncryptor()
10 CryptoStreamMode.Write);
11
12 // Create a StreamWriter to write a string
13 // to the stream.
14 StreamWriter sw = new StreamWriter(encStream);
15
16 // Write the plaintext to the stream.
17 sw.WriteLine(PlainText);
18
19 // Close the StreamWriter and CryptoStream.
20 sw.Close();
21 encStream.Close();
22
23 // Get an array of bytes that represents
24 // the memory stream.
25 byte[] buffer = ms.ToArray();
26
27 // Close the memory stream.
28 ms.Close();
29
30 // Return the encrypted byte array.
31 return buffer;
32 }
33
34 // Decrypt the byte array.
35 public string Decrypt(byte[] CypherText, SymmetricAlgorithm key)
36 {
37 // Create a memory stream to the passed buffer.
38 MemoryStream ms = new MemoryStream(CypherText);
39
40 // Create a CryptoStream using the memory stream and the
41 // CSP DES key.
42 CryptoStream encStream = new CryptoStream(ms, key.CreateDecryptor(),
43 CryptoStreamMode.Read);
44
45 // Create a StreamReader for reading the stream.
46 StreamReader sr = new StreamReader(encStream);
47
48 // Read the stream as a string.
49 string val = sr.ReadLine();
50
51 // Close the streams.
52 sr.Close();
53 encStream.Close();
54 ms.Close();
55
56 return val;
57 }


This is a great start if I wanted byte arrays. I need to flatten this array out so I can use it like a string. I decided to write a couple more methods named EncryptToString and DecryptToString.



1 public string EncryptToString(string PlainText, SymmetricAlgorithm key)
2 {
3 byte[] buffer = this.Encrypt(PlainText, key);
4 string temp = string.Empty;
5
6 for (int i = 0; i < buffer.Length; i++)
7 {
8 temp += buffer[i].ToString("x2");
9 }
10
11 return temp;
12 }
13
14 public string DecryptFromString(string CypherText, SymmetricAlgorithm key)
15 {
16 int arrayLen = (CypherText.Length / 2);
17 byte[] buffer = new byte[arrayLen];
18
19 for (int i = 0; i < (CypherText.Length / 2); i++)
20 {
21 buffer[i] = Convert.ToByte(Convert.ToInt32(CypherText.Substring(i * 2, 2), 16));
22 }
23
24 return this.Decrypt(buffer, key);
25 }


These methods simply use the two that MSDN provided, but add some app-centric features. EncryptToString will get the byte array from the Encrypt method. Then it will create, and append to, a string while converting each item in the byte array to a two-digit hexadecimal number. The reason I converted to hex was to shorten the total length of the string. A handy byproduct was that it added yet another layer of obfuscation to my data making that much more difficult to decrypt. DecryptFromString takes the encrypted string and essentially reverses the process.


What about the key?
This is my favorite part of the entire solution. The key is generated as an application level variable in the Global.asax. I have to admit that I don't use the Global.asax that much, but it seemed like a reasonable place for the code to generate the key.



1 void Application_Start(object sender, EventArgs e)
2 {
3 DESCryptoServiceProvider key = new DESCryptoServiceProvider();
4 Application.Add("SecureKey", key);
5 }


Of course, this could have been Session level, but I didn't need to change the key that frequently. When adding this code to your page be sure to import the namespace, <%@ Import Namespace="System.Security.Cryptography" %>.


In Action
The final piece is how we would use this in the code. Another reason I went this route was to simplify what I needed to code when using this class. In this example, we have made reference to the class containing the encryption/decryption methods. I called mine, Crypto. We'll be encoding an Exception string and sending it to an error page.



1 Crypto cryptKeeper = new Crypto();
2 string encryptedText = cryptKeeper.EncryptToString(
3 new Exception("The request could not be completed.").ToString(),
4 Application["SecureKey"] as System.Security.Cryptography.SymmetricAlgorithm);
5
6 Response.Redirect("~\\Error.aspx?msg=" + encryptedText);


The encryptedText variable looks something like this, 6998d04ec523b093fe0ce9ce9994915a076199ef1f1663d3273e23
72cb5b53dea83f931016f58dc9ae21a4f335de16038eecaee374cacc28. The error page takes the parameter and decrypts it.


1 Crypto cryptKeeper = new Crypto();
2 if (Request.Params["msg"] != null)
3 {
4 lblMsg.Text = cryptKeeper.DecryptFromString(Request.Params["msg"],
5 Application["SecureKey"] as SymmetricAlgorithm);
6 }

Notes
Because I made my key an Application variable, it will be the same for every user of the site and only change when the site is restarted. If I were to make it a Session variable, it would change for every user, and that may be the way to go for me in the future.


I think the possibilities with this are endless. You don't have to use it to pass secure data via a querystring. It can be used to securely store data in your database. You can code your app to generate and store the DES key daily, weekly, or monthly. Access levels to data should be setup to only allow authorized personnel access to the key table. It's not so much different than what my company spent $50,000 on.

Error When Writing to Custom Event Log

I was getting an error when attempting to write to a custom Event Log on Server 2003 from an ASP page. It looked something like this:

Cannot open log for source 'someSource'. You may not have write access.

The solution was to change the security descriptor on the custom event log's key. I did this by browsing to the custom log's folder in registry, HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Eventlog\<Custom Event Log>. In that folder there will be a key named CustomSD. Double-click the key to open it up for editing.

O:BAG:SYD:(D;;0xf0007;;;AN)(D;;0xf0007;;;BG)(A;;0xf0007;;;SY)(A;;0x7;;;BA)(A;;0x7;;;SO)(A;;0x3;;;IU)(A;;0x3;;;SU)(A;;0x3;;;S-1-5-3)

The section highlighted in red denies Read, Write, and Clear access to the log for Anonymous logons (AN) and Built-in guests (BG). This explicit deny overrode any other access I attempted to add to the string.

In order to grant my ASP permission to write to the log, I had to make some modifications. The IUSR account is part of two groups, Users and Guests. With Guests and Anonymous being denied, I now knew I needed to grant them access to this log. A couple of searches on MSDN brought up a full description of the SDDL (Security Descriptor Description Language).

Let's dissect the string (D;;0xf0007;;;AN). The 'D' that appears in the descriptor means 'Access Denied.' The 0xf00007 is hex that will basically deny any type of access currently available. I changed the string to explicitly grant write access to Anonymous logons, (A;;0x2;;;AN). The access rights are your cumulative rights where Read = 1, Write = 2, and Clear = 4. The hexadecimal value of 0x2 evaluates to 2 in decimal which is write access.

After making the change to that string and a similar one to (D;;0xf0007;;;BG) my string looked like:

O:BAG:SYD:(A;;0x2;;;AN)(A;;0x2;;;BG)(A;;0xf0007;;;SY)(A;;0x7;;;BA)(A;;0x7;;;SO)(A;;0x3;;;IU)(A;;0x3;;;SU)(A;;0x3;;;S-1-5-3)

I refreshed my test ASP page and the error was gone. I didn't have to reboot or restart IIS.

References

Corporate Loyalty

How much loyalty do people have for their employers these days? How much should we have? Is loyalty earned?

The company I work for unceremoniously laid off a few people so we could start outsourcing. It's hard to see people go, and the right way to do it is to give them a few weeks notice, or even a few days for that matter. But no, they got about 15 minutes notice (after lunch) to clear out their cubes before getting escorted to the door. No severance, no notice, and no goodbyes.

If I were offered a position tomorrow, I'm sure the company would expect two weeks notice. But, I can't help to ask why? Why should I extend that courtesy, when they couldn't do it for those laid off? Trust me, if they needed to lay off a few more people, I'm sure I'd be let go just like them. Yeah I'll hear, "you shouldn't burn bridges" or some other cliché. While that's true to a degree, I think you need to stand up for what you believe in morally and ethically. After all, you have to sleep! If the new company says they need me in two days, I can't say I would argue with them.