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.

Wednesday, July 05, 2006

Framework Errors Thanks to the IIS LockDown Tool

I started getting the following error after running the IIS LockDown Tool on my development PC at work. It was on a virtual machine so there was no real damage done. Use this as an example of what could happen.

“Cannot execute a program. The command being executed was "C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\csc.exe" /noconfig /fullpaths @"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\sharedservices\8edd40bf\a4dccc9b\f8suefbe.cmdline".

This was occurring for my 1.1 apps as well. After much searching and messing with permissions, I found the solution. A simple “repair” of the framework install does the trick and resets the permissions appropriately. I believe the reason I could not get the permissions correct is because the tool will reset registry persmissions for certain nodes. Depending on how many are change, you can see that this could become a tedious task if done manually.

If this post helps you, let me know an post a comment. - E

Thursday, June 15, 2006

Project REAL

This is a cool project to develop Business Intelligence standards using Microsoft SQL Server solutions.

http://www.microsoft.com/sql/solutions/bi/projectreal.mspx

Thursday, June 08, 2006

Updating a Database Using a DataSet With EntLib 2.0




public partial class StateDisclaimer : UserControl
{
private Database db;
private DataSet ds;
private DbCommand cmd;
private string strConfigParameters;

public StateDisclaimer()
{
InitializeComponent();
}

private void StateDisclaimer_Load(object sender, EventArgs e)
{
LoadDataGrid();
}

private void LoadDataGrid()
{
try
{
// Create the Database
db = DatabaseFactory.CreateDatabase("dbConnection");
// Retrieve the initial DataSet
ds = new DataSet();
cmd = db.GetStoredProcCommand("sp_getMiniMirandaInfo");
strConfigParameters = "ConfigParameters";
db.LoadDataSet(cmd, ds, strConfigParameters);
dataGridView1.DataSource = ds.Tables[0];
dataGridView1.Columns[0].Visible = false;
dataGridView1.Columns[1].ReadOnly = true;
dataGridView1.Sort(dataGridView1.Columns[1], ListSortDirection.Ascending);
dataGridView1.AutoResizeColumns();
}
catch (DbException ex)
{
MessageBox.Show(ex.ToString());
}
}

private void btnApply_Click(object sender, EventArgs e)
{
if (ds.HasChanges())
{
DbCommand updateCommand = db.GetStoredProcCommand("sp_UpdateConfigParametersDataValue");
db.AddInParameter(updateCommand, "Category", DbType.String, "Category", DataRowVersion.Current);
db.AddInParameter(updateCommand, "KeyValue", DbType.String, "KeyValue", DataRowVersion.Current);
db.AddInParameter(updateCommand, "DataValue", DbType.String, "DataValue", DataRowVersion.Current);
int rowsAffected = db.UpdateDataSet(ds, "ConfigParameters", null, updateCommand, null, UpdateBehavior.Standard);
MessageBox.Show(rowsAffected.ToString());
}
}

private void btnCancel_Click(object sender, EventArgs e)
{
if (ds.HasChanges())
{
try
{
ds.RejectChanges();
dataGridView1.Refresh();
}
catch (DbException ex)
{
MessageBox.Show(ex.ToString());
}
}
}
}

Wednesday, May 31, 2006

A Day of Mourning

Today is a day of mourning. A once great man has succumbed to the pressures of life. Wait, that’s a typo. He has succumbed to the pressures of WIFE, and went out and bought a minivan. Here’s to you, and your recently detached testicles. Godspeed!

 

You know who you are ;)

 

 

Wednesday, May 03, 2006

ModTool Gets a Name Change

Based upon beta-tester feedback I have decided to change the name of ModTool to WoW Mod Manager. This new name will be part of the next beta release due May 9, 2006.

Thanks for the great feedback!

Saturday, April 29, 2006

ModTool Goes to Beta

ModTool is a World of Warcraft UI modification management tool. It allows you to completely manage the mods you have installed. What's more, it will allow you to select which mods to enable/disable on a per character basis.

Select the mods you wish to manage and right-click to display a menu of options. Multiple mods can quickly be enabled, disabled, or deleted. You can also check for an updated version of a mod by right-clicking it, and selecting Update Mod. This action will load your default web browser and search for the latest version.

This release is beta so please post any comments to this blog entry.

Requirements

Download it here.

Friday, March 31, 2006

ASP.NET, PHP, ASP Performance Matrix

I was reading an article online comparing PHP to ASP.NET the other day. The author, an Oracle employee, had some valid claims regarding PHP’s advantages when it comes to costs and flexibility, but he also had some unfounded claims that PHP was more efficient and faster. I beg to differ, so I ran some tests using a similar function for all three languages.

Test Platform

The test was run on an AMD Athlon XP 2800+ with 2 GB of ram running Windows XP Professional SP2. We used a standard install of Internet Information Services and gave PHP the added benefit of installing Zend Optimizer v2.6.2. We used the php5isapi.dll to map and execute our PHP scripts.

  • AMD Athlon XP 2800+ (2.08 GHz)
  • 2 GB Ram
  • Windows XP Professional SP2
  • Internet Information Services v5.1
  • Zend Optimizer v2.6.2
  • PHP 5.1.2
  • ASP 3.0
Test Methodology

We wrote a similar script to test each language. It was a simple for loop that was programmed to loop 80 million times. You might need to increase the max execution settings in the php.ini file for PHP, or increase it programmatically. We chose to do it in the php.ini to make sure it was set correctly. The same holds true for ASP. You may need to increase the Server.ScriptTimeout value.

Some lines below might wrap, so be sure to check that before attempting to execute the code.

ASP Code


<%

'Server.ScriptTimeout = 360


'Response.Buffer = False





Dim i

Dim startTime


Dim endTime

Dim totalTime



For x = 1 To 10


  startTime = Now

  Response.Write("Start Time: " & startTime & "<br />")

 

  For i = 1 To 80000000

    If i > 0 Then

    End If

  Next



  endTime = Now


  totalTime = endTime - startTime





  Response.Write("End Time: " & endTime & "<br />")


  Response.Write("Total time: " & Minute(totalTime) & ":" & Second(totalTime) & "<br /><br />")

Next


%>



PHP Code

<?php

function getmicrotime()

{

  list($usec, $sec) = explode(" ",microtime());

  return ((float)$usec + (float)$sec);

}



for ($x=0; $x < 10; $x++)

{


  $number = 0;

  $time_start = getmicrotime();



  for ($i=0; $i < 80000000; $i++)

  {

    if ("hello" == "world") { }

  }



  $time_end = getmicrotime();


  $time = $time_end - $time_start;



  echo "Did 80M
additions in $time seconds";

}

?>



ASP.NET (VB.NET) Code

<%@ Language="VB" %>

<script runat="server" language="vb">

Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs)

  Dim i As Long

  Dim x As Int16

  Dim startTime As Decimal

  Dim endTime As Decimal

  Dim totalTime As Decimal



  For x = 1 To 10

    startTime = Convert.ToDecimal(DateTime.Now.ToString("ss.fff"))

    Response.Write("Start Time: " & startTime.ToString() & "<br />")

    

    For i = 1 To 80000000

      If "hello" Is "world" Then

      End If

    Next



    endTime = Convert.ToDecimal(DateTime.Now.ToString("ss.fff"))

    totalTime = endTime - startTime



    Response.Write("End Time: " & endTime.ToString() & "<br />")

    Response.Write("Total time: " & totalTime.ToString() & "<br /><br />")

  Next

End Sub



Results

It’s no surprise that ASP.NET is the clear winner. The results that did surprise me were that Zend Optimizer consistently increased PHP’s performance by nearly 50%. That is a huge number. It is nonsense not to use PHP with the Optimizer according to our study.

The PHP results were roughly double of the numbers listed below without the Zend Optimizer running. And, as you can see, ASP’s performance is just plain bad.































































ASP.NET
PHP
ASP
0.14
15.58716
33
0.125
15.92204
33
0.157
15.58681
35
0.124
15.58992
35
0.125
15.59410
33
0.125
15.93063
34
0.141
15.60623
33
0.125
15.60107
33
0.14
15.60247
34
0.125
16.14546
35

Results are in seconds



We welcome and performance enhancements and/or your own results. Just post your thoughts, results and any suggestions. I want to extend special thanks to Rufus Harvey for his technical prowess in helping put this test together!

- Ernest Carroll