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 void GenerateKey(string password)

    {

        SHA384Managed sha = new SHA384Managed();

        byte[] b = sha.ComputeHash(new ASCIIEncoding().GetBytes(password));

 

        Key = new byte[32];

        Vector = new byte[16];

 

        Array.Copy(b, 0, Key, 0, 32);

        Array.Copy(b, 32, Vector, 0, 16);

    }

 

    public string Encrypt(string plainText, string password)

    {

        GenerateKey(password);

        return Encrypt(plainText);

    }

 

    public string Encrypt(string plainText)

    {

        if (Key == null)

        {

            throw new InvalidOperationException("Password must be provided or set.");

        }

 

        byte[] data = new ASCIIEncoding().GetBytes(plainText);

 

        RijndaelManaged crypto = new RijndaelManaged();

        ICryptoTransform encryptor = crypto.CreateEncryptor(Key, Vector);

 

        MemoryStream memoryStream = new MemoryStream();

        CryptoStream crptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write);

 

        crptoStream.Write(data, 0, data.Length);

        crptoStream.FlushFinalBlock();

 

        crptoStream.Close();

        memoryStream.Close();

 

        return Convert.ToBase64String(memoryStream.ToArray());

    }

 

    public string Decrypt(string encryptedText, string password)

    {

        GenerateKey(password);

        return Decrypt(encryptedText);

    }

 

    public string Decrypt(string encryptedText)

    {

        if (Key == null)

        {

            throw new InvalidOperationException("Password must be provided or set.");

        }

 

        byte[] cipher = Convert.FromBase64String(encryptedText);

 

        RijndaelManaged crypto = new RijndaelManaged();

        ICryptoTransform encryptor = crypto.CreateDecryptor(Key, Vector);

 

        MemoryStream memoryStream = new MemoryStream(cipher);

        CryptoStream crptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Read);

 

        byte[] data = new byte[cipher.Length];

        int dataLength = crptoStream.Read(data, 0, data.Length);

 

        memoryStream.Close();

        crptoStream.Close();

 

        return (new ASCIIEncoding()).GetString(data, 0, dataLength);

    }

}

 

kick it on DotNetKicks.com   Sunday, January 08, 2006 11:46:32 AM (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [0]  | 
 Saturday, January 07, 2006

A client of mine was having issues while using an image control within a master page. This master page was reference by content pages at a number of different sub directory levels within the application. The browser was unable to resolve or display images located off of the application root when the ulr contained a tilde (~) at the beginning of the path.

<img src="~/Image/AndrewPix.jpg" />

The tilde (~) is a shortcut to the HttpRuntime.AppDomainAppVirtualPath which will normally resolve back to the root of the virtual directory but this shortcut will only be resolved within server side controls. The standard HTML image control (img) is not rendered at the server and will simply pass the tilde on to the client browser. The issue was related to this and not directly to the use of master pages.

The solution is to insure that the control is rendered at the server. This can be done by adding the runat="server" property to the standard HTML image control or by using the ASP.NET image control.

<img src="~/Image/AndrewPix.jpg" runat="server" />

<asp:Image runat="server" ImageUrl="~/Image/AndrewPix.jpg" />

 

kick it on DotNetKicks.com   Saturday, January 07, 2006 6:40:28 AM (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [1]  | 
 Wednesday, January 04, 2006

NOTE: I have slightly modified this post and the associated source. Make sure to take a look at v2 after reading this post:
http://blog.binaryocean.com/PermaLink,guid,a1ff6cab-dc2d-441c-8557-7dce920d4075.aspx

-Andrew

>>>

In ASP.NET version 1.x, I used the footer on the DataGrid control to insert new rows by placing a series of TextBox, DropDownLists, CheckBoxes etc. controls on it and then handling a save button. This also works with the GridView in version 2.0 but with one major exception: if your data source is empty, the GridView will hide your footer (and header) and only display the EmptyDataText.

Conventional wisdom says that you should use a DetailsView to insert rows, but I don't want to shift the user between two different controls. You can edit and delete rows with a GridView so why not Insert?

Here is the solution. I am using an ObjectDataSource control to wire up my data access class. If the DataTable coming back from my data class is empty, I add a null row. This works fine but now you get blank rows in the GridView. To handle that, I hide the rows during the RowCreated event. Lastly, I clear the controls off of the now hidden row so that there are no binding errors as would be the case with a cast boolean value which I do to set the value of a CheckBox in my GridView.

You can download the entire solution and sample database here:
http://download.binaryocean.com/GridViewInsertSolution.zip

private bool LoadDataEmpty

{

    //  some controls that are used within a GridView,

    //  such as the calendar control, can cuase post backs.

    //  we need to preserve LoadDataEmpty across post backs.

    get { return (bool)(ViewState["LoadDataEmpty"] ?? false); }

    set { ViewState["LoadDataEmpty"] = value; }

}

protected void ObjectDataSourceMain_Selected(object sender, ObjectDataSourceStatusEventArgs e)
{
    //  bubble exceptions before we touch e.ReturnValue
   
if (e.Exception != null)
   
{
       
throw e.Exception;
    
}

    // get the DataTable from the ODS select mothod
    DataTable dataTable = (DataTable)e.ReturnValue;

    // if rows=0 then add a dummy (null) row and set the LoadDataEmpty flag.

    if (dataTable.Rows.Count == 0)

    {

        dataTable.Rows.Add(dataTable.NewRow());

        LoadDataEmpty = true;

    }

    else

    {