Infosys Microsoft Alliance and Solutions blog

« December 2007 | Main | February 2008 »

January 31, 2008

Working with MSMQ Journal Queues

In one of our internal forums, recently, someone had posted a query regarding issues in connecting and receiving messages from journal queues. The exception they were getting was "Queue ID is not registered in DS". I hadn't worked with Journal queues before, but this error seemed to me to be related to the queue path.

In my earlier blogs (here and here), I had discussed about the importance of specifying the right name for the queue and also mentioned how the path will typically look like. I decided to give it a try and created a private queue on my local machine and enabled journaling on it. Enabling journaling is as trivial as checking a checkbox in the queue property window.  You can also enable it by setting the UseJournalQueue property on the message.

Having done this, I used the syntax as described on MSDN Documentation.

            MessageQueue queue = new MessageQueue(".\\test\\journal$");

To my surprise, I got an exception stating "A workgroup installation computer does not support the operation." Well, it isn't really a surprise, since this syntax is for public queues and that's what the error message is also indicating. The following are the various syntaxes that did work for me. You could use either of the following based on your own preferences.  Note that the "test" in the queue path is name of the private queue that i created.

            MessageQueue queue = new MessageQueue(".\\private$\\test\\journal$");

            MessageQueue queue = new MessageQueue(".\\private$\\test;journal");

The casing for "journal" doesn't matter and you can very well specify it as "JOURNAL". So much for local private queue. I quickly tried accessing the journal queue for a remote private queue as well, and following worked for me

            MessageQueue queue = new MessageQueue("FormatName:DIRECT=OS:server\\private$\\remote;journal");

Where "server" is the remote machine name. Again, "journal" can be specified as "JOURNAL" as well. The following, though is an extension of what worked in case of local private queue, didn't work for remote private queue and I got a "Format name is invalid" exception.

            MessageQueue queue = new MessageQueue("FormatName:DIRECT=OS:punhjw30076\\private$\\remote\\journal$");

January 28, 2008

Custom User Input Validation in WPF

Building any application that works with User Input cannot be complete usually without having some sort of validations for the input values. WPF is no exception. There is an interesting discussion around WPF validation on Paul Stovell's blog

Martin Bennedik has written a WPF Validation Block over Microsoft's Enterprise Library 3.0. WPF 3.5 also brings in additional validation support via the IDataEffortInfo Interface.

However, all said and done, these techniques typically work with data binding. One can always work with Data binding in WPF, but what if you don't have a need for that? What if there are say only one or two fields you want to validate? Also the WPF validation does provide feedback to the user when validation fails by using the ErrorTemplate, but to mark some field as mandatory, one typically still requires to be put the most used red asterix (*) near the mandatory fields.

Look at the screen shot of an application below. It has two text boxes for user's first name and last name. Both are mandatory. However instead of the asterix approach, I thought of why not the text box itself show a red border when it is empty and also a tooltip along with it to show what is required. Once the user enter's a valid value, the border can change back to the default border color. Finally, the text boxes also should be queriable for valid state so that when the form/dialog/window is submitted, I can loop through them and see if I can submit or not.

ValidationApp.jpg

First, to see how to have the TextBox check and provide feedback on it being valid. For this I used the new Extension Method functionality in C# 3.0. For a deep dive into this feature, check here. The following is the method I wrote where I just check to see if the TextBox control has some string entered or not and return true/false accordingly. Needless to say that the TextBox in the parameter below is from the System.Windows.Controls namespace, that is used in WPF.

    public static class CustomExtensions

    {

        public static bool IsValid(this TextBox obj)

        {

            if (obj.Text.Length > 0)

                return true;

 

            return false;

        }

    }

Next, to display the appropriate borders, I needed two brush resources - one, the default border brush that the TextBox uses and, two, a similar gradient brush that fills to red color to be used when the TextBox is invalid. Creating these with Expression Blend is very trivial. I saved these both as resources in the XAML file.

    <Window.Resources>

        <LinearGradientBrush x:Key="FaultyBorderBrush" EndPoint="0,20" StartPoint="0,0" MappingMode="Absolute">

            <GradientStop Color="#FFABADB3" Offset="0.05"/>

            <GradientStop Color="#FFE2E3EA" Offset="0.07"/>

            <GradientStop Color="#FFFF0000" Offset="1"/>

        </LinearGradientBrush>

        <LinearGradientBrush x:Key="DefaultBorderBrush" EndPoint="0,20" StartPoint="0,0" MappingMode="Absolute">

            <GradientStop Color="#FFABADB3" Offset="0.05"/>

            <GradientStop Color="#FFE2E3EA" Offset="0.07"/>

            <GradientStop Color="#FFE3E9EF" Offset="1"/>

        </LinearGradientBrush>

    </Window.Resources>

Finally, comes the logic to invoke the IsValid method at appropriate times so that the necessary feedback can be given to the user. The initial setting was done in the constructor of the Window so that i could initially show the TextBoxs with red border and the tooltip (as shown in the figure above). I also handled the TextChange event of the TextBoxes so that I could invoke the IsValid method to see if the TextBox is valid or not and change the border color appropriately. I also handled the Button's click event just to show that while submitting the form, I can invoke the same method and allow/prevent submission of the form accordingly. The following code shows the complete code behind for the Window.

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Shapes;

 

namespace GenericWPFApp

{

    /// <summary>

    /// Interaction logic for MainPane.xaml

    /// </summary>

    public partial class MainPane : Window

    {

        const string _firstNameMessage = "FirstName cannot be left blank";

        //following is used with the different extension method which supports min and max lengths

        const string _lastNameMessage = "LastName should be between 0 and 100 characters";

 

        public MainPane()

        {

            InitializeComponent();

 

            //since the text boxes are initially empty, they are invalid. Initialize them accordingly

            SetTextBoxProperties(txtFirstName, false, _firstNameMessage);

            SetTextBoxProperties(txtLastName, false, _lastNameMessage);

        }

 

        private void txtFirstName_TextChanged(object sender, TextChangedEventArgs e)

        {

            TextBox box = sender as TextBox;

            if (box != null)

                SetTextBoxProperties(box, box.IsValid(), _firstNameMessage );

        }

 

        private void txtLastName_TextChanged(object sender, TextChangedEventArgs e)

        {

            TextBox box = sender as TextBox;

            if (box != null)

                SetTextBoxProperties(box, box.IsValid(0, 100), _lastNameMessage);

        }

 

        private void SetTextBoxProperties(TextBox ctrl, bool valid, string tip)

        {

            if (valid)

            {

                ctrl.BorderBrush = (Brush)TryFindResource("DefaultBorderBrush");

                ctrl.ToolTip = null;

            }

            else

            {

                ctrl.BorderBrush = (Brush)TryFindResource("FaultyBorderBrush");

                ctrl.ToolTip = tip;

            }

        }

 

        private void Button_Click(object sender, RoutedEventArgs e)

        {

            bool valid = false;

            valid = txtFirstName.IsValid();

            if (!valid)

            {

                message.Text = "Form is invalid";

                return;

            }

 

            valid = txtLastName.IsValid(0, 100);

            if (!valid)

            {

                message.Text = "Form is invalid";

                return;

            }

 

            message.Text = "Form is valid";

        }

    }

}

Disclaimer: The code isn't optimized and you may find better ways to implement the same logic. I just wanted to show that we can get some of the validation functionality without necessarily going the DataBinding way. Another caveat is that since the extension method is written for the TextBox, it will be available for all the TextBoxes used in the application. While this is good, the issue could be that you may want different logic for different TextBoxes. You can always write different implementations for the same. One such method, which works with minimum and maxium length is shown below. The challenge however will be to invoke the right method for the appropriate TextBoxes.

        public static bool IsValid(this TextBox obj, int minLen, int maxLen)

        {

            int length = obj.Text.Length;

            if ((minLen < length)  && (maxLen > length))

                return true;

 

            return false;

        }

Since extension methods can be written for any control/class, you can write similar logic for any WPF control to provide your own validation mechanisms. Comments welcome !  

 

 

 

 

January 24, 2008

Importance of Application.DoEvents

Recently a colleague of mine shared a code that he was working on where he was playing around with Asynch ADO.NET. He had a simple WinForm application. On the click of a button, he was making calls to the DB to get data. To try out the asynch features, he had introduced delays in the code.

The code looked something like below

        private void button1_Click(object sender, EventArgs e)

        {

            //start with empty text boxes and these get populated once the DB query returns

            textBox1.Text = string.Empty;

            textBox2.Text = string.Empty;

            textBox3.Text = string.Empty;

 

            SqlCommand com1 = null; SqlCommand com2 = null; SqlCommand com3 = null;

            SqlConnection con1 = null; SqlConnection con2 = null; SqlConnection con3 = null;

            SqlDataReader dr1 = null; SqlDataReader dr2 = null; SqlDataReader dr3 = null;

            string str1 = string.Empty;

 

            try

            {

                string connection = "Data Source=.;Asynchronous Processing=true;Initial Catalog = pubs;Integrated Security=True";

                con1 = new SqlConnection(connection);

                con1.Open();

                com1 = new SqlCommand("SELECT * FROM authors", con1);

 

                con2 = new SqlConnection(connection);

                con2.Open();

                com2 = new SqlCommand("waitfor delay '00:00:05';SELECT * FROM authors", con2);

 

                con3 = new SqlConnection(connection);

                con3.Open();

                com3 = new SqlCommand("waitfor delay '00:00:10';SELECT * FROM authors", con3);

 

                IAsyncResult ar1 = com1.BeginExecuteReader();

                IAsyncResult ar2 = com2.BeginExecuteReader();

                IAsyncResult ar3 = com3.BeginExecuteReader();

 

                WaitHandle[] handles = new WaitHandle[3];

                handles[0] = ar1.AsyncWaitHandle;

                handles[1] = ar2.AsyncWaitHandle;

                handles[2] = ar3.AsyncWaitHandle;

 

                for (int results = 0; results < handles.GetLength(0); results++)

                {

                    // wait for any handle, then process results as they come

 

                    int index = WaitHandle.WaitAny(handles, -1, false); // infinite secs

                    //depending on which call completed, update the appropriate text box

                    if (index == 0)

                    {

                        dr1 = com1.EndExecuteReader(ar1);

                        while (dr1.Read())

                        {

                            str1 = str1 + dr1[0] + " " + dr1[1] + " " + dr1[2] + "\n";

                        }

                        textBox1.Text = str1;

                    }

                    else if (index == 1)

                    {

                        dr2 = com2.EndExecuteReader(ar2);

                        while (dr2.Read())

                        {

                            str1 = str1 + dr2[0] + " " + dr2[1] + " " + dr2[2] + "\n";

                        }

                        textBox2.Text = str1;

                    }

                    else if (index == 2)

                    {

                        dr3 = com3.EndExecuteReader(ar3);

                        while (dr3.Read())

                        {

                            str1 = str1 + dr3[0] + " " + dr3[1] + " " + dr3[2] + "\n";

                        }

                        textBox3.Text = str1;

                    }

                }

            }

            catch (Exception ex)

            {

                MessageBox.Show(ex.Message);

            }

            finally

            {

                con1.Close();

                con2.Close();

                con3.Close();

            }

        }

Is this the best code that could be written is debatable. One could write separate handlers to handle the completion of each of the Asynch queries. But that's not the point. Let's for the sake of argument assume there is a specific need to have written the code in this manner. When you run this code, you would get some unexpected results

1. If after return of all 3 queries, you click the button again, you would expect the text boxes to clear up and then get filled up again when the queries  return after 5 and 10 secs. But that doesn't happen and the text boxes don't seem to clear up.

2. Each time you re-run the application, the way in which the results get populated in the text boxes is unpredictable. Sometimes, it will appear to work find while in most cases, the data for all three text boxes gets displayed at one shot (essentially after the last query has also returned)

So what's happening here? If you have experience only with the current .net programming only, it will be difficult to understand. Those of you who have programmed for Windows applications using the Win32 APIs directly like say with Visual C++ 1.5 or 2.0 or even with MFC would recall some of the key concepts there

1. Windows relies heavily on messaging infrastructure. Actions on the windows/controls etc generate messages that the code handles by picking messages from a queue and then passing them to a windows procedure. While processing of a message is happening, other messages will wait in the queue to be picked up.

2. The messages in the queue have different priorities. Those with higher priority even if come in while some messages are already in the queue, will get pushed foward and will be processed before the low priority messages.

3. The visual rendering of windows and controls there-in happens by processing the WM_PAINT message. During the processing of this message, the code uses the device context provided to it (which is usually the display device context) and draws the contents on it, which show up on the desktop display

4. Each time a control is invalidated (property change) like say text is entered into a text box, it needs to redraw itself so that it can show the currently set text. This is done by calling a Win32 API to invalidate itself, which causes a WM_PAINT message to be posted to the message queue. When the application gets a chance to process the message, the text box will update itself and show the new text.

The key to the issue with the above code is related to all these points with specific focus on the last point. In the sample code above, the text box has been updated by setting its Text property. It would have in turn generated a WM_PAINT message, but since the code above is in a loop, it essentially doesn't allows the processing of the message queue and hence the WM_PAINT message isn't processed on time.

It is in such scenarios that the Application.DoEvents API is useful. As you would see in the documentation, it mentions that a call to this allows Windows to immediately process all messages in the queue. So if we add this  call at appropriate places (as shown below in bold face), the behavior of the UI will be more in line with what we expect. 

        private void button1_Click(object sender, EventArgs e)

        {

            //start with empty text boxes and these get populated once the DB query returns

            textBox1.Text = string.Empty;

            textBox2.Text = string.Empty;

            textBox3.Text = string.Empty;

            Application.DoEvents();

 

            SqlCommand com1 = null; SqlCommand com2 = null; SqlCommand com3 = null;

            SqlConnection con1 = null; SqlConnection con2 = null; SqlConnection con3 = null;

            SqlDataReader dr1 = null; SqlDataReader dr2 = null; SqlDataReader dr3 = null;

            string str1 = string.Empty;

 

            try

            {

                string connection = "Data Source=.;Asynchronous Processing=true;Initial Catalog = pubs;Integrated Security=True";

                con1 = new SqlConnection(connection);

                con1.Open();

                com1 = new SqlCommand("SELECT * FROM authors", con1);

 

                con2 = new SqlConnection(connection);

                con2.Open();

                com2 = new SqlCommand("waitfor delay '00:00:05';SELECT * FROM authors", con2);

 

                con3 = new SqlConnection(connection);

                con3.Open();

                com3 = new SqlCommand("waitfor delay '00:00:10';SELECT * FROM authors", con3);

 

                IAsyncResult ar1 = com1.BeginExecuteReader();

                IAsyncResult ar2 = com2.BeginExecuteReader();

                IAsyncResult ar3 = com3.BeginExecuteReader();

 

                WaitHandle[] handles = new WaitHandle[3];

                handles[0] = ar1.AsyncWaitHandle;

                handles[1] = ar2.AsyncWaitHandle;

                handles[2] = ar3.AsyncWaitHandle;

 

                for (int results = 0; results < handles.GetLength(0); results++)

                {

                    // wait for any handle, then process results as they come

 

                    int index = WaitHandle.WaitAny(handles, -1, false); // infinite secs

                    //depending on which call completed, update the appropriate text box

                    if (index == 0)

                    {

                        dr1 = com1.EndExecuteReader(ar1);

                        while (dr1.Read())

                        {

                            str1 = str1 + dr1[0] + " " + dr1[1] + " " + dr1[2] + "\n";

                        }

                        textBox1.Text = str1;

                        Application.DoEvents();

                    }

                    else if (index == 1)

                    {

                        dr2 = com2.EndExecuteReader(ar2);

                        while (dr2.Read())

                        {

                            str1 = str1 + dr2[0] + " " + dr2[1] + " " + dr2[2] + "\n";

                        }

                        textBox2.Text = str1;

                        Application.DoEvents();

                    }

                    else if (index == 2)

                    {

                        dr3 = com3.EndExecuteReader(ar3);

                        while (dr3.Read())

                        {

                            str1 = str1 + dr3[0] + " " + dr3[1] + " " + dr3[2] + "\n";

                        }

                        textBox3.Text = str1;

                  Application.DoEvents();

                    }

                }

            }

            catch (Exception ex)

            {

                MessageBox.Show(ex.Message);

            }

            finally

            {

                con1.Close();

                con2.Close();

                con3.Close();

            }

        }

A quick look into the .net code using Lutz Roeder's Reflector shows that Application.DoEvents invokes the aptly named RunMessageLoop on current ThreadContext class. This in turn calls the RunMessageLoopInner. The actual message processing happens inside the LocalModelMessageLoop of the ThreadContext class.

To summarize, if you want the UI to be responsive (apart from getting into multi-threading) do ensure that you call Application.DoEvents at appropriate places to refresh the UI.  

 

January 23, 2008

Unit Test Trust Issue on Vista

Further to my unit testing experiments that I have been blogging about in the last few days, I was playing around with the DashCommerce starter kit. After installing and configuring the site, I started by creating some tests for the code in the App_code folder.

The test creation was successful and I was all set to run. But luck was against me and I got compilation errors regarding missing namespace. As I had mentioned in my earlier blog, the testing happens via private accessor and since its creation happened successfully, ideally I shouldn't  have had a need for adding explicit references and namespaces to my test project.

It turns, out that this happened due to the particular component for which I had generated the test to refer to an external assembly (SubSonic.dll), which in turn depended on the code in App_code folder. This caused something like circular reference and hence when the private accessor was created, it required the web site's code be added as reference. The workaround we followed was to publish the site and then refer to the generated App_Code.dll.

This wasn't the main issue that I wanted to describe here. When the compilation errors resolved, when I tried to run the test, I got an exception of type "Microsoft.VisualStudio.TestTools.TestManagement.ExecutionException". It pointed to a "Test Run deployment issue" and stated that the "file or location of the SubSonic.dll isn't trusted". This was strange since I had ensured that necessary permissions were granted on the folder so why this error.

I recalled from my earlier experiments on Vista about the file streams and how the files downloaded from the internet get tagged with additional file streams that marks it unsafe for execution. So I unblocked the file via File > Properties as shown in the image below.  

Unblock.jpg

After this I got similar error for another file and I realized that since this is a code that I downloaded from the net, all files have been tagged in similar way and I will need to unblock all. Doing that eventually worked and I was able to complete the testing successfully.

The one painful item was having had to unblock all files manually. Selecting all and right clicking to check Properties didn't help. Then later I found this which will help unblock multiple files.

January 22, 2008

Debugging ASP.NET Unit Tests with VS 2008

In my previous blogs (here and here), I have been discussing about unit testing on an ASP.NET web site that is hosted on IIS. While doing the work on that, I also had a need to debug my test case. The documentation on MSDN talks about putting break points and attaching to w3wp process and then debug the tests.

This however didn't work for me on VS 2008. What worked was adding the System.Diagnostics.Debugger.Break() call in my code and then running the tests like normal. When the code hit the Debugger.Break line, it asked me, which debugger to use and I selected the already open VS 2008 instance and it took me to the correct line of code and i was able to debug.

January 21, 2008

ASP.NET Unit Testing on IIS

In my previous blog I had mentioned about the issue in generating private accessors for ASP.NET code that is hosted on IIS. Does this mean that we can't test the web site if hosted on IIS?

You can, but it will be a bit more complex and you may not be able to cover all scenarios. The idea is to work with the PrivateObject that is available from the test context. In the previous blog, I had talked about a web site with a Calculator class in the App_Code folder that i wanted to test. Let's continue with the same example.

In order to run the application, I had a page that had two text boxes from which I would enter the input values and it had four buttons, one each for invoking Add, Subtract, Multiply and Divide functionality and finally another text box that would capture the results.

Before being able to invoke the buttons, I had to ensure that the text boxes had appropriate values set. For this I used the MyTestInitialize routine so that the values will be set before each test is executed. The initialization I did was as follows

        //Use TestInitialize to run code before running each test

        [TestInitialize()]

        public void MyTestInitialize()

        {

            x = 20;

            y = 5;

 

            Page page = testContextInstance.RequestedPage;

            TextBox t1 = (TextBox)page.FindControl("TextBox1");

            t1.Text = x.ToString();

            TextBox t2 = (TextBox)page.FindControl("TextBox2");

            t2.Text = y.ToString();

        }

The test context allows access to the current page which can be set via the UrlToTest attribute set on each test method. Using this page object, I accessed the two text boxes and initialized them. Then came the actual test method and it looked something like below

        /// <summary>

        ///A test for Subtract

        ///</summary>

        [TestMethod()]

        [HostType("ASP.NET")]

        [UrlToTest("http://localhost/GenericWebApp")]

        public void SubtractTest()

        {

            Page page = testContextInstance.RequestedPage;

            Button b = (Button)page.FindControl("btnSubtract");

 

            PrivateObject po = new PrivateObject(page);

            po.Invoke("btnSubtract_Click", b, EventArgs.Empty);

 

            TextBox t = (TextBox)page.FindControl("TextBox3");

            int actual = Convert.ToInt32(t.Text);

            int result = 15;

            Assert.AreEqual<int>(result, actual);

        }

Few things to note in the above method

1. I used the PrivateObject to invoke the Subtract button's event handler in the page's code behind code.
2. The second parameter to the Invoke method could have been set as null also. If you are making use of the sender parameter in the button click event handler, then you need to send it, else you can very well just sent a null.
3. I used the generics overload of the Assert method to ensure that I can pass int to save on a bit of performance overhead of otherwise boxing and unboxing.

In a similar way, I can test all the other methods of my Calculator class. Needless to say this is a pretty simple scenario and hence invoking methods via the RequestPage object is easy. As the logic becomes complex, the effort to write the test method will increase as you may have to access multiple controls on the page and initialize them appropriately.

Comments are always welcome !

 

January 18, 2008

ASP.NET Unit Testing issue on IIS with VS 2008

I was recently trying to dirty my hands on using the Unit test framework that comes with Visual Studio. I decided to give it a try on my newly installed VS 2008. I created a pretty trivial ASP.NET site hosted on IIS with a Calculator class that I put in App_Code folder and generated the tests.

One of the key differences in working with Unit test framework for ASP.NET as compared to other projects like Winform or Class library is that the code in ASP.NET gets compiled dynamicall into various assemblies and hence we can't bind to a specific assembly upfront. Check more details on this here.

Hence to successfully test code in ASP.NET App_Code folder, the creation of private accessor is very important. However when the test case got generated, I saw the following code in the test method

        /// <summary>

        ///A test for Subtract

        ///</summary>

        [TestMethod()]

        [HostType("ASP.NET")]

        [UrlToTest("http://localhost/GenericWebApp")]

        public void SubtractTest()

        {

            // Creation of the private accessor for 'Microsoft.VisualStudio.TestTools.TypesAndSymbols.Assembly' failed

            Assert.Inconclusive("Creation of the private accessor for \'Microsoft.VisualStudio.TestTools.TypesAndSy" +

                    "mbols.Assembly\' failed");

        }

And I didnt' see any private accessor created for me. I opened the Calculator class code (from the App_Code) folder and from the context menu invoked the "Create Private Accessor" command, but got a message stating that "Private accessor creation failed for http://localhost/GenericWebApp". This was strange and needless to say that I wasn't able to run the tests.

While creating the tests, I had been prompted that Network Service didn't had sufficient rights on a particular folder and I had selected "Yes" to provide those rights. But still things didn't work. I then created an exact same ASP.NET Web site but this time on file folder. Again I created the tests and this time it worked like a breeze. See the code that got generated for one of the methods in the Calculator class.

        /// <summary>

        ///A test for Subtract

        ///</summary>

        [TestMethod()]

        [HostType("ASP.NET")]

        [AspNetDevelopmentServerHost("%PathToWebRoot%\\FileSystemWebApp", "/FileSystemWebApp")]

        [UrlToTest("http://localhost/FileSystemWebApp")]

        public void SubtractTest()

        {

            Calculator_Accessor target = new Calculator_Accessor(); // TODO: Initialize to an appropriate value

            int x = 0; // TODO: Initialize to an appropriate value

            int y = 0; // TODO: Initialize to an appropriate value

            int expected = 0; // TODO: Initialize to an appropriate value

            int actual;

            actual = target.Subtract(x, y);

            Assert.AreEqual(expected, actual);

            Assert.Inconclusive("Verify the correctness of this test method.");

        }

Clearly the private accessor (Calculator_Accessor) got created this time and after commenting the last Assert.Inconclusive line, I was able to successfully run the tests (the test would be more meaningful after making suitable changes to the values as well like x could be set to 10, y to 5 and hence expected will be 5).

This meant there was some issue in the way the tests were generated on IIS. I also did ensure that I was running VS as administrator (since I was working on Vista). I hence raised a query on the forum and got a confirmation that this is a bug in VS 2008 and we should get a fix in SP1. Till then, we will have to either test by having the site on a file folder or using the PrivateObject to test. I will discuss that in my next blog, so keep watching !

 

January 2, 2008

Querying user details from Active Directory

During a recent project, we had need for querying and working with Active Directory. Searching on net, gave lots of help, but most were around creating and managing users. Our need was simple - to get some user specific details from AD.

The trickest part of working with AD is the AD structure and what properties have been defined and hence available for querying. Without really knowing this, one can continue to grop in dark for long hours without much success.

Fortunately, there is a very handy free tool from Softerra called LDAPBrowser. Using this tool, you can get very useful insight into the AD structure that you want to work against and try your search queries to find the right syntax.

Once you download the tool, you need to do minimal configuration to work with it. Run the application and select "New Profile" from the File menu. Give a name of your liking for the profile and click Next. The figure below shows the screen where you need to configure the host information. The host name will be the name of your ad like for example test.com as shown in the figure below. If you aren't sure of what Base DN you want to work with, click the "Fetch DNs (only LDAP v.3)" button. As the name indicates this works only if your AD supports LDAP protocol version 3.

LDAPProfile.jpg

This will fetch a list and you can select one from that. Typically you may want to select something that looks like DC=test,DC=com as this the root path and all other objects in the AD will be inside of this. When working with AD, a few abbreviations are used and these are

CN - common name
DN - distinguished name
OU - organizational unit
DC - domain object class

Further to providing the host name and Base DN, you will then need to provide a user id and password using which you will query the AD. This will be a valid domain account. When accessing AD programmatically, you can always use the secure authentication mechanism to query AD without having to provide a user id and password.

With the profile created, you can navigate the AD structure and drill down to individual items. Checking their properties will show the PATH to that object which you can use to fine tune your queries. Usually queries will work with the root path as well, but if you can narrow your search by using a more detailed path, you will save time in obtaining query results. Needless to say that AD querying is highly optimized and in my minimal experimentation, i didn't find much difference between getting results using the root path or a more selective path.

Following code snippet shows how to query for a particular user

ADCode.jpg 

Similar code will work for both web or windows application. Once you have access to the User's directory entry, you can query a host of properties using the specific string name key. To find out these keys, the LDAPBrowser will come handy.

Hope this helps. Comments welcome. 

Subscribe to this blog's feed

Follow us on

Blogger Profiles

Infosys on Twitter