Sunday, January 22, 2006

I am working on a control that passes a small amount of encrypted data in the url. No need to encrypt it while running under the local Visual Studio Web Server. You can check to see if 

System.Diagnostics.Process.GetCurrentProcess().MainModule.ModuleName

returns 'WebDev.WebServer.EXE'. Cool little piece of code. Full details on Steven Smith's blog:

http://aspadvice.com/blogs/ssmith/

kick it on DotNetKicks.com   Sunday, January 22, 2006 2:12:09 PM (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [0]  | 
 Thursday, January 19, 2006

NOTE: I have been receiving a lot of traffic on this posting and don't see (m)any other solutions out there so I decided to port the code to Visual Basic and repost the entry. Most of the writeup is in C#, but a VB solution is included in the download. Sorry for the VB gripes, but I am trying to learn a bit here.... On another note, is reposting a social faux pas in the blog world?


I frequently use an ASP.NET TreeView as a navigation menu. This works well on a single page but if you include the control on a Master Page, it looses its expansion state as you move from one content page to another.

After populating my TreeView, either expand or collapse all of the nodes to insure that there is a consistent default behavior. Then restore your TreeView expansion state to a previously saved state by invoking the RestoreTreeView method of the TreeViewState class:

// set the default state of all nodes.

TreeViewMain.CollapseAll();

 

// get the saved state of all nodes.

new TreeViewState().RestoreTreeView(TreeViewMain, this.GetType().ToString());

Before navigating to a new page, save the existing state for future use but first you need to overcome the urge to use the NavigateUrl property on a TreeNode. Using this property will generate an HTML hyperlink tag on your TreeView which will cause the browser to initiate the transfer to the new Url. Control is not returned to the server and you will never have the opportunity to save the existing state. Instead, wire up the TreeView_SelectedNodeChanged event for the TreeView, put your page address into the Node.Value property and use a Response.Redirect():

protected void TreeViewMain_SelectedNodeChanged(object sender, EventArgs e)

{

    if (TreeViewMain.SelectedNode.Value != string.Empty)

    {

        Response.Redirect(TreeViewMain.SelectedNode.Value);

    }

}

Next, save the TreeView state by subscribing to the TreeView_Unload event. This event is fired just before the control is unloaded from memory. Pass your TreeView to the SaveTreeView method of the TreeViewState class:

protected void TreeViewMain_Unload(object sender, EventArgs e)

{

    // save the state of all nodes.

    new TreeViewState().SaveTreeView(TreeViewMain, this.GetType().ToString());

}

 

Within the SaveTreeView and RestoreTreeView methods of the class, I recursively walk the nodes collection and either save or restore the TreeNode.Expanded property in/from a generic list of type <bool?>. The TreeNode uses a nullable boolean value to store this state so I used the same type in a List.

The expansion state is saved in a Session variable. It is possible that you will want to save the state for two identically named TreeView controls on different master pages within your application. This would cause an issue with the name of the Session variable so I included a 'key' parameter that is concatenated with the ID of the TreeView to form the name of the session variable. You can pass any string value as the key, but using the name of the invoking master page class should eliminate most conflicts. I coded this as 'this.GetType().ToString()' which will return a string of the class type.

Lastly, if the number of nodes exceeds the element count in the List<> object, I simply return from the method. This might happen if the data source for your TreeView changes on the fly. If your data source if routinely changing, you will need to tweak the class to better handle this scenario.

As I indicated earlier, I have now included a VB.NET version of the solution. This is the most VB that I have coded since moving from VB6 to C# when I made the jump to .NET in the early beta days sometime in 2001. I love the simple syntax of C# compared to the wordiness of VB. Take a look as how the two handle generic collections:

VB.NET:

 

Public Sub RestoreTreeView(ByVal treeView As TreeView, ByVal key As String)

    Dim list As New List(Of Nullable(Of Boolean))

    If HttpContext.Current.Session(key + treeView.ID) IsNot Nothing Then

        list = CType(HttpContext.Current.Session(key + treeView.ID), List(Of Nullable(Of Boolean)))

    End If

 

    RestoreTreeViewIndex = 0

    RestoreTreeViewExpandedState(treeView.Nodes, list)

End Sub


C#:

 

public void RestoreTreeView(TreeView treeView, string key)

{

    RestoreTreeViewIndex = 0;

    RestoreTreeViewExpandedState(treeView.Nodes,

        (List<bool?>)HttpContext.Current.Session[key + treeView.ID] ?? new List<bool?>());

}

Is there a cleaner way of doing this in VB? Please let me know. Also is there a way of instantiating a class and invoking a method in one statement in VB.NET?

VB.NET:

Dim treeViewState As New TreeViewState()

treeViewState.SaveTreeView(TreeViewMain, Me.GetType.ToString())

 

C#:

new TreeViewState().SaveTreeView(TreeViewMain, this.GetType().ToString());

I am the first to admit that VB.NET has its place. I know that when it is time to work with Office automation, I will be using VB.NET so please, no holy wars.

Download the complete CS and VB solutions:
http://download.binaryocean.com/TreeViewSaveStateSolution.zip

Here is the complete CS class. The VB version is included in the download:

using System;

using System.Collections.Generic;

using System.Web;

using System.Web.UI.WebControls;

 

public class TreeViewState

{

    public void SaveTreeView(TreeView treeView, string key)

    {

        List<bool?> list = new List<bool?>();

        SaveTreeViewExpandedState(treeView.Nodes, list);

        HttpContext.Current.Session[key + treeView.ID] = list;

    }

 

    private int RestoreTreeViewIndex;

 

    public void RestoreTreeView(TreeView treeView, string key)

    {

        RestoreTreeViewIndex = 0;

        RestoreTreeViewExpandedState(treeView.Nodes,

            (List<bool?>)HttpContext.Current.Session[key + treeView.ID] ?? new List<bool?>());

    }

 

    private void SaveTreeViewExpandedState(TreeNodeCollection nodes, List<bool?> list)

    {

        foreach (TreeNode node in nodes)

        {

            list.Add(node.Expanded);

            if (node.ChildNodes.Count > 0)

            {

                SaveTreeViewExpandedState(node.ChildNodes, list);

            }

        }

    }

 

    private void RestoreTreeViewExpandedState(TreeNodeCollection nodes, List<bool?> list)

    {

        foreach (TreeNode node in nodes)

        {

            if (RestoreTreeViewIndex >= list.Count) break;

 

            node.Expanded = list[RestoreTreeViewIndex++];

            if (node.ChildNodes.Count > 0)

            {

                RestoreTreeViewExpandedState(node.ChildNodes, list);

            }

        }

    }

}

kick it on DotNetKicks.com   Thursday, January 19, 2006 10:05:42 AM (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [25]  | 
 Friday, January 13, 2006

This little registry hack will put ActiveSync into the "Guest Only" mode. Once enabled, it will no longer form partnerships with connecting devices. You can still explore connected devices and install software.

At home, I sync my PocketPC directly with Exchange using Mobile ActiveSync over WiFi so there is no need for it to form a partnership with my desktop. At work, I have a steady supply of Symbol MC50s running across my desk and then out into the field. I don't want my email and contact information following them out the door. "Guest Only" fixes both of these issues.

REGEDIT4

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows CE Services]

"GuestOnly"=dword:00000001

I have seen this elsewhere but I can never find it when I need it.

Download the REG file:
http://download.binaryocean.com/GuestOnly.zip

 

kick it on DotNetKicks.com   Friday, January 13, 2006 2:30:19 PM (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [1]  | 
 Tuesday, January 10, 2006

I needed a function that would return the index of one SubList within another List. Think of this as is List1 a sub list of List2.

{4, 5, 6} is a sublist of {1, 2, 3, 4, 5, 6, 7, 8, 9}
{4, 6, 5} is not a sublist of {1, 2, 3, 4, 5, 6, 7, 8, 9}

Note that these are lists and not sets. In the world of sets, the order is not important and both of the above tests would be true.

This class became the core of an application that searches for certain phrases within a larger body of text. Completing a text based search was pretty straight forward but I also wanted to implement a phonetic based search. It was easy to translate the original body of text into a List<string> of individual words and a second List<string> of their phonetic representation (using SoundEx or Metaphone). The number of elements in both lists was the same. This allowed me to do a search on the phonetic words and then translate them back to their original text by using the corresponding element in the original list. More on the word scanning project some other time.

The word project used Lists<string> but the class allows for any type. The example uses integers.

ListSearch<int> listSearch = new ListSearch<int>();

 

List<int> list = new List<int>(new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });

List<int> subList = new List<int>(new int[] { 4, 5, 6 });

 

Console.WriteLine("Should return 4  : {0}", listSearch.IndexOf(list, subList));

 

subList = new List<int>(new int[] { 4, 6, 5 });

Console.WriteLine("Should return -1 : {0}", listSearch.IndexOf(list, subList));

 

Console.ReadKey();

 

Here is the code. I had an initial version that used some recursion and worked fine. This version, based on a suggestion by Luke Zhang of Microsoft, is a bit cleaner. Thanks Luke!

 

public static class ListSearch<T>

{

    public static int IndexOf(List<T> list, List<T> sublist)

    {

        return IndexOf(0, list, sublist);

    }

 

    public static int IndexOf(int start, List<T> list, List<T> sublist)

    {

        for (int i = start; i <= list.Count - sublist.Count; i++)

        {

            int j = 0;

            while (j < sublist.Count)

            {

                if (!list[i + j].Equals(sublist[j])) break;

                if (j < sublist.Count) j++;

            }

 

            if (j == sublist.Count) return i;

        }

 

        return -1;

    }

}

 

kick it on DotNetKicks.com   Tuesday, January 10, 2006 3:49:22 PM (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [1]  | 

I have used the following bit of code to diagnose numerous errors on websites and learn about others which were unknown to me. This is an ASP.NET 2.0 version, but you could modify it to work with v1.x by changing the email/smtp portion. I have seen other similar exception handlers but none of them included this level of detail. The contents of the web form has turned out to be quite valuable from time to time. I guess you could also include the contents of the entire request and response stream?

You can download a complete sample solution:
http://download.binaryocean.com/WebExceptionSolution.zip

Modify your Global.asax so that the Application_Error event calls the WebExceptionEmailer class and set your from and to addresses. The to address needs to point to you. For the from address, I generally use a smtp address that incorporates the name of the application or domain name of the web site. Make sure you have a local smtp server or change the code to correctly point to one.

void Application_Error(object sender, EventArgs e)

{

    new WebExceptionEmailer().SendEmail("weberror@mywebapp.com", "myaddress@mycompany.com");

}

Here is the exception handling class:

using System;

using System.Configuration;

using System.Net.Mail;

using System.Text;

using System.Web;

 

public class WebExceptionEmailer

{

    public WebExceptionEmailer()

    {

    }

 

    private StringBuilder errorText = new StringBuilder();

 

    public void SendEmail(string fromAddress, string toAddress)

    {

        Exception ex = HttpContext.Current.Server.GetLastError().GetBaseException();

        BuildExceptionDetail(ex);

        BuildWebDetail();

 

        MailMessage message = new MailMessage();

 

        message.From = new MailAddress(fromAddress);

        message.To.Add(toAddress);

        message.Subject = ex.Message;

        message.Body = errorText.ToString();

 

        SmtpClient smtpClient = new SmtpClient();

        smtpClient.Host = "localhost";

        smtpClient.Send(message);

    }

 

    private void BuildExceptionDetail(Exception ex)

    {

        errorText.Append("Message: " + ex.Message + Environment.NewLine + Environment.NewLine);

        errorText.Append("Source: " + ex.Source + Environment.NewLine + Environment.NewLine);

        errorText.Append("TargetSite: " + ex.TargetSite + Environment.NewLine + Environment.NewLine);

        errorText.Append("StackTrace: " + ex.StackTrace + Environment.NewLine + Environment.NewLine);

 

        // loop recursivly through the inner exception if there are any.

        if (ex.InnerException != null)

        {

            errorText.Append("InnerException:" + Environment.NewLine + Environment.NewLine);

            BuildExceptionDetail(ex.InnerException);

        }

    }

 

    private void BuildWebDetail()

    {

        HttpContext context = HttpContext.Current;

 

        errorText.Append("FullUrl: " + HttpContext.Current.Request.Url.OriginalString + Environment.NewLine + Environment.NewLine);

        errorText.Append("Form Values:" + Environment.NewLine);

        if (context.Request.Form != null)

        {

            foreach (string key in context.Request.Form.Keys)

            {

                errorText.Append(key + ": " + context.Request.Form[key].ToString() + Environment.NewLine);

            }

        }

        errorText.Append(Environment.NewLine);

 

        errorText.Append("Referer: ");

        if (context.Request.ServerVariables["HTTP_REFERER"] != null)

        {

            errorText.Append(context.Request.ServerVariables["HTTP_REFERER"]);

        }

        errorText.Append(Environment.NewLine + Environment.NewLine);

 

        errorText.Append("Host Address: " + context.Request.ServerVariables["LOCAL_ADDR"] + Environment.NewLine);

        errorText.Append("Client Address: " + context.Request.UserHostAddress + Environment.NewLine + Environment.NewLine);

    }

}

kick it on DotNetKicks.com   Tuesday, January 10, 2006 2:13:46 PM (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [0]  | 
 Monday, January 09, 2006

I came up with a slightly different way of handling the insert code in my previous "Insert Rows with a GridView" posting. Instead of calling my data layer directly from my code behind page, I pass the values to my ObjectDataSource control and allow it to handle the inserts. Not a major change but a bit more consistent in that the ODS handles all of my CRUD logic and I never call my data layer directly.

My previous post:
http://blog.binaryocean.com/PermaLink,guid,eca3c5a1-08c1-4226-bfb5-d36fddaef93b.aspx

The complete Solution for this modified version:
http://download.binaryocean.com/GridViewInsertSolution2.zip

Make sure to wire up the OnInserting event in the ODS control:
OnInserting="ObjectDataSourceMain_Inserting"

protected void GridViewMain_RowCommand(object sender, GridViewCommandEventArgs e)

{

    //  handle the save button on the footer row.

    if (e.CommandName == "Save")

    {

        ObjectDataSourceMain.Insert();      

    }

}

 

protected void ObjectDataSourceMain_Inserting(object sender, ObjectDataSourceMethodEventArgs e)

{

    //  on insert, pass the values from the footer controls.

    e.InputParameters["LocationCode"] = ((TextBox)GridViewMain.FooterRow.FindControl("TextBoxCode")).Text;

    e.InputParameters["LocationDescription"] = ((TextBox)GridViewMain.FooterRow.FindControl("TextBoxDescription")).Text;

    e.InputParameters["LocationIsActive"] = ((CheckBox)GridViewMain.FooterRow.FindControl("CheckBoxIsActive")).Checked;

}

 

kick it on DotNetKicks.com   Monday, January 09, 2006 8:42:19 AM (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [4]  | 
 Sunday, January 08, 2006

Here is a class that handles symmetric encryption and decryption using the .NET Rijndael provider. This works well when passing data that you need to keep encrypted over a URL. Remember to UrlEncode the resulting Base64 string with Server.UrlEncode() if you plan on passing your encrypted string as part of the Query String. I tried to boil it down to its simplest form.

You can download a sample solution:
http://download.binaryocean.com/SymmetricEncryptionSolution.zip

using System;

using System.IO;

using System.Security.Cryptography;

using System.Text;

 

/// <summary>

/// Simple symmetric encryption using the .NET Rijndael provider

/// </summary>

public class SymmetricEncryption

{

    public SymmetricEncryption()

    {

    }

 

    public SymmetricEncryption(string password)

    {

        GenerateKey(password);

    }

 

    public string Password

    {

        set { GenerateKey(value); }

    }

 

    private byte[] Key;

    private byte[] Vector;

 

    private